WorkManagerScheduler.java
/*
* Copyright (C) 2019 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.media3.exoplayer.workmanager;
import android.content.Context;
import android.content.Intent;
import androidx.annotation.RequiresApi;
import androidx.media3.common.MediaLibraryInfo;
import androidx.media3.common.util.Assertions;
import androidx.media3.common.util.Log;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
import androidx.media3.exoplayer.scheduler.Requirements;
import androidx.media3.exoplayer.scheduler.Scheduler;
import androidx.work.Constraints;
import androidx.work.Data;
import androidx.work.ExistingWorkPolicy;
import androidx.work.NetworkType;
import androidx.work.OneTimeWorkRequest;
import androidx.work.WorkManager;
import androidx.work.Worker;
import androidx.work.WorkerParameters;
/** A {@link Scheduler} that uses {@link WorkManager}. */
@UnstableApi
public final class WorkManagerScheduler implements Scheduler {
static {
MediaLibraryInfo.registerModule("media3.exoplayer.workmanager");
}
private static final String TAG = "WorkManagerScheduler";
private static final String KEY_SERVICE_ACTION = "service_action";
private static final String KEY_SERVICE_PACKAGE = "service_package";
private static final String KEY_REQUIREMENTS = "requirements";
private static final int SUPPORTED_REQUIREMENTS =
Requirements.NETWORK
| Requirements.NETWORK_UNMETERED
| (Util.SDK_INT >= 23 ? Requirements.DEVICE_IDLE : 0)
| Requirements.DEVICE_CHARGING
| Requirements.DEVICE_STORAGE_NOT_LOW;
private final WorkManager workManager;
private final String workName;
/**
* @deprecated Call {@link #WorkManagerScheduler(Context, String)} instead.
*/
@Deprecated
@SuppressWarnings("deprecation")
public WorkManagerScheduler(String workName) {
this.workName = workName;
workManager = WorkManager.getInstance();
}
/**
* @param context A context.
* @param workName A name for work scheduled by this instance. If the same name was used by a
* previous instance, anything scheduled by the previous instance will be canceled by this
* instance if {@link #schedule(Requirements, String, String)} or {@link #cancel()} are
* called.
*/
public WorkManagerScheduler(Context context, String workName) {
this.workName = workName;
workManager = WorkManager.getInstance(context.getApplicationContext());
}
@Override
public boolean schedule(Requirements requirements, String servicePackage, String serviceAction) {
Constraints constraints = buildConstraints(requirements);
Data inputData = buildInputData(requirements, servicePackage, serviceAction);
OneTimeWorkRequest workRequest = buildWorkRequest(constraints, inputData);
workManager.enqueueUniqueWork(workName, ExistingWorkPolicy.REPLACE, workRequest);
return true;
}
@Override
public boolean cancel() {
workManager.cancelUniqueWork(workName);
return true;
}
@Override
public Requirements getSupportedRequirements(Requirements requirements) {
return requirements.filterRequirements(SUPPORTED_REQUIREMENTS);
}
private static Constraints buildConstraints(Requirements requirements) {
Requirements filteredRequirements = requirements.filterRequirements(SUPPORTED_REQUIREMENTS);
if (!filteredRequirements.equals(requirements)) {
Log.w(
TAG,
"Ignoring unsupported requirements: "
+ (filteredRequirements.getRequirements() ^ requirements.getRequirements()));
}
Constraints.Builder builder = new Constraints.Builder();
if (requirements.isUnmeteredNetworkRequired()) {
builder.setRequiredNetworkType(NetworkType.UNMETERED);
} else if (requirements.isNetworkRequired()) {
builder.setRequiredNetworkType(NetworkType.CONNECTED);
} else {
builder.setRequiredNetworkType(NetworkType.NOT_REQUIRED);
}
if (Util.SDK_INT >= 23 && requirements.isIdleRequired()) {
setRequiresDeviceIdle(builder);
}
if (requirements.isChargingRequired()) {
builder.setRequiresCharging(true);
}
if (requirements.isStorageNotLowRequired()) {
builder.setRequiresStorageNotLow(true);
}
return builder.build();
}
@RequiresApi(23)
private static void setRequiresDeviceIdle(Constraints.Builder builder) {
builder.setRequiresDeviceIdle(true);
}
private static Data buildInputData(
Requirements requirements, String servicePackage, String serviceAction) {
Data.Builder builder = new Data.Builder();
builder.putInt(KEY_REQUIREMENTS, requirements.getRequirements());
builder.putString(KEY_SERVICE_PACKAGE, servicePackage);
builder.putString(KEY_SERVICE_ACTION, serviceAction);
return builder.build();
}
private static OneTimeWorkRequest buildWorkRequest(Constraints constraints, Data inputData) {
OneTimeWorkRequest.Builder builder = new OneTimeWorkRequest.Builder(SchedulerWorker.class);
builder.setConstraints(constraints);
builder.setInputData(inputData);
return builder.build();
}
/** A {@link Worker} that starts the target service if the requirements are met. */
// This class needs to be public so that WorkManager can instantiate it.
public static final class SchedulerWorker extends Worker {
private final WorkerParameters workerParams;
private final Context context;
public SchedulerWorker(Context context, WorkerParameters workerParams) {
super(context, workerParams);
this.workerParams = workerParams;
this.context = context;
}
@Override
public Result doWork() {
Data inputData = Assertions.checkNotNull(workerParams.getInputData());
Requirements requirements = new Requirements(inputData.getInt(KEY_REQUIREMENTS, 0));
int notMetRequirements = requirements.getNotMetRequirements(context);
if (notMetRequirements == 0) {
String serviceAction = Assertions.checkNotNull(inputData.getString(KEY_SERVICE_ACTION));
String servicePackage = Assertions.checkNotNull(inputData.getString(KEY_SERVICE_PACKAGE));
Intent intent = new Intent(serviceAction).setPackage(servicePackage);
Util.startForegroundService(context, intent);
return Result.success();
} else {
Log.w(TAG, "Requirements not met: " + notMetRequirements);
return Result.retry();
}
}
}
}