| /* |
| * Copyright (C) 2017 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package androidx.work.impl.model; |
| |
| import static androidx.work.BaseWork.MAX_BACKOFF_MILLIS; |
| import static androidx.work.BaseWork.MIN_BACKOFF_MILLIS; |
| import static androidx.work.PeriodicWork.MIN_PERIODIC_FLEX_MILLIS; |
| import static androidx.work.PeriodicWork.MIN_PERIODIC_INTERVAL_MILLIS; |
| import static androidx.work.State.ENQUEUED; |
| |
| import android.arch.persistence.room.ColumnInfo; |
| import android.arch.persistence.room.Embedded; |
| import android.arch.persistence.room.Entity; |
| import android.arch.persistence.room.PrimaryKey; |
| import android.arch.persistence.room.Relation; |
| import android.support.annotation.NonNull; |
| |
| import java.util.List; |
| |
| import androidx.work.Arguments; |
| import androidx.work.BackoffPolicy; |
| import androidx.work.BaseWork; |
| import androidx.work.Constraints; |
| import androidx.work.State; |
| import androidx.work.WorkStatus; |
| import androidx.work.impl.logger.Logger; |
| |
| /** |
| * Stores information about a logical unit of work. |
| */ |
| @Entity |
| public class WorkSpec { |
| private static final String TAG = "WorkSpec"; |
| |
| @ColumnInfo(name = "id") |
| @PrimaryKey |
| @NonNull |
| String mId; |
| |
| @ColumnInfo(name = "state") |
| @NonNull |
| State mState = ENQUEUED; |
| |
| @ColumnInfo(name = "worker_class_name") |
| @NonNull |
| String mWorkerClassName; |
| |
| @ColumnInfo(name = "input_merger_class_name") |
| String mInputMergerClassName; |
| |
| @ColumnInfo(name = "arguments") |
| @NonNull |
| Arguments mArguments = Arguments.EMPTY; |
| |
| @ColumnInfo(name = "output") |
| @NonNull |
| Arguments mOutput = Arguments.EMPTY; |
| |
| @ColumnInfo(name = "initial_delay") |
| long mInitialDelay; |
| |
| @ColumnInfo(name = "interval_duration") |
| long mIntervalDuration; |
| |
| @ColumnInfo(name = "flex_duration") |
| long mFlexDuration; |
| |
| @Embedded |
| @NonNull |
| Constraints mConstraints = Constraints.NONE; |
| |
| @ColumnInfo(name = "run_attempt_count") |
| int mRunAttemptCount; |
| |
| // TODO(sumir): Should Backoff be disabled by default? |
| @ColumnInfo(name = "backoff_policy") |
| @NonNull |
| BackoffPolicy mBackoffPolicy = BackoffPolicy.EXPONENTIAL; |
| |
| @ColumnInfo(name = "backoff_delay_duration") |
| long mBackoffDelayDuration = BaseWork.DEFAULT_BACKOFF_DELAY_MILLIS; |
| |
| @ColumnInfo(name = "period_start_time") |
| long mPeriodStartTime; |
| |
| @ColumnInfo(name = "minimum_retention_duration") |
| long mMinimumRetentionDuration; |
| |
| public WorkSpec(@NonNull String id) { |
| mId = id; |
| } |
| |
| @NonNull |
| public String getId() { |
| return mId; |
| } |
| |
| public void setId(@NonNull String id) { |
| mId = id; |
| } |
| |
| public @NonNull State getState() { |
| return mState; |
| } |
| |
| public void setState(@NonNull State state) { |
| mState = state; |
| } |
| |
| public @NonNull String getWorkerClassName() { |
| return mWorkerClassName; |
| } |
| |
| public void setWorkerClassName(@NonNull String workerClassName) { |
| mWorkerClassName = workerClassName; |
| } |
| |
| public String getInputMergerClassName() { |
| return mInputMergerClassName; |
| } |
| |
| public void setInputMergerClassName(String inputMergerClassName) { |
| mInputMergerClassName = inputMergerClassName; |
| } |
| |
| public @NonNull Arguments getArguments() { |
| return mArguments; |
| } |
| |
| public void setArguments(@NonNull Arguments arguments) { |
| mArguments = arguments; |
| } |
| |
| public @NonNull Arguments getOutput() { |
| return mOutput; |
| } |
| |
| public void setOutput(@NonNull Arguments output) { |
| mOutput = output; |
| } |
| |
| public @NonNull Constraints getConstraints() { |
| return mConstraints; |
| } |
| |
| public void setConstraints(@NonNull Constraints constraints) { |
| mConstraints = constraints; |
| } |
| |
| public @NonNull BackoffPolicy getBackoffPolicy() { |
| return mBackoffPolicy; |
| } |
| |
| public void setBackoffPolicy(@NonNull BackoffPolicy backoffPolicy) { |
| mBackoffPolicy = backoffPolicy; |
| } |
| |
| public long getBackoffDelayDuration() { |
| return mBackoffDelayDuration; |
| } |
| |
| /** |
| * @param backoffDelayDuration The backoff delay duration in milliseconds |
| */ |
| public void setBackoffDelayDuration(long backoffDelayDuration) { |
| if (backoffDelayDuration > MAX_BACKOFF_MILLIS) { |
| Logger.warn(TAG, "Backoff delay duration exceeds maximum value"); |
| backoffDelayDuration = MAX_BACKOFF_MILLIS; |
| } |
| if (backoffDelayDuration < MIN_BACKOFF_MILLIS) { |
| Logger.warn(TAG, "Backoff delay duration less than minimum value"); |
| backoffDelayDuration = MIN_BACKOFF_MILLIS; |
| } |
| mBackoffDelayDuration = backoffDelayDuration; |
| } |
| |
| public long getInitialDelay() { |
| return mInitialDelay; |
| } |
| |
| public void setInitialDelay(long initialDelay) { |
| mInitialDelay = initialDelay; |
| } |
| |
| public boolean isPeriodic() { |
| return mIntervalDuration != 0L; |
| } |
| |
| public boolean isBackedOff() { |
| return mState == ENQUEUED && mRunAttemptCount > 0; |
| } |
| |
| /** |
| * Sets the periodic interval for this unit of work. |
| * |
| * @param intervalDuration The interval in milliseconds |
| */ |
| public void setPeriodic(long intervalDuration) { |
| if (intervalDuration < MIN_PERIODIC_INTERVAL_MILLIS) { |
| Logger.warn(TAG, "Interval duration lesser than minimum allowed value; Changed to %s", |
| MIN_PERIODIC_INTERVAL_MILLIS); |
| intervalDuration = MIN_PERIODIC_INTERVAL_MILLIS; |
| } |
| setPeriodic(intervalDuration, intervalDuration); |
| } |
| |
| /** |
| * Sets the periodic interval for this unit of work. |
| * |
| * @param intervalDuration The interval in milliseconds |
| * @param flexDuration The flex duration in milliseconds |
| */ |
| public void setPeriodic(long intervalDuration, long flexDuration) { |
| if (intervalDuration < MIN_PERIODIC_INTERVAL_MILLIS) { |
| Logger.warn(TAG, "Interval duration lesser than minimum allowed value; Changed to %s", |
| MIN_PERIODIC_INTERVAL_MILLIS); |
| intervalDuration = MIN_PERIODIC_INTERVAL_MILLIS; |
| } |
| if (flexDuration < MIN_PERIODIC_FLEX_MILLIS) { |
| Logger.warn(TAG, "Flex duration lesser than minimum allowed value; Changed to %s", |
| MIN_PERIODIC_FLEX_MILLIS); |
| flexDuration = MIN_PERIODIC_FLEX_MILLIS; |
| } |
| if (flexDuration > intervalDuration) { |
| Logger.warn(TAG, "Flex duration greater than interval duration; Changed to %s", |
| intervalDuration); |
| flexDuration = intervalDuration; |
| } |
| mIntervalDuration = intervalDuration; |
| mFlexDuration = flexDuration; |
| } |
| |
| public long getIntervalDuration() { |
| return mIntervalDuration; |
| } |
| |
| public void setIntervalDuration(long intervalDuration) { |
| mIntervalDuration = intervalDuration; |
| } |
| |
| public long getFlexDuration() { |
| return mFlexDuration; |
| } |
| |
| public void setFlexDuration(long flexDuration) { |
| mFlexDuration = flexDuration; |
| } |
| |
| public void setRunAttemptCount(int runAttemptCount) { |
| this.mRunAttemptCount = runAttemptCount; |
| } |
| |
| public int getRunAttemptCount() { |
| return mRunAttemptCount; |
| } |
| |
| /** |
| * For one-off work, this is the time that the work was unblocked by prerequisites. |
| * For periodic work, this is the time that the period started. |
| */ |
| public long getPeriodStartTime() { |
| return mPeriodStartTime; |
| } |
| |
| public void setPeriodStartTime(long periodStartTime) { |
| mPeriodStartTime = periodStartTime; |
| } |
| |
| public long getMinimumRetentionDuration() { |
| return mMinimumRetentionDuration; |
| } |
| |
| public void setMinimumRetentionDuration(long minimumRetentionDuration) { |
| mMinimumRetentionDuration = minimumRetentionDuration; |
| } |
| |
| /** |
| * Calculates the UTC time at which this {@link WorkSpec} should be allowed to run. |
| * This method accounts for work that is backed off or periodic. |
| * |
| * If Backoff Policy is set to {@link BackoffPolicy#EXPONENTIAL}, then delay |
| * increases at an exponential rate with respect to the run attempt count and is capped at |
| * {@link BaseWork#MAX_BACKOFF_MILLIS}. |
| * |
| * If Backoff Policy is set to {@link BackoffPolicy#LINEAR}, then delay |
| * increases at an linear rate with respect to the run attempt count and is capped at |
| * {@link BaseWork#MAX_BACKOFF_MILLIS}. |
| * |
| * Based on {@see https://android.googlesource.com/platform/frameworks/base/+/master/services/core/java/com/android/server/job/JobSchedulerService.java#1125} |
| * |
| * Note that this runtime is for WorkManager internal use and may not match what the OS |
| * considers to be the next runtime. |
| * |
| * For jobs with constraints, this represents the earliest time at which constraints |
| * should be monitored for this work. |
| * |
| * For jobs without constraints, this represents the earliest time at which this work is |
| * allowed to run. |
| * |
| * @return UTC time at which this {@link WorkSpec} should be allowed to run. |
| */ |
| public long calculateNextRunTime() { |
| if (isBackedOff()) { |
| boolean isLinearBackoff = (mBackoffPolicy == BackoffPolicy.LINEAR); |
| long delay = isLinearBackoff ? (mBackoffDelayDuration * mRunAttemptCount) |
| : (long) Math.scalb(mBackoffDelayDuration, mRunAttemptCount - 1); |
| return mPeriodStartTime + Math.min(BaseWork.MAX_BACKOFF_MILLIS, delay); |
| } else if (isPeriodic()) { |
| return mPeriodStartTime + mIntervalDuration - mFlexDuration; |
| } else { |
| return mPeriodStartTime + mInitialDelay; |
| } |
| } |
| |
| /** |
| * @return <code>true</code> if the {@link WorkSpec} has constraints. |
| */ |
| public boolean hasConstraints() { |
| return !Constraints.NONE.equals(getConstraints()); |
| } |
| |
| @Override |
| public boolean equals(Object o) { |
| if (this == o) return true; |
| if (o == null || getClass() != o.getClass()) return false; |
| |
| WorkSpec workSpec = (WorkSpec) o; |
| |
| if (mInitialDelay != workSpec.mInitialDelay) return false; |
| if (mIntervalDuration != workSpec.mIntervalDuration) return false; |
| if (mFlexDuration != workSpec.mFlexDuration) return false; |
| if (mRunAttemptCount != workSpec.mRunAttemptCount) return false; |
| if (mBackoffDelayDuration != workSpec.mBackoffDelayDuration) return false; |
| if (mPeriodStartTime != workSpec.mPeriodStartTime) return false; |
| if (mMinimumRetentionDuration != workSpec.mMinimumRetentionDuration) return false; |
| if (!mId.equals(workSpec.mId)) return false; |
| if (mState != workSpec.mState) return false; |
| if (!mWorkerClassName.equals(workSpec.mWorkerClassName)) return false; |
| if (mInputMergerClassName != null ? !mInputMergerClassName.equals( |
| workSpec.mInputMergerClassName) : workSpec.mInputMergerClassName != null) { |
| return false; |
| } |
| if (!mArguments.equals(workSpec.mArguments)) return false; |
| if (!mOutput.equals(workSpec.mOutput)) return false; |
| if (!mConstraints.equals(workSpec.mConstraints)) return false; |
| return mBackoffPolicy == workSpec.mBackoffPolicy; |
| } |
| |
| @Override |
| public int hashCode() { |
| int result = mId.hashCode(); |
| result = 31 * result + mState.hashCode(); |
| result = 31 * result + mWorkerClassName.hashCode(); |
| result = 31 * result + (mInputMergerClassName != null ? mInputMergerClassName.hashCode() |
| : 0); |
| result = 31 * result + mArguments.hashCode(); |
| result = 31 * result + mOutput.hashCode(); |
| result = 31 * result + (int) (mInitialDelay ^ (mInitialDelay >>> 32)); |
| result = 31 * result + (int) (mIntervalDuration ^ (mIntervalDuration >>> 32)); |
| result = 31 * result + (int) (mFlexDuration ^ (mFlexDuration >>> 32)); |
| result = 31 * result + mConstraints.hashCode(); |
| result = 31 * result + mRunAttemptCount; |
| result = 31 * result + mBackoffPolicy.hashCode(); |
| result = 31 * result + (int) (mBackoffDelayDuration ^ (mBackoffDelayDuration >>> 32)); |
| result = 31 * result + (int) (mPeriodStartTime ^ (mPeriodStartTime >>> 32)); |
| result = 31 * result + (int) (mMinimumRetentionDuration ^ (mMinimumRetentionDuration |
| >>> 32)); |
| return result; |
| } |
| |
| @Override |
| public String toString() { |
| return "{WorkSpec: " + mId + "}"; |
| } |
| |
| /** |
| * A POJO containing the ID and state of a WorkSpec. |
| */ |
| public static class IdAndState { |
| |
| @ColumnInfo(name = "id") |
| public String id; |
| |
| @ColumnInfo(name = "state") |
| public State state; |
| |
| @Override |
| public boolean equals(Object o) { |
| if (this == o) return true; |
| if (o == null || getClass() != o.getClass()) return false; |
| |
| IdAndState that = (IdAndState) o; |
| |
| if (state != that.state) return false; |
| return id.equals(that.id); |
| } |
| |
| @Override |
| public int hashCode() { |
| int result = id.hashCode(); |
| result = 31 * result + state.hashCode(); |
| return result; |
| } |
| } |
| |
| /** |
| * A POJO containing the ID, state, output, and tags of a WorkSpec. |
| */ |
| public static class WorkStatusPojo { |
| |
| @ColumnInfo(name = "id") |
| public String id; |
| |
| @ColumnInfo(name = "state") |
| public State state; |
| |
| @ColumnInfo(name = "output") |
| public Arguments output; |
| |
| @Relation( |
| parentColumn = "id", |
| entityColumn = "work_spec_id", |
| entity = WorkTag.class, |
| projection = {"tag"}) |
| public List<String> tags; |
| |
| /** |
| * Converts this POJO to a {@link WorkStatus}. |
| * |
| * @return The {@link WorkStatus} represented by this POJO |
| */ |
| public WorkStatus toWorkStatus() { |
| return new WorkStatus(id, state, output, tags); |
| } |
| |
| @Override |
| public boolean equals(Object o) { |
| if (this == o) return true; |
| if (o == null || getClass() != o.getClass()) return false; |
| |
| WorkStatusPojo that = (WorkStatusPojo) o; |
| |
| if (id != null ? !id.equals(that.id) : that.id != null) return false; |
| if (state != that.state) return false; |
| if (output != null ? !output.equals(that.output) : that.output != null) return false; |
| return tags != null ? tags.equals(that.tags) : that.tags == null; |
| } |
| |
| @Override |
| public int hashCode() { |
| int result = id != null ? id.hashCode() : 0; |
| result = 31 * result + (state != null ? state.hashCode() : 0); |
| result = 31 * result + (output != null ? output.hashCode() : 0); |
| result = 31 * result + (tags != null ? tags.hashCode() : 0); |
| return result; |
| } |
| } |
| } |