Home | History | Annotate | Download | only in controllers
      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 
     17 package com.android.server.job.controllers;
     18 
     19 import android.app.AppGlobals;
     20 import android.app.IActivityManager;
     21 import android.app.job.JobInfo;
     22 import android.app.job.JobWorkItem;
     23 import android.content.ClipData;
     24 import android.content.ComponentName;
     25 import android.net.Uri;
     26 import android.os.RemoteException;
     27 import android.os.SystemClock;
     28 import android.os.UserHandle;
     29 import android.text.format.Time;
     30 import android.util.ArraySet;
     31 import android.util.Pair;
     32 import android.util.Slog;
     33 import android.util.TimeUtils;
     34 
     35 import com.android.server.job.GrantedUriPermissions;
     36 import com.android.server.job.JobSchedulerService;
     37 
     38 import java.io.PrintWriter;
     39 import java.util.ArrayList;
     40 import java.util.Arrays;
     41 
     42 /**
     43  * Uniquely identifies a job internally.
     44  * Created from the public {@link android.app.job.JobInfo} object when it lands on the scheduler.
     45  * Contains current state of the requirements of the job, as well as a function to evaluate
     46  * whether it's ready to run.
     47  * This object is shared among the various controllers - hence why the different fields are atomic.
     48  * This isn't strictly necessary because each controller is only interested in a specific field,
     49  * and the receivers that are listening for global state change will all run on the main looper,
     50  * but we don't enforce that so this is safer.
     51  * @hide
     52  */
     53 public final class JobStatus {
     54     static final String TAG = "JobSchedulerService";
     55     static final boolean DEBUG = JobSchedulerService.DEBUG;
     56 
     57     public static final long NO_LATEST_RUNTIME = Long.MAX_VALUE;
     58     public static final long NO_EARLIEST_RUNTIME = 0L;
     59 
     60     static final int CONSTRAINT_CHARGING = JobInfo.CONSTRAINT_FLAG_CHARGING;
     61     static final int CONSTRAINT_IDLE = JobInfo.CONSTRAINT_FLAG_DEVICE_IDLE;
     62     static final int CONSTRAINT_BATTERY_NOT_LOW = JobInfo.CONSTRAINT_FLAG_BATTERY_NOT_LOW;
     63     static final int CONSTRAINT_STORAGE_NOT_LOW = JobInfo.CONSTRAINT_FLAG_STORAGE_NOT_LOW;
     64     static final int CONSTRAINT_TIMING_DELAY = 1<<31;
     65     static final int CONSTRAINT_DEADLINE = 1<<30;
     66     static final int CONSTRAINT_UNMETERED = 1<<29;
     67     static final int CONSTRAINT_CONNECTIVITY = 1<<28;
     68     static final int CONSTRAINT_APP_NOT_IDLE = 1<<27;
     69     static final int CONSTRAINT_CONTENT_TRIGGER = 1<<26;
     70     static final int CONSTRAINT_DEVICE_NOT_DOZING = 1<<25;
     71     static final int CONSTRAINT_NOT_ROAMING = 1<<24;
     72     static final int CONSTRAINT_METERED = 1<<23;
     73 
     74     static final int CONNECTIVITY_MASK =
     75             CONSTRAINT_UNMETERED | CONSTRAINT_CONNECTIVITY |
     76             CONSTRAINT_NOT_ROAMING | CONSTRAINT_METERED;
     77 
     78     // Soft override: ignore constraints like time that don't affect API availability
     79     public static final int OVERRIDE_SOFT = 1;
     80     // Full override: ignore all constraints including API-affecting like connectivity
     81     public static final int OVERRIDE_FULL = 2;
     82 
     83     /** If not specified, trigger update delay is 10 seconds. */
     84     public static final long DEFAULT_TRIGGER_UPDATE_DELAY = 10*1000;
     85 
     86     /** The minimum possible update delay is 1/2 second. */
     87     public static final long MIN_TRIGGER_UPDATE_DELAY = 500;
     88 
     89     /** If not specified, trigger maxumum delay is 2 minutes. */
     90     public static final long DEFAULT_TRIGGER_MAX_DELAY = 2*60*1000;
     91 
     92     /** The minimum possible update delay is 1 second. */
     93     public static final long MIN_TRIGGER_MAX_DELAY = 1000;
     94 
     95     final JobInfo job;
     96     /** Uid of the package requesting this job. */
     97     final int callingUid;
     98     final String batteryName;
     99 
    100     final String sourcePackageName;
    101     final int sourceUserId;
    102     final int sourceUid;
    103     final String sourceTag;
    104 
    105     final String tag;
    106 
    107     private GrantedUriPermissions uriPerms;
    108     private boolean prepared;
    109 
    110     static final boolean DEBUG_PREPARE = true;
    111     private Throwable unpreparedPoint = null;
    112 
    113     /**
    114      * Earliest point in the future at which this job will be eligible to run. A value of 0
    115      * indicates there is no delay constraint. See {@link #hasTimingDelayConstraint()}.
    116      */
    117     private final long earliestRunTimeElapsedMillis;
    118     /**
    119      * Latest point in the future at which this job must be run. A value of {@link Long#MAX_VALUE}
    120      * indicates there is no deadline constraint. See {@link #hasDeadlineConstraint()}.
    121      */
    122     private final long latestRunTimeElapsedMillis;
    123 
    124     /** How many times this job has failed, used to compute back-off. */
    125     private final int numFailures;
    126 
    127     // Constraints.
    128     final int requiredConstraints;
    129     int satisfiedConstraints = 0;
    130 
    131     // Set to true if doze constraint was satisfied due to app being whitelisted.
    132     public boolean dozeWhitelisted;
    133 
    134     /**
    135      * Flag for {@link #trackingControllers}: the battery controller is currently tracking this job.
    136      */
    137     public static final int TRACKING_BATTERY = 1<<0;
    138     /**
    139      * Flag for {@link #trackingControllers}: the network connectivity controller is currently
    140      * tracking this job.
    141      */
    142     public static final int TRACKING_CONNECTIVITY = 1<<1;
    143     /**
    144      * Flag for {@link #trackingControllers}: the content observer controller is currently
    145      * tracking this job.
    146      */
    147     public static final int TRACKING_CONTENT = 1<<2;
    148     /**
    149      * Flag for {@link #trackingControllers}: the idle controller is currently tracking this job.
    150      */
    151     public static final int TRACKING_IDLE = 1<<3;
    152     /**
    153      * Flag for {@link #trackingControllers}: the storage controller is currently tracking this job.
    154      */
    155     public static final int TRACKING_STORAGE = 1<<4;
    156     /**
    157      * Flag for {@link #trackingControllers}: the time controller is currently tracking this job.
    158      */
    159     public static final int TRACKING_TIME = 1<<5;
    160 
    161     /**
    162      * Bit mask of controllers that are currently tracking the job.
    163      */
    164     private int trackingControllers;
    165 
    166     // These are filled in by controllers when preparing for execution.
    167     public ArraySet<Uri> changedUris;
    168     public ArraySet<String> changedAuthorities;
    169 
    170     public int lastEvaluatedPriority;
    171 
    172     // If non-null, this is work that has been enqueued for the job.
    173     public ArrayList<JobWorkItem> pendingWork;
    174 
    175     // If non-null, this is work that is currently being executed.
    176     public ArrayList<JobWorkItem> executingWork;
    177 
    178     public int nextPendingWorkId = 1;
    179 
    180     // Used by shell commands
    181     public int overrideState = 0;
    182 
    183     // When this job was enqueued, for ordering.  (in elapsedRealtimeMillis)
    184     public long enqueueTime;
    185 
    186     // Metrics about queue latency.  (in uptimeMillis)
    187     public long madePending;
    188     public long madeActive;
    189 
    190     /**
    191      * Last time a job finished successfully for a periodic job, in the currentTimeMillis time,
    192      * for dumpsys.
    193      */
    194     private long mLastSuccessfulRunTime;
    195 
    196     /**
    197      * Last time a job finished unsuccessfully, in the currentTimeMillis time, for dumpsys.
    198      */
    199     private long mLastFailedRunTime;
    200 
    201     /**
    202      * Transient: when a job is inflated from disk before we have a reliable RTC clock time,
    203      * we retain the canonical (delay, deadline) scheduling tuple read out of the persistent
    204      * store in UTC so that we can fix up the job's scheduling criteria once we get a good
    205      * wall-clock time.  If we have to persist the job again before the clock has been updated,
    206      * we record these times again rather than calculating based on the earliest/latest elapsed
    207      * time base figures.
    208      *
    209      * 'first' is the earliest/delay time, and 'second' is the latest/deadline time.
    210      */
    211     private Pair<Long, Long> mPersistedUtcTimes;
    212 
    213     /**
    214      * For use only by ContentObserverController: state it is maintaining about content URIs
    215      * being observed.
    216      */
    217     ContentObserverController.JobInstance contentObserverJobInstance;
    218 
    219     /** Provide a handle to the service that this job will be run on. */
    220     public int getServiceToken() {
    221         return callingUid;
    222     }
    223 
    224     private JobStatus(JobInfo job, int callingUid, String sourcePackageName,
    225             int sourceUserId, String tag, int numFailures, long earliestRunTimeElapsedMillis,
    226             long latestRunTimeElapsedMillis, long lastSuccessfulRunTime, long lastFailedRunTime) {
    227         this.job = job;
    228         this.callingUid = callingUid;
    229 
    230         int tempSourceUid = -1;
    231         if (sourceUserId != -1 && sourcePackageName != null) {
    232             try {
    233                 tempSourceUid = AppGlobals.getPackageManager().getPackageUid(sourcePackageName, 0,
    234                         sourceUserId);
    235             } catch (RemoteException ex) {
    236                 // Can't happen, PackageManager runs in the same process.
    237             }
    238         }
    239         if (tempSourceUid == -1) {
    240             this.sourceUid = callingUid;
    241             this.sourceUserId = UserHandle.getUserId(callingUid);
    242             this.sourcePackageName = job.getService().getPackageName();
    243             this.sourceTag = null;
    244         } else {
    245             this.sourceUid = tempSourceUid;
    246             this.sourceUserId = sourceUserId;
    247             this.sourcePackageName = sourcePackageName;
    248             this.sourceTag = tag;
    249         }
    250 
    251         this.batteryName = this.sourceTag != null
    252                 ? this.sourceTag + ":" + job.getService().getPackageName()
    253                 : job.getService().flattenToShortString();
    254         this.tag = "*job*/" + this.batteryName;
    255 
    256         this.earliestRunTimeElapsedMillis = earliestRunTimeElapsedMillis;
    257         this.latestRunTimeElapsedMillis = latestRunTimeElapsedMillis;
    258         this.numFailures = numFailures;
    259 
    260         int requiredConstraints = job.getConstraintFlags();
    261 
    262         switch (job.getNetworkType()) {
    263             case JobInfo.NETWORK_TYPE_NONE:
    264                 // No constraint.
    265                 break;
    266             case JobInfo.NETWORK_TYPE_ANY:
    267                 requiredConstraints |= CONSTRAINT_CONNECTIVITY;
    268                 break;
    269             case JobInfo.NETWORK_TYPE_UNMETERED:
    270                 requiredConstraints |= CONSTRAINT_UNMETERED;
    271                 break;
    272             case JobInfo.NETWORK_TYPE_NOT_ROAMING:
    273                 requiredConstraints |= CONSTRAINT_NOT_ROAMING;
    274                 break;
    275             case JobInfo.NETWORK_TYPE_METERED:
    276                 requiredConstraints |= CONSTRAINT_METERED;
    277                 break;
    278             default:
    279                 Slog.w(TAG, "Unrecognized networking constraint " + job.getNetworkType());
    280                 break;
    281         }
    282 
    283         if (earliestRunTimeElapsedMillis != NO_EARLIEST_RUNTIME) {
    284             requiredConstraints |= CONSTRAINT_TIMING_DELAY;
    285         }
    286         if (latestRunTimeElapsedMillis != NO_LATEST_RUNTIME) {
    287             requiredConstraints |= CONSTRAINT_DEADLINE;
    288         }
    289         if (job.getTriggerContentUris() != null) {
    290             requiredConstraints |= CONSTRAINT_CONTENT_TRIGGER;
    291         }
    292         this.requiredConstraints = requiredConstraints;
    293 
    294         mLastSuccessfulRunTime = lastSuccessfulRunTime;
    295         mLastFailedRunTime = lastFailedRunTime;
    296     }
    297 
    298     /** Copy constructor: used specifically when cloning JobStatus objects for persistence,
    299      *   so we preserve RTC window bounds if the source object has them. */
    300     public JobStatus(JobStatus jobStatus) {
    301         this(jobStatus.getJob(), jobStatus.getUid(),
    302                 jobStatus.getSourcePackageName(), jobStatus.getSourceUserId(),
    303                 jobStatus.getSourceTag(), jobStatus.getNumFailures(),
    304                 jobStatus.getEarliestRunTime(), jobStatus.getLatestRunTimeElapsed(),
    305                 jobStatus.getLastSuccessfulRunTime(), jobStatus.getLastFailedRunTime());
    306         mPersistedUtcTimes = jobStatus.mPersistedUtcTimes;
    307         if (jobStatus.mPersistedUtcTimes != null) {
    308             if (DEBUG) {
    309                 Slog.i(TAG, "Cloning job with persisted run times", new RuntimeException("here"));
    310             }
    311         }
    312     }
    313 
    314     /**
    315      * Create a new JobStatus that was loaded from disk. We ignore the provided
    316      * {@link android.app.job.JobInfo} time criteria because we can load a persisted periodic job
    317      * from the {@link com.android.server.job.JobStore} and still want to respect its
    318      * wallclock runtime rather than resetting it on every boot.
    319      * We consider a freshly loaded job to no longer be in back-off.
    320      */
    321     public JobStatus(JobInfo job, int callingUid, String sourcePackageName, int sourceUserId,
    322             String sourceTag, long earliestRunTimeElapsedMillis, long latestRunTimeElapsedMillis,
    323             long lastSuccessfulRunTime, long lastFailedRunTime,
    324             Pair<Long, Long> persistedExecutionTimesUTC) {
    325         this(job, callingUid, sourcePackageName, sourceUserId, sourceTag, 0,
    326                 earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis,
    327                 lastSuccessfulRunTime, lastFailedRunTime);
    328 
    329         // Only during initial inflation do we record the UTC-timebase execution bounds
    330         // read from the persistent store.  If we ever have to recreate the JobStatus on
    331         // the fly, it means we're rescheduling the job; and this means that the calculated
    332         // elapsed timebase bounds intrinsically become correct.
    333         this.mPersistedUtcTimes = persistedExecutionTimesUTC;
    334         if (persistedExecutionTimesUTC != null) {
    335             if (DEBUG) {
    336                 Slog.i(TAG, "+ restored job with RTC times because of bad boot clock");
    337             }
    338         }
    339     }
    340 
    341     /** Create a new job to be rescheduled with the provided parameters. */
    342     public JobStatus(JobStatus rescheduling, long newEarliestRuntimeElapsedMillis,
    343             long newLatestRuntimeElapsedMillis, int backoffAttempt,
    344             long lastSuccessfulRunTime, long lastFailedRunTime) {
    345         this(rescheduling.job, rescheduling.getUid(),
    346                 rescheduling.getSourcePackageName(), rescheduling.getSourceUserId(),
    347                 rescheduling.getSourceTag(), backoffAttempt, newEarliestRuntimeElapsedMillis,
    348                 newLatestRuntimeElapsedMillis,
    349                 lastSuccessfulRunTime, lastFailedRunTime);
    350     }
    351 
    352     /**
    353      * Create a newly scheduled job.
    354      * @param callingUid Uid of the package that scheduled this job.
    355      * @param sourcePackageName Package name on whose behalf this job is scheduled. Null indicates
    356      *                          the calling package is the source.
    357      * @param sourceUserId User id for whom this job is scheduled. -1 indicates this is same as the
    358      */
    359     public static JobStatus createFromJobInfo(JobInfo job, int callingUid, String sourcePackageName,
    360             int sourceUserId, String tag) {
    361         final long elapsedNow = SystemClock.elapsedRealtime();
    362         final long earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis;
    363         if (job.isPeriodic()) {
    364             latestRunTimeElapsedMillis = elapsedNow + job.getIntervalMillis();
    365             earliestRunTimeElapsedMillis = latestRunTimeElapsedMillis - job.getFlexMillis();
    366         } else {
    367             earliestRunTimeElapsedMillis = job.hasEarlyConstraint() ?
    368                     elapsedNow + job.getMinLatencyMillis() : NO_EARLIEST_RUNTIME;
    369             latestRunTimeElapsedMillis = job.hasLateConstraint() ?
    370                     elapsedNow + job.getMaxExecutionDelayMillis() : NO_LATEST_RUNTIME;
    371         }
    372         return new JobStatus(job, callingUid, sourcePackageName, sourceUserId, tag, 0,
    373                 earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis,
    374                 0 /* lastSuccessfulRunTime */, 0 /* lastFailedRunTime */);
    375     }
    376 
    377     public void enqueueWorkLocked(IActivityManager am, JobWorkItem work) {
    378         if (pendingWork == null) {
    379             pendingWork = new ArrayList<>();
    380         }
    381         work.setWorkId(nextPendingWorkId);
    382         nextPendingWorkId++;
    383         if (work.getIntent() != null
    384                 && GrantedUriPermissions.checkGrantFlags(work.getIntent().getFlags())) {
    385             work.setGrants(GrantedUriPermissions.createFromIntent(am, work.getIntent(), sourceUid,
    386                     sourcePackageName, sourceUserId, toShortString()));
    387         }
    388         pendingWork.add(work);
    389     }
    390 
    391     public JobWorkItem dequeueWorkLocked() {
    392         if (pendingWork != null && pendingWork.size() > 0) {
    393             JobWorkItem work = pendingWork.remove(0);
    394             if (work != null) {
    395                 if (executingWork == null) {
    396                     executingWork = new ArrayList<>();
    397                 }
    398                 executingWork.add(work);
    399                 work.bumpDeliveryCount();
    400             }
    401             return work;
    402         }
    403         return null;
    404     }
    405 
    406     public boolean hasWorkLocked() {
    407         return (pendingWork != null && pendingWork.size() > 0) || hasExecutingWorkLocked();
    408     }
    409 
    410     public boolean hasExecutingWorkLocked() {
    411         return executingWork != null && executingWork.size() > 0;
    412     }
    413 
    414     private static void ungrantWorkItem(IActivityManager am, JobWorkItem work) {
    415         if (work.getGrants() != null) {
    416             ((GrantedUriPermissions)work.getGrants()).revoke(am);
    417         }
    418     }
    419 
    420     public boolean completeWorkLocked(IActivityManager am, int workId) {
    421         if (executingWork != null) {
    422             final int N = executingWork.size();
    423             for (int i = 0; i < N; i++) {
    424                 JobWorkItem work = executingWork.get(i);
    425                 if (work.getWorkId() == workId) {
    426                     executingWork.remove(i);
    427                     ungrantWorkItem(am, work);
    428                     return true;
    429                 }
    430             }
    431         }
    432         return false;
    433     }
    434 
    435     private static void ungrantWorkList(IActivityManager am, ArrayList<JobWorkItem> list) {
    436         if (list != null) {
    437             final int N = list.size();
    438             for (int i = 0; i < N; i++) {
    439                 ungrantWorkItem(am, list.get(i));
    440             }
    441         }
    442     }
    443 
    444     public void stopTrackingJobLocked(IActivityManager am, JobStatus incomingJob) {
    445         if (incomingJob != null) {
    446             // We are replacing with a new job -- transfer the work!  We do any executing
    447             // work first, since that was originally at the front of the pending work.
    448             if (executingWork != null && executingWork.size() > 0) {
    449                 incomingJob.pendingWork = executingWork;
    450             }
    451             if (incomingJob.pendingWork == null) {
    452                 incomingJob.pendingWork = pendingWork;
    453             } else if (pendingWork != null && pendingWork.size() > 0) {
    454                 incomingJob.pendingWork.addAll(pendingWork);
    455             }
    456             pendingWork = null;
    457             executingWork = null;
    458             incomingJob.nextPendingWorkId = nextPendingWorkId;
    459         } else {
    460             // We are completely stopping the job...  need to clean up work.
    461             ungrantWorkList(am, pendingWork);
    462             pendingWork = null;
    463             ungrantWorkList(am, executingWork);
    464             executingWork = null;
    465         }
    466     }
    467 
    468     public void prepareLocked(IActivityManager am) {
    469         if (prepared) {
    470             Slog.wtf(TAG, "Already prepared: " + this);
    471             return;
    472         }
    473         prepared = true;
    474         if (DEBUG_PREPARE) {
    475             unpreparedPoint = null;
    476         }
    477         final ClipData clip = job.getClipData();
    478         if (clip != null) {
    479             uriPerms = GrantedUriPermissions.createFromClip(am, clip, sourceUid, sourcePackageName,
    480                     sourceUserId, job.getClipGrantFlags(), toShortString());
    481         }
    482     }
    483 
    484     public void unprepareLocked(IActivityManager am) {
    485         if (!prepared) {
    486             Slog.wtf(TAG, "Hasn't been prepared: " + this);
    487             if (DEBUG_PREPARE && unpreparedPoint != null) {
    488                 Slog.e(TAG, "Was already unprepared at ", unpreparedPoint);
    489             }
    490             return;
    491         }
    492         prepared = false;
    493         if (DEBUG_PREPARE) {
    494             unpreparedPoint = new Throwable().fillInStackTrace();
    495         }
    496         if (uriPerms != null) {
    497             uriPerms.revoke(am);
    498             uriPerms = null;
    499         }
    500     }
    501 
    502     public boolean isPreparedLocked() {
    503         return prepared;
    504     }
    505 
    506     public JobInfo getJob() {
    507         return job;
    508     }
    509 
    510     public int getJobId() {
    511         return job.getId();
    512     }
    513 
    514     public void printUniqueId(PrintWriter pw) {
    515         UserHandle.formatUid(pw, callingUid);
    516         pw.print("/");
    517         pw.print(job.getId());
    518     }
    519 
    520     public int getNumFailures() {
    521         return numFailures;
    522     }
    523 
    524     public ComponentName getServiceComponent() {
    525         return job.getService();
    526     }
    527 
    528     public String getSourcePackageName() {
    529         return sourcePackageName;
    530     }
    531 
    532     public int getSourceUid() {
    533         return sourceUid;
    534     }
    535 
    536     public int getSourceUserId() {
    537         return sourceUserId;
    538     }
    539 
    540     public int getUserId() {
    541         return UserHandle.getUserId(callingUid);
    542     }
    543 
    544     public String getSourceTag() {
    545         return sourceTag;
    546     }
    547 
    548     public int getUid() {
    549         return callingUid;
    550     }
    551 
    552     public String getBatteryName() {
    553         return batteryName;
    554     }
    555 
    556     public String getTag() {
    557         return tag;
    558     }
    559 
    560     public int getPriority() {
    561         return job.getPriority();
    562     }
    563 
    564     public int getFlags() {
    565         return job.getFlags();
    566     }
    567 
    568     /** Does this job have any sort of networking constraint? */
    569     public boolean hasConnectivityConstraint() {
    570         return (requiredConstraints&CONNECTIVITY_MASK) != 0;
    571     }
    572 
    573     public boolean needsAnyConnectivity() {
    574         return (requiredConstraints&CONSTRAINT_CONNECTIVITY) != 0;
    575     }
    576 
    577     public boolean needsUnmeteredConnectivity() {
    578         return (requiredConstraints&CONSTRAINT_UNMETERED) != 0;
    579     }
    580 
    581     public boolean needsMeteredConnectivity() {
    582         return (requiredConstraints&CONSTRAINT_METERED) != 0;
    583     }
    584 
    585     public boolean needsNonRoamingConnectivity() {
    586         return (requiredConstraints&CONSTRAINT_NOT_ROAMING) != 0;
    587     }
    588 
    589     public boolean hasChargingConstraint() {
    590         return (requiredConstraints&CONSTRAINT_CHARGING) != 0;
    591     }
    592 
    593     public boolean hasBatteryNotLowConstraint() {
    594         return (requiredConstraints&CONSTRAINT_BATTERY_NOT_LOW) != 0;
    595     }
    596 
    597     public boolean hasPowerConstraint() {
    598         return (requiredConstraints&(CONSTRAINT_CHARGING|CONSTRAINT_BATTERY_NOT_LOW)) != 0;
    599     }
    600 
    601     public boolean hasStorageNotLowConstraint() {
    602         return (requiredConstraints&CONSTRAINT_STORAGE_NOT_LOW) != 0;
    603     }
    604 
    605     public boolean hasTimingDelayConstraint() {
    606         return (requiredConstraints&CONSTRAINT_TIMING_DELAY) != 0;
    607     }
    608 
    609     public boolean hasDeadlineConstraint() {
    610         return (requiredConstraints&CONSTRAINT_DEADLINE) != 0;
    611     }
    612 
    613     public boolean hasIdleConstraint() {
    614         return (requiredConstraints&CONSTRAINT_IDLE) != 0;
    615     }
    616 
    617     public boolean hasContentTriggerConstraint() {
    618         return (requiredConstraints&CONSTRAINT_CONTENT_TRIGGER) != 0;
    619     }
    620 
    621     public long getTriggerContentUpdateDelay() {
    622         long time = job.getTriggerContentUpdateDelay();
    623         if (time < 0) {
    624             return DEFAULT_TRIGGER_UPDATE_DELAY;
    625         }
    626         return Math.max(time, MIN_TRIGGER_UPDATE_DELAY);
    627     }
    628 
    629     public long getTriggerContentMaxDelay() {
    630         long time = job.getTriggerContentMaxDelay();
    631         if (time < 0) {
    632             return DEFAULT_TRIGGER_MAX_DELAY;
    633         }
    634         return Math.max(time, MIN_TRIGGER_MAX_DELAY);
    635     }
    636 
    637     public boolean isPersisted() {
    638         return job.isPersisted();
    639     }
    640 
    641     public long getEarliestRunTime() {
    642         return earliestRunTimeElapsedMillis;
    643     }
    644 
    645     public long getLatestRunTimeElapsed() {
    646         return latestRunTimeElapsedMillis;
    647     }
    648 
    649     public Pair<Long, Long> getPersistedUtcTimes() {
    650         return mPersistedUtcTimes;
    651     }
    652 
    653     public void clearPersistedUtcTimes() {
    654         mPersistedUtcTimes = null;
    655     }
    656 
    657     boolean setChargingConstraintSatisfied(boolean state) {
    658         return setConstraintSatisfied(CONSTRAINT_CHARGING, state);
    659     }
    660 
    661     boolean setBatteryNotLowConstraintSatisfied(boolean state) {
    662         return setConstraintSatisfied(CONSTRAINT_BATTERY_NOT_LOW, state);
    663     }
    664 
    665     boolean setStorageNotLowConstraintSatisfied(boolean state) {
    666         return setConstraintSatisfied(CONSTRAINT_STORAGE_NOT_LOW, state);
    667     }
    668 
    669     boolean setTimingDelayConstraintSatisfied(boolean state) {
    670         return setConstraintSatisfied(CONSTRAINT_TIMING_DELAY, state);
    671     }
    672 
    673     boolean setDeadlineConstraintSatisfied(boolean state) {
    674         return setConstraintSatisfied(CONSTRAINT_DEADLINE, state);
    675     }
    676 
    677     boolean setIdleConstraintSatisfied(boolean state) {
    678         return setConstraintSatisfied(CONSTRAINT_IDLE, state);
    679     }
    680 
    681     boolean setConnectivityConstraintSatisfied(boolean state) {
    682         return setConstraintSatisfied(CONSTRAINT_CONNECTIVITY, state);
    683     }
    684 
    685     boolean setUnmeteredConstraintSatisfied(boolean state) {
    686         return setConstraintSatisfied(CONSTRAINT_UNMETERED, state);
    687     }
    688 
    689     boolean setMeteredConstraintSatisfied(boolean state) {
    690         return setConstraintSatisfied(CONSTRAINT_METERED, state);
    691     }
    692 
    693     boolean setNotRoamingConstraintSatisfied(boolean state) {
    694         return setConstraintSatisfied(CONSTRAINT_NOT_ROAMING, state);
    695     }
    696 
    697     boolean setAppNotIdleConstraintSatisfied(boolean state) {
    698         return setConstraintSatisfied(CONSTRAINT_APP_NOT_IDLE, state);
    699     }
    700 
    701     boolean setContentTriggerConstraintSatisfied(boolean state) {
    702         return setConstraintSatisfied(CONSTRAINT_CONTENT_TRIGGER, state);
    703     }
    704 
    705     boolean setDeviceNotDozingConstraintSatisfied(boolean state, boolean whitelisted) {
    706         dozeWhitelisted = whitelisted;
    707         return setConstraintSatisfied(CONSTRAINT_DEVICE_NOT_DOZING, state);
    708     }
    709 
    710     boolean setConstraintSatisfied(int constraint, boolean state) {
    711         boolean old = (satisfiedConstraints&constraint) != 0;
    712         if (old == state) {
    713             return false;
    714         }
    715         satisfiedConstraints = (satisfiedConstraints&~constraint) | (state ? constraint : 0);
    716         return true;
    717     }
    718 
    719     boolean isConstraintSatisfied(int constraint) {
    720         return (satisfiedConstraints&constraint) != 0;
    721     }
    722 
    723     boolean clearTrackingController(int which) {
    724         if ((trackingControllers&which) != 0) {
    725             trackingControllers &= ~which;
    726             return true;
    727         }
    728         return false;
    729     }
    730 
    731     void setTrackingController(int which) {
    732         trackingControllers |= which;
    733     }
    734 
    735     public long getLastSuccessfulRunTime() {
    736         return mLastSuccessfulRunTime;
    737     }
    738 
    739     public long getLastFailedRunTime() {
    740         return mLastFailedRunTime;
    741     }
    742 
    743     public boolean shouldDump(int filterUid) {
    744         return filterUid == -1 || UserHandle.getAppId(getUid()) == filterUid
    745                 || UserHandle.getAppId(getSourceUid()) == filterUid;
    746     }
    747 
    748     /**
    749      * @return Whether or not this job is ready to run, based on its requirements. This is true if
    750      * the constraints are satisfied <strong>or</strong> the deadline on the job has expired.
    751      * TODO: This function is called a *lot*.  We should probably just have it check an
    752      * already-computed boolean, which we updated whenever we see one of the states it depends
    753      * on here change.
    754      */
    755     public boolean isReady() {
    756         // Deadline constraint trumps other constraints (except for periodic jobs where deadline
    757         // is an implementation detail. A periodic job should only run if its constraints are
    758         // satisfied).
    759         // AppNotIdle implicit constraint must be satisfied
    760         // DeviceNotDozing implicit constraint must be satisfied
    761         final boolean deadlineSatisfied = (!job.isPeriodic() && hasDeadlineConstraint()
    762                 && (satisfiedConstraints & CONSTRAINT_DEADLINE) != 0);
    763         final boolean notIdle = (satisfiedConstraints & CONSTRAINT_APP_NOT_IDLE) != 0;
    764         final boolean notDozing = (satisfiedConstraints & CONSTRAINT_DEVICE_NOT_DOZING) != 0
    765                 || (job.getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0;
    766         return (isConstraintsSatisfied() || deadlineSatisfied) && notIdle && notDozing;
    767     }
    768 
    769     static final int CONSTRAINTS_OF_INTEREST =
    770             CONSTRAINT_CHARGING | CONSTRAINT_BATTERY_NOT_LOW | CONSTRAINT_STORAGE_NOT_LOW |
    771             CONSTRAINT_TIMING_DELAY |
    772             CONSTRAINT_CONNECTIVITY | CONSTRAINT_UNMETERED |
    773             CONSTRAINT_NOT_ROAMING | CONSTRAINT_METERED |
    774             CONSTRAINT_IDLE | CONSTRAINT_CONTENT_TRIGGER;
    775 
    776     // Soft override covers all non-"functional" constraints
    777     static final int SOFT_OVERRIDE_CONSTRAINTS =
    778             CONSTRAINT_CHARGING | CONSTRAINT_BATTERY_NOT_LOW | CONSTRAINT_STORAGE_NOT_LOW
    779                     | CONSTRAINT_TIMING_DELAY | CONSTRAINT_IDLE;
    780 
    781     /**
    782      * @return Whether the constraints set on this job are satisfied.
    783      */
    784     public boolean isConstraintsSatisfied() {
    785         if (overrideState == OVERRIDE_FULL) {
    786             // force override: the job is always runnable
    787             return true;
    788         }
    789 
    790         final int req = requiredConstraints & CONSTRAINTS_OF_INTEREST;
    791 
    792         int sat = satisfiedConstraints & CONSTRAINTS_OF_INTEREST;
    793         if (overrideState == OVERRIDE_SOFT) {
    794             // override: pretend all 'soft' requirements are satisfied
    795             sat |= (requiredConstraints & SOFT_OVERRIDE_CONSTRAINTS);
    796         }
    797 
    798         return (sat & req) == req;
    799     }
    800 
    801     public boolean matches(int uid, int jobId) {
    802         return this.job.getId() == jobId && this.callingUid == uid;
    803     }
    804 
    805     @Override
    806     public String toString() {
    807         StringBuilder sb = new StringBuilder(128);
    808         sb.append("JobStatus{");
    809         sb.append(Integer.toHexString(System.identityHashCode(this)));
    810         sb.append(" #");
    811         UserHandle.formatUid(sb, callingUid);
    812         sb.append("/");
    813         sb.append(job.getId());
    814         sb.append(' ');
    815         sb.append(batteryName);
    816         sb.append(" u=");
    817         sb.append(getUserId());
    818         sb.append(" s=");
    819         sb.append(getSourceUid());
    820         if (earliestRunTimeElapsedMillis != NO_EARLIEST_RUNTIME
    821                 || latestRunTimeElapsedMillis != NO_LATEST_RUNTIME) {
    822             long now = SystemClock.elapsedRealtime();
    823             sb.append(" TIME=");
    824             formatRunTime(sb, earliestRunTimeElapsedMillis, NO_EARLIEST_RUNTIME, now);
    825             sb.append(":");
    826             formatRunTime(sb, latestRunTimeElapsedMillis, NO_LATEST_RUNTIME, now);
    827         }
    828         if (job.getNetworkType() != JobInfo.NETWORK_TYPE_NONE) {
    829             sb.append(" NET=");
    830             sb.append(job.getNetworkType());
    831         }
    832         if (job.isRequireCharging()) {
    833             sb.append(" CHARGING");
    834         }
    835         if (job.isRequireBatteryNotLow()) {
    836             sb.append(" BATNOTLOW");
    837         }
    838         if (job.isRequireStorageNotLow()) {
    839             sb.append(" STORENOTLOW");
    840         }
    841         if (job.isRequireDeviceIdle()) {
    842             sb.append(" IDLE");
    843         }
    844         if (job.isPeriodic()) {
    845             sb.append(" PERIODIC");
    846         }
    847         if (job.isPersisted()) {
    848             sb.append(" PERSISTED");
    849         }
    850         if ((satisfiedConstraints&CONSTRAINT_APP_NOT_IDLE) == 0) {
    851             sb.append(" WAIT:APP_NOT_IDLE");
    852         }
    853         if ((satisfiedConstraints&CONSTRAINT_DEVICE_NOT_DOZING) == 0) {
    854             sb.append(" WAIT:DEV_NOT_DOZING");
    855         }
    856         if (job.getTriggerContentUris() != null) {
    857             sb.append(" URIS=");
    858             sb.append(Arrays.toString(job.getTriggerContentUris()));
    859         }
    860         if (numFailures != 0) {
    861             sb.append(" failures=");
    862             sb.append(numFailures);
    863         }
    864         if (isReady()) {
    865             sb.append(" READY");
    866         }
    867         sb.append("}");
    868         return sb.toString();
    869     }
    870 
    871     private void formatRunTime(PrintWriter pw, long runtime, long  defaultValue, long now) {
    872         if (runtime == defaultValue) {
    873             pw.print("none");
    874         } else {
    875             TimeUtils.formatDuration(runtime - now, pw);
    876         }
    877     }
    878 
    879     private void formatRunTime(StringBuilder sb, long runtime, long  defaultValue, long now) {
    880         if (runtime == defaultValue) {
    881             sb.append("none");
    882         } else {
    883             TimeUtils.formatDuration(runtime - now, sb);
    884         }
    885     }
    886 
    887     /**
    888      * Convenience function to identify a job uniquely without pulling all the data that
    889      * {@link #toString()} returns.
    890      */
    891     public String toShortString() {
    892         StringBuilder sb = new StringBuilder();
    893         sb.append(Integer.toHexString(System.identityHashCode(this)));
    894         sb.append(" #");
    895         UserHandle.formatUid(sb, callingUid);
    896         sb.append("/");
    897         sb.append(job.getId());
    898         sb.append(' ');
    899         sb.append(batteryName);
    900         return sb.toString();
    901     }
    902 
    903     /**
    904      * Convenience function to identify a job uniquely without pulling all the data that
    905      * {@link #toString()} returns.
    906      */
    907     public String toShortStringExceptUniqueId() {
    908         StringBuilder sb = new StringBuilder();
    909         sb.append(Integer.toHexString(System.identityHashCode(this)));
    910         sb.append(' ');
    911         sb.append(batteryName);
    912         return sb.toString();
    913     }
    914 
    915     void dumpConstraints(PrintWriter pw, int constraints) {
    916         if ((constraints&CONSTRAINT_CHARGING) != 0) {
    917             pw.print(" CHARGING");
    918         }
    919         if ((constraints& CONSTRAINT_BATTERY_NOT_LOW) != 0) {
    920             pw.print(" BATTERY_NOT_LOW");
    921         }
    922         if ((constraints& CONSTRAINT_STORAGE_NOT_LOW) != 0) {
    923             pw.print(" STORAGE_NOT_LOW");
    924         }
    925         if ((constraints&CONSTRAINT_TIMING_DELAY) != 0) {
    926             pw.print(" TIMING_DELAY");
    927         }
    928         if ((constraints&CONSTRAINT_DEADLINE) != 0) {
    929             pw.print(" DEADLINE");
    930         }
    931         if ((constraints&CONSTRAINT_IDLE) != 0) {
    932             pw.print(" IDLE");
    933         }
    934         if ((constraints&CONSTRAINT_CONNECTIVITY) != 0) {
    935             pw.print(" CONNECTIVITY");
    936         }
    937         if ((constraints&CONSTRAINT_UNMETERED) != 0) {
    938             pw.print(" UNMETERED");
    939         }
    940         if ((constraints&CONSTRAINT_NOT_ROAMING) != 0) {
    941             pw.print(" NOT_ROAMING");
    942         }
    943         if ((constraints&CONSTRAINT_METERED) != 0) {
    944             pw.print(" METERED");
    945         }
    946         if ((constraints&CONSTRAINT_APP_NOT_IDLE) != 0) {
    947             pw.print(" APP_NOT_IDLE");
    948         }
    949         if ((constraints&CONSTRAINT_CONTENT_TRIGGER) != 0) {
    950             pw.print(" CONTENT_TRIGGER");
    951         }
    952         if ((constraints&CONSTRAINT_DEVICE_NOT_DOZING) != 0) {
    953             pw.print(" DEVICE_NOT_DOZING");
    954         }
    955     }
    956 
    957     private void dumpJobWorkItem(PrintWriter pw, String prefix, JobWorkItem work, int index) {
    958         pw.print(prefix); pw.print("  #"); pw.print(index); pw.print(": #");
    959         pw.print(work.getWorkId()); pw.print(" "); pw.print(work.getDeliveryCount());
    960         pw.print("x "); pw.println(work.getIntent());
    961         if (work.getGrants() != null) {
    962             pw.print(prefix); pw.println("  URI grants:");
    963             ((GrantedUriPermissions)work.getGrants()).dump(pw, prefix + "    ");
    964         }
    965     }
    966 
    967     // Dumpsys infrastructure
    968     public void dump(PrintWriter pw, String prefix, boolean full, long elapsedRealtimeMillis) {
    969         pw.print(prefix); UserHandle.formatUid(pw, callingUid);
    970         pw.print(" tag="); pw.println(tag);
    971         pw.print(prefix);
    972         pw.print("Source: uid="); UserHandle.formatUid(pw, getSourceUid());
    973         pw.print(" user="); pw.print(getSourceUserId());
    974         pw.print(" pkg="); pw.println(getSourcePackageName());
    975         if (full) {
    976             pw.print(prefix); pw.println("JobInfo:");
    977             pw.print(prefix); pw.print("  Service: ");
    978             pw.println(job.getService().flattenToShortString());
    979             if (job.isPeriodic()) {
    980                 pw.print(prefix); pw.print("  PERIODIC: interval=");
    981                 TimeUtils.formatDuration(job.getIntervalMillis(), pw);
    982                 pw.print(" flex="); TimeUtils.formatDuration(job.getFlexMillis(), pw);
    983                 pw.println();
    984             }
    985             if (job.isPersisted()) {
    986                 pw.print(prefix); pw.println("  PERSISTED");
    987             }
    988             if (job.getPriority() != 0) {
    989                 pw.print(prefix); pw.print("  Priority: "); pw.println(job.getPriority());
    990             }
    991             if (job.getFlags() != 0) {
    992                 pw.print(prefix); pw.print("  Flags: ");
    993                 pw.println(Integer.toHexString(job.getFlags()));
    994             }
    995             pw.print(prefix); pw.print("  Requires: charging=");
    996             pw.print(job.isRequireCharging()); pw.print(" batteryNotLow=");
    997             pw.print(job.isRequireBatteryNotLow()); pw.print(" deviceIdle=");
    998             pw.println(job.isRequireDeviceIdle());
    999             if (job.getTriggerContentUris() != null) {
   1000                 pw.print(prefix); pw.println("  Trigger content URIs:");
   1001                 for (int i = 0; i < job.getTriggerContentUris().length; i++) {
   1002                     JobInfo.TriggerContentUri trig = job.getTriggerContentUris()[i];
   1003                     pw.print(prefix); pw.print("    ");
   1004                     pw.print(Integer.toHexString(trig.getFlags()));
   1005                     pw.print(' '); pw.println(trig.getUri());
   1006                 }
   1007                 if (job.getTriggerContentUpdateDelay() >= 0) {
   1008                     pw.print(prefix); pw.print("  Trigger update delay: ");
   1009                     TimeUtils.formatDuration(job.getTriggerContentUpdateDelay(), pw);
   1010                     pw.println();
   1011                 }
   1012                 if (job.getTriggerContentMaxDelay() >= 0) {
   1013                     pw.print(prefix); pw.print("  Trigger max delay: ");
   1014                     TimeUtils.formatDuration(job.getTriggerContentMaxDelay(), pw);
   1015                     pw.println();
   1016                 }
   1017             }
   1018             if (job.getExtras() != null && !job.getExtras().maybeIsEmpty()) {
   1019                 pw.print(prefix); pw.print("  Extras: ");
   1020                 pw.println(job.getExtras().toShortString());
   1021             }
   1022             if (job.getTransientExtras() != null && !job.getTransientExtras().maybeIsEmpty()) {
   1023                 pw.print(prefix); pw.print("  Transient extras: ");
   1024                 pw.println(job.getTransientExtras().toShortString());
   1025             }
   1026             if (job.getClipData() != null) {
   1027                 pw.print(prefix); pw.print("  Clip data: ");
   1028                 StringBuilder b = new StringBuilder(128);
   1029                 job.getClipData().toShortString(b);
   1030                 pw.println(b);
   1031             }
   1032             if (uriPerms != null) {
   1033                 pw.print(prefix); pw.println("  Granted URI permissions:");
   1034                 uriPerms.dump(pw, prefix + "  ");
   1035             }
   1036             if (job.getNetworkType() != JobInfo.NETWORK_TYPE_NONE) {
   1037                 pw.print(prefix); pw.print("  Network type: "); pw.println(job.getNetworkType());
   1038             }
   1039             if (job.getMinLatencyMillis() != 0) {
   1040                 pw.print(prefix); pw.print("  Minimum latency: ");
   1041                 TimeUtils.formatDuration(job.getMinLatencyMillis(), pw);
   1042                 pw.println();
   1043             }
   1044             if (job.getMaxExecutionDelayMillis() != 0) {
   1045                 pw.print(prefix); pw.print("  Max execution delay: ");
   1046                 TimeUtils.formatDuration(job.getMaxExecutionDelayMillis(), pw);
   1047                 pw.println();
   1048             }
   1049             pw.print(prefix); pw.print("  Backoff: policy="); pw.print(job.getBackoffPolicy());
   1050             pw.print(" initial="); TimeUtils.formatDuration(job.getInitialBackoffMillis(), pw);
   1051             pw.println();
   1052             if (job.hasEarlyConstraint()) {
   1053                 pw.print(prefix); pw.println("  Has early constraint");
   1054             }
   1055             if (job.hasLateConstraint()) {
   1056                 pw.print(prefix); pw.println("  Has late constraint");
   1057             }
   1058         }
   1059         pw.print(prefix); pw.print("Required constraints:");
   1060         dumpConstraints(pw, requiredConstraints);
   1061         pw.println();
   1062         if (full) {
   1063             pw.print(prefix); pw.print("Satisfied constraints:");
   1064             dumpConstraints(pw, satisfiedConstraints);
   1065             pw.println();
   1066             pw.print(prefix); pw.print("Unsatisfied constraints:");
   1067             dumpConstraints(pw, (requiredConstraints & ~satisfiedConstraints));
   1068             pw.println();
   1069             if (dozeWhitelisted) {
   1070                 pw.print(prefix); pw.println("Doze whitelisted: true");
   1071             }
   1072         }
   1073         if (trackingControllers != 0) {
   1074             pw.print(prefix); pw.print("Tracking:");
   1075             if ((trackingControllers&TRACKING_BATTERY) != 0) pw.print(" BATTERY");
   1076             if ((trackingControllers&TRACKING_CONNECTIVITY) != 0) pw.print(" CONNECTIVITY");
   1077             if ((trackingControllers&TRACKING_CONTENT) != 0) pw.print(" CONTENT");
   1078             if ((trackingControllers&TRACKING_IDLE) != 0) pw.print(" IDLE");
   1079             if ((trackingControllers&TRACKING_STORAGE) != 0) pw.print(" STORAGE");
   1080             if ((trackingControllers&TRACKING_TIME) != 0) pw.print(" TIME");
   1081             pw.println();
   1082         }
   1083         if (changedAuthorities != null) {
   1084             pw.print(prefix); pw.println("Changed authorities:");
   1085             for (int i=0; i<changedAuthorities.size(); i++) {
   1086                 pw.print(prefix); pw.print("  "); pw.println(changedAuthorities.valueAt(i));
   1087             }
   1088             if (changedUris != null) {
   1089                 pw.print(prefix); pw.println("Changed URIs:");
   1090                 for (int i=0; i<changedUris.size(); i++) {
   1091                     pw.print(prefix); pw.print("  "); pw.println(changedUris.valueAt(i));
   1092                 }
   1093             }
   1094         }
   1095         if (pendingWork != null && pendingWork.size() > 0) {
   1096             pw.print(prefix); pw.println("Pending work:");
   1097             for (int i = 0; i < pendingWork.size(); i++) {
   1098                 dumpJobWorkItem(pw, prefix, pendingWork.get(i), i);
   1099             }
   1100         }
   1101         if (executingWork != null && executingWork.size() > 0) {
   1102             pw.print(prefix); pw.println("Executing work:");
   1103             for (int i = 0; i < executingWork.size(); i++) {
   1104                 dumpJobWorkItem(pw, prefix, executingWork.get(i), i);
   1105             }
   1106         }
   1107         pw.print(prefix); pw.print("Enqueue time: ");
   1108         TimeUtils.formatDuration(enqueueTime, elapsedRealtimeMillis, pw);
   1109         pw.println();
   1110         pw.print(prefix); pw.print("Run time: earliest=");
   1111         formatRunTime(pw, earliestRunTimeElapsedMillis, NO_EARLIEST_RUNTIME, elapsedRealtimeMillis);
   1112         pw.print(", latest=");
   1113         formatRunTime(pw, latestRunTimeElapsedMillis, NO_LATEST_RUNTIME, elapsedRealtimeMillis);
   1114         pw.println();
   1115         if (numFailures != 0) {
   1116             pw.print(prefix); pw.print("Num failures: "); pw.println(numFailures);
   1117         }
   1118         final Time t = new Time();
   1119         final String format = "%Y-%m-%d %H:%M:%S";
   1120         if (mLastSuccessfulRunTime != 0) {
   1121             pw.print(prefix); pw.print("Last successful run: ");
   1122             t.set(mLastSuccessfulRunTime);
   1123             pw.println(t.format(format));
   1124         }
   1125         if (mLastFailedRunTime != 0) {
   1126             pw.print(prefix); pw.print("Last failed run: ");
   1127             t.set(mLastFailedRunTime);
   1128             pw.println(t.format(format));
   1129         }
   1130     }
   1131 }
   1132