public final class

WorkSpec

extends java.lang.Object

 java.lang.Object

↳androidx.work.impl.model.WorkSpec

Overview

Stores information about a logical unit of work.

Summary

Fields
public longbackoffDelayDuration

public BackoffPolicybackoffPolicy

public Constraintsconstraints

public longflexDuration

public java.lang.Stringid

public longinitialDelay

public Datainput

public java.lang.StringinputMergerClassName

public longintervalDuration

public longminimumRetentionDuration

public Dataoutput

public longperiodStartTime

For one-off work, this is the time that the work was unblocked by prerequisites.

public intrunAttemptCount

public booleanrunInForeground

This is true when the WorkSpec needs to be hosted by a foreground service.

public static final longSCHEDULE_NOT_REQUESTED_YET

public longscheduleRequestedAt

This field tells us if this WorkSpec instance, is actually currently scheduled and being counted against the SCHEDULER_LIMIT.

public WorkInfo.Statestate

public static final Function<java.util.List, java.util.List>WORK_INFO_MAPPER

public java.lang.StringworkerClassName

Constructors
publicWorkSpec(java.lang.String id, java.lang.String workerClassName)

publicWorkSpec(WorkSpec other)

Methods
public longcalculateNextRunTime()

Calculates the UTC time at which this WorkSpec should be allowed to run.

public booleanequals(java.lang.Object o)

public booleanhasConstraints()

public inthashCode()

public booleanisBackedOff()

public booleanisPeriodic()

public voidsetBackoffDelayDuration(long backoffDelayDuration)

public voidsetPeriodic(long intervalDuration)

Sets the periodic interval for this unit of work.

public voidsetPeriodic(long intervalDuration, long flexDuration)

Sets the periodic interval for this unit of work.

public java.lang.StringtoString()

from java.lang.Objectclone, finalize, getClass, notify, notifyAll, wait, wait, wait

Fields

public static final long SCHEDULE_NOT_REQUESTED_YET

public java.lang.String id

public WorkInfo.State state

public java.lang.String workerClassName

public java.lang.String inputMergerClassName

public Data input

public Data output

public long initialDelay

public long intervalDuration

public long flexDuration

public Constraints constraints

public int runAttemptCount

public BackoffPolicy backoffPolicy

public long backoffDelayDuration

public long periodStartTime

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 minimumRetentionDuration

public long scheduleRequestedAt

This field tells us if this WorkSpec instance, is actually currently scheduled and being counted against the SCHEDULER_LIMIT. This bit is reset for PeriodicWorkRequests in API < 23, because AlarmManager does not know of PeriodicWorkRequests. So for the next request to be rescheduled this field has to be reset to SCHEDULE_NOT_REQUESTED_AT. For the JobScheduler implementation, we don't reset this field because JobScheduler natively supports PeriodicWorkRequests.

public boolean runInForeground

This is true when the WorkSpec needs to be hosted by a foreground service.

public static final Function<java.util.List, java.util.List> WORK_INFO_MAPPER

Constructors

public WorkSpec(java.lang.String id, java.lang.String workerClassName)

public WorkSpec(WorkSpec other)

Methods

public void setBackoffDelayDuration(long backoffDelayDuration)

Parameters:

backoffDelayDuration: The backoff delay duration in milliseconds

public boolean isPeriodic()

public boolean isBackedOff()

public void setPeriodic(long intervalDuration)

Sets the periodic interval for this unit of work.

Parameters:

intervalDuration: The interval in milliseconds

public void setPeriodic(long intervalDuration, long flexDuration)

Sets the periodic interval for this unit of work.

Parameters:

intervalDuration: The interval in milliseconds
flexDuration: The flex duration in milliseconds

public long calculateNextRunTime()

Calculates the UTC time at which this WorkSpec should be allowed to run. This method accounts for work that is backed off or periodic. If Backoff Policy is set to BackoffPolicy.EXPONENTIAL, then delay increases at an exponential rate with respect to the run attempt count and is capped at WorkRequest.MAX_BACKOFF_MILLIS. If Backoff Policy is set to BackoffPolicy.LINEAR, then delay increases at an linear rate with respect to the run attempt count and is capped at WorkRequest.MAX_BACKOFF_MILLIS. Based on 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.

Returns:

UTC time at which this WorkSpec should be allowed to run.

public boolean hasConstraints()

Returns:

true if the WorkSpec has constraints.

public boolean equals(java.lang.Object o)

public int hashCode()

public java.lang.String toString()

Source

