1 /*
2  * Copyright 2018 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 androidx.work.impl.utils.PackageManagerHelper.setComponentEnabled;
20 
21 import android.content.Context;
22 import android.os.Build;
23 import android.support.annotation.NonNull;
24 import android.support.annotation.RestrictTo;
25 import android.support.annotation.VisibleForTesting;
26 import android.util.Log;
27 
28 import androidx.work.Configuration;
29 import androidx.work.impl.background.systemalarm.SystemAlarmScheduler;
30 import androidx.work.impl.background.systemalarm.SystemAlarmService;
31 import androidx.work.impl.background.systemjob.SystemJobScheduler;
32 import androidx.work.impl.background.systemjob.SystemJobService;
33 import androidx.work.impl.model.WorkSpec;
34 import androidx.work.impl.model.WorkSpecDao;
35 
36 import java.lang.reflect.InvocationTargetException;
37 import java.util.List;
38 
39 /**
40  * Helper methods for {@link Scheduler}s.
41  *
42  * Helps schedule {@link androidx.work.impl.model.WorkSpec}s while enforcing
43  * {@link Scheduler#MAX_SCHEDULER_LIMIT}s.
44  *
45  * @hide
46  */
47 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
48 public class Schedulers {
49 
50     private static final String TAG = "Schedulers";
51     private static final String FIREBASE_JOB_SCHEDULER_CLASSNAME =
52             "androidx.work.impl.background.firebase.FirebaseJobScheduler";
53 
54     @VisibleForTesting
55     static final String FIREBASE_JOB_SERVICE_CLASSNAME =
56             "androidx.work.impl.background.firebase.FirebaseJobService";
57 
58     /**
59      * Schedules {@link WorkSpec}s while honoring the {@link Scheduler#MAX_SCHEDULER_LIMIT}.
60      *
61      * @param workDatabase The {@link WorkDatabase}.
62      * @param schedulers   The {@link List} of {@link Scheduler}s to delegate to.
63      */
schedule( @onNull Configuration configuration, @NonNull WorkDatabase workDatabase, List<Scheduler> schedulers)64     public static void schedule(
65             @NonNull Configuration configuration,
66             @NonNull WorkDatabase workDatabase,
67             List<Scheduler> schedulers) {
68 
69         WorkSpecDao workSpecDao = workDatabase.workSpecDao();
70         List<WorkSpec> eligibleWorkSpecs =
71                 workSpecDao.getEligibleWorkForScheduling(
72                         configuration.getMaxSchedulerLimit());
73         scheduleInternal(workDatabase, schedulers, eligibleWorkSpecs);
74     }
75 
scheduleInternal( @onNull WorkDatabase workDatabase, List<Scheduler> schedulers, List<WorkSpec> workSpecs)76     private static void scheduleInternal(
77             @NonNull WorkDatabase workDatabase,
78             List<Scheduler> schedulers,
79             List<WorkSpec> workSpecs) {
80 
81         if (workSpecs == null || schedulers == null) {
82             return;
83         }
84 
85         long now = System.currentTimeMillis();
86         WorkSpecDao workSpecDao = workDatabase.workSpecDao();
87         // Mark all the WorkSpecs as scheduled.
88         // Calls to Scheduler#schedule() could potentially result in more schedules
89         // on a separate thread. Therefore, this needs to be done first.
90         workDatabase.beginTransaction();
91         try {
92             for (WorkSpec workSpec : workSpecs) {
93                 workSpecDao.markWorkSpecScheduled(workSpec.id, now);
94             }
95             workDatabase.setTransactionSuccessful();
96         } finally {
97             workDatabase.endTransaction();
98         }
99         WorkSpec[] eligibleWorkSpecsArray = workSpecs.toArray(new WorkSpec[0]);
100         // Delegate to the underlying scheduler.
101         for (Scheduler scheduler : schedulers) {
102             scheduler.schedule(eligibleWorkSpecsArray);
103         }
104     }
105 
createBestAvailableBackgroundScheduler( @onNull Context context, @NonNull WorkManagerImpl workManager)106     static @NonNull Scheduler createBestAvailableBackgroundScheduler(
107             @NonNull Context context,
108             @NonNull WorkManagerImpl workManager) {
109 
110         Scheduler scheduler;
111         boolean enableFirebaseJobService = false;
112         boolean enableSystemAlarmService = false;
113 
114         if (Build.VERSION.SDK_INT >= WorkManagerImpl.MIN_JOB_SCHEDULER_API_LEVEL) {
115             scheduler = new SystemJobScheduler(context, workManager);
116             setComponentEnabled(context, SystemJobService.class, true);
117             Log.d(TAG, "Created SystemJobScheduler and enabled SystemJobService");
118         } else {
119             try {
120                 scheduler = tryCreateFirebaseJobScheduler(context);
121                 enableFirebaseJobService = true;
122                 Log.d(TAG, "Created FirebaseJobScheduler");
123             } catch (Exception e) {
124                 // Also catches the exception thrown if Play Services was not found on the device.
125                 scheduler = new SystemAlarmScheduler(context);
126                 enableSystemAlarmService = true;
127                 Log.d(TAG, "Created SystemAlarmScheduler");
128             }
129         }
130 
131         try {
132             Class firebaseJobServiceClass = Class.forName(FIREBASE_JOB_SERVICE_CLASSNAME);
133             setComponentEnabled(context, firebaseJobServiceClass, enableFirebaseJobService);
134         } catch (ClassNotFoundException e) {
135             // Do nothing.
136         }
137 
138         setComponentEnabled(context, SystemAlarmService.class, enableSystemAlarmService);
139 
140         return scheduler;
141     }
142 
143     @NonNull
tryCreateFirebaseJobScheduler(@onNull Context context)144     private static Scheduler tryCreateFirebaseJobScheduler(@NonNull Context context)
145             throws ClassNotFoundException, IllegalAccessException, InstantiationException,
146             InvocationTargetException, NoSuchMethodException {
147         Class<?> firebaseJobSchedulerClass = Class.forName(FIREBASE_JOB_SCHEDULER_CLASSNAME);
148         return (Scheduler) firebaseJobSchedulerClass
149                 .getConstructor(Context.class)
150                 .newInstance(context);
151     }
152 
Schedulers()153     private Schedulers() {
154     }
155 }
156