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 static android.app.PendingIntent.FLAG_MUTABLE; 20 import static android.app.PendingIntent.FLAG_UPDATE_CURRENT; 21 import static android.text.TextUtils.isEmpty; 22 23 import static androidx.work.ListenableFutureKt.executeAsync; 24 import static androidx.work.impl.UnfinishedWorkListenerKt.maybeLaunchUnfinishedWorkListener; 25 import static androidx.work.impl.WorkManagerImplExtKt.createWorkManager; 26 import static androidx.work.impl.WorkManagerImplExtKt.createWorkManagerScope; 27 import static androidx.work.impl.WorkerUpdater.enqueueUniquelyNamedPeriodic; 28 import static androidx.work.impl.foreground.SystemForegroundDispatcher.createCancelWorkIntent; 29 import static androidx.work.impl.model.RawWorkInfoDaoKt.getWorkInfoPojosFlow; 30 import static androidx.work.impl.model.WorkSpecDaoKt.getWorkStatusPojoFlowDataForIds; 31 import static androidx.work.impl.model.WorkSpecDaoKt.getWorkStatusPojoFlowForName; 32 import static androidx.work.impl.model.WorkSpecDaoKt.getWorkStatusPojoFlowForTag; 33 34 import android.app.PendingIntent; 35 import android.content.BroadcastReceiver; 36 import android.content.Context; 37 import android.content.Intent; 38 import android.os.Build; 39 40 import androidx.annotation.RequiresApi; 41 import androidx.annotation.RestrictTo; 42 import androidx.arch.core.util.Function; 43 import androidx.lifecycle.LiveData; 44 import androidx.work.Configuration; 45 import androidx.work.ExistingPeriodicWorkPolicy; 46 import androidx.work.ExistingWorkPolicy; 47 import androidx.work.Logger; 48 import androidx.work.OneTimeWorkRequest; 49 import androidx.work.Operation; 50 import androidx.work.PeriodicWorkRequest; 51 import androidx.work.StopReason; 52 import androidx.work.TracerKt; 53 import androidx.work.WorkContinuation; 54 import androidx.work.WorkInfo; 55 import androidx.work.WorkManager; 56 import androidx.work.WorkQuery; 57 import androidx.work.WorkRequest; 58 import androidx.work.impl.background.systemalarm.RescheduleReceiver; 59 import androidx.work.impl.background.systemjob.SystemJobScheduler; 60 import androidx.work.impl.constraints.trackers.Trackers; 61 import androidx.work.impl.model.RawWorkInfoDao; 62 import androidx.work.impl.model.WorkGenerationalId; 63 import androidx.work.impl.model.WorkSpec; 64 import androidx.work.impl.model.WorkSpecDao; 65 import androidx.work.impl.utils.CancelWorkRunnable; 66 import androidx.work.impl.utils.ForceStopRunnable; 67 import androidx.work.impl.utils.LiveDataUtils; 68 import androidx.work.impl.utils.PreferenceUtils; 69 import androidx.work.impl.utils.PruneWorkRunnableKt; 70 import androidx.work.impl.utils.RawQueries; 71 import androidx.work.impl.utils.StatusRunnable; 72 import androidx.work.impl.utils.StopWorkRunnable; 73 import androidx.work.impl.utils.taskexecutor.TaskExecutor; 74 import androidx.work.multiprocess.RemoteWorkManager; 75 76 import com.google.common.util.concurrent.ListenableFuture; 77 78 import kotlin.Unit; 79 80 import kotlinx.coroutines.CoroutineScope; 81 import kotlinx.coroutines.flow.Flow; 82 83 import org.jspecify.annotations.NonNull; 84 import org.jspecify.annotations.Nullable; 85 86 import java.util.Collections; 87 import java.util.List; 88 import java.util.UUID; 89 90 /** 91 * A concrete implementation of {@link WorkManager}. 92 */ 93 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) 94 public class WorkManagerImpl extends WorkManager { 95 96 private static final String TAG = Logger.tagWithPrefix("WorkManagerImpl"); 97 public static final int MAX_PRE_JOB_SCHEDULER_API_LEVEL = 22; 98 public static final int MIN_JOB_SCHEDULER_API_LEVEL = 23; 99 public static final int CONTENT_URI_TRIGGER_API_LEVEL = 24; 100 public static final String REMOTE_WORK_MANAGER_CLIENT = 101 "androidx.work.multiprocess.RemoteWorkManagerClient"; 102 103 private Context mContext; 104 private Configuration mConfiguration; 105 private WorkDatabase mWorkDatabase; 106 private TaskExecutor mWorkTaskExecutor; 107 private List<Scheduler> mSchedulers; 108 private Processor mProcessor; 109 private PreferenceUtils mPreferenceUtils; 110 private boolean mForceStopRunnableCompleted = false; 111 private BroadcastReceiver.PendingResult mRescheduleReceiverResult; 112 private volatile RemoteWorkManager mRemoteWorkManager; 113 private final Trackers mTrackers; 114 /** 115 * Job for the scope of the whole WorkManager 116 */ 117 private final CoroutineScope mWorkManagerScope; 118 private static WorkManagerImpl sDelegatedInstance = null; 119 private static WorkManagerImpl sDefaultInstance = null; 120 private static final Object sLock = new Object(); 121 122 /** 123 * @param delegate The delegate for {@link WorkManagerImpl} for testing; {@code null} to use the 124 * default instance 125 */ 126 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) setDelegate(@ullable WorkManagerImpl delegate)127 public static void setDelegate(@Nullable WorkManagerImpl delegate) { 128 synchronized (sLock) { 129 sDelegatedInstance = delegate; 130 } 131 } 132 133 /** 134 * Retrieves the singleton instance of {@link WorkManagerImpl}. 135 * 136 * @return The singleton instance of {@link WorkManagerImpl} 137 * @deprecated Call {@link WorkManagerImpl#getInstance(Context)} instead. 138 */ 139 @Deprecated 140 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) 141 @SuppressWarnings("NullableProblems") getInstance()142 public static @Nullable WorkManagerImpl getInstance() { 143 synchronized (sLock) { 144 if (sDelegatedInstance != null) { 145 return sDelegatedInstance; 146 } 147 148 return sDefaultInstance; 149 } 150 } 151 152 /** 153 * 154 */ 155 @SuppressWarnings("deprecation") 156 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) isInitialized()157 public static boolean isInitialized() { 158 WorkManagerImpl instance = getInstance(); 159 return instance != null; 160 } 161 162 /** 163 * Retrieves the singleton instance of {@link WorkManagerImpl}. 164 * 165 * @param context A context for on-demand initialization. 166 * @return The singleton instance of {@link WorkManagerImpl} 167 */ 168 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) getInstance(@onNull Context context)169 public static @NonNull WorkManagerImpl getInstance(@NonNull Context context) { 170 synchronized (sLock) { 171 WorkManagerImpl instance = getInstance(); 172 if (instance == null) { 173 Context appContext = context.getApplicationContext(); 174 if (appContext instanceof Configuration.Provider) { 175 initialize( 176 appContext, 177 ((Configuration.Provider) appContext).getWorkManagerConfiguration()); 178 instance = getInstance(appContext); 179 } else { 180 throw new IllegalStateException("WorkManager is not initialized properly. You " 181 + "have explicitly disabled WorkManagerInitializer in your manifest, " 182 + "have not manually called WorkManager#initialize at this point, and " 183 + "your Application does not implement Configuration.Provider."); 184 } 185 } 186 187 return instance; 188 } 189 } 190 191 /** 192 * Initializes the singleton instance of {@link WorkManagerImpl}. You should only do this if 193 * you want to use a custom {@link Configuration} object and have disabled 194 * WorkManagerInitializer. 195 * 196 * @param context A {@link Context} object for configuration purposes. Internally, this 197 * class will call {@link Context#getApplicationContext()}, so you may 198 * safely pass in any Context without risking a memory leak. 199 * @param configuration The {@link Configuration} for used to set up WorkManager. 200 */ 201 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) initialize(@onNull Context context, @NonNull Configuration configuration)202 public static void initialize(@NonNull Context context, @NonNull Configuration configuration) { 203 synchronized (sLock) { 204 if (sDelegatedInstance != null && sDefaultInstance != null) { 205 throw new IllegalStateException("WorkManager is already initialized. Did you " 206 + "try to initialize it manually without disabling " 207 + "WorkManagerInitializer? See " 208 + "WorkManager#initialize(Context, Configuration) or the class level " 209 + "Javadoc for more information."); 210 } 211 212 if (sDelegatedInstance == null) { 213 context = context.getApplicationContext(); 214 if (sDefaultInstance == null) { 215 sDefaultInstance = createWorkManager(context, configuration); 216 } 217 sDelegatedInstance = sDefaultInstance; 218 } 219 } 220 } 221 222 /** 223 * Create an instance of {@link WorkManagerImpl}. 224 * 225 * @param context The application {@link Context} 226 * @param configuration The {@link Configuration} configuration 227 * @param workTaskExecutor The {@link TaskExecutor} for running "processing" jobs, such as 228 * enqueueing, scheduling, cancellation, etc. 229 * @param workDatabase The {@link WorkDatabase} instance 230 * @param processor The {@link Processor} instance 231 * @param trackers Trackers 232 */ 233 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) WorkManagerImpl( @onNull Context context, @NonNull Configuration configuration, @NonNull TaskExecutor workTaskExecutor, @NonNull WorkDatabase workDatabase, @NonNull List<Scheduler> schedulers, @NonNull Processor processor, @NonNull Trackers trackers)234 public WorkManagerImpl( 235 @NonNull Context context, 236 @NonNull Configuration configuration, 237 @NonNull TaskExecutor workTaskExecutor, 238 @NonNull WorkDatabase workDatabase, 239 @NonNull List<Scheduler> schedulers, 240 @NonNull Processor processor, 241 @NonNull Trackers trackers) { 242 context = context.getApplicationContext(); 243 // Check for direct boot mode 244 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && Api24Impl.isDeviceProtectedStorage( 245 context)) { 246 throw new IllegalStateException("Cannot initialize WorkManager in direct boot mode"); 247 } 248 Logger.setLogger(new Logger.LogcatLogger(configuration.getMinimumLoggingLevel())); 249 mContext = context; 250 mWorkTaskExecutor = workTaskExecutor; 251 mWorkDatabase = workDatabase; 252 mProcessor = processor; 253 mTrackers = trackers; 254 mConfiguration = configuration; 255 mSchedulers = schedulers; 256 mWorkManagerScope = createWorkManagerScope(mWorkTaskExecutor); 257 mPreferenceUtils = new PreferenceUtils(mWorkDatabase); 258 Schedulers.registerRescheduling(schedulers, mProcessor, 259 workTaskExecutor.getSerialTaskExecutor(), mWorkDatabase, configuration); 260 // Checks for app force stops. 261 mWorkTaskExecutor.executeOnTaskThread(new ForceStopRunnable(context, this)); 262 maybeLaunchUnfinishedWorkListener(mWorkManagerScope, mContext, configuration, workDatabase); 263 } 264 265 /** 266 * @return The application {@link Context} associated with this WorkManager. 267 */ 268 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) getApplicationContext()269 public @NonNull Context getApplicationContext() { 270 return mContext; 271 } 272 273 /** 274 * @return The {@link WorkDatabase} instance associated with this WorkManager. 275 */ 276 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) getWorkDatabase()277 public @NonNull WorkDatabase getWorkDatabase() { 278 return mWorkDatabase; 279 } 280 281 /** 282 * @return workmanager's CoroutineScope 283 */ getWorkManagerScope()284 @NonNull CoroutineScope getWorkManagerScope() { 285 return mWorkManagerScope; 286 } 287 288 /** 289 * @return The {@link Configuration} instance associated with this WorkManager. 290 */ 291 @Override getConfiguration()292 public @NonNull Configuration getConfiguration() { 293 return mConfiguration; 294 } 295 296 /** 297 * @return The {@link Scheduler}s associated with this WorkManager based on the device's 298 * capabilities, SDK version, etc. 299 */ 300 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) getSchedulers()301 public @NonNull List<Scheduler> getSchedulers() { 302 return mSchedulers; 303 } 304 305 /** 306 * @return The {@link Processor} used to process background work. 307 */ 308 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) getProcessor()309 public @NonNull Processor getProcessor() { 310 return mProcessor; 311 } 312 313 /** 314 * @return the {@link TaskExecutor} used by the instance of {@link WorkManager}. 315 */ 316 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) getWorkTaskExecutor()317 public @NonNull TaskExecutor getWorkTaskExecutor() { 318 return mWorkTaskExecutor; 319 } 320 321 /** 322 * @return the {@link PreferenceUtils} used by the instance of {@link WorkManager}. 323 */ 324 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) getPreferenceUtils()325 public @NonNull PreferenceUtils getPreferenceUtils() { 326 return mPreferenceUtils; 327 } 328 329 /** 330 * @return the {@link Trackers} used by {@link WorkManager} 331 */ 332 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) getTrackers()333 public @NonNull Trackers getTrackers() { 334 return mTrackers; 335 } 336 337 @Override enqueue( @onNull List<? extends WorkRequest> requests)338 public @NonNull Operation enqueue( 339 @NonNull List<? extends WorkRequest> requests) { 340 341 // This error is not being propagated as part of the Operation, as we want the 342 // app to crash during development. Having no workRequests is always a developer error. 343 if (requests.isEmpty()) { 344 throw new IllegalArgumentException( 345 "enqueue needs at least one WorkRequest."); 346 } 347 return new WorkContinuationImpl(this, requests).enqueue(); 348 } 349 350 @Override beginWith(@onNull List<OneTimeWorkRequest> requests)351 public @NonNull WorkContinuation beginWith(@NonNull List<OneTimeWorkRequest> requests) { 352 if (requests.isEmpty()) { 353 throw new IllegalArgumentException( 354 "beginWith needs at least one OneTimeWorkRequest."); 355 } 356 return new WorkContinuationImpl(this, requests); 357 } 358 359 @Override beginUniqueWork( @onNull String uniqueWorkName, @NonNull ExistingWorkPolicy existingWorkPolicy, @NonNull List<OneTimeWorkRequest> requests)360 public @NonNull WorkContinuation beginUniqueWork( 361 @NonNull String uniqueWorkName, 362 @NonNull ExistingWorkPolicy existingWorkPolicy, 363 @NonNull List<OneTimeWorkRequest> requests) { 364 if (requests.isEmpty()) { 365 throw new IllegalArgumentException( 366 "beginUniqueWork needs at least one OneTimeWorkRequest."); 367 } 368 return new WorkContinuationImpl(this, uniqueWorkName, existingWorkPolicy, requests); 369 } 370 371 @Override enqueueUniqueWork(@onNull String uniqueWorkName, @NonNull ExistingWorkPolicy existingWorkPolicy, @NonNull List<OneTimeWorkRequest> requests)372 public @NonNull Operation enqueueUniqueWork(@NonNull String uniqueWorkName, 373 @NonNull ExistingWorkPolicy existingWorkPolicy, 374 @NonNull List<OneTimeWorkRequest> requests) { 375 return new WorkContinuationImpl(this, uniqueWorkName, 376 existingWorkPolicy, requests).enqueue(); 377 } 378 379 @Override enqueueUniquePeriodicWork( @onNull String uniqueWorkName, @NonNull ExistingPeriodicWorkPolicy existingPeriodicWorkPolicy, @NonNull PeriodicWorkRequest request)380 public @NonNull Operation enqueueUniquePeriodicWork( 381 @NonNull String uniqueWorkName, 382 @NonNull ExistingPeriodicWorkPolicy existingPeriodicWorkPolicy, 383 @NonNull PeriodicWorkRequest request) { 384 if (existingPeriodicWorkPolicy == ExistingPeriodicWorkPolicy.UPDATE) { 385 return enqueueUniquelyNamedPeriodic(this, uniqueWorkName, request); 386 } 387 return createWorkContinuationForUniquePeriodicWork( 388 uniqueWorkName, 389 existingPeriodicWorkPolicy, 390 request) 391 .enqueue(); 392 } 393 394 /** 395 * Creates a {@link WorkContinuation} for the given unique {@link PeriodicWorkRequest}. 396 */ createWorkContinuationForUniquePeriodicWork( @onNull String uniqueWorkName, @NonNull ExistingPeriodicWorkPolicy existingPeriodicWorkPolicy, @NonNull PeriodicWorkRequest periodicWork)397 public @NonNull WorkContinuationImpl createWorkContinuationForUniquePeriodicWork( 398 @NonNull String uniqueWorkName, 399 @NonNull ExistingPeriodicWorkPolicy existingPeriodicWorkPolicy, 400 @NonNull PeriodicWorkRequest periodicWork) { 401 ExistingWorkPolicy existingWorkPolicy; 402 if (existingPeriodicWorkPolicy == ExistingPeriodicWorkPolicy.KEEP) { 403 existingWorkPolicy = ExistingWorkPolicy.KEEP; 404 } else { 405 existingWorkPolicy = ExistingWorkPolicy.REPLACE; 406 } 407 return new WorkContinuationImpl( 408 this, 409 uniqueWorkName, 410 existingWorkPolicy, 411 Collections.singletonList(periodicWork)); 412 } 413 414 @Override cancelWorkById(@onNull UUID id)415 public @NonNull Operation cancelWorkById(@NonNull UUID id) { 416 return CancelWorkRunnable.forId(id, this); 417 } 418 419 @Override cancelAllWorkByTag(final @NonNull String tag)420 public @NonNull Operation cancelAllWorkByTag(final @NonNull String tag) { 421 return CancelWorkRunnable.forTag(tag, this); 422 } 423 424 @Override cancelUniqueWork(@onNull String uniqueWorkName)425 public @NonNull Operation cancelUniqueWork(@NonNull String uniqueWorkName) { 426 return CancelWorkRunnable.forName(uniqueWorkName, this); 427 } 428 429 @Override cancelAllWork()430 public @NonNull Operation cancelAllWork() { 431 return CancelWorkRunnable.forAll(this); 432 } 433 434 @Override createCancelPendingIntent(@onNull UUID id)435 public @NonNull PendingIntent createCancelPendingIntent(@NonNull UUID id) { 436 Intent intent = createCancelWorkIntent(mContext, id.toString()); 437 int flags = FLAG_UPDATE_CURRENT; 438 if (Build.VERSION.SDK_INT >= 31) { 439 flags |= FLAG_MUTABLE; 440 } 441 return PendingIntent.getService(mContext, 0, intent, flags); 442 } 443 444 @Override getLastCancelAllTimeMillisLiveData()445 public @NonNull LiveData<Long> getLastCancelAllTimeMillisLiveData() { 446 return mPreferenceUtils.getLastCancelAllTimeMillisLiveData(); 447 } 448 449 @Override getLastCancelAllTimeMillis()450 public @NonNull ListenableFuture<Long> getLastCancelAllTimeMillis() { 451 final PreferenceUtils preferenceUtils = mPreferenceUtils; 452 return executeAsync(mWorkTaskExecutor.getSerialTaskExecutor(), 453 "getLastCancelAllTimeMillis", preferenceUtils::getLastCancelAllTimeMillis); 454 } 455 456 @Override pruneWork()457 public @NonNull Operation pruneWork() { 458 return PruneWorkRunnableKt.pruneWork(mWorkDatabase, mConfiguration, mWorkTaskExecutor); 459 } 460 461 @Override getWorkInfoByIdLiveData(@onNull UUID id)462 public @NonNull LiveData<WorkInfo> getWorkInfoByIdLiveData(@NonNull UUID id) { 463 WorkSpecDao dao = mWorkDatabase.workSpecDao(); 464 LiveData<List<WorkSpec.WorkInfoPojo>> inputLiveData = 465 dao.getWorkStatusPojoLiveDataForIds(Collections.singletonList(id.toString())); 466 return LiveDataUtils.dedupedMappedLiveDataFor(inputLiveData, 467 new Function<List<WorkSpec.WorkInfoPojo>, WorkInfo>() { 468 @Override 469 public WorkInfo apply(List<WorkSpec.WorkInfoPojo> input) { 470 WorkInfo workInfo = null; 471 if (input != null && input.size() > 0) { 472 workInfo = input.get(0).toWorkInfo(); 473 } 474 return workInfo; 475 } 476 }, 477 mWorkTaskExecutor); 478 } 479 480 @Override 481 public @NonNull Flow<WorkInfo> getWorkInfoByIdFlow(@NonNull UUID id) { 482 return getWorkStatusPojoFlowDataForIds(getWorkDatabase().workSpecDao(), id); 483 } 484 485 @Override 486 public @NonNull ListenableFuture<WorkInfo> getWorkInfoById(@NonNull UUID id) { 487 return StatusRunnable.forUUID(mWorkDatabase, mWorkTaskExecutor, id); 488 } 489 490 @Override 491 public @NonNull Flow<List<WorkInfo>> getWorkInfosByTagFlow(@NonNull String tag) { 492 WorkSpecDao workSpecDao = mWorkDatabase.workSpecDao(); 493 return getWorkStatusPojoFlowForTag(workSpecDao, 494 mWorkTaskExecutor.getTaskCoroutineDispatcher(), tag); 495 } 496 497 @Override 498 public @NonNull LiveData<List<WorkInfo>> getWorkInfosByTagLiveData(@NonNull String tag) { 499 WorkSpecDao workSpecDao = mWorkDatabase.workSpecDao(); 500 LiveData<List<WorkSpec.WorkInfoPojo>> inputLiveData = 501 workSpecDao.getWorkStatusPojoLiveDataForTag(tag); 502 return LiveDataUtils.dedupedMappedLiveDataFor( 503 inputLiveData, 504 WorkSpec.WORK_INFO_MAPPER, 505 mWorkTaskExecutor); 506 } 507 508 @Override 509 public @NonNull ListenableFuture<List<WorkInfo>> getWorkInfosByTag(@NonNull String tag) { 510 return StatusRunnable.forTag(mWorkDatabase, mWorkTaskExecutor, tag); 511 } 512 513 @Override 514 public @NonNull LiveData<List<WorkInfo>> getWorkInfosForUniqueWorkLiveData( 515 @NonNull String uniqueWorkName) { 516 WorkSpecDao workSpecDao = mWorkDatabase.workSpecDao(); 517 LiveData<List<WorkSpec.WorkInfoPojo>> inputLiveData = 518 workSpecDao.getWorkStatusPojoLiveDataForName(uniqueWorkName); 519 return LiveDataUtils.dedupedMappedLiveDataFor( 520 inputLiveData, 521 WorkSpec.WORK_INFO_MAPPER, 522 mWorkTaskExecutor); 523 } 524 525 @Override 526 public @NonNull Flow<List<WorkInfo>> getWorkInfosForUniqueWorkFlow( 527 @NonNull String uniqueWorkName) { 528 WorkSpecDao workSpecDao = mWorkDatabase.workSpecDao(); 529 return getWorkStatusPojoFlowForName(workSpecDao, 530 mWorkTaskExecutor.getTaskCoroutineDispatcher(), uniqueWorkName); 531 } 532 533 @Override 534 public @NonNull ListenableFuture<List<WorkInfo>> getWorkInfosForUniqueWork( 535 @NonNull String uniqueWorkName) { 536 return StatusRunnable.forUniqueWork(mWorkDatabase, mWorkTaskExecutor, uniqueWorkName); 537 } 538 539 @Override 540 public @NonNull LiveData<List<WorkInfo>> getWorkInfosLiveData( 541 @NonNull WorkQuery workQuery) { 542 RawWorkInfoDao rawWorkInfoDao = mWorkDatabase.rawWorkInfoDao(); 543 LiveData<List<WorkSpec.WorkInfoPojo>> inputLiveData = 544 rawWorkInfoDao.getWorkInfoPojosLiveData( 545 RawQueries.toRawQuery(workQuery)); 546 return LiveDataUtils.dedupedMappedLiveDataFor( 547 inputLiveData, 548 WorkSpec.WORK_INFO_MAPPER, 549 mWorkTaskExecutor); 550 } 551 552 @Override 553 public @NonNull Flow<List<WorkInfo>> getWorkInfosFlow(@NonNull WorkQuery workQuery) { 554 RawWorkInfoDao rawWorkInfoDao = mWorkDatabase.rawWorkInfoDao(); 555 return getWorkInfoPojosFlow(rawWorkInfoDao, mWorkTaskExecutor.getTaskCoroutineDispatcher(), 556 RawQueries.toRawQuery(workQuery)); 557 } 558 559 @Override 560 public @NonNull ListenableFuture<List<WorkInfo>> getWorkInfos(@NonNull WorkQuery workQuery) { 561 return StatusRunnable.forWorkQuerySpec(mWorkDatabase, mWorkTaskExecutor, workQuery); 562 } 563 564 @Override 565 public @NonNull ListenableFuture<UpdateResult> updateWork(@NonNull WorkRequest request) { 566 return WorkerUpdater.updateWorkImpl(this, request); 567 } 568 569 LiveData<List<WorkInfo>> getWorkInfosById(@NonNull List<String> workSpecIds) { 570 WorkSpecDao dao = mWorkDatabase.workSpecDao(); 571 LiveData<List<WorkSpec.WorkInfoPojo>> inputLiveData = 572 dao.getWorkStatusPojoLiveDataForIds(workSpecIds); 573 return LiveDataUtils.dedupedMappedLiveDataFor( 574 inputLiveData, 575 WorkSpec.WORK_INFO_MAPPER, 576 mWorkTaskExecutor); 577 } 578 579 /** 580 * 581 */ 582 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) 583 public @Nullable RemoteWorkManager getRemoteWorkManager() { 584 if (mRemoteWorkManager == null) { 585 synchronized (sLock) { 586 if (mRemoteWorkManager == null) { 587 // Initialize multi-process support. 588 tryInitializeMultiProcessSupport(); 589 if (mRemoteWorkManager == null && !isEmpty( 590 mConfiguration.getDefaultProcessName())) { 591 String message = "Invalid multiprocess configuration. Define an " 592 + "`implementation` dependency on :work:work-multiprocess library"; 593 throw new IllegalStateException(message); 594 } 595 } 596 } 597 } 598 return mRemoteWorkManager; 599 } 600 601 /** 602 * @param id The {@link WorkSpec} id to stop when running in the context of a 603 * foreground service. 604 */ 605 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) 606 public void stopForegroundWork(@NonNull WorkGenerationalId id, @StopReason int reason) { 607 mWorkTaskExecutor.executeOnTaskThread(new StopWorkRunnable(mProcessor, 608 new StartStopToken(id), true, reason)); 609 } 610 611 /** 612 * Reschedules all the eligible work. Useful for cases like, app was force stopped or 613 * BOOT_COMPLETED, TIMEZONE_CHANGED and TIME_SET for AlarmManager. 614 */ 615 public void rescheduleEligibleWork() { 616 // Delegate to the getter so mocks continue to work when testing. 617 Configuration configuration = getConfiguration(); 618 TracerKt.traced(configuration.getTracer(), "ReschedulingWork", () -> { 619 // This gives us an easy way to clear persisted work state, and then reschedule work 620 // that WorkManager is aware of. Ideally, we do something similar for other 621 // persistent schedulers. 622 if (Build.VERSION.SDK_INT >= WorkManagerImpl.MIN_JOB_SCHEDULER_API_LEVEL) { 623 SystemJobScheduler.cancelAllInAllNamespaces(getApplicationContext()); 624 } 625 626 // Reset scheduled state. 627 getWorkDatabase().workSpecDao().resetScheduledState(); 628 629 // Delegate to the WorkManager's schedulers. 630 // Using getters here so we can use from a mocked instance 631 // of WorkManagerImpl. 632 Schedulers.schedule(getConfiguration(), getWorkDatabase(), getSchedulers()); 633 return Unit.INSTANCE; 634 }); 635 } 636 637 /** 638 * A way for {@link ForceStopRunnable} to tell {@link WorkManagerImpl} that it has completed. 639 */ 640 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) 641 public void onForceStopRunnableCompleted() { 642 synchronized (sLock) { 643 mForceStopRunnableCompleted = true; 644 if (mRescheduleReceiverResult != null) { 645 mRescheduleReceiverResult.finish(); 646 mRescheduleReceiverResult = null; 647 } 648 } 649 } 650 651 /** 652 * This method is invoked by 653 * {@link RescheduleReceiver} 654 * after a call to {@link BroadcastReceiver#goAsync()}. Once {@link ForceStopRunnable} is done, 655 * we can safely call {@link BroadcastReceiver.PendingResult#finish()}. 656 */ 657 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) 658 public void setReschedulePendingResult( 659 BroadcastReceiver.@NonNull PendingResult rescheduleReceiverResult) { 660 synchronized (sLock) { 661 // if we have two broadcast in the row, finish old one and use new one 662 if (mRescheduleReceiverResult != null) { 663 mRescheduleReceiverResult.finish(); 664 } 665 mRescheduleReceiverResult = rescheduleReceiverResult; 666 if (mForceStopRunnableCompleted) { 667 mRescheduleReceiverResult.finish(); 668 mRescheduleReceiverResult = null; 669 } 670 } 671 } 672 673 /** 674 * Cancels workmanager's scope and closes the database 675 */ 676 public void closeDatabase() { 677 WorkManagerImplExtKt.close(this); 678 } 679 680 /** 681 * Tries to find a multi-process safe implementation for {@link WorkManager}. 682 */ 683 private void tryInitializeMultiProcessSupport() { 684 try { 685 Class<?> klass = Class.forName(REMOTE_WORK_MANAGER_CLIENT); 686 mRemoteWorkManager = (RemoteWorkManager) klass.getConstructor( 687 Context.class, WorkManagerImpl.class 688 ).newInstance(mContext, this); 689 } catch (Throwable throwable) { 690 Logger.get().debug(TAG, "Unable to initialize multi-process support", throwable); 691 } 692 } 693 694 @RequiresApi(24) 695 static class Api24Impl { 696 private Api24Impl() { 697 // This class is not instantiable. 698 } 699 700 static boolean isDeviceProtectedStorage(Context context) { 701 return context.isDeviceProtectedStorage(); 702 } 703 } 704 } 705