WorkSpec.java revision cc5ae8c86fd7aca80917de26548646b0a9acc498
1/*
2 * Copyright (C) 2017 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.model;
18
19import static androidx.work.BaseWork.MAX_BACKOFF_MILLIS;
20import static androidx.work.BaseWork.MIN_BACKOFF_MILLIS;
21import static androidx.work.PeriodicWork.MIN_PERIODIC_FLEX_MILLIS;
22import static androidx.work.PeriodicWork.MIN_PERIODIC_INTERVAL_MILLIS;
23import static androidx.work.State.ENQUEUED;
24
25import android.arch.persistence.room.ColumnInfo;
26import android.arch.persistence.room.Embedded;
27import android.arch.persistence.room.Entity;
28import android.arch.persistence.room.PrimaryKey;
29import android.arch.persistence.room.Relation;
30import android.support.annotation.NonNull;
31
32import java.util.List;
33
34import androidx.work.Arguments;
35import androidx.work.BackoffPolicy;
36import androidx.work.BaseWork;
37import androidx.work.Constraints;
38import androidx.work.State;
39import androidx.work.WorkStatus;
40import androidx.work.impl.logger.Logger;
41
42/**
43 * Stores information about a logical unit of work.
44 */
45@Entity
46public class WorkSpec {
47    private static final String TAG = "WorkSpec";
48
49    @ColumnInfo(name = "id")
50    @PrimaryKey
51    @NonNull
52    String mId;
53
54    @ColumnInfo(name = "state")
55    @NonNull
56    State mState = ENQUEUED;
57
58    @ColumnInfo(name = "worker_class_name")
59    @NonNull
60    String mWorkerClassName;
61
62    @ColumnInfo(name = "input_merger_class_name")
63    String mInputMergerClassName;
64
65    @ColumnInfo(name = "arguments")
66    @NonNull
67    Arguments mArguments = Arguments.EMPTY;
68
69    @ColumnInfo(name = "output")
70    @NonNull
71    Arguments mOutput = Arguments.EMPTY;
72
73    @ColumnInfo(name = "initial_delay")
74    long mInitialDelay;
75
76    @ColumnInfo(name = "interval_duration")
77    long mIntervalDuration;
78
79    @ColumnInfo(name = "flex_duration")
80    long mFlexDuration;
81
82    @Embedded
83    @NonNull
84    Constraints mConstraints = Constraints.NONE;
85
86    @ColumnInfo(name = "run_attempt_count")
87    int mRunAttemptCount;
88
89    // TODO(sumir): Should Backoff be disabled by default?
90    @ColumnInfo(name = "backoff_policy")
91    @NonNull
92    BackoffPolicy mBackoffPolicy = BackoffPolicy.EXPONENTIAL;
93
94    @ColumnInfo(name = "backoff_delay_duration")
95    long mBackoffDelayDuration = BaseWork.DEFAULT_BACKOFF_DELAY_MILLIS;
96
97    @ColumnInfo(name = "period_start_time")
98    long mPeriodStartTime;
99
100    @ColumnInfo(name = "minimum_retention_duration")
101    long mMinimumRetentionDuration;
102
103    public WorkSpec(@NonNull String id) {
104        mId = id;
105    }
106
107    @NonNull
108    public String getId() {
109        return mId;
110    }
111
112    public void setId(@NonNull String id) {
113        mId = id;
114    }
115
116    public @NonNull State getState() {
117        return mState;
118    }
119
120    public void setState(@NonNull State state) {
121        mState = state;
122    }
123
124    public @NonNull String getWorkerClassName() {
125        return mWorkerClassName;
126    }
127
128    public void setWorkerClassName(@NonNull String workerClassName) {
129        mWorkerClassName = workerClassName;
130    }
131
132    public String getInputMergerClassName() {
133        return mInputMergerClassName;
134    }
135
136    public void setInputMergerClassName(String inputMergerClassName) {
137        mInputMergerClassName = inputMergerClassName;
138    }
139
140    public @NonNull Arguments getArguments() {
141        return mArguments;
142    }
143
144    public void setArguments(@NonNull Arguments arguments) {
145        mArguments = arguments;
146    }
147
148    public @NonNull Arguments getOutput() {
149        return mOutput;
150    }
151
152    public void setOutput(@NonNull Arguments output) {
153        mOutput = output;
154    }
155
156    public @NonNull Constraints getConstraints() {
157        return mConstraints;
158    }
159
160    public void setConstraints(@NonNull Constraints constraints) {
161        mConstraints = constraints;
162    }
163
164    public @NonNull BackoffPolicy getBackoffPolicy() {
165        return mBackoffPolicy;
166    }
167
168    public void setBackoffPolicy(@NonNull BackoffPolicy backoffPolicy) {
169        mBackoffPolicy = backoffPolicy;
170    }
171
172    public long getBackoffDelayDuration() {
173        return mBackoffDelayDuration;
174    }
175
176    /**
177     * @param backoffDelayDuration The backoff delay duration in milliseconds
178     */
179    public void setBackoffDelayDuration(long backoffDelayDuration) {
180        if (backoffDelayDuration > MAX_BACKOFF_MILLIS) {
181            Logger.warn(TAG, "Backoff delay duration exceeds maximum value");
182            backoffDelayDuration = MAX_BACKOFF_MILLIS;
183        }
184        if (backoffDelayDuration < MIN_BACKOFF_MILLIS) {
185            Logger.warn(TAG, "Backoff delay duration less than minimum value");
186            backoffDelayDuration = MIN_BACKOFF_MILLIS;
187        }
188        mBackoffDelayDuration = backoffDelayDuration;
189    }
190
191    public long getInitialDelay() {
192        return mInitialDelay;
193    }
194
195    public void setInitialDelay(long initialDelay) {
196        mInitialDelay = initialDelay;
197    }
198
199    public boolean isPeriodic() {
200        return mIntervalDuration != 0L;
201    }
202
203    public boolean isBackedOff() {
204        return mState == ENQUEUED && mRunAttemptCount > 0;
205    }
206
207    /**
208     * Sets the periodic interval for this unit of work.
209     *
210     * @param intervalDuration The interval in milliseconds
211     */
212    public void setPeriodic(long intervalDuration) {
213        if (intervalDuration < MIN_PERIODIC_INTERVAL_MILLIS) {
214            Logger.warn(TAG, "Interval duration lesser than minimum allowed value; Changed to %s",
215                    MIN_PERIODIC_INTERVAL_MILLIS);
216            intervalDuration = MIN_PERIODIC_INTERVAL_MILLIS;
217        }
218        setPeriodic(intervalDuration, intervalDuration);
219    }
220
221    /**
222     * Sets the periodic interval for this unit of work.
223     *
224     * @param intervalDuration The interval in milliseconds
225     * @param flexDuration The flex duration in milliseconds
226     */
227    public void setPeriodic(long intervalDuration, long flexDuration) {
228        if (intervalDuration < MIN_PERIODIC_INTERVAL_MILLIS) {
229            Logger.warn(TAG, "Interval duration lesser than minimum allowed value; Changed to %s",
230                    MIN_PERIODIC_INTERVAL_MILLIS);
231            intervalDuration = MIN_PERIODIC_INTERVAL_MILLIS;
232        }
233        if (flexDuration < MIN_PERIODIC_FLEX_MILLIS) {
234            Logger.warn(TAG, "Flex duration lesser than minimum allowed value; Changed to %s",
235                    MIN_PERIODIC_FLEX_MILLIS);
236            flexDuration = MIN_PERIODIC_FLEX_MILLIS;
237        }
238        if (flexDuration > intervalDuration) {
239            Logger.warn(TAG, "Flex duration greater than interval duration; Changed to %s",
240                    intervalDuration);
241            flexDuration = intervalDuration;
242        }
243        mIntervalDuration = intervalDuration;
244        mFlexDuration = flexDuration;
245    }
246
247    public long getIntervalDuration() {
248        return mIntervalDuration;
249    }
250
251    public void setIntervalDuration(long intervalDuration) {
252        mIntervalDuration = intervalDuration;
253    }
254
255    public long getFlexDuration() {
256        return mFlexDuration;
257    }
258
259    public void setFlexDuration(long flexDuration) {
260        mFlexDuration = flexDuration;
261    }
262
263    public void setRunAttemptCount(int runAttemptCount) {
264        this.mRunAttemptCount = runAttemptCount;
265    }
266
267    public int getRunAttemptCount() {
268        return mRunAttemptCount;
269    }
270
271    /**
272     * For one-off work, this is the time that the work was unblocked by prerequisites.
273     * For periodic work, this is the time that the period started.
274     */
275    public long getPeriodStartTime() {
276        return mPeriodStartTime;
277    }
278
279    public void setPeriodStartTime(long periodStartTime) {
280        mPeriodStartTime = periodStartTime;
281    }
282
283    public long getMinimumRetentionDuration() {
284        return mMinimumRetentionDuration;
285    }
286
287    public void setMinimumRetentionDuration(long minimumRetentionDuration) {
288        mMinimumRetentionDuration = minimumRetentionDuration;
289    }
290
291    /**
292     * Calculates the UTC time at which this {@link WorkSpec} should be allowed to run.
293     * This method accounts for work that is backed off or periodic.
294     *
295     * If Backoff Policy is set to {@link BackoffPolicy#EXPONENTIAL}, then delay
296     * increases at an exponential rate with respect to the run attempt count and is capped at
297     * {@link BaseWork#MAX_BACKOFF_MILLIS}.
298     *
299     * If Backoff Policy is set to {@link BackoffPolicy#LINEAR}, then delay
300     * increases at an linear rate with respect to the run attempt count and is capped at
301     * {@link BaseWork#MAX_BACKOFF_MILLIS}.
302     *
303     * Based on {@see https://android.googlesource.com/platform/frameworks/base/+/master/services/core/java/com/android/server/job/JobSchedulerService.java#1125}
304     *
305     * Note that this runtime is for WorkManager internal use and may not match what the OS
306     * considers to be the next runtime.
307     *
308     * For jobs with constraints, this represents the earliest time at which constraints
309     * should be monitored for this work.
310     *
311     * For jobs without constraints, this represents the earliest time at which this work is
312     * allowed to run.
313     *
314     * @return UTC time at which this {@link WorkSpec} should be allowed to run.
315     */
316    public long calculateNextRunTime() {
317        if (isBackedOff()) {
318            boolean isLinearBackoff = (mBackoffPolicy == BackoffPolicy.LINEAR);
319            long delay = isLinearBackoff ? (mBackoffDelayDuration * mRunAttemptCount)
320                    : (long) Math.scalb(mBackoffDelayDuration, mRunAttemptCount - 1);
321            return mPeriodStartTime + Math.min(BaseWork.MAX_BACKOFF_MILLIS, delay);
322        } else if (isPeriodic()) {
323            return mPeriodStartTime + mIntervalDuration - mFlexDuration;
324        } else {
325            return mPeriodStartTime + mInitialDelay;
326        }
327    }
328
329    /**
330     * @return <code>true</code> if the {@link WorkSpec} has constraints.
331     */
332    public boolean hasConstraints() {
333        return !Constraints.NONE.equals(getConstraints());
334    }
335
336    @Override
337    public boolean equals(Object o) {
338        if (this == o) return true;
339        if (o == null || getClass() != o.getClass()) return false;
340
341        WorkSpec workSpec = (WorkSpec) o;
342
343        if (mInitialDelay != workSpec.mInitialDelay) return false;
344        if (mIntervalDuration != workSpec.mIntervalDuration) return false;
345        if (mFlexDuration != workSpec.mFlexDuration) return false;
346        if (mRunAttemptCount != workSpec.mRunAttemptCount) return false;
347        if (mBackoffDelayDuration != workSpec.mBackoffDelayDuration) return false;
348        if (mPeriodStartTime != workSpec.mPeriodStartTime) return false;
349        if (mMinimumRetentionDuration != workSpec.mMinimumRetentionDuration) return false;
350        if (!mId.equals(workSpec.mId)) return false;
351        if (mState != workSpec.mState) return false;
352        if (!mWorkerClassName.equals(workSpec.mWorkerClassName)) return false;
353        if (mInputMergerClassName != null ? !mInputMergerClassName.equals(
354                workSpec.mInputMergerClassName) : workSpec.mInputMergerClassName != null) {
355            return false;
356        }
357        if (!mArguments.equals(workSpec.mArguments)) return false;
358        if (!mOutput.equals(workSpec.mOutput)) return false;
359        if (!mConstraints.equals(workSpec.mConstraints)) return false;
360        return mBackoffPolicy == workSpec.mBackoffPolicy;
361    }
362
363    @Override
364    public int hashCode() {
365        int result = mId.hashCode();
366        result = 31 * result + mState.hashCode();
367        result = 31 * result + mWorkerClassName.hashCode();
368        result = 31 * result + (mInputMergerClassName != null ? mInputMergerClassName.hashCode()
369                : 0);
370        result = 31 * result + mArguments.hashCode();
371        result = 31 * result + mOutput.hashCode();
372        result = 31 * result + (int) (mInitialDelay ^ (mInitialDelay >>> 32));
373        result = 31 * result + (int) (mIntervalDuration ^ (mIntervalDuration >>> 32));
374        result = 31 * result + (int) (mFlexDuration ^ (mFlexDuration >>> 32));
375        result = 31 * result + mConstraints.hashCode();
376        result = 31 * result + mRunAttemptCount;
377        result = 31 * result + mBackoffPolicy.hashCode();
378        result = 31 * result + (int) (mBackoffDelayDuration ^ (mBackoffDelayDuration >>> 32));
379        result = 31 * result + (int) (mPeriodStartTime ^ (mPeriodStartTime >>> 32));
380        result = 31 * result + (int) (mMinimumRetentionDuration ^ (mMinimumRetentionDuration
381                >>> 32));
382        return result;
383    }
384
385    @Override
386    public String toString() {
387        return "{WorkSpec: " + mId + "}";
388    }
389
390    /**
391     * A POJO containing the ID and state of a WorkSpec.
392     */
393    public static class IdAndState {
394
395        @ColumnInfo(name = "id")
396        public String id;
397
398        @ColumnInfo(name = "state")
399        public State state;
400
401        @Override
402        public boolean equals(Object o) {
403            if (this == o) return true;
404            if (o == null || getClass() != o.getClass()) return false;
405
406            IdAndState that = (IdAndState) o;
407
408            if (state != that.state) return false;
409            return id.equals(that.id);
410        }
411
412        @Override
413        public int hashCode() {
414            int result = id.hashCode();
415            result = 31 * result + state.hashCode();
416            return result;
417        }
418    }
419
420    /**
421     * A POJO containing the ID, state, output, and tags of a WorkSpec.
422     */
423    public static class WorkStatusPojo {
424
425        @ColumnInfo(name = "id")
426        public String id;
427
428        @ColumnInfo(name = "state")
429        public State state;
430
431        @ColumnInfo(name = "output")
432        public Arguments output;
433
434        @Relation(
435                parentColumn = "id",
436                entityColumn = "work_spec_id",
437                entity = WorkTag.class,
438                projection = {"tag"})
439        public List<String> tags;
440
441        /**
442         * Converts this POJO to a {@link WorkStatus}.
443         *
444         * @return The {@link WorkStatus} represented by this POJO
445         */
446        public WorkStatus toWorkStatus() {
447            return new WorkStatus(id, state, output, tags);
448        }
449
450        @Override
451        public boolean equals(Object o) {
452            if (this == o) return true;
453            if (o == null || getClass() != o.getClass()) return false;
454
455            WorkStatusPojo that = (WorkStatusPojo) o;
456
457            if (id != null ? !id.equals(that.id) : that.id != null) return false;
458            if (state != that.state) return false;
459            if (output != null ? !output.equals(that.output) : that.output != null) return false;
460            return tags != null ? tags.equals(that.tags) : that.tags == null;
461        }
462
463        @Override
464        public int hashCode() {
465            int result = id != null ? id.hashCode() : 0;
466            result = 31 * result + (state != null ? state.hashCode() : 0);
467            result = 31 * result + (output != null ? output.hashCode() : 0);
468            result = 31 * result + (tags != null ? tags.hashCode() : 0);
469            return result;
470        }
471    }
472}
473