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 17 package androidx.work.impl.background.systemalarm; 18 19 import android.content.Context; 20 import android.content.Intent; 21 import android.os.PowerManager; 22 import android.support.annotation.NonNull; 23 import android.support.annotation.Nullable; 24 import android.support.annotation.RestrictTo; 25 import android.support.annotation.WorkerThread; 26 import android.util.Log; 27 28 import androidx.work.impl.ExecutionListener; 29 import androidx.work.impl.constraints.WorkConstraintsCallback; 30 import androidx.work.impl.constraints.WorkConstraintsTracker; 31 import androidx.work.impl.model.WorkSpec; 32 import androidx.work.impl.utils.WakeLocks; 33 34 import java.util.Collections; 35 import java.util.List; 36 37 /** 38 * This is a command handler which attempts to run a work spec given its id. 39 * Also handles constraints gracefully. 40 * 41 * @hide 42 */ 43 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) 44 public class DelayMetCommandHandler implements 45 WorkConstraintsCallback, 46 ExecutionListener, 47 WorkTimer.TimeLimitExceededListener { 48 49 private static final String TAG = "DelayMetCommandHandler"; 50 51 private final Context mContext; 52 private final int mStartId; 53 private final String mWorkSpecId; 54 private final SystemAlarmDispatcher mDispatcher; 55 private final WorkConstraintsTracker mWorkConstraintsTracker; 56 private final Object mLock; 57 private boolean mHasPendingStopWorkCommand; 58 59 @Nullable private PowerManager.WakeLock mWakeLock; 60 private boolean mHasConstraints; 61 DelayMetCommandHandler( @onNull Context context, int startId, @NonNull String workSpecId, @NonNull SystemAlarmDispatcher dispatcher)62 DelayMetCommandHandler( 63 @NonNull Context context, 64 int startId, 65 @NonNull String workSpecId, 66 @NonNull SystemAlarmDispatcher dispatcher) { 67 68 mContext = context; 69 mStartId = startId; 70 mDispatcher = dispatcher; 71 mWorkSpecId = workSpecId; 72 mWorkConstraintsTracker = new WorkConstraintsTracker(mContext, this); 73 mHasConstraints = false; 74 mHasPendingStopWorkCommand = false; 75 mLock = new Object(); 76 } 77 78 @Override onAllConstraintsMet(@onNull List<String> ignored)79 public void onAllConstraintsMet(@NonNull List<String> ignored) { 80 Log.d(TAG, String.format("onAllConstraintsMet for %s", mWorkSpecId)); 81 // Constraints met, schedule execution 82 83 // Not using WorkManagerImpl#startWork() here because we need to know if the processor 84 // actually enqueued the work here. 85 // TODO(rahulrav@) Once WorkManagerImpl provides a callback for acknowledging if 86 // work was enqueued, call WorkManagerImpl#startWork(). 87 boolean isEnqueued = mDispatcher.getProcessor().startWork(mWorkSpecId); 88 89 if (isEnqueued) { 90 // setup timers to enforce quotas on workers that have 91 // been enqueued 92 mDispatcher.getWorkTimer() 93 .startTimer(mWorkSpecId, CommandHandler.WORK_PROCESSING_TIME_IN_MS, this); 94 } else { 95 // if we did not actually enqueue the work, it was enqueued before 96 // cleanUp and pretend this never happened. 97 cleanUp(); 98 } 99 } 100 101 @Override onExecuted( @onNull String workSpecId, boolean isSuccessful, boolean needsReschedule)102 public void onExecuted( 103 @NonNull String workSpecId, 104 boolean isSuccessful, 105 boolean needsReschedule) { 106 107 Log.d(TAG, String.format( 108 "onExecuted %s, %s, %s", workSpecId, isSuccessful, needsReschedule)); 109 110 cleanUp(); 111 112 if (mHasConstraints) { 113 // The WorkSpec had constraints. Once the execution of the worker is complete, 114 // we might need to disable constraint proxies which were previously enabled for 115 // this WorkSpec. Hence, trigger a constraints changed command. 116 Intent intent = CommandHandler.createConstraintsChangedIntent(mContext); 117 mDispatcher.postOnMainThread( 118 new SystemAlarmDispatcher.AddRunnable(mDispatcher, intent, mStartId)); 119 } 120 } 121 122 @Override onTimeLimitExceeded(@onNull String workSpecId)123 public void onTimeLimitExceeded(@NonNull String workSpecId) { 124 Log.d(TAG, String.format("Exceeded time limits on execution for %s", workSpecId)); 125 stopWork(); 126 } 127 128 @Override onAllConstraintsNotMet(@onNull List<String> ignored)129 public void onAllConstraintsNotMet(@NonNull List<String> ignored) { 130 stopWork(); 131 } 132 133 @WorkerThread handleProcessWork()134 void handleProcessWork() { 135 mWakeLock = WakeLocks.newWakeLock( 136 mContext, 137 String.format("%s (%s)", mWorkSpecId, mStartId)); 138 Log.d(TAG, String.format("Acquiring wakelock %s for WorkSpec %s", mWakeLock, mWorkSpecId)); 139 mWakeLock.acquire(); 140 141 WorkSpec workSpec = mDispatcher.getWorkManager() 142 .getWorkDatabase() 143 .workSpecDao() 144 .getWorkSpec(mWorkSpecId); 145 146 // Keep track of whether the WorkSpec had constraints. This is useful for updating the 147 // state of constraint proxies when onExecuted(). 148 mHasConstraints = workSpec.hasConstraints(); 149 150 if (!mHasConstraints) { 151 Log.d(TAG, String.format("No constraints for %s", mWorkSpecId)); 152 onAllConstraintsMet(Collections.singletonList(mWorkSpecId)); 153 } else { 154 // Allow tracker to report constraint changes 155 mWorkConstraintsTracker.replace(Collections.singletonList(workSpec)); 156 } 157 } 158 stopWork()159 private void stopWork() { 160 // No need to release the wake locks here. The stopWork command will eventually call 161 // onExecuted() if there is a corresponding pending delay met command handler; which in 162 // turn calls cleanUp(). 163 164 // Needs to be synchronized, as the stopWork() request can potentially come from the 165 // WorkTimer thread as well as the command executor service in SystemAlarmDispatcher. 166 synchronized (mLock) { 167 if (!mHasPendingStopWorkCommand) { 168 Log.d(TAG, String.format("Stopping work for workspec %s", mWorkSpecId)); 169 Intent stopWork = CommandHandler.createStopWorkIntent(mContext, mWorkSpecId); 170 mDispatcher.postOnMainThread( 171 new SystemAlarmDispatcher.AddRunnable(mDispatcher, stopWork, mStartId)); 172 // There are cases where the work may not have been enqueued at all, and therefore 173 // the processor is completely unaware of such a workSpecId in which case a 174 // reschedule should not happen. For e.g. DELAY_MET when constraints are not met, 175 // should not result in a reschedule. 176 if (mDispatcher.getProcessor().isEnqueued(mWorkSpecId)) { 177 Log.d(TAG, String.format("WorkSpec %s needs to be rescheduled", mWorkSpecId)); 178 Intent reschedule = CommandHandler.createScheduleWorkIntent(mContext, 179 mWorkSpecId); 180 mDispatcher.postOnMainThread( 181 new SystemAlarmDispatcher.AddRunnable(mDispatcher, reschedule, 182 mStartId)); 183 } else { 184 Log.d(TAG, String.format( 185 "Processor does not have WorkSpec %s. No need to reschedule ", 186 mWorkSpecId)); 187 } 188 mHasPendingStopWorkCommand = true; 189 } else { 190 Log.d(TAG, String.format("Already stopped work for %s", mWorkSpecId)); 191 } 192 } 193 } 194 cleanUp()195 private void cleanUp() { 196 // cleanUp() may occur from one of 2 threads. 197 // * In the call to bgProcessor.startWork() returns false, 198 // it probably means that the worker is already being processed 199 // so we just need to call cleanUp to release wakelocks on the command processor thread. 200 // * It could also happen on the onExecutionCompleted() pass of the bgProcessor. 201 // To avoid calling mWakeLock.release() twice, we are synchronizing here. 202 synchronized (mLock) { 203 // stop timers 204 mDispatcher.getWorkTimer().stopTimer(mWorkSpecId); 205 206 // release wake locks 207 if (mWakeLock != null && mWakeLock.isHeld()) { 208 Log.d(TAG, String.format( 209 "Releasing wakelock %s for WorkSpec %s", mWakeLock, mWorkSpecId)); 210 mWakeLock.release(); 211 } 212 } 213 } 214 } 215