JobStatus.java revision 8db0fc15b85c6501a0418b17edee2d9c447b408a
1/* 2 * Copyright (C) 2014 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 com.android.server.job.controllers; 18 19import android.app.AppGlobals; 20import android.app.job.JobInfo; 21import android.content.ComponentName; 22import android.net.Uri; 23import android.os.PersistableBundle; 24import android.os.RemoteException; 25import android.os.SystemClock; 26import android.os.UserHandle; 27import android.text.format.DateUtils; 28import android.util.ArraySet; 29import android.util.TimeUtils; 30 31import java.io.PrintWriter; 32import java.util.concurrent.atomic.AtomicBoolean; 33 34/** 35 * Uniquely identifies a job internally. 36 * Created from the public {@link android.app.job.JobInfo} object when it lands on the scheduler. 37 * Contains current state of the requirements of the job, as well as a function to evaluate 38 * whether it's ready to run. 39 * This object is shared among the various controllers - hence why the different fields are atomic. 40 * This isn't strictly necessary because each controller is only interested in a specific field, 41 * and the receivers that are listening for global state change will all run on the main looper, 42 * but we don't enforce that so this is safer. 43 * @hide 44 */ 45public final class JobStatus { 46 public static final long NO_LATEST_RUNTIME = Long.MAX_VALUE; 47 public static final long NO_EARLIEST_RUNTIME = 0L; 48 49 static final int CONSTRAINT_CHARGING = 1<<0; 50 static final int CONSTRAINT_TIMING_DELAY = 1<<1; 51 static final int CONSTRAINT_DEADLINE = 1<<2; 52 static final int CONSTRAINT_IDLE = 1<<3; 53 static final int CONSTRAINT_UNMETERED = 1<<4; 54 static final int CONSTRAINT_CONNECTIVITY = 1<<5; 55 static final int CONSTRAINT_APP_NOT_IDLE = 1<<6; 56 static final int CONSTRAINT_CONTENT_TRIGGER = 1<<7; 57 static final int CONSTRAINT_DEVICE_NOT_DOZING = 1<<8; 58 59 // Soft override: ignore constraints like time that don't affect API availability 60 public static final int OVERRIDE_SOFT = 1; 61 // Full override: ignore all constraints including API-affecting like connectivity 62 public static final int OVERRIDE_FULL = 2; 63 64 /** If not specified, trigger update delay is 10 seconds. */ 65 public static final long DEFAULT_TRIGGER_UPDATE_DELAY = 10*1000; 66 67 /** The minimum possible update delay is 1/2 second. */ 68 public static final long MIN_TRIGGER_UPDATE_DELAY = 500; 69 70 /** If not specified, trigger maxumum delay is 2 minutes. */ 71 public static final long DEFAULT_TRIGGER_MAX_DELAY = 2*60*1000; 72 73 /** The minimum possible update delay is 1 second. */ 74 public static final long MIN_TRIGGER_MAX_DELAY = 1000; 75 76 final JobInfo job; 77 /** Uid of the package requesting this job. */ 78 final int callingUid; 79 final String batteryName; 80 81 final String sourcePackageName; 82 final int sourceUserId; 83 final int sourceUid; 84 final String sourceTag; 85 86 final String tag; 87 88 /** 89 * Earliest point in the future at which this job will be eligible to run. A value of 0 90 * indicates there is no delay constraint. See {@link #hasTimingDelayConstraint()}. 91 */ 92 private final long earliestRunTimeElapsedMillis; 93 /** 94 * Latest point in the future at which this job must be run. A value of {@link Long#MAX_VALUE} 95 * indicates there is no deadline constraint. See {@link #hasDeadlineConstraint()}. 96 */ 97 private final long latestRunTimeElapsedMillis; 98 99 /** How many times this job has failed, used to compute back-off. */ 100 private final int numFailures; 101 102 // Constraints. 103 final int requiredConstraints; 104 int satisfiedConstraints = 0; 105 106 // These are filled in by controllers when preparing for execution. 107 public ArraySet<Uri> changedUris; 108 public ArraySet<String> changedAuthorities; 109 110 public int lastEvaluatedPriority; 111 112 // Used by shell commands 113 public int overrideState = 0; 114 115 /** 116 * For use only by ContentObserverController: state it is maintaining about content URIs 117 * being observed. 118 */ 119 ContentObserverController.JobInstance contentObserverJobInstance; 120 121 /** Provide a handle to the service that this job will be run on. */ 122 public int getServiceToken() { 123 return callingUid; 124 } 125 126 private JobStatus(JobInfo job, int callingUid, String sourcePackageName, 127 int sourceUserId, String tag, int numFailures, long earliestRunTimeElapsedMillis, 128 long latestRunTimeElapsedMillis) { 129 this.job = job; 130 this.callingUid = callingUid; 131 132 int tempSourceUid = -1; 133 if (sourceUserId != -1 && sourcePackageName != null) { 134 try { 135 tempSourceUid = AppGlobals.getPackageManager().getPackageUid(sourcePackageName, 0, 136 sourceUserId); 137 } catch (RemoteException ex) { 138 // Can't happen, PackageManager runs in the same process. 139 } 140 } 141 if (tempSourceUid == -1) { 142 this.sourceUid = callingUid; 143 this.sourceUserId = UserHandle.getUserId(callingUid); 144 this.sourcePackageName = job.getService().getPackageName(); 145 this.sourceTag = null; 146 } else { 147 this.sourceUid = tempSourceUid; 148 this.sourceUserId = sourceUserId; 149 this.sourcePackageName = sourcePackageName; 150 this.sourceTag = tag; 151 } 152 153 if (this.sourceTag != null) { 154 StringBuilder sb = new StringBuilder(); 155 sb.append(job.getService().getPackageName()); 156 sb.append('/'); 157 sb.append(this.sourceTag); 158 if (sourcePackageName != null) { 159 sb.append('/'); 160 sb.append(this.sourcePackageName); 161 } 162 this.batteryName = sb.toString(); 163 } else { 164 this.batteryName = job.getService().flattenToShortString(); 165 } 166 this.tag = "*job*/" + this.batteryName; 167 168 this.earliestRunTimeElapsedMillis = earliestRunTimeElapsedMillis; 169 this.latestRunTimeElapsedMillis = latestRunTimeElapsedMillis; 170 this.numFailures = numFailures; 171 172 int requiredConstraints = 0; 173 if (job.getNetworkType() == JobInfo.NETWORK_TYPE_ANY) { 174 requiredConstraints |= CONSTRAINT_CONNECTIVITY; 175 } 176 if (job.getNetworkType() == JobInfo.NETWORK_TYPE_UNMETERED) { 177 requiredConstraints |= CONSTRAINT_UNMETERED; 178 } 179 if (job.isRequireCharging()) { 180 requiredConstraints |= CONSTRAINT_CHARGING; 181 } 182 if (earliestRunTimeElapsedMillis != NO_EARLIEST_RUNTIME) { 183 requiredConstraints |= CONSTRAINT_TIMING_DELAY; 184 } 185 if (latestRunTimeElapsedMillis != NO_LATEST_RUNTIME) { 186 requiredConstraints |= CONSTRAINT_DEADLINE; 187 } 188 if (job.isRequireDeviceIdle()) { 189 requiredConstraints |= CONSTRAINT_IDLE; 190 } 191 if (job.getTriggerContentUris() != null) { 192 requiredConstraints |= CONSTRAINT_CONTENT_TRIGGER; 193 } 194 this.requiredConstraints = requiredConstraints; 195 } 196 197 /** Copy constructor. */ 198 public JobStatus(JobStatus jobStatus) { 199 this(jobStatus.getJob(), jobStatus.getUid(), 200 jobStatus.getSourcePackageName(), jobStatus.getSourceUserId(), 201 jobStatus.getSourceTag(), jobStatus.getNumFailures(), 202 jobStatus.getEarliestRunTime(), jobStatus.getLatestRunTimeElapsed()); 203 } 204 205 /** 206 * Create a new JobStatus that was loaded from disk. We ignore the provided 207 * {@link android.app.job.JobInfo} time criteria because we can load a persisted periodic job 208 * from the {@link com.android.server.job.JobStore} and still want to respect its 209 * wallclock runtime rather than resetting it on every boot. 210 * We consider a freshly loaded job to no longer be in back-off. 211 */ 212 public JobStatus(JobInfo job, int callingUid, String sourcePackageName, int sourceUserId, 213 String sourceTag, long earliestRunTimeElapsedMillis, long latestRunTimeElapsedMillis) { 214 this(job, callingUid, sourcePackageName, sourceUserId, sourceTag, 0, 215 earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis); 216 } 217 218 /** Create a new job to be rescheduled with the provided parameters. */ 219 public JobStatus(JobStatus rescheduling, long newEarliestRuntimeElapsedMillis, 220 long newLatestRuntimeElapsedMillis, int backoffAttempt) { 221 this(rescheduling.job, rescheduling.getUid(), 222 rescheduling.getSourcePackageName(), rescheduling.getSourceUserId(), 223 rescheduling.getSourceTag(), backoffAttempt, newEarliestRuntimeElapsedMillis, 224 newLatestRuntimeElapsedMillis); 225 } 226 227 /** 228 * Create a newly scheduled job. 229 * @param callingUid Uid of the package that scheduled this job. 230 * @param sourcePackageName Package name on whose behalf this job is scheduled. Null indicates 231 * the calling package is the source. 232 * @param sourceUserId User id for whom this job is scheduled. -1 indicates this is same as the 233 */ 234 public static JobStatus createFromJobInfo(JobInfo job, int callingUid, String sourcePackageName, 235 int sourceUserId, String tag) { 236 final long elapsedNow = SystemClock.elapsedRealtime(); 237 final long earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis; 238 if (job.isPeriodic()) { 239 latestRunTimeElapsedMillis = elapsedNow + job.getIntervalMillis(); 240 earliestRunTimeElapsedMillis = latestRunTimeElapsedMillis - job.getFlexMillis(); 241 } else { 242 earliestRunTimeElapsedMillis = job.hasEarlyConstraint() ? 243 elapsedNow + job.getMinLatencyMillis() : NO_EARLIEST_RUNTIME; 244 latestRunTimeElapsedMillis = job.hasLateConstraint() ? 245 elapsedNow + job.getMaxExecutionDelayMillis() : NO_LATEST_RUNTIME; 246 } 247 return new JobStatus(job, callingUid, sourcePackageName, sourceUserId, tag, 0, 248 earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis); 249 } 250 251 public JobInfo getJob() { 252 return job; 253 } 254 255 public int getJobId() { 256 return job.getId(); 257 } 258 259 public int getNumFailures() { 260 return numFailures; 261 } 262 263 public ComponentName getServiceComponent() { 264 return job.getService(); 265 } 266 267 public String getSourcePackageName() { 268 return sourcePackageName; 269 } 270 271 public int getSourceUid() { 272 return sourceUid; 273 } 274 275 public int getSourceUserId() { 276 return sourceUserId; 277 } 278 279 public int getUserId() { 280 return UserHandle.getUserId(callingUid); 281 } 282 283 public String getSourceTag() { 284 return sourceTag; 285 } 286 287 public int getUid() { 288 return callingUid; 289 } 290 291 public String getBatteryName() { 292 return batteryName; 293 } 294 295 public String getTag() { 296 return tag; 297 } 298 299 public PersistableBundle getExtras() { 300 return job.getExtras(); 301 } 302 303 public int getPriority() { 304 return job.getPriority(); 305 } 306 307 public boolean hasConnectivityConstraint() { 308 return (requiredConstraints&CONSTRAINT_CONNECTIVITY) != 0; 309 } 310 311 public boolean hasUnmeteredConstraint() { 312 return (requiredConstraints&CONSTRAINT_UNMETERED) != 0; 313 } 314 315 public boolean hasChargingConstraint() { 316 return (requiredConstraints&CONSTRAINT_CHARGING) != 0; 317 } 318 319 public boolean hasTimingDelayConstraint() { 320 return (requiredConstraints&CONSTRAINT_TIMING_DELAY) != 0; 321 } 322 323 public boolean hasDeadlineConstraint() { 324 return (requiredConstraints&CONSTRAINT_DEADLINE) != 0; 325 } 326 327 public boolean hasIdleConstraint() { 328 return (requiredConstraints&CONSTRAINT_IDLE) != 0; 329 } 330 331 public boolean hasContentTriggerConstraint() { 332 return (requiredConstraints&CONSTRAINT_CONTENT_TRIGGER) != 0; 333 } 334 335 public long getTriggerContentUpdateDelay() { 336 long time = job.getTriggerContentUpdateDelay(); 337 if (time < 0) { 338 return DEFAULT_TRIGGER_UPDATE_DELAY; 339 } 340 return Math.max(time, MIN_TRIGGER_UPDATE_DELAY); 341 } 342 343 public long getTriggerContentMaxDelay() { 344 long time = job.getTriggerContentMaxDelay(); 345 if (time < 0) { 346 return DEFAULT_TRIGGER_MAX_DELAY; 347 } 348 return Math.max(time, MIN_TRIGGER_MAX_DELAY); 349 } 350 351 public boolean isPersisted() { 352 return job.isPersisted(); 353 } 354 355 public long getEarliestRunTime() { 356 return earliestRunTimeElapsedMillis; 357 } 358 359 public long getLatestRunTimeElapsed() { 360 return latestRunTimeElapsedMillis; 361 } 362 363 boolean setChargingConstraintSatisfied(boolean state) { 364 return setConstraintSatisfied(CONSTRAINT_CHARGING, state); 365 } 366 367 boolean setTimingDelayConstraintSatisfied(boolean state) { 368 return setConstraintSatisfied(CONSTRAINT_TIMING_DELAY, state); 369 } 370 371 boolean setDeadlineConstraintSatisfied(boolean state) { 372 return setConstraintSatisfied(CONSTRAINT_DEADLINE, state); 373 } 374 375 boolean setIdleConstraintSatisfied(boolean state) { 376 return setConstraintSatisfied(CONSTRAINT_IDLE, state); 377 } 378 379 boolean setUnmeteredConstraintSatisfied(boolean state) { 380 return setConstraintSatisfied(CONSTRAINT_UNMETERED, state); 381 } 382 383 boolean setConnectivityConstraintSatisfied(boolean state) { 384 return setConstraintSatisfied(CONSTRAINT_CONNECTIVITY, state); 385 } 386 387 boolean setAppNotIdleConstraintSatisfied(boolean state) { 388 return setConstraintSatisfied(CONSTRAINT_APP_NOT_IDLE, state); 389 } 390 391 boolean setContentTriggerConstraintSatisfied(boolean state) { 392 return setConstraintSatisfied(CONSTRAINT_CONTENT_TRIGGER, state); 393 } 394 395 boolean setDeviceNotDozingConstraintSatisfied(boolean state) { 396 return setConstraintSatisfied(CONSTRAINT_DEVICE_NOT_DOZING, state); 397 } 398 399 boolean setConstraintSatisfied(int constraint, boolean state) { 400 boolean old = (satisfiedConstraints&constraint) != 0; 401 if (old == state) { 402 return false; 403 } 404 satisfiedConstraints = (satisfiedConstraints&~constraint) | (state ? constraint : 0); 405 return true; 406 } 407 408 /** 409 * @return Whether or not this job is ready to run, based on its requirements. This is true if 410 * the constraints are satisfied <strong>or</strong> the deadline on the job has expired. 411 */ 412 public boolean isReady() { 413 // Deadline constraint trumps other constraints (except for periodic jobs where deadline 414 // is an implementation detail. A periodic job should only run if its constraints are 415 // satisfied). 416 // AppNotIdle implicit constraint must be satisfied 417 // DeviceNotDozing implicit constraint must be satisfied 418 return (isConstraintsSatisfied() 419 || (!job.isPeriodic() 420 && hasDeadlineConstraint() && (satisfiedConstraints&CONSTRAINT_DEADLINE) != 0) 421 ) 422 && (satisfiedConstraints & CONSTRAINT_APP_NOT_IDLE) != 0 423 && (satisfiedConstraints & CONSTRAINT_DEVICE_NOT_DOZING) != 0; 424 } 425 426 static final int CONSTRAINTS_OF_INTEREST = 427 CONSTRAINT_CHARGING | CONSTRAINT_TIMING_DELAY | 428 CONSTRAINT_CONNECTIVITY | CONSTRAINT_UNMETERED | 429 CONSTRAINT_IDLE | CONSTRAINT_CONTENT_TRIGGER; 430 431 // Soft override covers all non-"functional" constraints 432 static final int SOFT_OVERRIDE_CONSTRAINTS = 433 CONSTRAINT_CHARGING | CONSTRAINT_TIMING_DELAY | CONSTRAINT_IDLE; 434 435 /** 436 * @return Whether the constraints set on this job are satisfied. 437 */ 438 public boolean isConstraintsSatisfied() { 439 if (overrideState == OVERRIDE_FULL) { 440 // force override: the job is always runnable 441 return true; 442 } 443 444 final int req = requiredConstraints & CONSTRAINTS_OF_INTEREST; 445 446 int sat = satisfiedConstraints & CONSTRAINTS_OF_INTEREST; 447 if (overrideState == OVERRIDE_SOFT) { 448 // override: pretend all 'soft' requirements are satisfied 449 sat |= (requiredConstraints & SOFT_OVERRIDE_CONSTRAINTS); 450 } 451 452 return (sat & req) == req; 453 } 454 455 public boolean matches(int uid, int jobId) { 456 return this.job.getId() == jobId && this.callingUid == uid; 457 } 458 459 @Override 460 public String toString() { 461 return String.valueOf(hashCode()).substring(0, 3) + ".." 462 + ":[" + job.getService() 463 + ",jId=" + job.getId() 464 + ",u" + getUserId() 465 + ",R=(" + formatRunTime(earliestRunTimeElapsedMillis, NO_EARLIEST_RUNTIME) 466 + "," + formatRunTime(latestRunTimeElapsedMillis, NO_LATEST_RUNTIME) + ")" 467 + ",N=" + job.getNetworkType() + ",C=" + job.isRequireCharging() 468 + ",I=" + job.isRequireDeviceIdle() 469 + ",U=" + (job.getTriggerContentUris() != null) 470 + ",F=" + numFailures + ",P=" + job.isPersisted() 471 + ",ANI=" + ((satisfiedConstraints&CONSTRAINT_APP_NOT_IDLE) != 0) 472 + ",DND=" + ((satisfiedConstraints&CONSTRAINT_DEVICE_NOT_DOZING) != 0) 473 + (isReady() ? "(READY)" : "") 474 + "]"; 475 } 476 477 private String formatRunTime(long runtime, long defaultValue) { 478 if (runtime == defaultValue) { 479 return "none"; 480 } else { 481 long elapsedNow = SystemClock.elapsedRealtime(); 482 long nextRuntime = runtime - elapsedNow; 483 if (nextRuntime > 0) { 484 return DateUtils.formatElapsedTime(nextRuntime / 1000); 485 } else { 486 return "-" + DateUtils.formatElapsedTime(nextRuntime / -1000); 487 } 488 } 489 } 490 491 /** 492 * Convenience function to identify a job uniquely without pulling all the data that 493 * {@link #toString()} returns. 494 */ 495 public String toShortString() { 496 StringBuilder sb = new StringBuilder(); 497 sb.append(Integer.toHexString(System.identityHashCode(this))); 498 sb.append(" jId="); 499 sb.append(job.getId()); 500 sb.append(' '); 501 UserHandle.formatUid(sb, callingUid); 502 sb.append(' '); 503 sb.append(batteryName); 504 return sb.toString(); 505 } 506 507 void dumpConstraints(PrintWriter pw, int constraints) { 508 if ((constraints&CONSTRAINT_CHARGING) != 0) { 509 pw.print(" CHARGING"); 510 } 511 if ((constraints&CONSTRAINT_TIMING_DELAY) != 0) { 512 pw.print(" TIMING_DELAY"); 513 } 514 if ((constraints&CONSTRAINT_DEADLINE) != 0) { 515 pw.print(" DEADLINE"); 516 } 517 if ((constraints&CONSTRAINT_IDLE) != 0) { 518 pw.print(" IDLE"); 519 } 520 if ((constraints&CONSTRAINT_UNMETERED) != 0) { 521 pw.print(" UNMETERED"); 522 } 523 if ((constraints&CONSTRAINT_CONNECTIVITY) != 0) { 524 pw.print(" CONNECTIVITY"); 525 } 526 if ((constraints&CONSTRAINT_APP_NOT_IDLE) != 0) { 527 pw.print(" APP_NOT_IDLE"); 528 } 529 if ((constraints&CONSTRAINT_CONTENT_TRIGGER) != 0) { 530 pw.print(" CONTENT_TRIGGER"); 531 } 532 if ((constraints&CONSTRAINT_DEVICE_NOT_DOZING) != 0) { 533 pw.print(" DEVICE_NOT_DOZING"); 534 } 535 } 536 537 // Dumpsys infrastructure 538 public void dump(PrintWriter pw, String prefix, boolean full) { 539 pw.print(prefix); UserHandle.formatUid(pw, callingUid); 540 pw.print(" tag="); pw.println(tag); 541 pw.print(prefix); 542 pw.print("Source: uid="); UserHandle.formatUid(pw, getSourceUid()); 543 pw.print(" user="); pw.print(getSourceUserId()); 544 pw.print(" pkg="); pw.println(getSourcePackageName()); 545 if (full) { 546 pw.print(prefix); pw.println("JobInfo:"); pw.print(prefix); 547 pw.print(" Service: "); pw.println(job.getService().flattenToShortString()); 548 if (job.isPeriodic()) { 549 pw.print(prefix); pw.print(" PERIODIC: interval="); 550 TimeUtils.formatDuration(job.getIntervalMillis(), pw); 551 pw.print(" flex="); TimeUtils.formatDuration(job.getFlexMillis(), pw); 552 pw.println(); 553 } 554 if (job.isPersisted()) { 555 pw.print(prefix); pw.println(" PERSISTED"); 556 } 557 if (job.getPriority() != 0) { 558 pw.print(prefix); pw.print(" Priority: "); pw.println(job.getPriority()); 559 } 560 pw.print(prefix); pw.print(" Requires: charging="); 561 pw.print(job.isRequireCharging()); pw.print(" deviceIdle="); 562 pw.println(job.isRequireDeviceIdle()); 563 if (job.getTriggerContentUris() != null) { 564 pw.print(prefix); pw.println(" Trigger content URIs:"); 565 for (int i = 0; i < job.getTriggerContentUris().length; i++) { 566 JobInfo.TriggerContentUri trig = job.getTriggerContentUris()[i]; 567 pw.print(prefix); pw.print(" "); 568 pw.print(Integer.toHexString(trig.getFlags())); 569 pw.print(' '); pw.println(trig.getUri()); 570 } 571 if (job.getTriggerContentUpdateDelay() >= 0) { 572 pw.print(prefix); pw.print(" Trigger update delay: "); 573 TimeUtils.formatDuration(job.getTriggerContentUpdateDelay(), pw); 574 pw.println(); 575 } 576 if (job.getTriggerContentMaxDelay() >= 0) { 577 pw.print(prefix); pw.print(" Trigger max delay: "); 578 TimeUtils.formatDuration(job.getTriggerContentMaxDelay(), pw); 579 pw.println(); 580 } 581 } 582 if (job.getNetworkType() != JobInfo.NETWORK_TYPE_NONE) { 583 pw.print(prefix); pw.print(" Network type: "); pw.println(job.getNetworkType()); 584 } 585 if (job.getMinLatencyMillis() != 0) { 586 pw.print(prefix); pw.print(" Minimum latency: "); 587 TimeUtils.formatDuration(job.getMinLatencyMillis(), pw); 588 pw.println(); 589 } 590 if (job.getMaxExecutionDelayMillis() != 0) { 591 pw.print(prefix); pw.print(" Max execution delay: "); 592 TimeUtils.formatDuration(job.getMaxExecutionDelayMillis(), pw); 593 pw.println(); 594 } 595 pw.print(prefix); pw.print(" Backoff: policy="); pw.print(job.getBackoffPolicy()); 596 pw.print(" initial="); TimeUtils.formatDuration(job.getInitialBackoffMillis(), pw); 597 pw.println(); 598 if (job.hasEarlyConstraint()) { 599 pw.print(prefix); pw.println(" Has early constraint"); 600 } 601 if (job.hasLateConstraint()) { 602 pw.print(prefix); pw.println(" Has late constraint"); 603 } 604 } 605 pw.print(prefix); pw.print("Required constraints:"); 606 dumpConstraints(pw, requiredConstraints); 607 pw.println(); 608 if (full) { 609 pw.print(prefix); pw.print("Satisfied constraints:"); 610 dumpConstraints(pw, satisfiedConstraints); 611 pw.println(); 612 } 613 if (changedAuthorities != null) { 614 pw.print(prefix); pw.println("Changed authorities:"); 615 for (int i=0; i<changedAuthorities.size(); i++) { 616 pw.print(prefix); pw.print(" "); pw.println(changedAuthorities.valueAt(i)); 617 } 618 if (changedUris != null) { 619 pw.print(prefix); pw.println("Changed URIs:"); 620 for (int i=0; i<changedUris.size(); i++) { 621 pw.print(prefix); pw.print(" "); pw.println(changedUris.valueAt(i)); 622 } 623 } 624 } 625 pw.print(prefix); pw.print("Earliest run time: "); 626 pw.println(formatRunTime(earliestRunTimeElapsedMillis, NO_EARLIEST_RUNTIME)); 627 pw.print(prefix); pw.print("Latest run time: "); 628 pw.println(formatRunTime(latestRunTimeElapsedMillis, NO_LATEST_RUNTIME)); 629 if (numFailures != 0) { 630 pw.print(prefix); pw.print("Num failures: "); pw.println(numFailures); 631 } 632 } 633} 634