blob: 86b7edf5f39952f7a88a9308e864611d9ef5d28f [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;
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.RestrictTo;
import android.util.Log;
import androidx.work.Configuration;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Executor;
/**
* A Processor can intelligently schedule and execute work on demand.
*
* @hide
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public class Processor implements ExecutionListener {
private static final String TAG = "Processor";
private Context mAppContext;
private Configuration mConfiguration;
private WorkDatabase mWorkDatabase;
private Map<String, WorkerWrapper> mEnqueuedWorkMap;
private List<Scheduler> mSchedulers;
private Executor mExecutor;
private Set<String> mCancelledIds;
private final List<ExecutionListener> mOuterListeners;
public Processor(
Context appContext,
Configuration configuration,
WorkDatabase workDatabase,
List<Scheduler> schedulers,
Executor executor) {
mAppContext = appContext;
mConfiguration = configuration;
mWorkDatabase = workDatabase;
mEnqueuedWorkMap = new HashMap<>();
mSchedulers = schedulers;
mExecutor = executor;
mCancelledIds = new HashSet<>();
mOuterListeners = new ArrayList<>();
}
/**
* Starts a given unit of work in the background.
*
* @param id The work id to execute.
* @return {@code true} if the work was successfully enqueued for processing
*/
public synchronized boolean startWork(String id) {
return startWork(id, null);
}
/**
* Starts a given unit of work in the background.
*
* @param id The work id to execute.
* @param runtimeExtras The {@link Extras.RuntimeExtras} for this work, if any.
* @return {@code true} if the work was successfully enqueued for processing
*/
public synchronized boolean startWork(String id, Extras.RuntimeExtras runtimeExtras) {
// Work may get triggered multiple times if they have passing constraints and new work with
// those constraints are added.
if (mEnqueuedWorkMap.containsKey(id)) {
Log.d(TAG, String.format("Work %s is already enqueued for processing", id));
return false;
}
WorkerWrapper workWrapper =
new WorkerWrapper.Builder(mAppContext, mConfiguration, mWorkDatabase, id)
.withListener(this)
.withSchedulers(mSchedulers)
.withRuntimeExtras(runtimeExtras)
.build();
mEnqueuedWorkMap.put(id, workWrapper);
mExecutor.execute(workWrapper);
Log.d(TAG, String.format("%s: processing %s", getClass().getSimpleName(), id));
return true;
}
/**
* Stops a unit of work.
*
* @param id The work id to stop
* @return {@code true} if the work was stopped successfully
*/
public synchronized boolean stopWork(String id) {
Log.d(TAG, String.format("Processor stopping %s", id));
WorkerWrapper wrapper = mEnqueuedWorkMap.remove(id);
if (wrapper != null) {
wrapper.interrupt(false);
Log.d(TAG, String.format("WorkerWrapper stopped for %s", id));
return true;
}
Log.d(TAG, String.format("WorkerWrapper could not be found for %s", id));
return false;
}
/**
* Stops a unit of work and marks it as cancelled.
*
* @param id The work id to stop and cancel
* @return {@code true} if the work was stopped successfully
*/
public synchronized boolean stopAndCancelWork(String id) {
Log.d(TAG, String.format("Processor cancelling %s", id));
mCancelledIds.add(id);
WorkerWrapper wrapper = mEnqueuedWorkMap.remove(id);
if (wrapper != null) {
wrapper.interrupt(true);
Log.d(TAG, String.format("WorkerWrapper cancelled for %s", id));
return true;
}
Log.d(TAG, String.format("WorkerWrapper could not be found for %s", id));
return false;
}
/**
* Determines if the given {@code id} is marked as cancelled.
*
* @param id The work id to query
* @return {@code true} if the id has already been marked as cancelled
*/
public synchronized boolean isCancelled(String id) {
return mCancelledIds.contains(id);
}
/**
* @return {@code true} if the processor has work to process.
*/
public synchronized boolean hasWork() {
return !mEnqueuedWorkMap.isEmpty();
}
/**
* @param workSpecId The {@link androidx.work.impl.model.WorkSpec} id
* @return {@code true} if the id was enqueued in the processor.
*/
public synchronized boolean isEnqueued(@NonNull String workSpecId) {
return mEnqueuedWorkMap.containsKey(workSpecId);
}
/**
* Adds an {@link ExecutionListener} to track when work finishes.
*
* @param executionListener The {@link ExecutionListener} to add
*/
public synchronized void addExecutionListener(ExecutionListener executionListener) {
// TODO(sumir): Let's get some synchronization guarantees here.
mOuterListeners.add(executionListener);
}
/**
* Removes a tracked {@link ExecutionListener}.
*
* @param executionListener The {@link ExecutionListener} to remove
*/
public synchronized void removeExecutionListener(ExecutionListener executionListener) {
// TODO(sumir): Let's get some synchronization guarantees here.
mOuterListeners.remove(executionListener);
}
@Override
public synchronized void onExecuted(
@NonNull String workSpecId,
boolean isSuccessful,
boolean needsReschedule) {
mEnqueuedWorkMap.remove(workSpecId);
Log.d(TAG, String.format("%s %s executed; isSuccessful = %s, reschedule = %s",
getClass().getSimpleName(), workSpecId, isSuccessful, needsReschedule));
// TODO(sumir): Let's get some synchronization guarantees here.
for (ExecutionListener executionListener : mOuterListeners) {
executionListener.onExecuted(workSpecId, isSuccessful, needsReschedule);
}
}
}