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