Home | History | Annotate | Download | only in model
      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 
     17 package androidx.work.impl.model;
     18 
     19 import static androidx.work.PeriodicWorkRequest.MIN_PERIODIC_FLEX_MILLIS;
     20 import static androidx.work.PeriodicWorkRequest.MIN_PERIODIC_INTERVAL_MILLIS;
     21 import static androidx.work.State.ENQUEUED;
     22 import static androidx.work.WorkRequest.MAX_BACKOFF_MILLIS;
     23 import static androidx.work.WorkRequest.MIN_BACKOFF_MILLIS;
     24 
     25 import android.arch.core.util.Function;
     26 import android.arch.persistence.room.ColumnInfo;
     27 import android.arch.persistence.room.Embedded;
     28 import android.arch.persistence.room.Entity;
     29 import android.arch.persistence.room.Index;
     30 import android.arch.persistence.room.PrimaryKey;
     31 import android.arch.persistence.room.Relation;
     32 import android.support.annotation.NonNull;
     33 import android.support.annotation.RestrictTo;
     34 import android.util.Log;
     35 
     36 import androidx.work.BackoffPolicy;
     37 import androidx.work.Constraints;
     38 import androidx.work.Data;
     39 import androidx.work.State;
     40 import androidx.work.WorkRequest;
     41 import androidx.work.WorkStatus;
     42 
     43 import java.util.ArrayList;
     44 import java.util.List;
     45 import java.util.UUID;
     46 
     47 /**
     48  * Stores information about a logical unit of work.
     49  *
     50  * @hide
     51  */
     52 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
     53 @Entity(
     54         indices = {@Index(value = {"schedule_requested_at"})}
     55 )
     56 public class WorkSpec {
     57     private static final String TAG = "WorkSpec";
     58     public static final long SCHEDULE_NOT_REQUESTED_YET = -1;
     59 
     60     @ColumnInfo(name = "id")
     61     @PrimaryKey
     62     @NonNull
     63     public String id;
     64 
     65     @ColumnInfo(name = "state")
     66     @NonNull
     67     public State state = ENQUEUED;
     68 
     69     @ColumnInfo(name = "worker_class_name")
     70     @NonNull
     71     public String workerClassName;
     72 
     73     @ColumnInfo(name = "input_merger_class_name")
     74     public String inputMergerClassName;
     75 
     76     @ColumnInfo(name = "input")
     77     @NonNull
     78     public Data input = Data.EMPTY;
     79 
     80     @ColumnInfo(name = "output")
     81     @NonNull
     82     public Data output = Data.EMPTY;
     83 
     84     @ColumnInfo(name = "initial_delay")
     85     public long initialDelay;
     86 
     87     @ColumnInfo(name = "interval_duration")
     88     public long intervalDuration;
     89 
     90     @ColumnInfo(name = "flex_duration")
     91     public long flexDuration;
     92 
     93     @Embedded
     94     @NonNull
     95     public Constraints constraints = Constraints.NONE;
     96 
     97     @ColumnInfo(name = "run_attempt_count")
     98     public int runAttemptCount;
     99 
    100     @ColumnInfo(name = "backoff_policy")
    101     @NonNull
    102     public BackoffPolicy backoffPolicy = BackoffPolicy.EXPONENTIAL;
    103 
    104     @ColumnInfo(name = "backoff_delay_duration")
    105     public long backoffDelayDuration = WorkRequest.DEFAULT_BACKOFF_DELAY_MILLIS;
    106 
    107     /**
    108      * For one-off work, this is the time that the work was unblocked by prerequisites.
    109      * For periodic work, this is the time that the period started.
    110      */
    111     @ColumnInfo(name = "period_start_time")
    112     public long periodStartTime;
    113 
    114     @ColumnInfo(name = "minimum_retention_duration")
    115     public long minimumRetentionDuration;
    116 
    117     @ColumnInfo(name = "schedule_requested_at")
    118     public long scheduleRequestedAt = SCHEDULE_NOT_REQUESTED_YET;
    119 
    120     public WorkSpec(@NonNull String id, @NonNull String workerClassName) {
    121         this.id = id;
    122         this.workerClassName = workerClassName;
    123     }
    124 
    125     /**
    126      * @param backoffDelayDuration The backoff delay duration in milliseconds
    127      */
    128     public void setBackoffDelayDuration(long backoffDelayDuration) {
    129         if (backoffDelayDuration > MAX_BACKOFF_MILLIS) {
    130             Log.w(TAG, "Backoff delay duration exceeds maximum value");
    131             backoffDelayDuration = MAX_BACKOFF_MILLIS;
    132         }
    133         if (backoffDelayDuration < MIN_BACKOFF_MILLIS) {
    134             Log.w(TAG, "Backoff delay duration less than minimum value");
    135             backoffDelayDuration = MIN_BACKOFF_MILLIS;
    136         }
    137         this.backoffDelayDuration = backoffDelayDuration;
    138     }
    139 
    140 
    141     public boolean isPeriodic() {
    142         return intervalDuration != 0L;
    143     }
    144 
    145     public boolean isBackedOff() {
    146         return state == ENQUEUED && runAttemptCount > 0;
    147     }
    148 
    149     /**
    150      * Sets the periodic interval for this unit of work.
    151      *
    152      * @param intervalDuration The interval in milliseconds
    153      */
    154     public void setPeriodic(long intervalDuration) {
    155         if (intervalDuration < MIN_PERIODIC_INTERVAL_MILLIS) {
    156             Log.w(TAG, String.format(
    157                     "Interval duration lesser than minimum allowed value; Changed to %s",
    158                     MIN_PERIODIC_INTERVAL_MILLIS));
    159             intervalDuration = MIN_PERIODIC_INTERVAL_MILLIS;
    160         }
    161         setPeriodic(intervalDuration, intervalDuration);
    162     }
    163 
    164     /**
    165      * Sets the periodic interval for this unit of work.
    166      *
    167      * @param intervalDuration The interval in milliseconds
    168      * @param flexDuration The flex duration in milliseconds
    169      */
    170     public void setPeriodic(long intervalDuration, long flexDuration) {
    171         if (intervalDuration < MIN_PERIODIC_INTERVAL_MILLIS) {
    172             Log.w(TAG, String.format(
    173                     "Interval duration lesser than minimum allowed value; Changed to %s",
    174                     MIN_PERIODIC_INTERVAL_MILLIS));
    175             intervalDuration = MIN_PERIODIC_INTERVAL_MILLIS;
    176         }
    177         if (flexDuration < MIN_PERIODIC_FLEX_MILLIS) {
    178             Log.w(TAG,
    179                     String.format("Flex duration lesser than minimum allowed value; Changed to %s",
    180                             MIN_PERIODIC_FLEX_MILLIS));
    181             flexDuration = MIN_PERIODIC_FLEX_MILLIS;
    182         }
    183         if (flexDuration > intervalDuration) {
    184             Log.w(TAG, String.format("Flex duration greater than interval duration; Changed to %s",
    185                     intervalDuration));
    186             flexDuration = intervalDuration;
    187         }
    188         this.intervalDuration = intervalDuration;
    189         this.flexDuration = flexDuration;
    190     }
    191 
    192     /**
    193      * Calculates the UTC time at which this {@link WorkSpec} should be allowed to run.
    194      * This method accounts for work that is backed off or periodic.
    195      *
    196      * If Backoff Policy is set to {@link BackoffPolicy#EXPONENTIAL}, then delay
    197      * increases at an exponential rate with respect to the run attempt count and is capped at
    198      * {@link WorkRequest#MAX_BACKOFF_MILLIS}.
    199      *
    200      * If Backoff Policy is set to {@link BackoffPolicy#LINEAR}, then delay
    201      * increases at an linear rate with respect to the run attempt count and is capped at
    202      * {@link WorkRequest#MAX_BACKOFF_MILLIS}.
    203      *
    204      * Based on {@see https://android.googlesource.com/platform/frameworks/base/+/master/services/core/java/com/android/server/job/JobSchedulerService.java#1125}
    205      *
    206      * Note that this runtime is for WorkManager internal use and may not match what the OS
    207      * considers to be the next runtime.
    208      *
    209      * For jobs with constraints, this represents the earliest time at which constraints
    210      * should be monitored for this work.
    211      *
    212      * For jobs without constraints, this represents the earliest time at which this work is
    213      * allowed to run.
    214      *
    215      * @return UTC time at which this {@link WorkSpec} should be allowed to run.
    216      */
    217     public long calculateNextRunTime() {
    218         if (isBackedOff()) {
    219             boolean isLinearBackoff = (backoffPolicy == BackoffPolicy.LINEAR);
    220             long delay = isLinearBackoff ? (backoffDelayDuration * runAttemptCount)
    221                     : (long) Math.scalb(backoffDelayDuration, runAttemptCount - 1);
    222             return periodStartTime + Math.min(WorkRequest.MAX_BACKOFF_MILLIS, delay);
    223         } else if (isPeriodic()) {
    224             return periodStartTime + intervalDuration - flexDuration;
    225         } else {
    226             return periodStartTime + initialDelay;
    227         }
    228     }
    229 
    230     /**
    231      * @return <code>true</code> if the {@link WorkSpec} has constraints.
    232      */
    233     public boolean hasConstraints() {
    234         return !Constraints.NONE.equals(constraints);
    235     }
    236 
    237     @Override
    238     public boolean equals(Object o) {
    239         if (this == o) return true;
    240         if (o == null || getClass() != o.getClass()) return false;
    241 
    242         WorkSpec workSpec = (WorkSpec) o;
    243 
    244         if (initialDelay != workSpec.initialDelay) return false;
    245         if (intervalDuration != workSpec.intervalDuration) return false;
    246         if (flexDuration != workSpec.flexDuration) return false;
    247         if (runAttemptCount != workSpec.runAttemptCount) return false;
    248         if (backoffDelayDuration != workSpec.backoffDelayDuration) return false;
    249         if (periodStartTime != workSpec.periodStartTime) return false;
    250         if (minimumRetentionDuration != workSpec.minimumRetentionDuration) return false;
    251         if (scheduleRequestedAt != workSpec.scheduleRequestedAt) return false;
    252         if (!id.equals(workSpec.id)) return false;
    253         if (state != workSpec.state) return false;
    254         if (!workerClassName.equals(workSpec.workerClassName)) return false;
    255         if (inputMergerClassName != null ? !inputMergerClassName.equals(
    256                 workSpec.inputMergerClassName)
    257                 : workSpec.inputMergerClassName != null) {
    258             return false;
    259         }
    260         if (!input.equals(workSpec.input)) return false;
    261         if (!output.equals(workSpec.output)) return false;
    262         if (!constraints.equals(workSpec.constraints)) return false;
    263         return backoffPolicy == workSpec.backoffPolicy;
    264     }
    265 
    266     @Override
    267     public int hashCode() {
    268         int result = id.hashCode();
    269         result = 31 * result + state.hashCode();
    270         result = 31 * result + workerClassName.hashCode();
    271         result = 31 * result + (inputMergerClassName != null ? inputMergerClassName.hashCode() : 0);
    272         result = 31 * result + input.hashCode();
    273         result = 31 * result + output.hashCode();
    274         result = 31 * result + (int) (initialDelay ^ (initialDelay >>> 32));
    275         result = 31 * result + (int) (intervalDuration ^ (intervalDuration >>> 32));
    276         result = 31 * result + (int) (flexDuration ^ (flexDuration >>> 32));
    277         result = 31 * result + constraints.hashCode();
    278         result = 31 * result + runAttemptCount;
    279         result = 31 * result + backoffPolicy.hashCode();
    280         result = 31 * result + (int) (backoffDelayDuration ^ (backoffDelayDuration >>> 32));
    281         result = 31 * result + (int) (periodStartTime ^ (periodStartTime >>> 32));
    282         result = 31 * result + (int) (minimumRetentionDuration ^ (minimumRetentionDuration >>> 32));
    283         result = 31 * result + (int) (scheduleRequestedAt ^ (scheduleRequestedAt >>> 32));
    284         return result;
    285     }
    286 
    287     @Override
    288     public String toString() {
    289         return "{WorkSpec: " + id + "}";
    290     }
    291 
    292     /**
    293      * A POJO containing the ID and state of a WorkSpec.
    294      */
    295     public static class IdAndState {
    296 
    297         @ColumnInfo(name = "id")
    298         public String id;
    299 
    300         @ColumnInfo(name = "state")
    301         public State state;
    302 
    303         @Override
    304         public boolean equals(Object o) {
    305             if (this == o) return true;
    306             if (o == null || getClass() != o.getClass()) return false;
    307 
    308             IdAndState that = (IdAndState) o;
    309 
    310             if (state != that.state) return false;
    311             return id.equals(that.id);
    312         }
    313 
    314         @Override
    315         public int hashCode() {
    316             int result = id.hashCode();
    317             result = 31 * result + state.hashCode();
    318             return result;
    319         }
    320     }
    321 
    322     /**
    323      * A POJO containing the ID, state, output, and tags of a WorkSpec.
    324      */
    325     public static class WorkStatusPojo {
    326 
    327         @ColumnInfo(name = "id")
    328         public String id;
    329 
    330         @ColumnInfo(name = "state")
    331         public State state;
    332 
    333         @ColumnInfo(name = "output")
    334         public Data output;
    335 
    336         @Relation(
    337                 parentColumn = "id",
    338                 entityColumn = "work_spec_id",
    339                 entity = WorkTag.class,
    340                 projection = {"tag"})
    341         public List<String> tags;
    342 
    343         /**
    344          * Converts this POJO to a {@link WorkStatus}.
    345          *
    346          * @return The {@link WorkStatus} represented by this POJO
    347          */
    348         public WorkStatus toWorkStatus() {
    349             return new WorkStatus(UUID.fromString(id), state, output, tags);
    350         }
    351 
    352         @Override
    353         public boolean equals(Object o) {
    354             if (this == o) return true;
    355             if (o == null || getClass() != o.getClass()) return false;
    356 
    357             WorkStatusPojo that = (WorkStatusPojo) o;
    358 
    359             if (id != null ? !id.equals(that.id) : that.id != null) return false;
    360             if (state != that.state) return false;
    361             if (output != null ? !output.equals(that.output) : that.output != null) return false;
    362             return tags != null ? tags.equals(that.tags) : that.tags == null;
    363         }
    364 
    365         @Override
    366         public int hashCode() {
    367             int result = id != null ? id.hashCode() : 0;
    368             result = 31 * result + (state != null ? state.hashCode() : 0);
    369             result = 31 * result + (output != null ? output.hashCode() : 0);
    370             result = 31 * result + (tags != null ? tags.hashCode() : 0);
    371             return result;
    372         }
    373     }
    374 
    375     public static final Function<List<WorkStatusPojo>, List<WorkStatus>> WORK_STATUS_MAPPER =
    376             new Function<List<WorkStatusPojo>, List<WorkStatus>>() {
    377                 @Override
    378                 public List<WorkStatus> apply(List<WorkStatusPojo> input) {
    379                     if (input == null) {
    380                         return null;
    381                     }
    382                     List<WorkStatus> output = new ArrayList<>(input.size());
    383                     for (WorkStatusPojo in : input) {
    384                         output.add(in.toWorkStatus());
    385                     }
    386                     return output;
    387                 }
    388             };
    389 }
    390