1/*
2 * Copyright 2018 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
17package androidx.work.impl.background.greedy;
18
19import android.content.Context;
20import android.os.Build;
21import android.support.annotation.NonNull;
22import android.support.annotation.RestrictTo;
23import android.support.annotation.VisibleForTesting;
24import android.util.Log;
25
26import androidx.work.State;
27import androidx.work.impl.ExecutionListener;
28import androidx.work.impl.Scheduler;
29import androidx.work.impl.WorkManagerImpl;
30import androidx.work.impl.constraints.WorkConstraintsCallback;
31import androidx.work.impl.constraints.WorkConstraintsTracker;
32import androidx.work.impl.model.WorkSpec;
33
34import java.util.ArrayList;
35import java.util.List;
36
37/**
38 * A greedy {@link Scheduler} that schedules unconstrained, non-timed work.  It intentionally does
39 * not acquire any WakeLocks, instead trying to brute-force them as time allows before the process
40 * gets killed.
41 *
42 * @hide
43 */
44@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
45public class GreedyScheduler implements Scheduler, WorkConstraintsCallback, ExecutionListener {
46
47    private static final String TAG = "GreedyScheduler";
48
49    private WorkManagerImpl mWorkManagerImpl;
50    private WorkConstraintsTracker mWorkConstraintsTracker;
51    private List<WorkSpec> mConstrainedWorkSpecs = new ArrayList<>();
52
53    public GreedyScheduler(Context context, WorkManagerImpl workManagerImpl) {
54        mWorkManagerImpl = workManagerImpl;
55        mWorkConstraintsTracker = new WorkConstraintsTracker(context, this);
56    }
57
58    @VisibleForTesting
59    public GreedyScheduler(WorkManagerImpl workManagerImpl,
60            WorkConstraintsTracker workConstraintsTracker) {
61        mWorkManagerImpl = workManagerImpl;
62        mWorkManagerImpl.getProcessor().addExecutionListener(this);
63        mWorkConstraintsTracker = workConstraintsTracker;
64    }
65
66    @Override
67    public synchronized void schedule(WorkSpec... workSpecs) {
68        int originalSize = mConstrainedWorkSpecs.size();
69
70        for (WorkSpec workSpec : workSpecs) {
71            if (workSpec.state == State.ENQUEUED
72                    && !workSpec.isPeriodic()
73                    && workSpec.initialDelay == 0L) {
74                if (workSpec.hasConstraints()) {
75                    // Exclude content URI triggers - we don't know how to handle them here so the
76                    // background scheduler should take care of them.
77                    if (Build.VERSION.SDK_INT < 24
78                            || !workSpec.constraints.hasContentUriTriggers()) {
79                        Log.d(TAG, String.format("Starting tracking for %s", workSpec.id));
80                        mConstrainedWorkSpecs.add(workSpec);
81                    }
82                } else {
83                    mWorkManagerImpl.startWork(workSpec.id);
84                }
85            }
86        }
87
88        if (originalSize != mConstrainedWorkSpecs.size()) {
89            mWorkConstraintsTracker.replace(mConstrainedWorkSpecs);
90        }
91    }
92
93    @Override
94    public synchronized void cancel(@NonNull String workSpecId) {
95        Log.d(TAG, String.format("Cancelling work ID %s", workSpecId));
96        mWorkManagerImpl.stopWork(workSpecId);
97        removeConstraintTrackingFor(workSpecId);
98    }
99
100    @Override
101    public synchronized void onAllConstraintsMet(@NonNull List<String> workSpecIds) {
102        for (String workSpecId : workSpecIds) {
103            Log.d(TAG, String.format("Constraints met: Scheduling work ID %s", workSpecId));
104            mWorkManagerImpl.startWork(workSpecId);
105        }
106    }
107
108    @Override
109    public synchronized void onAllConstraintsNotMet(@NonNull List<String> workSpecIds) {
110        for (String workSpecId : workSpecIds) {
111            Log.d(TAG, String.format("Constraints not met: Cancelling work ID %s", workSpecId));
112            mWorkManagerImpl.stopWork(workSpecId);
113        }
114    }
115
116    @Override
117    public synchronized void onExecuted(@NonNull String workSpecId,
118            boolean isSuccessful,
119            boolean needsReschedule) {
120        removeConstraintTrackingFor(workSpecId);
121    }
122
123    private synchronized void removeConstraintTrackingFor(@NonNull String workSpecId) {
124        for (int i = 0, size = mConstrainedWorkSpecs.size(); i < size; ++i) {
125            if (mConstrainedWorkSpecs.get(i).id.equals(workSpecId)) {
126                Log.d(TAG, String.format("Stopping tracking for %s", workSpecId));
127                mConstrainedWorkSpecs.remove(i);
128                mWorkConstraintsTracker.replace(mConstrainedWorkSpecs);
129                break;
130            }
131        }
132    }
133}
134