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