/*
 * 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.PeriodicWorkRequest.MIN_PERIODIC_FLEX_MILLIS;
import static androidx.work.PeriodicWorkRequest.MIN_PERIODIC_INTERVAL_MILLIS;
import static androidx.work.WorkInfo.State.ENQUEUED;
import static androidx.work.WorkRequest.MAX_BACKOFF_MILLIS;
import static androidx.work.WorkRequest.MIN_BACKOFF_MILLIS;

import androidx.annotation.IntRange;
import androidx.annotation.NonNull;
import androidx.annotation.RestrictTo;
import androidx.arch.core.util.Function;
import androidx.room.ColumnInfo;
import androidx.room.Embedded;
import androidx.room.Entity;
import androidx.room.Index;
import androidx.room.PrimaryKey;
import androidx.room.Relation;
import androidx.work.BackoffPolicy;
import androidx.work.Constraints;
import androidx.work.Data;
import androidx.work.Logger;
import androidx.work.WorkInfo;
import androidx.work.WorkRequest;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

/**
 * Stores information about a logical unit of work.
 *
 * @hide
 */
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@Entity(
        indices = {
                @Index(value = {"schedule_requested_at"}),
                @Index(value = {"period_start_time"})
        }
)
public final class WorkSpec {
    private static final String TAG = Logger.tagWithPrefix("WorkSpec");
    public static final long SCHEDULE_NOT_REQUESTED_YET = -1;

    @ColumnInfo(name = "id")
    @PrimaryKey
    @NonNull
    public String id;

    @ColumnInfo(name = "state")
    @NonNull
    public WorkInfo.State state = ENQUEUED;

    @ColumnInfo(name = "worker_class_name")
    @NonNull
    public String workerClassName;

    @ColumnInfo(name = "input_merger_class_name")
    public String inputMergerClassName;

    @ColumnInfo(name = "input")
    @NonNull
    public Data input = Data.EMPTY;

    @ColumnInfo(name = "output")
    @NonNull
    public Data output = Data.EMPTY;

    @ColumnInfo(name = "initial_delay")
    public long initialDelay;

    @ColumnInfo(name = "interval_duration")
    public long intervalDuration;

    @ColumnInfo(name = "flex_duration")
    public long flexDuration;

    @Embedded
    @NonNull
    public Constraints constraints = Constraints.NONE;

    @ColumnInfo(name = "run_attempt_count")
    @IntRange(from = 0)
    public int runAttemptCount;

    @ColumnInfo(name = "backoff_policy")
    @NonNull
    public BackoffPolicy backoffPolicy = BackoffPolicy.EXPONENTIAL;

    @ColumnInfo(name = "backoff_delay_duration")
    public long backoffDelayDuration = WorkRequest.DEFAULT_BACKOFF_DELAY_MILLIS;

    /**
     * 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.
     */
    @ColumnInfo(name = "period_start_time")
    public long periodStartTime;

    @ColumnInfo(name = "minimum_retention_duration")
    public long minimumRetentionDuration;

    /**
     * This field tells us if this {@link WorkSpec} instance, is actually currently scheduled and
     * being counted against the {@code SCHEDULER_LIMIT}. This bit is reset for PeriodicWorkRequests
     * in API < 23, because AlarmManager does not know of PeriodicWorkRequests. So for the next
     * request to be rescheduled this field has to be reset to {@code SCHEDULE_NOT_REQUESTED_AT}.
     * For the JobScheduler implementation, we don't reset this field because JobScheduler natively
     * supports PeriodicWorkRequests.
     */
    @ColumnInfo(name = "schedule_requested_at")
    public long scheduleRequestedAt = SCHEDULE_NOT_REQUESTED_YET;

    /**
     * This is {@code true} when the WorkSpec needs to be hosted by a foreground service.
     */
    @ColumnInfo(name = "run_in_foreground")
    public boolean runInForeground;

    public WorkSpec(@NonNull String id, @NonNull String workerClassName) {
        this.id = id;
        this.workerClassName = workerClassName;
    }

    public WorkSpec(@NonNull WorkSpec other) {
        id = other.id;
        workerClassName = other.workerClassName;
        state = other.state;
        inputMergerClassName = other.inputMergerClassName;
        input = new Data(other.input);
        output = new Data(other.output);
        initialDelay = other.initialDelay;
        intervalDuration = other.intervalDuration;
        flexDuration = other.flexDuration;
        constraints = new Constraints(other.constraints);
        runAttemptCount = other.runAttemptCount;
        backoffPolicy = other.backoffPolicy;
        backoffDelayDuration = other.backoffDelayDuration;
        periodStartTime = other.periodStartTime;
        minimumRetentionDuration = other.minimumRetentionDuration;
        scheduleRequestedAt = other.scheduleRequestedAt;
        runInForeground = other.runInForeground;
    }

