/* * Copyright (C) 2014 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package com.android.server.job.controllers; import android.app.AppGlobals; import android.app.IActivityManager; import android.app.job.JobInfo; import android.app.job.JobWorkItem; import android.content.ClipData; import android.content.ComponentName; import android.net.Uri; import android.os.RemoteException; import android.os.SystemClock; import android.os.UserHandle; import android.util.ArraySet; import android.util.Slog; import android.util.TimeUtils; import com.android.server.job.GrantedUriPermissions; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; /** * Uniquely identifies a job internally. * Created from the public {@link android.app.job.JobInfo} object when it lands on the scheduler. * Contains current state of the requirements of the job, as well as a function to evaluate * whether it's ready to run. * This object is shared among the various controllers - hence why the different fields are atomic. * This isn't strictly necessary because each controller is only interested in a specific field, * and the receivers that are listening for global state change will all run on the main looper, * but we don't enforce that so this is safer. * @hide */ public final class JobStatus { static final String TAG = "JobSchedulerService"; public static final long NO_LATEST_RUNTIME = Long.MAX_VALUE; public static final long NO_EARLIEST_RUNTIME = 0L; static final int CONSTRAINT_CHARGING = JobInfo.CONSTRAINT_FLAG_CHARGING; static final int CONSTRAINT_IDLE = JobInfo.CONSTRAINT_FLAG_DEVICE_IDLE; static final int CONSTRAINT_BATTERY_NOT_LOW = JobInfo.CONSTRAINT_FLAG_BATTERY_NOT_LOW; static final int CONSTRAINT_STORAGE_NOT_LOW = JobInfo.CONSTRAINT_FLAG_STORAGE_NOT_LOW; static final int CONSTRAINT_TIMING_DELAY = 1<<31; static final int CONSTRAINT_DEADLINE = 1<<30; static final int CONSTRAINT_UNMETERED = 1<<29; static final int CONSTRAINT_CONNECTIVITY = 1<<28; static final int CONSTRAINT_APP_NOT_IDLE = 1<<27; static final int CONSTRAINT_CONTENT_TRIGGER = 1<<26; static final int CONSTRAINT_DEVICE_NOT_DOZING = 1<<25; static final int CONSTRAINT_NOT_ROAMING = 1<<24; static final int CONSTRAINT_METERED = 1<<23; static final int CONNECTIVITY_MASK = CONSTRAINT_UNMETERED | CONSTRAINT_CONNECTIVITY | CONSTRAINT_NOT_ROAMING | CONSTRAINT_METERED; // Soft override: ignore constraints like time that don't affect API availability public static final int OVERRIDE_SOFT = 1; // Full override: ignore all constraints including API-affecting like connectivity public static final int OVERRIDE_FULL = 2; /** If not specified, trigger update delay is 10 seconds. */ public static final long DEFAULT_TRIGGER_UPDATE_DELAY = 10*1000; /** The minimum possible update delay is 1/2 second. */ public static final long MIN_TRIGGER_UPDATE_DELAY = 500; /** If not specified, trigger maxumum delay is 2 minutes. */ public static final long DEFAULT_TRIGGER_MAX_DELAY = 2*60*1000; /** The minimum possible update delay is 1 second. */ public static final long MIN_TRIGGER_MAX_DELAY = 1000; final JobInfo job; /** Uid of the package requesting this job. */ final int callingUid; final String batteryName; final String sourcePackageName; final int sourceUserId; final int sourceUid; final String sourceTag; final String tag; private GrantedUriPermissions uriPerms; private boolean prepared; static final boolean DEBUG_PREPARE = true; private Throwable unpreparedPoint = null; /** * Earliest point in the future at which this job will be eligible to run. A value of 0 * indicates there is no delay constraint. See {@link #hasTimingDelayConstraint()}. */ private final long earliestRunTimeElapsedMillis; /** * Latest point in the future at which this job must be run. A value of {@link Long#MAX_VALUE} * indicates there is no deadline constraint. See {@link #hasDeadlineConstraint()}. */ private final long latestRunTimeElapsedMillis; /** How many times this job has failed, used to compute back-off. */ private final int numFailures; // Constraints. final int requiredConstraints; int satisfiedConstraints = 0; // Set to true if doze constraint was satisfied due to app being whitelisted. public boolean dozeWhitelisted; /** * Flag for {@link #trackingControllers}: the battery controller is currently tracking this job. */ public static final int TRACKING_BATTERY = 1<<0; /** * Flag for {@link #trackingControllers}: the network connectivity controller is currently * tracking this job. */ public static final int TRACKING_CONNECTIVITY = 1<<1; /** * Flag for {@link #trackingControllers}: the content observer controller is currently * tracking this job. */ public static final int TRACKING_CONTENT = 1<<2; /** * Flag for {@link #trackingControllers}: the idle controller is currently tracking this job. */ public static final int TRACKING_IDLE = 1<<3; /** * Flag for {@link #trackingControllers}: the storage controller is currently tracking this job. */ public static final int TRACKING_STORAGE = 1<<4; /** * Flag for {@link #trackingControllers}: the time controller is currently tracking this job. */ public static final int TRACKING_TIME = 1<<5; /** * Bit mask of controllers that are currently tracking the job. */ private int trackingControllers; // These are filled in by controllers when preparing for execution. public ArraySet changedUris; public ArraySet changedAuthorities; public int lastEvaluatedPriority; // If non-null, this is work that has been enqueued for the job. public ArrayList pendingWork; // If non-null, this is work that is currently being executed. public ArrayList executingWork; public int nextPendingWorkId = 1; // Used by shell commands public int overrideState = 0; // When this job was enqueued, for ordering. (in elapsedRealtimeMillis) public long enqueueTime; // Metrics about queue latency. (in uptimeMillis) public long madePending; public long madeActive; /** * For use only by ContentObserverController: state it is maintaining about content URIs * being observed. */ ContentObserverController.JobInstance contentObserverJobInstance; /** Provide a handle to the service that this job will be run on. */ public int getServiceToken() { return callingUid; } private JobStatus(JobInfo job, int callingUid, String sourcePackageName, int sourceUserId, String tag, int numFailures, long earliestRunTimeElapsedMillis, long latestRunTimeElapsedMillis) { this.job = job; this.callingUid = callingUid; int tempSourceUid = -1; if (sourceUserId != -1 && sourcePackageName != null) { try { tempSourceUid = AppGlobals.getPackageManager().getPackageUid(sourcePackageName, 0, sourceUserId); } catch (RemoteException ex) { // Can't happen, PackageManager runs in the same process. } } if (tempSourceUid == -1) { this.sourceUid = callingUid; this.sourceUserId = UserHandle.getUserId(callingUid); this.sourcePackageName = job.getService().getPackageName(); this.sourceTag = null; } else { this.sourceUid = tempSourceUid; this.sourceUserId = sourceUserId; this.sourcePackageName = sourcePackageName; this.sourceTag = tag; } this.batteryName = this.sourceTag != null ? this.sourceTag + ":" + job.getService().getPackageName() : job.getService().flattenToShortString(); this.tag = "*job*/" + this.batteryName; this.earliestRunTimeElapsedMillis = earliestRunTimeElapsedMillis; this.latestRunTimeElapsedMillis = latestRunTimeElapsedMillis; this.numFailures = numFailures; int requiredConstraints = job.getConstraintFlags(); switch (job.getNetworkType()) { case JobInfo.NETWORK_TYPE_NONE: // No constraint. break; case JobInfo.NETWORK_TYPE_ANY: requiredConstraints |= CONSTRAINT_CONNECTIVITY; break; case JobInfo.NETWORK_TYPE_UNMETERED: requiredConstraints |= CONSTRAINT_UNMETERED; break; case JobInfo.NETWORK_TYPE_NOT_ROAMING: requiredConstraints |= CONSTRAINT_NOT_ROAMING; break; case JobInfo.NETWORK_TYPE_METERED: requiredConstraints |= CONSTRAINT_METERED; break; default: Slog.w(TAG, "Unrecognized networking constraint " + job.getNetworkType()); break; } if (earliestRunTimeElapsedMillis != NO_EARLIEST_RUNTIME) { requiredConstraints |= CONSTRAINT_TIMING_DELAY; } if (latestRunTimeElapsedMillis != NO_LATEST_RUNTIME) { requiredConstraints |= CONSTRAINT_DEADLINE; } if (job.getTriggerContentUris() != null) { requiredConstraints |= CONSTRAINT_CONTENT_TRIGGER; } this.requiredConstraints = requiredConstraints; } /** Copy constructor. */ public JobStatus(JobStatus jobStatus) { this(jobStatus.getJob(), jobStatus.getUid(), jobStatus.getSourcePackageName(), jobStatus.getSourceUserId(), jobStatus.getSourceTag(), jobStatus.getNumFailures(), jobStatus.getEarliestRunTime(), jobStatus.getLatestRunTimeElapsed()); } /** * Create a new JobStatus that was loaded from disk. We ignore the provided * {@link android.app.job.JobInfo} time criteria because we can load a persisted periodic job * from the {@link com.android.server.job.JobStore} and still want to respect its * wallclock runtime rather than resetting it on every boot. * We consider a freshly loaded job to no longer be in back-off. */ public JobStatus(JobInfo job, int callingUid, String sourcePackageName, int sourceUserId, String sourceTag, long earliestRunTimeElapsedMillis, long latestRunTimeElapsedMillis) { this(job, callingUid, sourcePackageName, sourceUserId, sourceTag, 0, earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis); } /** Create a new job to be rescheduled with the provided parameters. */ public JobStatus(JobStatus rescheduling, long newEarliestRuntimeElapsedMillis, long newLatestRuntimeElapsedMillis, int backoffAttempt) { this(rescheduling.job, rescheduling.getUid(), rescheduling.getSourcePackageName(), rescheduling.getSourceUserId(), rescheduling.getSourceTag(), backoffAttempt, newEarliestRuntimeElapsedMillis, newLatestRuntimeElapsedMillis); } /** * Create a newly scheduled job. * @param callingUid Uid of the package that scheduled this job. * @param sourcePackageName Package name on whose behalf this job is scheduled. Null indicates * the calling package is the source. * @param sourceUserId User id for whom this job is scheduled. -1 indicates this is same as the */ public static JobStatus createFromJobInfo(JobInfo job, int callingUid, String sourcePackageName, int sourceUserId, String tag) { final long elapsedNow = SystemClock.elapsedRealtime(); final long earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis; if (job.isPeriodic()) { latestRunTimeElapsedMillis = elapsedNow + job.getIntervalMillis(); earliestRunTimeElapsedMillis = latestRunTimeElapsedMillis - job.getFlexMillis(); } else { earliestRunTimeElapsedMillis = job.hasEarlyConstraint() ? elapsedNow + job.getMinLatencyMillis() : NO_EARLIEST_RUNTIME; latestRunTimeElapsedMillis = job.hasLateConstraint() ? elapsedNow + job.getMaxExecutionDelayMillis() : NO_LATEST_RUNTIME; } return new JobStatus(job, callingUid, sourcePackageName, sourceUserId, tag, 0, earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis); } public void enqueueWorkLocked(IActivityManager am, JobWorkItem work) { if (pendingWork == null) { pendingWork = new ArrayList<>(); } work.setWorkId(nextPendingWorkId); nextPendingWorkId++; if (work.getIntent() != null && GrantedUriPermissions.checkGrantFlags(work.getIntent().getFlags())) { work.setGrants(GrantedUriPermissions.createFromIntent(am, work.getIntent(), sourceUid, sourcePackageName, sourceUserId, toShortString())); } pendingWork.add(work); } public JobWorkItem dequeueWorkLocked() { if (pendingWork != null && pendingWork.size() > 0) { JobWorkItem work = pendingWork.remove(0); if (work != null) { if (executingWork == null) { executingWork = new ArrayList<>(); } executingWork.add(work); work.bumpDeliveryCount(); } return work; } return null; } public boolean hasWorkLocked() { return (pendingWork != null && pendingWork.size() > 0) || hasExecutingWorkLocked(); } public boolean hasExecutingWorkLocked() { return executingWork != null && executingWork.size() > 0; } private static void ungrantWorkItem(IActivityManager am, JobWorkItem work) { if (work.getGrants() != null) { ((GrantedUriPermissions)work.getGrants()).revoke(am); } } public boolean completeWorkLocked(IActivityManager am, int workId) { if (executingWork != null) { final int N = executingWork.size(); for (int i = 0; i < N; i++) { JobWorkItem work = executingWork.get(i); if (work.getWorkId() == workId) { executingWork.remove(i); ungrantWorkItem(am, work); return true; } } } return false; } private static void ungrantWorkList(IActivityManager am, ArrayList list) { if (list != null) { final int N = list.size(); for (int i = 0; i < N; i++) { ungrantWorkItem(am, list.get(i)); } } } public void stopTrackingJobLocked(IActivityManager am, JobStatus incomingJob) { if (incomingJob != null) { // We are replacing with a new job -- transfer the work! We do any executing // work first, since that was originally at the front of the pending work. if (executingWork != null && executingWork.size() > 0) { incomingJob.pendingWork = executingWork; } if (incomingJob.pendingWork == null) { incomingJob.pendingWork = pendingWork; } else if (pendingWork != null && pendingWork.size() > 0) { incomingJob.pendingWork.addAll(pendingWork); } pendingWork = null; executingWork = null; incomingJob.nextPendingWorkId = nextPendingWorkId; } else { // We are completely stopping the job... need to clean up work. ungrantWorkList(am, pendingWork); pendingWork = null; ungrantWorkList(am, executingWork); executingWork = null; } } public void prepareLocked(IActivityManager am) { if (prepared) { Slog.wtf(TAG, "Already prepared: " + this); return; } prepared = true; if (DEBUG_PREPARE) { unpreparedPoint = null; } final ClipData clip = job.getClipData(); if (clip != null) { uriPerms = GrantedUriPermissions.createFromClip(am, clip, sourceUid, sourcePackageName, sourceUserId, job.getClipGrantFlags(), toShortString()); } } public void unprepareLocked(IActivityManager am) { if (!prepared) { Slog.wtf(TAG, "Hasn't been prepared: " + this); if (DEBUG_PREPARE && unpreparedPoint != null) { Slog.e(TAG, "Was already unprepared at ", unpreparedPoint); } return; } prepared = false; if (DEBUG_PREPARE) { unpreparedPoint = new Throwable().fillInStackTrace(); } if (uriPerms != null) { uriPerms.revoke(am); uriPerms = null; } } public boolean isPreparedLocked() { return prepared; } public JobInfo getJob() { return job; } public int getJobId() { return job.getId(); } public void printUniqueId(PrintWriter pw) { UserHandle.formatUid(pw, callingUid); pw.print("/"); pw.print(job.getId()); } public int getNumFailures() { return numFailures; } public ComponentName getServiceComponent() { return job.getService(); } public String getSourcePackageName() { return sourcePackageName; } public int getSourceUid() { return sourceUid; } public int getSourceUserId() { return sourceUserId; } public int getUserId() { return UserHandle.getUserId(callingUid); } public String getSourceTag() { return sourceTag; } public int getUid() { return callingUid; } public String getBatteryName() { return batteryName; } public String getTag() { return tag; } public int getPriority() { return job.getPriority(); } public int getFlags() { return job.getFlags(); } /** Does this job have any sort of networking constraint? */ public boolean hasConnectivityConstraint() { return (requiredConstraints&CONNECTIVITY_MASK) != 0; } public boolean needsAnyConnectivity() { return (requiredConstraints&CONSTRAINT_CONNECTIVITY) != 0; } public boolean needsUnmeteredConnectivity() { return (requiredConstraints&CONSTRAINT_UNMETERED) != 0; } public boolean needsMeteredConnectivity() { return (requiredConstraints&CONSTRAINT_METERED) != 0; } public boolean needsNonRoamingConnectivity() { return (requiredConstraints&CONSTRAINT_NOT_ROAMING) != 0; } public boolean hasChargingConstraint() { return (requiredConstraints&CONSTRAINT_CHARGING) != 0; } public boolean hasBatteryNotLowConstraint() { return (requiredConstraints&CONSTRAINT_BATTERY_NOT_LOW) != 0; } public boolean hasPowerConstraint() { return (requiredConstraints&(CONSTRAINT_CHARGING|CONSTRAINT_BATTERY_NOT_LOW)) != 0; } public boolean hasStorageNotLowConstraint() { return (requiredConstraints&CONSTRAINT_STORAGE_NOT_LOW) != 0; } public boolean hasTimingDelayConstraint() { return (requiredConstraints&CONSTRAINT_TIMING_DELAY) != 0; } public boolean hasDeadlineConstraint() { return (requiredConstraints&CONSTRAINT_DEADLINE) != 0; } public boolean hasIdleConstraint() { return (requiredConstraints&CONSTRAINT_IDLE) != 0; } public boolean hasContentTriggerConstraint() { return (requiredConstraints&CONSTRAINT_CONTENT_TRIGGER) != 0; } public long getTriggerContentUpdateDelay() { long time = job.getTriggerContentUpdateDelay(); if (time < 0) { return DEFAULT_TRIGGER_UPDATE_DELAY; } return Math.max(time, MIN_TRIGGER_UPDATE_DELAY); } public long getTriggerContentMaxDelay() { long time = job.getTriggerContentMaxDelay(); if (time < 0) { return DEFAULT_TRIGGER_MAX_DELAY; } return Math.max(time, MIN_TRIGGER_MAX_DELAY); } public boolean isPersisted() { return job.isPersisted(); } public long getEarliestRunTime() { return earliestRunTimeElapsedMillis; } public long getLatestRunTimeElapsed() { return latestRunTimeElapsedMillis; } boolean setChargingConstraintSatisfied(boolean state) { return setConstraintSatisfied(CONSTRAINT_CHARGING, state); } boolean setBatteryNotLowConstraintSatisfied(boolean state) { return setConstraintSatisfied(CONSTRAINT_BATTERY_NOT_LOW, state); } boolean setStorageNotLowConstraintSatisfied(boolean state) { return setConstraintSatisfied(CONSTRAINT_STORAGE_NOT_LOW, state); } boolean setTimingDelayConstraintSatisfied(boolean state) { return setConstraintSatisfied(CONSTRAINT_TIMING_DELAY, state); } boolean setDeadlineConstraintSatisfied(boolean state) { return setConstraintSatisfied(CONSTRAINT_DEADLINE, state); } boolean setIdleConstraintSatisfied(boolean state) { return setConstraintSatisfied(CONSTRAINT_IDLE, state); } boolean setConnectivityConstraintSatisfied(boolean state) { return setConstraintSatisfied(CONSTRAINT_CONNECTIVITY, state); } boolean setUnmeteredConstraintSatisfied(boolean state) { return setConstraintSatisfied(CONSTRAINT_UNMETERED, state); } boolean setMeteredConstraintSatisfied(boolean state) { return setConstraintSatisfied(CONSTRAINT_METERED, state); } boolean setNotRoamingConstraintSatisfied(boolean state) { return setConstraintSatisfied(CONSTRAINT_NOT_ROAMING, state); } boolean setAppNotIdleConstraintSatisfied(boolean state) { return setConstraintSatisfied(CONSTRAINT_APP_NOT_IDLE, state); } boolean setContentTriggerConstraintSatisfied(boolean state) { return setConstraintSatisfied(CONSTRAINT_CONTENT_TRIGGER, state); } boolean setDeviceNotDozingConstraintSatisfied(boolean state, boolean whitelisted) { dozeWhitelisted = whitelisted; return setConstraintSatisfied(CONSTRAINT_DEVICE_NOT_DOZING, state); } boolean setConstraintSatisfied(int constraint, boolean state) { boolean old = (satisfiedConstraints&constraint) != 0; if (old == state) { return false; } satisfiedConstraints = (satisfiedConstraints&~constraint) | (state ? constraint : 0); return true; } boolean isConstraintSatisfied(int constraint) { return (satisfiedConstraints&constraint) != 0; } boolean clearTrackingController(int which) { if ((trackingControllers&which) != 0) { trackingControllers &= ~which; return true; } return false; } void setTrackingController(int which) { trackingControllers |= which; } public boolean shouldDump(int filterUid) { return filterUid == -1 || UserHandle.getAppId(getUid()) == filterUid || UserHandle.getAppId(getSourceUid()) == filterUid; } /** * @return Whether or not this job is ready to run, based on its requirements. This is true if * the constraints are satisfied or the deadline on the job has expired. * TODO: This function is called a *lot*. We should probably just have it check an * already-computed boolean, which we updated whenever we see one of the states it depends * on here change. */ public boolean isReady() { // Deadline constraint trumps other constraints (except for periodic jobs where deadline // is an implementation detail. A periodic job should only run if its constraints are // satisfied). // AppNotIdle implicit constraint must be satisfied // DeviceNotDozing implicit constraint must be satisfied final boolean deadlineSatisfied = (!job.isPeriodic() && hasDeadlineConstraint() && (satisfiedConstraints & CONSTRAINT_DEADLINE) != 0); final boolean notIdle = (satisfiedConstraints & CONSTRAINT_APP_NOT_IDLE) != 0; final boolean notDozing = (satisfiedConstraints & CONSTRAINT_DEVICE_NOT_DOZING) != 0 || (job.getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0; return (isConstraintsSatisfied() || deadlineSatisfied) && notIdle && notDozing; } static final int CONSTRAINTS_OF_INTEREST = CONSTRAINT_CHARGING | CONSTRAINT_BATTERY_NOT_LOW | CONSTRAINT_STORAGE_NOT_LOW | CONSTRAINT_TIMING_DELAY | CONSTRAINT_CONNECTIVITY | CONSTRAINT_UNMETERED | CONSTRAINT_NOT_ROAMING | CONSTRAINT_METERED | CONSTRAINT_IDLE | CONSTRAINT_CONTENT_TRIGGER; // Soft override covers all non-"functional" constraints static final int SOFT_OVERRIDE_CONSTRAINTS = CONSTRAINT_CHARGING | CONSTRAINT_BATTERY_NOT_LOW | CONSTRAINT_STORAGE_NOT_LOW | CONSTRAINT_TIMING_DELAY | CONSTRAINT_IDLE; /** * @return Whether the constraints set on this job are satisfied. */ public boolean isConstraintsSatisfied() { if (overrideState == OVERRIDE_FULL) { // force override: the job is always runnable return true; } final int req = requiredConstraints & CONSTRAINTS_OF_INTEREST; int sat = satisfiedConstraints & CONSTRAINTS_OF_INTEREST; if (overrideState == OVERRIDE_SOFT) { // override: pretend all 'soft' requirements are satisfied sat |= (requiredConstraints & SOFT_OVERRIDE_CONSTRAINTS); } return (sat & req) == req; } public boolean matches(int uid, int jobId) { return this.job.getId() == jobId && this.callingUid == uid; } @Override public String toString() { StringBuilder sb = new StringBuilder(128); sb.append("JobStatus{"); sb.append(Integer.toHexString(System.identityHashCode(this))); sb.append(" #"); UserHandle.formatUid(sb, callingUid); sb.append("/"); sb.append(job.getId()); sb.append(' '); sb.append(batteryName); sb.append(" u="); sb.append(getUserId()); sb.append(" s="); sb.append(getSourceUid()); if (earliestRunTimeElapsedMillis != NO_EARLIEST_RUNTIME || latestRunTimeElapsedMillis != NO_LATEST_RUNTIME) { long now = SystemClock.elapsedRealtime(); sb.append(" TIME="); formatRunTime(sb, earliestRunTimeElapsedMillis, NO_EARLIEST_RUNTIME, now); sb.append(":"); formatRunTime(sb, latestRunTimeElapsedMillis, NO_LATEST_RUNTIME, now); } if (job.getNetworkType() != JobInfo.NETWORK_TYPE_NONE) { sb.append(" NET="); sb.append(job.getNetworkType()); } if (job.isRequireCharging()) { sb.append(" CHARGING"); } if (job.isRequireBatteryNotLow()) { sb.append(" BATNOTLOW"); } if (job.isRequireStorageNotLow()) { sb.append(" STORENOTLOW"); } if (job.isRequireDeviceIdle()) { sb.append(" IDLE"); } if (job.isPersisted()) { sb.append(" PERSISTED"); } if ((satisfiedConstraints&CONSTRAINT_APP_NOT_IDLE) == 0) { sb.append(" WAIT:APP_NOT_IDLE"); } if ((satisfiedConstraints&CONSTRAINT_DEVICE_NOT_DOZING) == 0) { sb.append(" WAIT:DEV_NOT_DOZING"); } if (job.getTriggerContentUris() != null) { sb.append(" URIS="); sb.append(Arrays.toString(job.getTriggerContentUris())); } if (numFailures != 0) { sb.append(" failures="); sb.append(numFailures); } if (isReady()) { sb.append(" READY"); } sb.append("}"); return sb.toString(); } private void formatRunTime(PrintWriter pw, long runtime, long defaultValue, long now) { if (runtime == defaultValue) { pw.print("none"); } else { TimeUtils.formatDuration(runtime - now, pw); } } private void formatRunTime(StringBuilder sb, long runtime, long defaultValue, long now) { if (runtime == defaultValue) { sb.append("none"); } else { TimeUtils.formatDuration(runtime - now, sb); } } /** * Convenience function to identify a job uniquely without pulling all the data that * {@link #toString()} returns. */ public String toShortString() { StringBuilder sb = new StringBuilder(); sb.append(Integer.toHexString(System.identityHashCode(this))); sb.append(" #"); UserHandle.formatUid(sb, callingUid); sb.append("/"); sb.append(job.getId()); sb.append(' '); sb.append(batteryName); return sb.toString(); } /** * Convenience function to identify a job uniquely without pulling all the data that * {@link #toString()} returns. */ public String toShortStringExceptUniqueId() { StringBuilder sb = new StringBuilder(); sb.append(Integer.toHexString(System.identityHashCode(this))); sb.append(' '); sb.append(batteryName); return sb.toString(); } void dumpConstraints(PrintWriter pw, int constraints) { if ((constraints&CONSTRAINT_CHARGING) != 0) { pw.print(" CHARGING"); } if ((constraints& CONSTRAINT_BATTERY_NOT_LOW) != 0) { pw.print(" BATTERY_NOT_LOW"); } if ((constraints& CONSTRAINT_STORAGE_NOT_LOW) != 0) { pw.print(" STORAGE_NOT_LOW"); } if ((constraints&CONSTRAINT_TIMING_DELAY) != 0) { pw.print(" TIMING_DELAY"); } if ((constraints&CONSTRAINT_DEADLINE) != 0) { pw.print(" DEADLINE"); } if ((constraints&CONSTRAINT_IDLE) != 0) { pw.print(" IDLE"); } if ((constraints&CONSTRAINT_CONNECTIVITY) != 0) { pw.print(" CONNECTIVITY"); } if ((constraints&CONSTRAINT_UNMETERED) != 0) { pw.print(" UNMETERED"); } if ((constraints&CONSTRAINT_NOT_ROAMING) != 0) { pw.print(" NOT_ROAMING"); } if ((constraints&CONSTRAINT_METERED) != 0) { pw.print(" METERED"); } if ((constraints&CONSTRAINT_APP_NOT_IDLE) != 0) { pw.print(" APP_NOT_IDLE"); } if ((constraints&CONSTRAINT_CONTENT_TRIGGER) != 0) { pw.print(" CONTENT_TRIGGER"); } if ((constraints&CONSTRAINT_DEVICE_NOT_DOZING) != 0) { pw.print(" DEVICE_NOT_DOZING"); } } private void dumpJobWorkItem(PrintWriter pw, String prefix, JobWorkItem work, int index) { pw.print(prefix); pw.print(" #"); pw.print(index); pw.print(": #"); pw.print(work.getWorkId()); pw.print(" "); pw.print(work.getDeliveryCount()); pw.print("x "); pw.println(work.getIntent()); if (work.getGrants() != null) { pw.print(prefix); pw.println(" URI grants:"); ((GrantedUriPermissions)work.getGrants()).dump(pw, prefix + " "); } } // Dumpsys infrastructure public void dump(PrintWriter pw, String prefix, boolean full, long elapsedRealtimeMillis) { pw.print(prefix); UserHandle.formatUid(pw, callingUid); pw.print(" tag="); pw.println(tag); pw.print(prefix); pw.print("Source: uid="); UserHandle.formatUid(pw, getSourceUid()); pw.print(" user="); pw.print(getSourceUserId()); pw.print(" pkg="); pw.println(getSourcePackageName()); if (full) { pw.print(prefix); pw.println("JobInfo:"); pw.print(prefix); pw.print(" Service: "); pw.println(job.getService().flattenToShortString()); if (job.isPeriodic()) { pw.print(prefix); pw.print(" PERIODIC: interval="); TimeUtils.formatDuration(job.getIntervalMillis(), pw); pw.print(" flex="); TimeUtils.formatDuration(job.getFlexMillis(), pw); pw.println(); } if (job.isPersisted()) { pw.print(prefix); pw.println(" PERSISTED"); } if (job.getPriority() != 0) { pw.print(prefix); pw.print(" Priority: "); pw.println(job.getPriority()); } if (job.getFlags() != 0) { pw.print(prefix); pw.print(" Flags: "); pw.println(Integer.toHexString(job.getFlags())); } pw.print(prefix); pw.print(" Requires: charging="); pw.print(job.isRequireCharging()); pw.print(" batteryNotLow="); pw.print(job.isRequireBatteryNotLow()); pw.print(" deviceIdle="); pw.println(job.isRequireDeviceIdle()); if (job.getTriggerContentUris() != null) { pw.print(prefix); pw.println(" Trigger content URIs:"); for (int i = 0; i < job.getTriggerContentUris().length; i++) { JobInfo.TriggerContentUri trig = job.getTriggerContentUris()[i]; pw.print(prefix); pw.print(" "); pw.print(Integer.toHexString(trig.getFlags())); pw.print(' '); pw.println(trig.getUri()); } if (job.getTriggerContentUpdateDelay() >= 0) { pw.print(prefix); pw.print(" Trigger update delay: "); TimeUtils.formatDuration(job.getTriggerContentUpdateDelay(), pw); pw.println(); } if (job.getTriggerContentMaxDelay() >= 0) { pw.print(prefix); pw.print(" Trigger max delay: "); TimeUtils.formatDuration(job.getTriggerContentMaxDelay(), pw); pw.println(); } } if (job.getExtras() != null && !job.getExtras().maybeIsEmpty()) { pw.print(prefix); pw.print(" Extras: "); pw.println(job.getExtras().toShortString()); } if (job.getTransientExtras() != null && !job.getTransientExtras().maybeIsEmpty()) { pw.print(prefix); pw.print(" Transient extras: "); pw.println(job.getTransientExtras().toShortString()); } if (job.getClipData() != null) { pw.print(prefix); pw.print(" Clip data: "); StringBuilder b = new StringBuilder(128); job.getClipData().toShortString(b); pw.println(b); } if (uriPerms != null) { pw.print(prefix); pw.println(" Granted URI permissions:"); uriPerms.dump(pw, prefix + " "); } if (job.getNetworkType() != JobInfo.NETWORK_TYPE_NONE) { pw.print(prefix); pw.print(" Network type: "); pw.println(job.getNetworkType()); } if (job.getMinLatencyMillis() != 0) { pw.print(prefix); pw.print(" Minimum latency: "); TimeUtils.formatDuration(job.getMinLatencyMillis(), pw); pw.println(); } if (job.getMaxExecutionDelayMillis() != 0) { pw.print(prefix); pw.print(" Max execution delay: "); TimeUtils.formatDuration(job.getMaxExecutionDelayMillis(), pw); pw.println(); } pw.print(prefix); pw.print(" Backoff: policy="); pw.print(job.getBackoffPolicy()); pw.print(" initial="); TimeUtils.formatDuration(job.getInitialBackoffMillis(), pw); pw.println(); if (job.hasEarlyConstraint()) { pw.print(prefix); pw.println(" Has early constraint"); } if (job.hasLateConstraint()) { pw.print(prefix); pw.println(" Has late constraint"); } } pw.print(prefix); pw.print("Required constraints:"); dumpConstraints(pw, requiredConstraints); pw.println(); if (full) { pw.print(prefix); pw.print("Satisfied constraints:"); dumpConstraints(pw, satisfiedConstraints); pw.println(); pw.print(prefix); pw.print("Unsatisfied constraints:"); dumpConstraints(pw, (requiredConstraints & ~satisfiedConstraints)); pw.println(); if (dozeWhitelisted) { pw.print(prefix); pw.println("Doze whitelisted: true"); } } if (trackingControllers != 0) { pw.print(prefix); pw.print("Tracking:"); if ((trackingControllers&TRACKING_BATTERY) != 0) pw.print(" BATTERY"); if ((trackingControllers&TRACKING_CONNECTIVITY) != 0) pw.print(" CONNECTIVITY"); if ((trackingControllers&TRACKING_CONTENT) != 0) pw.print(" CONTENT"); if ((trackingControllers&TRACKING_IDLE) != 0) pw.print(" IDLE"); if ((trackingControllers&TRACKING_STORAGE) != 0) pw.print(" STORAGE"); if ((trackingControllers&TRACKING_TIME) != 0) pw.print(" TIME"); pw.println(); } if (changedAuthorities != null) { pw.print(prefix); pw.println("Changed authorities:"); for (int i=0; i 0) { pw.print(prefix); pw.println("Pending work:"); for (int i = 0; i < pendingWork.size(); i++) { dumpJobWorkItem(pw, prefix, pendingWork.get(i), i); } } if (executingWork != null && executingWork.size() > 0) { pw.print(prefix); pw.println("Executing work:"); for (int i = 0; i < executingWork.size(); i++) { dumpJobWorkItem(pw, prefix, executingWork.get(i), i); } } pw.print(prefix); pw.print("Enqueue time: "); TimeUtils.formatDuration(enqueueTime, elapsedRealtimeMillis, pw); pw.println(); pw.print(prefix); pw.print("Run time: earliest="); formatRunTime(pw, earliestRunTimeElapsedMillis, NO_EARLIEST_RUNTIME, elapsedRealtimeMillis); pw.print(", latest="); formatRunTime(pw, latestRunTimeElapsedMillis, NO_LATEST_RUNTIME, elapsedRealtimeMillis); pw.println(); if (numFailures != 0) { pw.print(prefix); pw.print("Num failures: "); pw.println(numFailures); } } }