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