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