    /**
     * @param backoffDelayDuration The backoff delay duration in milliseconds
     */
    public void setBackoffDelayDuration(long backoffDelayDuration) {
        if (backoffDelayDuration > MAX_BACKOFF_MILLIS) {
            Logger.get().warning(TAG, "Backoff delay duration exceeds maximum value");
            backoffDelayDuration = MAX_BACKOFF_MILLIS;
        }
        if (backoffDelayDuration < MIN_BACKOFF_MILLIS) {
            Logger.get().warning(TAG, "Backoff delay duration less than minimum value");
            backoffDelayDuration = MIN_BACKOFF_MILLIS;
        }
        this.backoffDelayDuration = backoffDelayDuration;
    }


    public boolean isPeriodic() {
        return intervalDuration != 0L;
    }

    public boolean isBackedOff() {
        return state == ENQUEUED && runAttemptCount > 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.get().warning(TAG, String.format(
                    "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.get().warning(TAG, String.format(
                    "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.get().warning(TAG,
                    String.format("Flex duration lesser than minimum allowed value; Changed to %s",
                            MIN_PERIODIC_FLEX_MILLIS));
            flexDuration = MIN_PERIODIC_FLEX_MILLIS;
        }
        if (flexDuration > intervalDuration) {
            Logger.get().warning(TAG,
                    String.format("Flex duration greater than interval duration; Changed to %s",
                            intervalDuration));
            flexDuration = intervalDuration;
        }
        this.intervalDuration = intervalDuration;
        this.flexDuration = flexDuration;
    }

