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