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