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