blob: bbc87a028038cdf3f9ba9f71d2ac172f3cb65d09 [file] [log] [blame]
Jan Clarin7a8d14a2017-09-14 16:18:12 -07001/*
Jan Clarin1ecbe022017-11-13 15:19:00 -08002 * Copyright 2017 The Android Open Source Project
Jan Clarin7a8d14a2017-09-14 16:18:12 -07003 *
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
Sumir Kataria564e4302018-02-14 11:22:30 -080017package androidx.work.impl.background.systemjob;
Jan Clarin7a8d14a2017-09-14 16:18:12 -070018
Rahul Ravikumara1957df2019-07-29 09:48:05 -070019import static androidx.work.impl.background.systemjob.SystemJobInfoConverter.EXTRA_WORK_SPEC_ID;
20
Sumir Katariade7fdf72018-07-02 15:09:37 -070021import android.app.Application;
Jan Clarin7a8d14a2017-09-14 16:18:12 -070022import android.app.job.JobParameters;
23import android.app.job.JobService;
Sumir Kataria681a8d82018-03-21 10:34:32 -070024import android.os.Build;
Jan Clarinfc844392017-11-13 13:52:57 -080025import android.os.PersistableBundle;
Xyan Bhatnagardc31aa82017-09-26 10:53:38 -070026import android.text.TextUtils;
Jan Clarin7a8d14a2017-09-14 16:18:12 -070027
Rahul Ravikumare5356662019-03-04 16:22:14 -080028import androidx.annotation.NonNull;
Rahul Ravikumara1957df2019-07-29 09:48:05 -070029import androidx.annotation.Nullable;
Rahul Ravikumare5356662019-03-04 16:22:14 -080030import androidx.annotation.RequiresApi;
31import androidx.annotation.RestrictTo;
Sumir Katariafd60d222018-06-22 16:23:41 -070032import androidx.work.Logger;
Sumir Katariaf82a3d62018-09-12 14:18:23 -070033import androidx.work.WorkerParameters;
Sumir Kataria564e4302018-02-14 11:22:30 -080034import androidx.work.impl.ExecutionListener;
35import androidx.work.impl.WorkManagerImpl;
Sumir Kataria564e4302018-02-14 11:22:30 -080036
Rahul Ravikumarb3a76ba2018-10-12 14:28:06 -070037import java.util.Arrays;
Sumir Katariab5728f42018-03-19 12:58:41 -070038import java.util.HashMap;
39import java.util.Map;
40
Jan Clarin7a8d14a2017-09-14 16:18:12 -070041/**
42 * Service invoked by {@link android.app.job.JobScheduler} to run work tasks.
Sumir Kataria28815092017-11-01 14:06:08 -070043 *
44 * @hide
Jan Clarin7a8d14a2017-09-14 16:18:12 -070045 */
Sumir Kataria28815092017-11-01 14:06:08 -070046@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
Jake Wharton2c8307172018-06-23 01:24:14 -040047@RequiresApi(WorkManagerImpl.MIN_JOB_SCHEDULER_API_LEVEL)
Sumir Kataria115eb6a2017-09-28 16:04:07 -070048public class SystemJobService extends JobService implements ExecutionListener {
Sumir Katariabce60532019-01-23 00:05:10 +000049 private static final String TAG = Logger.tagWithPrefix("SystemJobService");
Sumir Katariac458f4a2018-02-08 16:52:08 -080050 private WorkManagerImpl mWorkManagerImpl;
Sumir Katariac458f4a2018-02-08 16:52:08 -080051 private final Map<String, JobParameters> mJobParameters = new HashMap<>();
Jan Clarin9a45df62017-09-22 15:41:43 -070052
53 @Override
54 public void onCreate() {
55 super.onCreate();
Sumir Katariae14104e2019-06-24 10:50:57 -070056 try {
57 mWorkManagerImpl = WorkManagerImpl.getInstance(getApplicationContext());
58 mWorkManagerImpl.getProcessor().addExecutionListener(this);
59 } catch (IllegalStateException e) {
Sumir Katariade7fdf72018-07-02 15:09:37 -070060 // This can occur if...
61 // 1. The app is performing an auto-backup. Prior to O, JobScheduler could erroneously
62 // try to send commands to JobService in this state (b/32180780). Since neither
63 // Application#onCreate nor ContentProviders have run, WorkManager won't be
64 // initialized. In this case, we should ignore all JobScheduler commands and tell it
65 // to retry.
66 // 2. The app is not performing auto-backup. WorkManagerInitializer has been disabled
67 // but WorkManager is not manually initialized in Application#onCreate. This is a
68 // developer error and we should throw an Exception.
69 if (!Application.class.equals(getApplication().getClass())) {
70 // During auto-backup, we don't get a custom Application subclass. This code path
71 // indicates we are either performing auto-backup or the user never used a custom
72 // Application class (or both).
73 throw new IllegalStateException("WorkManager needs to be initialized via a "
74 + "ContentProvider#onCreate() or an Application#onCreate().");
75 }
Sumir Katariae14104e2019-06-24 10:50:57 -070076 Logger.get().warning(TAG, "Could not find WorkManager instance; this may be because "
77 + "an auto-backup is in progress. Ignoring JobScheduler commands for now. "
78 + "Please make sure that you are initializing WorkManager if you have manually "
Sumir Katariade7fdf72018-07-02 15:09:37 -070079 + "disabled WorkManagerInitializer.");
Sumir Katariade7fdf72018-07-02 15:09:37 -070080 }
Sumir Katariaf0d52dd2018-02-06 14:23:41 -080081 }
82
83 @Override
84 public void onDestroy() {
85 super.onDestroy();
Sumir Katariade7fdf72018-07-02 15:09:37 -070086 if (mWorkManagerImpl != null) {
87 mWorkManagerImpl.getProcessor().removeExecutionListener(this);
88 }
Jan Clarin9a45df62017-09-22 15:41:43 -070089 }
Jan Clarin7a8d14a2017-09-14 16:18:12 -070090
91 @Override
Rahul Ravikumara1957df2019-07-29 09:48:05 -070092 public boolean onStartJob(@NonNull JobParameters params) {
Sumir Katariade7fdf72018-07-02 15:09:37 -070093 if (mWorkManagerImpl == null) {
Sumir Katariabd134a02018-11-29 16:02:17 -080094 Logger.get().debug(TAG, "WorkManager is not initialized; requesting retry.");
Sumir Katariade7fdf72018-07-02 15:09:37 -070095 jobFinished(params, true);
96 return false;
97 }
98
Rahul Ravikumara1957df2019-07-29 09:48:05 -070099 String workSpecId = getWorkSpecIdFromJobParameters(params);
Xyan Bhatnagardc31aa82017-09-26 10:53:38 -0700100 if (TextUtils.isEmpty(workSpecId)) {
Sumir Katariabd134a02018-11-29 16:02:17 -0800101 Logger.get().error(TAG, "WorkSpec id not found!");
Xyan Bhatnagardc31aa82017-09-26 10:53:38 -0700102 return false;
103 }
Sumir Kataria5683c482017-10-25 17:03:07 -0700104
Sumir Katariac458f4a2018-02-08 16:52:08 -0800105 synchronized (mJobParameters) {
Sumir Katariac68c7cd2018-02-20 15:06:24 -0800106 if (mJobParameters.containsKey(workSpecId)) {
107 // This condition may happen due to our workaround for an undesired behavior in API
108 // 23. See the documentation in {@link SystemJobScheduler#schedule}.
Sumir Katariabd134a02018-11-29 16:02:17 -0800109 Logger.get().debug(TAG, String.format(
Rahul Ravikumar697d6a42018-04-16 15:44:09 -0700110 "Job is already being executed by SystemJobService: %s", workSpecId));
Sumir Katariac68c7cd2018-02-20 15:06:24 -0800111 return false;
112 }
113
Rahul Ravikumar2aa2b402018-07-18 13:23:21 -0700114 // We don't need to worry about the case where JobParams#isOverrideDeadlineExpired()
115 // returns true. This is because JobScheduler ensures that for PeriodicWork, constraints
116 // are actually met irrespective.
Sumir Katariac68c7cd2018-02-20 15:06:24 -0800117
Sumir Katariabd134a02018-11-29 16:02:17 -0800118 Logger.get().debug(TAG, String.format("onStartJob for %s", workSpecId));
Sumir Katariac458f4a2018-02-08 16:52:08 -0800119 mJobParameters.put(workSpecId, params);
120 }
Sumir Katariac68c7cd2018-02-20 15:06:24 -0800121
Sumir Katariaf82a3d62018-09-12 14:18:23 -0700122 WorkerParameters.RuntimeExtras runtimeExtras = null;
Sumir Kataria681a8d82018-03-21 10:34:32 -0700123 if (Build.VERSION.SDK_INT >= 24) {
Sumir Katariaf82a3d62018-09-12 14:18:23 -0700124 runtimeExtras = new WorkerParameters.RuntimeExtras();
Rahul Ravikumarb3a76ba2018-10-12 14:28:06 -0700125 if (params.getTriggeredContentUris() != null) {
126 runtimeExtras.triggeredContentUris =
127 Arrays.asList(params.getTriggeredContentUris());
Sumir Kataria681a8d82018-03-21 10:34:32 -0700128 }
Rahul Ravikumarb3a76ba2018-10-12 14:28:06 -0700129 if (params.getTriggeredContentAuthorities() != null) {
130 runtimeExtras.triggeredContentAuthorities =
131 Arrays.asList(params.getTriggeredContentAuthorities());
132 }
Sumir Katariaeda66d62018-05-31 15:24:48 -0700133 if (Build.VERSION.SDK_INT >= 28) {
134 runtimeExtras.network = params.getNetwork();
135 }
Sumir Kataria681a8d82018-03-21 10:34:32 -0700136 }
137
Rahul Ravikumare3abf462019-01-24 10:24:46 -0800138 // It is important that we return true, and hang on this onStartJob() request.
139 // The call to startWork() may no-op because the WorkRequest could have been picked up
140 // by the GreedyScheduler, and was already being executed. GreedyScheduler does not
141 // handle retries, and the Processor notifies all Schedulers about an intent to reschedule.
142 // In such cases, we rely on SystemJobService to ask for a reschedule by calling
143 // jobFinished(params, true) in onExecuted(...);
144 // For more information look at b/123211993
Sumir Katariabce60532019-01-23 00:05:10 +0000145 mWorkManagerImpl.startWork(workSpecId, runtimeExtras);
Jan Clarin7a8d14a2017-09-14 16:18:12 -0700146 return true;
147 }
148
149 @Override
Rahul Ravikumara1957df2019-07-29 09:48:05 -0700150 public boolean onStopJob(@NonNull JobParameters params) {
Sumir Katariade7fdf72018-07-02 15:09:37 -0700151 if (mWorkManagerImpl == null) {
Sumir Katariabd134a02018-11-29 16:02:17 -0800152 Logger.get().debug(TAG, "WorkManager is not initialized; requesting retry.");
Sumir Katariade7fdf72018-07-02 15:09:37 -0700153 return true;
154 }
155
Rahul Ravikumara1957df2019-07-29 09:48:05 -0700156 String workSpecId = getWorkSpecIdFromJobParameters(params);
Xyan Bhatnagardc31aa82017-09-26 10:53:38 -0700157 if (TextUtils.isEmpty(workSpecId)) {
Sumir Katariabd134a02018-11-29 16:02:17 -0800158 Logger.get().error(TAG, "WorkSpec id not found!");
Xyan Bhatnagardc31aa82017-09-26 10:53:38 -0700159 return false;
160 }
Sumir Katariac458f4a2018-02-08 16:52:08 -0800161
Sumir Katariabd134a02018-11-29 16:02:17 -0800162 Logger.get().debug(TAG, String.format("onStopJob for %s", workSpecId));
Sumir Katariac458f4a2018-02-08 16:52:08 -0800163
164 synchronized (mJobParameters) {
165 mJobParameters.remove(workSpecId);
166 }
167 mWorkManagerImpl.stopWork(workSpecId);
Sumir Kataria137c84c2018-02-13 15:13:33 -0800168 return !mWorkManagerImpl.getProcessor().isCancelled(workSpecId);
Xyan Bhatnagardc31aa82017-09-26 10:53:38 -0700169 }
170
171 @Override
Sumir Kataria23845242018-09-05 13:54:15 -0700172 public void onExecuted(@NonNull String workSpecId, boolean needsReschedule) {
Sumir Katariabd134a02018-11-29 16:02:17 -0800173 Logger.get().debug(TAG, String.format("%s executed on JobScheduler", workSpecId));
Sumir Katariac458f4a2018-02-08 16:52:08 -0800174 JobParameters parameters;
175 synchronized (mJobParameters) {
Sumir Kataria26a1f5c2018-08-20 15:38:18 -0700176 parameters = mJobParameters.remove(workSpecId);
Sumir Katariac458f4a2018-02-08 16:52:08 -0800177 }
178 if (parameters != null) {
179 jobFinished(parameters, needsReschedule);
180 }
Jan Clarin7a8d14a2017-09-14 16:18:12 -0700181 }
Rahul Ravikumara1957df2019-07-29 09:48:05 -0700182
183 @Nullable
184 @SuppressWarnings("ConstantConditions")
185 private static String getWorkSpecIdFromJobParameters(@NonNull JobParameters parameters) {
186 try {
187 PersistableBundle extras = parameters.getExtras();
188 if (extras != null && extras.containsKey(EXTRA_WORK_SPEC_ID)) {
189 return extras.getString(EXTRA_WORK_SPEC_ID);
190 }
191 } catch (NullPointerException e) {
192 // b/138441699: BaseBundle.getString sometimes throws an NPE. Ignore and return null.
193 }
194 return null;
195 }
Jan Clarin7a8d14a2017-09-14 16:18:12 -0700196}