DelayMetCommandHandler.java revision b5728f4e1a4b3f4f1fabf033b1363ca6b1cffdef
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.systemalarm; 18 19import android.content.Context; 20import android.content.Intent; 21import android.os.PowerManager; 22import android.support.annotation.NonNull; 23import android.support.annotation.Nullable; 24import android.support.annotation.RestrictTo; 25import android.support.annotation.WorkerThread; 26 27import androidx.work.impl.ExecutionListener; 28import androidx.work.impl.constraints.WorkConstraintsCallback; 29import androidx.work.impl.constraints.WorkConstraintsTracker; 30import androidx.work.impl.logger.Logger; 31import androidx.work.impl.model.WorkSpec; 32import androidx.work.impl.utils.WakeLocks; 33 34import java.util.Collections; 35import 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) 44public 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 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 79 public void onAllConstraintsMet(@NonNull List<String> ignored) { 80 Logger.debug(TAG, "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 102 public void onExecuted( 103 @NonNull String workSpecId, 104 boolean isSuccessful, 105 boolean needsReschedule) { 106 107 Logger.debug(TAG, "onExecuted %s, %s, %s", workSpecId, isSuccessful, needsReschedule); 108 cleanUp(); 109 110 if (mHasConstraints) { 111 // The WorkSpec had constraints. Once the execution of the worker is complete, 112 // we might need to disable constraint proxies which were previously enabled for 113 // this WorkSpec. Hence, trigger a constraints changed command. 114 Intent intent = CommandHandler.createConstraintsChangedIntent(mContext); 115 mDispatcher.postOnMainThread( 116 new SystemAlarmDispatcher.AddRunnable(mDispatcher, intent, mStartId)); 117 } 118 } 119 120 @Override 121 public void onTimeLimitExceeded(@NonNull String workSpecId) { 122 Logger.debug(TAG, "Exceeded time limits on execution for %s", workSpecId); 123 stopWork(); 124 } 125 126 @Override 127 public void onAllConstraintsNotMet(@NonNull List<String> ignored) { 128 stopWork(); 129 } 130 131 @WorkerThread 132 void handleProcessWork() { 133 mWakeLock = WakeLocks.newWakeLock( 134 mContext, 135 String.format("%s (%s)", mWorkSpecId, mStartId)); 136 Logger.debug(TAG, "Acquiring wakelock %s for WorkSpec %s", mWakeLock, mWorkSpecId); 137 mWakeLock.acquire(); 138 139 WorkSpec workSpec = mDispatcher.getWorkManager() 140 .getWorkDatabase() 141 .workSpecDao() 142 .getWorkSpec(mWorkSpecId); 143 144 // Keep track of whether the WorkSpec had constraints. This is useful for updating the 145 // state of constraint proxies when onExecuted(). 146 mHasConstraints = workSpec.hasConstraints(); 147 148 if (!mHasConstraints) { 149 Logger.debug(TAG, "No constraints for %s", mWorkSpecId); 150 onAllConstraintsMet(Collections.singletonList(mWorkSpecId)); 151 } else { 152 // Allow tracker to report constraint changes 153 mWorkConstraintsTracker.replace(Collections.singletonList(workSpec)); 154 } 155 } 156 157 private void stopWork() { 158 // No need to release the wake locks here. The stopWork command will eventually call 159 // onExecuted() if there is a corresponding pending delay met command handler; which in 160 // turn calls cleanUp(). 161 162 // Needs to be synchronized, as the stopWork() request can potentially come from the 163 // WorkTimer thread as well as the command executor service in SystemAlarmDispatcher. 164 synchronized (mLock) { 165 if (!mHasPendingStopWorkCommand) { 166 Logger.debug(TAG, "Stopping work for workspec %s", mWorkSpecId); 167 Intent stopWork = CommandHandler.createStopWorkIntent(mContext, mWorkSpecId); 168 mDispatcher.postOnMainThread( 169 new SystemAlarmDispatcher.AddRunnable(mDispatcher, stopWork, mStartId)); 170 // There are cases where the work may not have been enqueued at all, and therefore 171 // the processor is completely unaware of such a workSpecId in which case a 172 // reschedule should not happen. For e.g. DELAY_MET when constraints are not met, 173 // should not result in a reschedule. 174 if (mDispatcher.getProcessor().isEnqueued(mWorkSpecId)) { 175 Logger.debug(TAG, "WorkSpec %s needs to be rescheduled", mWorkSpecId); 176 Intent reschedule = CommandHandler.createScheduleWorkIntent(mContext, 177 mWorkSpecId); 178 mDispatcher.postOnMainThread( 179 new SystemAlarmDispatcher.AddRunnable(mDispatcher, reschedule, 180 mStartId)); 181 } else { 182 Logger.debug(TAG, "Processor does not have WorkSpec %s. No need to reschedule ", 183 mWorkSpecId); 184 } 185 mHasPendingStopWorkCommand = true; 186 } else { 187 Logger.debug(TAG, "Already stopped work for %s", mWorkSpecId); 188 } 189 } 190 } 191 192 private void cleanUp() { 193 // cleanUp() may occur from one of 2 threads. 194 // * In the call to bgProcessor.startWork() returns false, 195 // it probably means that the worker is already being processed 196 // so we just need to call cleanUp to release wakelocks on the command processor thread. 197 // * It could also happen on the onExecutionCompleted() pass of the bgProcessor. 198 // To avoid calling mWakeLock.release() twice, we are synchronizing here. 199 synchronized (mLock) { 200 // stop timers 201 mDispatcher.getWorkTimer().stopTimer(mWorkSpecId); 202 203 // release wake locks 204 if (mWakeLock != null && mWakeLock.isHeld()) { 205 Logger.debug(TAG, "Releasing wakelock %s for WorkSpec %s", mWakeLock, mWorkSpecId); 206 mWakeLock.release(); 207 } 208 } 209 } 210} 211