1 /* 2 * Copyright 2017 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 package androidx.work.impl; 17 18 import android.content.Context; 19 import android.support.annotation.NonNull; 20 import android.support.annotation.RestrictTo; 21 import android.util.Log; 22 23 import androidx.work.Configuration; 24 25 import java.util.ArrayList; 26 import java.util.HashMap; 27 import java.util.HashSet; 28 import java.util.List; 29 import java.util.Map; 30 import java.util.Set; 31 import java.util.concurrent.Executor; 32 33 /** 34 * A Processor can intelligently schedule and execute work on demand. 35 * 36 * @hide 37 */ 38 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) 39 public class Processor implements ExecutionListener { 40 private static final String TAG = "Processor"; 41 42 private Context mAppContext; 43 private Configuration mConfiguration; 44 private WorkDatabase mWorkDatabase; 45 private Map<String, WorkerWrapper> mEnqueuedWorkMap; 46 private List<Scheduler> mSchedulers; 47 private Executor mExecutor; 48 49 private Set<String> mCancelledIds; 50 51 private final List<ExecutionListener> mOuterListeners; 52 Processor( Context appContext, Configuration configuration, WorkDatabase workDatabase, List<Scheduler> schedulers, Executor executor)53 public Processor( 54 Context appContext, 55 Configuration configuration, 56 WorkDatabase workDatabase, 57 List<Scheduler> schedulers, 58 Executor executor) { 59 mAppContext = appContext; 60 mConfiguration = configuration; 61 mWorkDatabase = workDatabase; 62 mEnqueuedWorkMap = new HashMap<>(); 63 mSchedulers = schedulers; 64 mExecutor = executor; 65 mCancelledIds = new HashSet<>(); 66 mOuterListeners = new ArrayList<>(); 67 } 68 69 /** 70 * Starts a given unit of work in the background. 71 * 72 * @param id The work id to execute. 73 * @return {@code true} if the work was successfully enqueued for processing 74 */ startWork(String id)75 public synchronized boolean startWork(String id) { 76 return startWork(id, null); 77 } 78 79 /** 80 * Starts a given unit of work in the background. 81 * 82 * @param id The work id to execute. 83 * @param runtimeExtras The {@link Extras.RuntimeExtras} for this work, if any. 84 * @return {@code true} if the work was successfully enqueued for processing 85 */ startWork(String id, Extras.RuntimeExtras runtimeExtras)86 public synchronized boolean startWork(String id, Extras.RuntimeExtras runtimeExtras) { 87 // Work may get triggered multiple times if they have passing constraints and new work with 88 // those constraints are added. 89 if (mEnqueuedWorkMap.containsKey(id)) { 90 Log.d(TAG, String.format("Work %s is already enqueued for processing", id)); 91 return false; 92 } 93 94 WorkerWrapper workWrapper = 95 new WorkerWrapper.Builder(mAppContext, mConfiguration, mWorkDatabase, id) 96 .withListener(this) 97 .withSchedulers(mSchedulers) 98 .withRuntimeExtras(runtimeExtras) 99 .build(); 100 mEnqueuedWorkMap.put(id, workWrapper); 101 mExecutor.execute(workWrapper); 102 Log.d(TAG, String.format("%s: processing %s", getClass().getSimpleName(), id)); 103 return true; 104 } 105 106 /** 107 * Stops a unit of work. 108 * 109 * @param id The work id to stop 110 * @return {@code true} if the work was stopped successfully 111 */ stopWork(String id)112 public synchronized boolean stopWork(String id) { 113 Log.d(TAG, String.format("Processor stopping %s", id)); 114 WorkerWrapper wrapper = mEnqueuedWorkMap.remove(id); 115 if (wrapper != null) { 116 wrapper.interrupt(false); 117 Log.d(TAG, String.format("WorkerWrapper stopped for %s", id)); 118 return true; 119 } 120 Log.d(TAG, String.format("WorkerWrapper could not be found for %s", id)); 121 return false; 122 } 123 124 /** 125 * Stops a unit of work and marks it as cancelled. 126 * 127 * @param id The work id to stop and cancel 128 * @return {@code true} if the work was stopped successfully 129 */ stopAndCancelWork(String id)130 public synchronized boolean stopAndCancelWork(String id) { 131 Log.d(TAG, String.format("Processor cancelling %s", id)); 132 mCancelledIds.add(id); 133 WorkerWrapper wrapper = mEnqueuedWorkMap.remove(id); 134 if (wrapper != null) { 135 wrapper.interrupt(true); 136 Log.d(TAG, String.format("WorkerWrapper cancelled for %s", id)); 137 return true; 138 } 139 Log.d(TAG, String.format("WorkerWrapper could not be found for %s", id)); 140 return false; 141 } 142 143 /** 144 * Determines if the given {@code id} is marked as cancelled. 145 * 146 * @param id The work id to query 147 * @return {@code true} if the id has already been marked as cancelled 148 */ isCancelled(String id)149 public synchronized boolean isCancelled(String id) { 150 return mCancelledIds.contains(id); 151 } 152 153 /** 154 * @return {@code true} if the processor has work to process. 155 */ hasWork()156 public synchronized boolean hasWork() { 157 return !mEnqueuedWorkMap.isEmpty(); 158 } 159 160 /** 161 * @param workSpecId The {@link androidx.work.impl.model.WorkSpec} id 162 * @return {@code true} if the id was enqueued in the processor. 163 */ isEnqueued(@onNull String workSpecId)164 public synchronized boolean isEnqueued(@NonNull String workSpecId) { 165 return mEnqueuedWorkMap.containsKey(workSpecId); 166 } 167 168 /** 169 * Adds an {@link ExecutionListener} to track when work finishes. 170 * 171 * @param executionListener The {@link ExecutionListener} to add 172 */ addExecutionListener(ExecutionListener executionListener)173 public synchronized void addExecutionListener(ExecutionListener executionListener) { 174 // TODO(sumir): Let's get some synchronization guarantees here. 175 mOuterListeners.add(executionListener); 176 } 177 178 /** 179 * Removes a tracked {@link ExecutionListener}. 180 * 181 * @param executionListener The {@link ExecutionListener} to remove 182 */ removeExecutionListener(ExecutionListener executionListener)183 public synchronized void removeExecutionListener(ExecutionListener executionListener) { 184 // TODO(sumir): Let's get some synchronization guarantees here. 185 mOuterListeners.remove(executionListener); 186 } 187 188 @Override onExecuted( @onNull String workSpecId, boolean isSuccessful, boolean needsReschedule)189 public synchronized void onExecuted( 190 @NonNull String workSpecId, 191 boolean isSuccessful, 192 boolean needsReschedule) { 193 194 mEnqueuedWorkMap.remove(workSpecId); 195 Log.d(TAG, String.format("%s %s executed; isSuccessful = %s, reschedule = %s", 196 getClass().getSimpleName(), workSpecId, isSuccessful, needsReschedule)); 197 198 // TODO(sumir): Let's get some synchronization guarantees here. 199 for (ExecutionListener executionListener : mOuterListeners) { 200 executionListener.onExecuted(workSpecId, isSuccessful, needsReschedule); 201 } 202 } 203 } 204