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; 18 19 import android.arch.core.util.Function; 20 import android.arch.lifecycle.LiveData; 21 import android.content.Context; 22 import android.os.Looper; 23 import android.support.annotation.NonNull; 24 import android.support.annotation.Nullable; 25 import android.support.annotation.RestrictTo; 26 import android.support.annotation.WorkerThread; 27 28 import androidx.work.Configuration; 29 import androidx.work.ExistingPeriodicWorkPolicy; 30 import androidx.work.ExistingWorkPolicy; 31 import androidx.work.OneTimeWorkRequest; 32 import androidx.work.PeriodicWorkRequest; 33 import androidx.work.R; 34 import androidx.work.SynchronousWorkManager; 35 import androidx.work.WorkContinuation; 36 import androidx.work.WorkManager; 37 import androidx.work.WorkRequest; 38 import androidx.work.WorkStatus; 39 import androidx.work.impl.background.greedy.GreedyScheduler; 40 import androidx.work.impl.model.WorkSpec; 41 import androidx.work.impl.model.WorkSpecDao; 42 import androidx.work.impl.utils.CancelWorkRunnable; 43 import androidx.work.impl.utils.ForceStopRunnable; 44 import androidx.work.impl.utils.LiveDataUtils; 45 import androidx.work.impl.utils.Preferences; 46 import androidx.work.impl.utils.PruneWorkRunnable; 47 import androidx.work.impl.utils.StartWorkRunnable; 48 import androidx.work.impl.utils.StopWorkRunnable; 49 import androidx.work.impl.utils.taskexecutor.TaskExecutor; 50 import androidx.work.impl.utils.taskexecutor.WorkManagerTaskExecutor; 51 52 import java.util.Arrays; 53 import java.util.Collections; 54 import java.util.List; 55 import java.util.UUID; 56 57 /** 58 * A concrete implementation of {@link WorkManager}. 59 * 60 * @hide 61 */ 62 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) 63 public class WorkManagerImpl extends WorkManager implements SynchronousWorkManager { 64 65 public static final int MAX_PRE_JOB_SCHEDULER_API_LEVEL = 22; 66 public static final int MIN_JOB_SCHEDULER_API_LEVEL = 23; 67 68 private Context mContext; 69 private Configuration mConfiguration; 70 private WorkDatabase mWorkDatabase; 71 private TaskExecutor mTaskExecutor; 72 private List<Scheduler> mSchedulers; 73 private Processor mProcessor; 74 private Preferences mPreferences; 75 76 private static WorkManagerImpl sDelegatedInstance = null; 77 private static WorkManagerImpl sDefaultInstance = null; 78 private static final Object sLock = new Object(); 79 80 81 /** 82 * @param delegate The delegate for {@link WorkManagerImpl} for testing; {@code null} to use the 83 * default instance 84 * @hide 85 */ 86 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) setDelegate(WorkManagerImpl delegate)87 public static void setDelegate(WorkManagerImpl delegate) { 88 synchronized (sLock) { 89 sDelegatedInstance = delegate; 90 } 91 } 92 93 /** 94 * Retrieves the singleton instance of {@link WorkManagerImpl}. 95 * 96 * @return The singleton instance of {@link WorkManagerImpl} 97 * @hide 98 */ 99 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) getInstance()100 public static WorkManagerImpl getInstance() { 101 synchronized (sLock) { 102 if (sDelegatedInstance != null) { 103 return sDelegatedInstance; 104 } 105 106 return sDefaultInstance; 107 } 108 } 109 110 /** 111 * Initializes the singleton instance of {@link WorkManagerImpl}. 112 * 113 * @param context A {@link Context} object for configuration purposes. Internally, this class 114 * will call {@link Context#getApplicationContext()}, so you may safely pass in 115 * any Context without risking a memory leak. 116 * @param configuration The {@link Configuration} for used to set up WorkManager. 117 * 118 * @hide 119 */ 120 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) initialize(@onNull Context context, @NonNull Configuration configuration)121 public static void initialize(@NonNull Context context, @NonNull Configuration configuration) { 122 synchronized (sLock) { 123 if (sDelegatedInstance == null) { 124 context = context.getApplicationContext(); 125 if (sDefaultInstance == null) { 126 sDefaultInstance = new WorkManagerImpl(context, configuration); 127 } 128 sDelegatedInstance = sDefaultInstance; 129 } 130 } 131 } 132 133 /** 134 * Create an instance of {@link WorkManagerImpl}. 135 * 136 * @param context The application {@link Context} 137 * @param configuration The {@link Configuration} configuration. 138 * @hide 139 */ 140 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) WorkManagerImpl( @onNull Context context, @NonNull Configuration configuration)141 public WorkManagerImpl( 142 @NonNull Context context, 143 @NonNull Configuration configuration) { 144 this(context, 145 configuration, 146 context.getResources().getBoolean(R.bool.workmanager_test_configuration)); 147 } 148 149 /** 150 * Create an instance of {@link WorkManagerImpl}. 151 * 152 * @param context The application {@link Context} 153 * @param configuration The {@link Configuration} configuration. 154 * @param useTestDatabase {@code true} If using an in-memory test database. 155 * @hide 156 */ 157 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) WorkManagerImpl( @onNull Context context, @NonNull Configuration configuration, boolean useTestDatabase)158 public WorkManagerImpl( 159 @NonNull Context context, 160 @NonNull Configuration configuration, 161 boolean useTestDatabase) { 162 163 context = context.getApplicationContext(); 164 mContext = context; 165 mConfiguration = configuration; 166 mWorkDatabase = WorkDatabase.create(context, useTestDatabase); 167 mTaskExecutor = WorkManagerTaskExecutor.getInstance(); 168 mProcessor = new Processor( 169 context, 170 mConfiguration, 171 mWorkDatabase, 172 getSchedulers(), 173 configuration.getExecutor()); 174 mPreferences = new Preferences(mContext); 175 176 // Checks for app force stops. 177 mTaskExecutor.executeOnBackgroundThread(new ForceStopRunnable(context, this)); 178 } 179 180 /** 181 * @return The application {@link Context} associated with this WorkManager. 182 * @hide 183 */ 184 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) getApplicationContext()185 public Context getApplicationContext() { 186 return mContext; 187 } 188 189 /** 190 * @return The {@link WorkDatabase} instance associated with this WorkManager. 191 * @hide 192 */ 193 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) getWorkDatabase()194 public WorkDatabase getWorkDatabase() { 195 return mWorkDatabase; 196 } 197 198 /** 199 * @return The {@link Configuration} instance associated with this WorkManager. 200 * @hide 201 */ 202 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) 203 @NonNull getConfiguration()204 public Configuration getConfiguration() { 205 return mConfiguration; 206 } 207 208 /** 209 * @return The {@link Scheduler}s associated with this WorkManager based on the device's 210 * capabilities, SDK version, etc. 211 * @hide 212 */ 213 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) getSchedulers()214 public @NonNull List<Scheduler> getSchedulers() { 215 // Initialized at construction time. So no need to synchronize. 216 if (mSchedulers == null) { 217 mSchedulers = Arrays.asList( 218 Schedulers.createBestAvailableBackgroundScheduler(mContext, this), 219 new GreedyScheduler(mContext, this)); 220 } 221 return mSchedulers; 222 } 223 224 /** 225 * @return The {@link Processor} used to process background work. 226 * @hide 227 */ 228 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) getProcessor()229 public @NonNull Processor getProcessor() { 230 return mProcessor; 231 } 232 233 /** 234 * @return the {@link TaskExecutor} used by the instance of {@link WorkManager}. 235 * @hide 236 */ 237 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) getTaskExecutor()238 public @NonNull TaskExecutor getTaskExecutor() { 239 return mTaskExecutor; 240 } 241 242 /** 243 * @return the {@link Preferences} used by the instance of {@link WorkManager}. 244 * @hide 245 */ 246 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) getPreferences()247 public @NonNull Preferences getPreferences() { 248 return mPreferences; 249 } 250 251 @Override enqueue(@onNull List<? extends WorkRequest> workRequests)252 public void enqueue(@NonNull List<? extends WorkRequest> workRequests) { 253 new WorkContinuationImpl(this, workRequests).enqueue(); 254 } 255 256 @Override enqueueSync(@onNull WorkRequest... workRequest)257 public void enqueueSync(@NonNull WorkRequest... workRequest) { 258 enqueueSync(Arrays.asList(workRequest)); 259 } 260 261 @Override enqueueSync(@onNull List<? extends WorkRequest> workRequest)262 public void enqueueSync(@NonNull List<? extends WorkRequest> workRequest) { 263 assertBackgroundThread("Cannot enqueueSync on main thread!"); 264 new WorkContinuationImpl(this, workRequest).enqueueSync(); 265 } 266 267 @Override beginWith(@onNull List<OneTimeWorkRequest> work)268 public WorkContinuation beginWith(@NonNull List<OneTimeWorkRequest> work) { 269 return new WorkContinuationImpl(this, work); 270 } 271 272 @Override beginUniqueWork( @onNull String uniqueWorkName, @NonNull ExistingWorkPolicy existingWorkPolicy, @NonNull List<OneTimeWorkRequest> work)273 public WorkContinuation beginUniqueWork( 274 @NonNull String uniqueWorkName, 275 @NonNull ExistingWorkPolicy existingWorkPolicy, 276 @NonNull List<OneTimeWorkRequest> work) { 277 return new WorkContinuationImpl(this, uniqueWorkName, existingWorkPolicy, work); 278 } 279 280 @Override enqueueUniquePeriodicWork( @onNull String uniqueWorkName, @NonNull ExistingPeriodicWorkPolicy existingPeriodicWorkPolicy, @NonNull PeriodicWorkRequest periodicWork)281 public void enqueueUniquePeriodicWork( 282 @NonNull String uniqueWorkName, 283 @NonNull ExistingPeriodicWorkPolicy existingPeriodicWorkPolicy, 284 @NonNull PeriodicWorkRequest periodicWork) { 285 createWorkContinuationForUniquePeriodicWork( 286 uniqueWorkName, 287 existingPeriodicWorkPolicy, 288 periodicWork) 289 .enqueue(); 290 } 291 292 @Override enqueueUniquePeriodicWorkSync( @onNull String uniqueWorkName, @NonNull ExistingPeriodicWorkPolicy existingPeriodicWorkPolicy, @NonNull PeriodicWorkRequest periodicWork)293 public void enqueueUniquePeriodicWorkSync( 294 @NonNull String uniqueWorkName, 295 @NonNull ExistingPeriodicWorkPolicy existingPeriodicWorkPolicy, 296 @NonNull PeriodicWorkRequest periodicWork) { 297 assertBackgroundThread("Cannot enqueueUniquePeriodicWorkSync on main thread!"); 298 createWorkContinuationForUniquePeriodicWork( 299 uniqueWorkName, 300 existingPeriodicWorkPolicy, 301 periodicWork) 302 .enqueueSync(); 303 } 304 createWorkContinuationForUniquePeriodicWork( @onNull String uniqueWorkName, @NonNull ExistingPeriodicWorkPolicy existingPeriodicWorkPolicy, @NonNull PeriodicWorkRequest periodicWork)305 private WorkContinuationImpl createWorkContinuationForUniquePeriodicWork( 306 @NonNull String uniqueWorkName, 307 @NonNull ExistingPeriodicWorkPolicy existingPeriodicWorkPolicy, 308 @NonNull PeriodicWorkRequest periodicWork) { 309 ExistingWorkPolicy existingWorkPolicy; 310 if (existingPeriodicWorkPolicy == ExistingPeriodicWorkPolicy.KEEP) { 311 existingWorkPolicy = ExistingWorkPolicy.KEEP; 312 } else { 313 existingWorkPolicy = ExistingWorkPolicy.REPLACE; 314 } 315 return new WorkContinuationImpl( 316 this, 317 uniqueWorkName, 318 existingWorkPolicy, 319 Collections.singletonList(periodicWork)); 320 } 321 322 @Override cancelWorkById(@onNull UUID id)323 public void cancelWorkById(@NonNull UUID id) { 324 mTaskExecutor.executeOnBackgroundThread(CancelWorkRunnable.forId(id, this)); 325 } 326 327 @Override 328 @WorkerThread cancelWorkByIdSync(@onNull UUID id)329 public void cancelWorkByIdSync(@NonNull UUID id) { 330 assertBackgroundThread("Cannot cancelWorkByIdSync on main thread!"); 331 CancelWorkRunnable.forId(id, this).run(); 332 } 333 334 @Override cancelAllWorkByTag(@onNull final String tag)335 public void cancelAllWorkByTag(@NonNull final String tag) { 336 mTaskExecutor.executeOnBackgroundThread( 337 CancelWorkRunnable.forTag(tag, this)); 338 } 339 340 @Override 341 @WorkerThread cancelAllWorkByTagSync(@onNull String tag)342 public void cancelAllWorkByTagSync(@NonNull String tag) { 343 assertBackgroundThread("Cannot cancelAllWorkByTagSync on main thread!"); 344 CancelWorkRunnable.forTag(tag, this).run(); 345 } 346 347 @Override cancelUniqueWork(@onNull String uniqueWorkName)348 public void cancelUniqueWork(@NonNull String uniqueWorkName) { 349 mTaskExecutor.executeOnBackgroundThread( 350 CancelWorkRunnable.forName(uniqueWorkName, this)); 351 } 352 353 @Override 354 @WorkerThread cancelUniqueWorkSync(@onNull String uniqueWorkName)355 public void cancelUniqueWorkSync(@NonNull String uniqueWorkName) { 356 assertBackgroundThread("Cannot cancelAllWorkByNameBlocking on main thread!"); 357 CancelWorkRunnable.forName(uniqueWorkName, this).run(); 358 } 359 360 @Override cancelAllWork()361 public void cancelAllWork() { 362 mTaskExecutor.executeOnBackgroundThread(CancelWorkRunnable.forAll(this)); 363 } 364 365 @Override 366 @WorkerThread cancelAllWorkSync()367 public void cancelAllWorkSync() { 368 assertBackgroundThread("Cannot cancelAllWorkSync on main thread!"); 369 CancelWorkRunnable.forAll(this).run(); 370 } 371 372 @Override getLastCancelAllTimeMillis()373 public LiveData<Long> getLastCancelAllTimeMillis() { 374 return mPreferences.getLastCancelAllTimeMillisLiveData(); 375 } 376 377 @Override getLastCancelAllTimeMillisSync()378 public long getLastCancelAllTimeMillisSync() { 379 return mPreferences.getLastCancelAllTimeMillis(); 380 } 381 382 @Override pruneWork()383 public void pruneWork() { 384 mTaskExecutor.executeOnBackgroundThread(new PruneWorkRunnable(this)); 385 } 386 387 @Override 388 @WorkerThread pruneWorkSync()389 public void pruneWorkSync() { 390 assertBackgroundThread("Cannot pruneWork on main thread!"); 391 new PruneWorkRunnable(this).run(); 392 } 393 394 @Override getStatusById(@onNull UUID id)395 public LiveData<WorkStatus> getStatusById(@NonNull UUID id) { 396 WorkSpecDao dao = mWorkDatabase.workSpecDao(); 397 LiveData<List<WorkSpec.WorkStatusPojo>> inputLiveData = 398 dao.getWorkStatusPojoLiveDataForIds(Collections.singletonList(id.toString())); 399 return LiveDataUtils.dedupedMappedLiveDataFor(inputLiveData, 400 new Function<List<WorkSpec.WorkStatusPojo>, WorkStatus>() { 401 @Override 402 public WorkStatus apply(List<WorkSpec.WorkStatusPojo> input) { 403 WorkStatus workStatus = null; 404 if (input != null && input.size() > 0) { 405 workStatus = input.get(0).toWorkStatus(); 406 } 407 return workStatus; 408 } 409 }); 410 } 411 412 @Override 413 @WorkerThread 414 public @Nullable WorkStatus getStatusByIdSync(@NonNull UUID id) { 415 assertBackgroundThread("Cannot call getStatusByIdSync on main thread!"); 416 WorkSpec.WorkStatusPojo workStatusPojo = 417 mWorkDatabase.workSpecDao().getWorkStatusPojoForId(id.toString()); 418 if (workStatusPojo != null) { 419 return workStatusPojo.toWorkStatus(); 420 } else { 421 return null; 422 } 423 } 424 425 @Override 426 public LiveData<List<WorkStatus>> getStatusesByTag(@NonNull String tag) { 427 WorkSpecDao workSpecDao = mWorkDatabase.workSpecDao(); 428 LiveData<List<WorkSpec.WorkStatusPojo>> inputLiveData = 429 workSpecDao.getWorkStatusPojoLiveDataForTag(tag); 430 return LiveDataUtils.dedupedMappedLiveDataFor(inputLiveData, WorkSpec.WORK_STATUS_MAPPER); 431 } 432 433 @Override 434 public List<WorkStatus> getStatusesByTagSync(@NonNull String tag) { 435 assertBackgroundThread("Cannot call getStatusesByTagSync on main thread!"); 436 WorkSpecDao workSpecDao = mWorkDatabase.workSpecDao(); 437 List<WorkSpec.WorkStatusPojo> input = workSpecDao.getWorkStatusPojoForTag(tag); 438 return WorkSpec.WORK_STATUS_MAPPER.apply(input); 439 } 440 441 @Override 442 public LiveData<List<WorkStatus>> getStatusesForUniqueWork(@NonNull String uniqueWorkName) { 443 WorkSpecDao workSpecDao = mWorkDatabase.workSpecDao(); 444 LiveData<List<WorkSpec.WorkStatusPojo>> inputLiveData = 445 workSpecDao.getWorkStatusPojoLiveDataForName(uniqueWorkName); 446 return LiveDataUtils.dedupedMappedLiveDataFor(inputLiveData, WorkSpec.WORK_STATUS_MAPPER); 447 } 448 449 @Override 450 public List<WorkStatus> getStatusesForUniqueWorkSync(@NonNull String uniqueWorkName) { 451 assertBackgroundThread("Cannot call getStatusesByNameBlocking on main thread!"); 452 WorkSpecDao workSpecDao = mWorkDatabase.workSpecDao(); 453 List<WorkSpec.WorkStatusPojo> input = workSpecDao.getWorkStatusPojoForName(uniqueWorkName); 454 return WorkSpec.WORK_STATUS_MAPPER.apply(input); 455 } 456 457 @Override 458 public SynchronousWorkManager synchronous() { 459 return this; 460 } 461 462 LiveData<List<WorkStatus>> getStatusesById(@NonNull List<String> workSpecIds) { 463 WorkSpecDao dao = mWorkDatabase.workSpecDao(); 464 LiveData<List<WorkSpec.WorkStatusPojo>> inputLiveData = 465 dao.getWorkStatusPojoLiveDataForIds(workSpecIds); 466 return LiveDataUtils.dedupedMappedLiveDataFor(inputLiveData, WorkSpec.WORK_STATUS_MAPPER); 467 } 468 469 List<WorkStatus> getStatusesByIdSync(@NonNull List<String> workSpecIds) { 470 List<WorkSpec.WorkStatusPojo> workStatusPojos = mWorkDatabase.workSpecDao() 471 .getWorkStatusPojoForIds(workSpecIds); 472 473 return WorkSpec.WORK_STATUS_MAPPER.apply(workStatusPojos); 474 } 475 476 /** 477 * @param workSpecId The {@link WorkSpec} id to start 478 * @hide 479 */ 480 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) 481 public void startWork(String workSpecId) { 482 startWork(workSpecId, null); 483 } 484 485 /** 486 * @param workSpecId The {@link WorkSpec} id to start 487 * @param runtimeExtras The {@link Extras.RuntimeExtras} associated with this work 488 * @hide 489 */ 490 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) 491 public void startWork(String workSpecId, Extras.RuntimeExtras runtimeExtras) { 492 mTaskExecutor.executeOnBackgroundThread( 493 new StartWorkRunnable(this, workSpecId, runtimeExtras)); 494 } 495 496 /** 497 * @param workSpecId The {@link WorkSpec} id to stop 498 * @hide 499 */ 500 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) 501 public void stopWork(String workSpecId) { 502 mTaskExecutor.executeOnBackgroundThread(new StopWorkRunnable(this, workSpecId)); 503 } 504 505 /** 506 * Reschedules all the eligible work. Useful for cases like, app was force stopped or 507 * BOOT_COMPLETED, TIMEZONE_CHANGED and TIME_SET for AlarmManager. 508 * 509 * @hide 510 */ 511 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) 512 public void rescheduleEligibleWork() { 513 // Reset scheduled state. 514 getWorkDatabase().workSpecDao().resetScheduledState(); 515 516 // Delegate to the WorkManager's schedulers. 517 // Using getters here so we can use from a mocked instance 518 // of WorkManagerImpl. 519 Schedulers.schedule(getConfiguration(), getWorkDatabase(), getSchedulers()); 520 } 521 522 private void assertBackgroundThread(String errorMessage) { 523 if (Looper.getMainLooper().getThread() == Thread.currentThread()) { 524 throw new IllegalStateException(errorMessage); 525 } 526 } 527 } 528