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