    /**
     * 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 WorkRequest#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 WorkRequest#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 = (backoffPolicy == BackoffPolicy.LINEAR);
            long delay = isLinearBackoff ? (backoffDelayDuration * runAttemptCount)
                    : (long) Math.scalb(backoffDelayDuration, runAttemptCount - 1);
            return periodStartTime + Math.min(WorkRequest.MAX_BACKOFF_MILLIS, delay);
        } else if (isPeriodic()) {
            long now = System.currentTimeMillis();
            long start = periodStartTime == 0 ? (now + initialDelay) : periodStartTime;
            boolean isFlexApplicable = flexDuration != intervalDuration;
            if (isFlexApplicable) {
                // To correctly emulate flex, we need to set it
                // to now, so the PeriodicWorkRequest has an initial delay of
                // initialDelay + (interval - flex).

                // The subsequent runs will only add the interval duration and no flex.
                // This gives us the following behavior:
                // 1 => now + (interval - flex) + initialDelay = firstRunTime
                // 2 => firstRunTime + 2 * interval - flex
                // 3 => firstRunTime + 3 * interval - flex
                long offset = periodStartTime == 0 ? (-1 * flexDuration) : 0;
                return start + intervalDuration + offset;
            } else {
                // Don't use flexDuration for determining next run time for PeriodicWork
                // This is because intervalDuration could equal flexDuration.

                // The first run of a periodic work request is immediate in JobScheduler, and we
                // need to emulate this behavior.
                long offset = periodStartTime == 0 ? 0 : intervalDuration;
                return start + offset;
            }
        } else {
            // We are checking for (periodStartTime == 0) to support our testing use case.
            // For newly created WorkSpecs periodStartTime will always be 0.
            long start = (periodStartTime == 0) ? System.currentTimeMillis() : periodStartTime;
            return start + initialDelay;
        }
    }

    /**
     * @return <code>true</code> if the {@link WorkSpec} has constraints.
     */
    public boolean hasConstraints() {
        return !Constraints.NONE.equals(constraints);
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof WorkSpec)) return false;

        WorkSpec workSpec = (WorkSpec) o;

        if (initialDelay != workSpec.initialDelay) return false;
        if (intervalDuration != workSpec.intervalDuration) return false;
        if (flexDuration != workSpec.flexDuration) return false;
        if (runAttemptCount != workSpec.runAttemptCount) return false;
        if (backoffDelayDuration != workSpec.backoffDelayDuration) return false;
        if (periodStartTime != workSpec.periodStartTime) return false;
        if (minimumRetentionDuration != workSpec.minimumRetentionDuration) return false;
        if (scheduleRequestedAt != workSpec.scheduleRequestedAt) return false;
        if (runInForeground != workSpec.runInForeground) return false;
        if (!id.equals(workSpec.id)) return false;
        if (state != workSpec.state) return false;
        if (!workerClassName.equals(workSpec.workerClassName)) return false;
        if (inputMergerClassName != null ? !inputMergerClassName.equals(
                workSpec.inputMergerClassName)
                : workSpec.inputMergerClassName != null) {
            return false;
        }
        if (!input.equals(workSpec.input)) return false;
        if (!output.equals(workSpec.output)) return false;
        if (!constraints.equals(workSpec.constraints)) return false;
        return backoffPolicy == workSpec.backoffPolicy;
    }

    @Override
    public int hashCode() {
        int result = id.hashCode();
        result = 31 * result + state.hashCode();
        result = 31 * result + workerClassName.hashCode();
        result = 31 * result + (inputMergerClassName != null ? inputMergerClassName.hashCode() : 0);
        result = 31 * result + input.hashCode();
        result = 31 * result + output.hashCode();
        result = 31 * result + (int) (initialDelay ^ (initialDelay >>> 32));
        result = 31 * result + (int) (intervalDuration ^ (intervalDuration >>> 32));
        result = 31 * result + (int) (flexDuration ^ (flexDuration >>> 32));
        result = 31 * result + constraints.hashCode();
        result = 31 * result + runAttemptCount;
        result = 31 * result + backoffPolicy.hashCode();
        result = 31 * result + (int) (backoffDelayDuration ^ (backoffDelayDuration >>> 32));
        result = 31 * result + (int) (periodStartTime ^ (periodStartTime >>> 32));
        result = 31 * result + (int) (minimumRetentionDuration ^ (minimumRetentionDuration >>> 32));
        result = 31 * result + (int) (scheduleRequestedAt ^ (scheduleRequestedAt >>> 32));
        result = 31 * result + (runInForeground ? 1 : 0);
        return result;
    }

    @NonNull
    @Override
    public String toString() {
        return "{WorkSpec: " + id + "}";
    }

    /**
     * A POJO containing the ID and state of a WorkSpec.
     */
    public static class IdAndState {

        @ColumnInfo(name = "id")
        public String id;

        @ColumnInfo(name = "state")
        public WorkInfo.State state;

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (!(o instanceof IdAndState)) 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, tags, and run attempt count of a WorkSpec.
     */
    public static class WorkInfoPojo {

        @ColumnInfo(name = "id")
        public String id;

        @ColumnInfo(name = "state")
        public WorkInfo.State state;

        @ColumnInfo(name = "output")
        public Data output;

        @ColumnInfo(name = "run_attempt_count")
        public int runAttemptCount;

        @Relation(
                parentColumn = "id",
                entityColumn = "work_spec_id",
                entity = WorkTag.class,
                projection = {"tag"})
        public List<String> tags;

        // This is actually a 1-1 relationship. However Room 2.1 models the type as a List.
        // This will change in Room 2.2
        @Relation(
                parentColumn = "id",
                entityColumn = "work_spec_id",
                entity = WorkProgress.class,
                projection = {"progress"})
        public List<Data> progress;

        /**
         * Converts this POJO to a {@link WorkInfo}.
         *
         * @return The {@link WorkInfo} represented by this POJO
         */
        @NonNull
        public WorkInfo toWorkInfo() {
            Data progress = this.progress != null && !this.progress.isEmpty()
                    ? this.progress.get(0)
                    : Data.EMPTY;

            return new WorkInfo(
                    UUID.fromString(id),
                    state,
                    output,
                    tags,
                    progress,
                    runAttemptCount);
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (!(o instanceof WorkInfoPojo)) return false;

            WorkInfoPojo that = (WorkInfoPojo) o;

            if (runAttemptCount != that.runAttemptCount) return false;
            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;
            if (tags != null ? !tags.equals(that.tags) : that.tags != null) return false;
            return progress != null ? progress.equals(that.progress) : that.progress == 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 + runAttemptCount;
            result = 31 * result + (tags != null ? tags.hashCode() : 0);
            result = 31 * result + (progress != null ? progress.hashCode() : 0);
            return result;
        }
    }

    public static final Function<List<WorkInfoPojo>, List<WorkInfo>> WORK_INFO_MAPPER =
            new Function<List<WorkInfoPojo>, List<WorkInfo>>() {
                @Override
                public List<WorkInfo> apply(List<WorkInfoPojo> input) {
                    if (input == null) {
                        return null;
                    }
                    List<WorkInfo> output = new ArrayList<>(input.size());
                    for (WorkInfoPojo in : input) {
                        output.add(in.toWorkInfo());
                    }
                    return output;
                }
            };
}