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