WorkSpec.java revision cc5ae8c86fd7aca80917de26548646b0a9acc498
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.persistence.room.ColumnInfo; 26import android.arch.persistence.room.Embedded; 27import android.arch.persistence.room.Entity; 28import android.arch.persistence.room.PrimaryKey; 29import android.arch.persistence.room.Relation; 30import android.support.annotation.NonNull; 31 32import java.util.List; 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 42/** 43 * Stores information about a logical unit of work. 44 */ 45@Entity 46public class WorkSpec { 47 private static final String TAG = "WorkSpec"; 48 49 @ColumnInfo(name = "id") 50 @PrimaryKey 51 @NonNull 52 String mId; 53 54 @ColumnInfo(name = "state") 55 @NonNull 56 State mState = ENQUEUED; 57 58 @ColumnInfo(name = "worker_class_name") 59 @NonNull 60 String mWorkerClassName; 61 62 @ColumnInfo(name = "input_merger_class_name") 63 String mInputMergerClassName; 64 65 @ColumnInfo(name = "arguments") 66 @NonNull 67 Arguments mArguments = Arguments.EMPTY; 68 69 @ColumnInfo(name = "output") 70 @NonNull 71 Arguments mOutput = Arguments.EMPTY; 72 73 @ColumnInfo(name = "initial_delay") 74 long mInitialDelay; 75 76 @ColumnInfo(name = "interval_duration") 77 long mIntervalDuration; 78 79 @ColumnInfo(name = "flex_duration") 80 long mFlexDuration; 81 82 @Embedded 83 @NonNull 84 Constraints mConstraints = Constraints.NONE; 85 86 @ColumnInfo(name = "run_attempt_count") 87 int mRunAttemptCount; 88 89 // TODO(sumir): Should Backoff be disabled by default? 90 @ColumnInfo(name = "backoff_policy") 91 @NonNull 92 BackoffPolicy mBackoffPolicy = BackoffPolicy.EXPONENTIAL; 93 94 @ColumnInfo(name = "backoff_delay_duration") 95 long mBackoffDelayDuration = BaseWork.DEFAULT_BACKOFF_DELAY_MILLIS; 96 97 @ColumnInfo(name = "period_start_time") 98 long mPeriodStartTime; 99 100 @ColumnInfo(name = "minimum_retention_duration") 101 long mMinimumRetentionDuration; 102 103 public WorkSpec(@NonNull String id) { 104 mId = id; 105 } 106 107 @NonNull 108 public String getId() { 109 return mId; 110 } 111 112 public void setId(@NonNull String id) { 113 mId = id; 114 } 115 116 public @NonNull State getState() { 117 return mState; 118 } 119 120 public void setState(@NonNull State state) { 121 mState = state; 122 } 123 124 public @NonNull String getWorkerClassName() { 125 return mWorkerClassName; 126 } 127 128 public void setWorkerClassName(@NonNull String workerClassName) { 129 mWorkerClassName = workerClassName; 130 } 131 132 public String getInputMergerClassName() { 133 return mInputMergerClassName; 134 } 135 136 public void setInputMergerClassName(String inputMergerClassName) { 137 mInputMergerClassName = inputMergerClassName; 138 } 139 140 public @NonNull Arguments getArguments() { 141 return mArguments; 142 } 143 144 public void setArguments(@NonNull Arguments arguments) { 145 mArguments = arguments; 146 } 147 148 public @NonNull Arguments getOutput() { 149 return mOutput; 150 } 151 152 public void setOutput(@NonNull Arguments output) { 153 mOutput = output; 154 } 155 156 public @NonNull Constraints getConstraints() { 157 return mConstraints; 158 } 159 160 public void setConstraints(@NonNull Constraints constraints) { 161 mConstraints = constraints; 162 } 163 164 public @NonNull BackoffPolicy getBackoffPolicy() { 165 return mBackoffPolicy; 166 } 167 168 public void setBackoffPolicy(@NonNull BackoffPolicy backoffPolicy) { 169 mBackoffPolicy = backoffPolicy; 170 } 171 172 public long getBackoffDelayDuration() { 173 return mBackoffDelayDuration; 174 } 175 176 /** 177 * @param backoffDelayDuration The backoff delay duration in milliseconds 178 */ 179 public void setBackoffDelayDuration(long backoffDelayDuration) { 180 if (backoffDelayDuration > MAX_BACKOFF_MILLIS) { 181 Logger.warn(TAG, "Backoff delay duration exceeds maximum value"); 182 backoffDelayDuration = MAX_BACKOFF_MILLIS; 183 } 184 if (backoffDelayDuration < MIN_BACKOFF_MILLIS) { 185 Logger.warn(TAG, "Backoff delay duration less than minimum value"); 186 backoffDelayDuration = MIN_BACKOFF_MILLIS; 187 } 188 mBackoffDelayDuration = backoffDelayDuration; 189 } 190 191 public long getInitialDelay() { 192 return mInitialDelay; 193 } 194 195 public void setInitialDelay(long initialDelay) { 196 mInitialDelay = initialDelay; 197 } 198 199 public boolean isPeriodic() { 200 return mIntervalDuration != 0L; 201 } 202 203 public boolean isBackedOff() { 204 return mState == ENQUEUED && mRunAttemptCount > 0; 205 } 206 207 /** 208 * Sets the periodic interval for this unit of work. 209 * 210 * @param intervalDuration The interval in milliseconds 211 */ 212 public void setPeriodic(long intervalDuration) { 213 if (intervalDuration < MIN_PERIODIC_INTERVAL_MILLIS) { 214 Logger.warn(TAG, "Interval duration lesser than minimum allowed value; Changed to %s", 215 MIN_PERIODIC_INTERVAL_MILLIS); 216 intervalDuration = MIN_PERIODIC_INTERVAL_MILLIS; 217 } 218 setPeriodic(intervalDuration, intervalDuration); 219 } 220 221 /** 222 * Sets the periodic interval for this unit of work. 223 * 224 * @param intervalDuration The interval in milliseconds 225 * @param flexDuration The flex duration in milliseconds 226 */ 227 public void setPeriodic(long intervalDuration, long flexDuration) { 228 if (intervalDuration < MIN_PERIODIC_INTERVAL_MILLIS) { 229 Logger.warn(TAG, "Interval duration lesser than minimum allowed value; Changed to %s", 230 MIN_PERIODIC_INTERVAL_MILLIS); 231 intervalDuration = MIN_PERIODIC_INTERVAL_MILLIS; 232 } 233 if (flexDuration < MIN_PERIODIC_FLEX_MILLIS) { 234 Logger.warn(TAG, "Flex duration lesser than minimum allowed value; Changed to %s", 235 MIN_PERIODIC_FLEX_MILLIS); 236 flexDuration = MIN_PERIODIC_FLEX_MILLIS; 237 } 238 if (flexDuration > intervalDuration) { 239 Logger.warn(TAG, "Flex duration greater than interval duration; Changed to %s", 240 intervalDuration); 241 flexDuration = intervalDuration; 242 } 243 mIntervalDuration = intervalDuration; 244 mFlexDuration = flexDuration; 245 } 246 247 public long getIntervalDuration() { 248 return mIntervalDuration; 249 } 250 251 public void setIntervalDuration(long intervalDuration) { 252 mIntervalDuration = intervalDuration; 253 } 254 255 public long getFlexDuration() { 256 return mFlexDuration; 257 } 258 259 public void setFlexDuration(long flexDuration) { 260 mFlexDuration = flexDuration; 261 } 262 263 public void setRunAttemptCount(int runAttemptCount) { 264 this.mRunAttemptCount = runAttemptCount; 265 } 266 267 public int getRunAttemptCount() { 268 return mRunAttemptCount; 269 } 270 271 /** 272 * For one-off work, this is the time that the work was unblocked by prerequisites. 273 * For periodic work, this is the time that the period started. 274 */ 275 public long getPeriodStartTime() { 276 return mPeriodStartTime; 277 } 278 279 public void setPeriodStartTime(long periodStartTime) { 280 mPeriodStartTime = periodStartTime; 281 } 282 283 public long getMinimumRetentionDuration() { 284 return mMinimumRetentionDuration; 285 } 286 287 public void setMinimumRetentionDuration(long minimumRetentionDuration) { 288 mMinimumRetentionDuration = minimumRetentionDuration; 289 } 290 291 /** 292 * Calculates the UTC time at which this {@link WorkSpec} should be allowed to run. 293 * This method accounts for work that is backed off or periodic. 294 * 295 * If Backoff Policy is set to {@link BackoffPolicy#EXPONENTIAL}, then delay 296 * increases at an exponential rate with respect to the run attempt count and is capped at 297 * {@link BaseWork#MAX_BACKOFF_MILLIS}. 298 * 299 * If Backoff Policy is set to {@link BackoffPolicy#LINEAR}, then delay 300 * increases at an linear rate with respect to the run attempt count and is capped at 301 * {@link BaseWork#MAX_BACKOFF_MILLIS}. 302 * 303 * Based on {@see https://android.googlesource.com/platform/frameworks/base/+/master/services/core/java/com/android/server/job/JobSchedulerService.java#1125} 304 * 305 * Note that this runtime is for WorkManager internal use and may not match what the OS 306 * considers to be the next runtime. 307 * 308 * For jobs with constraints, this represents the earliest time at which constraints 309 * should be monitored for this work. 310 * 311 * For jobs without constraints, this represents the earliest time at which this work is 312 * allowed to run. 313 * 314 * @return UTC time at which this {@link WorkSpec} should be allowed to run. 315 */ 316 public long calculateNextRunTime() { 317 if (isBackedOff()) { 318 boolean isLinearBackoff = (mBackoffPolicy == BackoffPolicy.LINEAR); 319 long delay = isLinearBackoff ? (mBackoffDelayDuration * mRunAttemptCount) 320 : (long) Math.scalb(mBackoffDelayDuration, mRunAttemptCount - 1); 321 return mPeriodStartTime + Math.min(BaseWork.MAX_BACKOFF_MILLIS, delay); 322 } else if (isPeriodic()) { 323 return mPeriodStartTime + mIntervalDuration - mFlexDuration; 324 } else { 325 return mPeriodStartTime + mInitialDelay; 326 } 327 } 328 329 /** 330 * @return <code>true</code> if the {@link WorkSpec} has constraints. 331 */ 332 public boolean hasConstraints() { 333 return !Constraints.NONE.equals(getConstraints()); 334 } 335 336 @Override 337 public boolean equals(Object o) { 338 if (this == o) return true; 339 if (o == null || getClass() != o.getClass()) return false; 340 341 WorkSpec workSpec = (WorkSpec) o; 342 343 if (mInitialDelay != workSpec.mInitialDelay) return false; 344 if (mIntervalDuration != workSpec.mIntervalDuration) return false; 345 if (mFlexDuration != workSpec.mFlexDuration) return false; 346 if (mRunAttemptCount != workSpec.mRunAttemptCount) return false; 347 if (mBackoffDelayDuration != workSpec.mBackoffDelayDuration) return false; 348 if (mPeriodStartTime != workSpec.mPeriodStartTime) return false; 349 if (mMinimumRetentionDuration != workSpec.mMinimumRetentionDuration) return false; 350 if (!mId.equals(workSpec.mId)) return false; 351 if (mState != workSpec.mState) return false; 352 if (!mWorkerClassName.equals(workSpec.mWorkerClassName)) return false; 353 if (mInputMergerClassName != null ? !mInputMergerClassName.equals( 354 workSpec.mInputMergerClassName) : workSpec.mInputMergerClassName != null) { 355 return false; 356 } 357 if (!mArguments.equals(workSpec.mArguments)) return false; 358 if (!mOutput.equals(workSpec.mOutput)) return false; 359 if (!mConstraints.equals(workSpec.mConstraints)) return false; 360 return mBackoffPolicy == workSpec.mBackoffPolicy; 361 } 362 363 @Override 364 public int hashCode() { 365 int result = mId.hashCode(); 366 result = 31 * result + mState.hashCode(); 367 result = 31 * result + mWorkerClassName.hashCode(); 368 result = 31 * result + (mInputMergerClassName != null ? mInputMergerClassName.hashCode() 369 : 0); 370 result = 31 * result + mArguments.hashCode(); 371 result = 31 * result + mOutput.hashCode(); 372 result = 31 * result + (int) (mInitialDelay ^ (mInitialDelay >>> 32)); 373 result = 31 * result + (int) (mIntervalDuration ^ (mIntervalDuration >>> 32)); 374 result = 31 * result + (int) (mFlexDuration ^ (mFlexDuration >>> 32)); 375 result = 31 * result + mConstraints.hashCode(); 376 result = 31 * result + mRunAttemptCount; 377 result = 31 * result + mBackoffPolicy.hashCode(); 378 result = 31 * result + (int) (mBackoffDelayDuration ^ (mBackoffDelayDuration >>> 32)); 379 result = 31 * result + (int) (mPeriodStartTime ^ (mPeriodStartTime >>> 32)); 380 result = 31 * result + (int) (mMinimumRetentionDuration ^ (mMinimumRetentionDuration 381 >>> 32)); 382 return result; 383 } 384 385 @Override 386 public String toString() { 387 return "{WorkSpec: " + mId + "}"; 388 } 389 390 /** 391 * A POJO containing the ID and state of a WorkSpec. 392 */ 393 public static class IdAndState { 394 395 @ColumnInfo(name = "id") 396 public String id; 397 398 @ColumnInfo(name = "state") 399 public State state; 400 401 @Override 402 public boolean equals(Object o) { 403 if (this == o) return true; 404 if (o == null || getClass() != o.getClass()) return false; 405 406 IdAndState that = (IdAndState) o; 407 408 if (state != that.state) return false; 409 return id.equals(that.id); 410 } 411 412 @Override 413 public int hashCode() { 414 int result = id.hashCode(); 415 result = 31 * result + state.hashCode(); 416 return result; 417 } 418 } 419 420 /** 421 * A POJO containing the ID, state, output, and tags of a WorkSpec. 422 */ 423 public static class WorkStatusPojo { 424 425 @ColumnInfo(name = "id") 426 public String id; 427 428 @ColumnInfo(name = "state") 429 public State state; 430 431 @ColumnInfo(name = "output") 432 public Arguments output; 433 434 @Relation( 435 parentColumn = "id", 436 entityColumn = "work_spec_id", 437 entity = WorkTag.class, 438 projection = {"tag"}) 439 public List<String> tags; 440 441 /** 442 * Converts this POJO to a {@link WorkStatus}. 443 * 444 * @return The {@link WorkStatus} represented by this POJO 445 */ 446 public WorkStatus toWorkStatus() { 447 return new WorkStatus(id, state, output, tags); 448 } 449 450 @Override 451 public boolean equals(Object o) { 452 if (this == o) return true; 453 if (o == null || getClass() != o.getClass()) return false; 454 455 WorkStatusPojo that = (WorkStatusPojo) o; 456 457 if (id != null ? !id.equals(that.id) : that.id != null) return false; 458 if (state != that.state) return false; 459 if (output != null ? !output.equals(that.output) : that.output != null) return false; 460 return tags != null ? tags.equals(that.tags) : that.tags == null; 461 } 462 463 @Override 464 public int hashCode() { 465 int result = id != null ? id.hashCode() : 0; 466 result = 31 * result + (state != null ? state.hashCode() : 0); 467 result = 31 * result + (output != null ? output.hashCode() : 0); 468 result = 31 * result + (tags != null ? tags.hashCode() : 0); 469 return result; 470 } 471 } 472} 473