blob: bbc87a028038cdf3f9ba9f71d2ac172f3cb65d09 [file] [log] [blame]
/*
* Copyright 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.work.impl.background.systemjob;
import static androidx.work.impl.background.systemjob.SystemJobInfoConverter.EXTRA_WORK_SPEC_ID;
import android.app.Application;
import android.app.job.JobParameters;
import android.app.job.JobService;
import android.os.Build;
import android.os.PersistableBundle;
import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.annotation.RestrictTo;
import androidx.work.Logger;
import androidx.work.WorkerParameters;
import androidx.work.impl.ExecutionListener;
import androidx.work.impl.WorkManagerImpl;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
/**
* Service invoked by {@link android.app.job.JobScheduler} to run work tasks.
*
* @hide
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@RequiresApi(WorkManagerImpl.MIN_JOB_SCHEDULER_API_LEVEL)
public class SystemJobService extends JobService implements ExecutionListener {
private static final String TAG = Logger.tagWithPrefix("SystemJobService");
private WorkManagerImpl mWorkManagerImpl;
private final Map<String, JobParameters> mJobParameters = new HashMap<>();
@Override
public void onCreate() {
super.onCreate();
try {
mWorkManagerImpl = WorkManagerImpl.getInstance(getApplicationContext());
mWorkManagerImpl.getProcessor().addExecutionListener(this);
} catch (IllegalStateException e) {
// This can occur if...
// 1. The app is performing an auto-backup. Prior to O, JobScheduler could erroneously
// try to send commands to JobService in this state (b/32180780). Since neither
// Application#onCreate nor ContentProviders have run, WorkManager won't be
// initialized. In this case, we should ignore all JobScheduler commands and tell it
// to retry.
// 2. The app is not performing auto-backup. WorkManagerInitializer has been disabled
// but WorkManager is not manually initialized in Application#onCreate. This is a
// developer error and we should throw an Exception.
if (!Application.class.equals(getApplication().getClass())) {
// During auto-backup, we don't get a custom Application subclass. This code path
// indicates we are either performing auto-backup or the user never used a custom
// Application class (or both).
throw new IllegalStateException("WorkManager needs to be initialized via a "
+ "ContentProvider#onCreate() or an Application#onCreate().");
}
Logger.get().warning(TAG, "Could not find WorkManager instance; this may be because "
+ "an auto-backup is in progress. Ignoring JobScheduler commands for now. "
+ "Please make sure that you are initializing WorkManager if you have manually "
+ "disabled WorkManagerInitializer.");
}
}
@Override
public void onDestroy() {
super.onDestroy();
if (mWorkManagerImpl != null) {
mWorkManagerImpl.getProcessor().removeExecutionListener(this);
}
}
@Override
public boolean onStartJob(@NonNull JobParameters params) {
if (mWorkManagerImpl == null) {
Logger.get().debug(TAG, "WorkManager is not initialized; requesting retry.");
jobFinished(params, true);
return false;
}
String workSpecId = getWorkSpecIdFromJobParameters(params);
if (TextUtils.isEmpty(workSpecId)) {
Logger.get().error(TAG, "WorkSpec id not found!");
return false;
}
synchronized (mJobParameters) {
if (mJobParameters.containsKey(workSpecId)) {
// This condition may happen due to our workaround for an undesired behavior in API
// 23. See the documentation in {@link SystemJobScheduler#schedule}.
Logger.get().debug(TAG, String.format(
"Job is already being executed by SystemJobService: %s", workSpecId));
return false;
}
// We don't need to worry about the case where JobParams#isOverrideDeadlineExpired()
// returns true. This is because JobScheduler ensures that for PeriodicWork, constraints
// are actually met irrespective.
Logger.get().debug(TAG, String.format("onStartJob for %s", workSpecId));
mJobParameters.put(workSpecId, params);
}
WorkerParameters.RuntimeExtras runtimeExtras = null;
if (Build.VERSION.SDK_INT >= 24) {
runtimeExtras = new WorkerParameters.RuntimeExtras();
if (params.getTriggeredContentUris() != null) {
runtimeExtras.triggeredContentUris =
Arrays.asList(params.getTriggeredContentUris());
}
if (params.getTriggeredContentAuthorities() != null) {
runtimeExtras.triggeredContentAuthorities =
Arrays.asList(params.getTriggeredContentAuthorities());
}
if (Build.VERSION.SDK_INT >= 28) {
runtimeExtras.network = params.getNetwork();
}
}
// It is important that we return true, and hang on this onStartJob() request.
// The call to startWork() may no-op because the WorkRequest could have been picked up
// by the GreedyScheduler, and was already being executed. GreedyScheduler does not
// handle retries, and the Processor notifies all Schedulers about an intent to reschedule.
// In such cases, we rely on SystemJobService to ask for a reschedule by calling
// jobFinished(params, true) in onExecuted(...);
// For more information look at b/123211993
mWorkManagerImpl.startWork(workSpecId, runtimeExtras);
return true;
}
@Override
public boolean onStopJob(@NonNull JobParameters params) {
if (mWorkManagerImpl == null) {
Logger.get().debug(TAG, "WorkManager is not initialized; requesting retry.");
return true;
}
String workSpecId = getWorkSpecIdFromJobParameters(params);
if (TextUtils.isEmpty(workSpecId)) {
Logger.get().error(TAG, "WorkSpec id not found!");
return false;
}
Logger.get().debug(TAG, String.format("onStopJob for %s", workSpecId));
synchronized (mJobParameters) {
mJobParameters.remove(workSpecId);
}
mWorkManagerImpl.stopWork(workSpecId);
return !mWorkManagerImpl.getProcessor().isCancelled(workSpecId);
}
@Override
public void onExecuted(@NonNull String workSpecId, boolean needsReschedule) {
Logger.get().debug(TAG, String.format("%s executed on JobScheduler", workSpecId));
JobParameters parameters;
synchronized (mJobParameters) {
parameters = mJobParameters.remove(workSpecId);
}
if (parameters != null) {
jobFinished(parameters, needsReschedule);
}
}
@Nullable
@SuppressWarnings("ConstantConditions")
private static String getWorkSpecIdFromJobParameters(@NonNull JobParameters parameters) {
try {
PersistableBundle extras = parameters.getExtras();
if (extras != null && extras.containsKey(EXTRA_WORK_SPEC_ID)) {
return extras.getString(EXTRA_WORK_SPEC_ID);
}
} catch (NullPointerException e) {
// b/138441699: BaseBundle.getString sometimes throws an NPE. Ignore and return null.
}
return null;
}
}