blob: 6f2b3345389171efca6f450891b22a6d044e37e3 [file] [log] [blame]
/*
* 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 static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
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.Network;
import android.net.Uri;
import android.os.RemoteException;
import android.os.UserHandle;
import android.text.format.TimeMigrationUtils;
import android.util.ArraySet;
import android.util.Pair;
import android.util.Slog;
import android.util.StatsLog;
import android.util.TimeUtils;
import android.util.proto.ProtoOutputStream;
import com.android.server.LocalServices;
import com.android.server.job.GrantedUriPermissions;
import com.android.server.job.JobSchedulerInternal;
import com.android.server.job.JobSchedulerService;
import com.android.server.job.JobServerProtoEnums;
import com.android.server.job.JobStatusDumpProto;
import com.android.server.job.JobStatusShortInfoProto;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.function.Predicate;
/**
* 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.
*
* Test: atest com.android.server.job.controllers.JobStatusTest
* @hide
*/
public final class JobStatus {
static final String TAG = "JobSchedulerService";
static final boolean DEBUG = JobSchedulerService.DEBUG;
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; // 1 < 0
static final int CONSTRAINT_IDLE = JobInfo.CONSTRAINT_FLAG_DEVICE_IDLE; // 1 << 2
static final int CONSTRAINT_BATTERY_NOT_LOW = JobInfo.CONSTRAINT_FLAG_BATTERY_NOT_LOW; // 1 << 1
static final int CONSTRAINT_STORAGE_NOT_LOW = JobInfo.CONSTRAINT_FLAG_STORAGE_NOT_LOW; // 1 << 3
static final int CONSTRAINT_TIMING_DELAY = 1<<31;
static final int CONSTRAINT_DEADLINE = 1<<30;
static final int CONSTRAINT_CONNECTIVITY = 1<<28;
static final int CONSTRAINT_CONTENT_TRIGGER = 1<<26;
static final int CONSTRAINT_DEVICE_NOT_DOZING = 1 << 25; // Implicit constraint
static final int CONSTRAINT_WITHIN_QUOTA = 1 << 24; // Implicit constraint
static final int CONSTRAINT_BACKGROUND_NOT_RESTRICTED = 1 << 22; // Implicit constraint
/**
* The constraints that we want to log to statsd.
*
* Constraints that can be inferred from other atoms have been excluded to avoid logging too
* much information and to reduce redundancy:
*
* * CONSTRAINT_CHARGING can be inferred with PluggedStateChanged (Atom #32)
* * CONSTRAINT_BATTERY_NOT_LOW can be inferred with BatteryLevelChanged (Atom #30)
* * CONSTRAINT_CONNECTIVITY can be partially inferred with ConnectivityStateChanged
* (Atom #98) and BatterySaverModeStateChanged (Atom #20).
* * CONSTRAINT_DEVICE_NOT_DOZING can be mostly inferred with DeviceIdleModeStateChanged
* (Atom #21)
* * CONSTRAINT_BACKGROUND_NOT_RESTRICTED can be inferred with BatterySaverModeStateChanged
* (Atom #20)
*/
private static final int STATSD_CONSTRAINTS_TO_LOG = CONSTRAINT_CONTENT_TRIGGER
| CONSTRAINT_DEADLINE
| CONSTRAINT_IDLE
| CONSTRAINT_STORAGE_NOT_LOW
| CONSTRAINT_TIMING_DELAY
| CONSTRAINT_WITHIN_QUOTA;
// TODO(b/129954980)
private static final boolean STATS_LOG_ENABLED = false;
// 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. This can differ from the "source"
* uid when the job was scheduled on the app's behalf, such as with the jobs
* that underly Sync Manager operation.
*/
final int callingUid;
final String batteryName;
/**
* Identity of the app in which the job is hosted.
*/
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;
/**
* Valid only for periodic jobs. The original latest point in the future at which this
* job was expected to run.
*/
private long mOriginalLatestRunTimeElapsedMillis;
/** How many times this job has failed, used to compute back-off. */
private final int numFailures;
/**
* Current standby heartbeat when this job was scheduled or last ran. Used to
* pin the runnability check regardless of the job's app moving between buckets.
*/
private final long baseHeartbeat;
/**
* Which app standby bucket this job's app is in. Updated when the app is moved to a
* different bucket.
*/
private int standbyBucket;
/**
* Debugging: timestamp if we ever defer this job based on standby bucketing, this
* is when we did so.
*/
private long whenStandbyDeferred;
/** The first time this job was force batched. */
private long mFirstForceBatchedTimeElapsed;
// Constraints.
final int requiredConstraints;
private final int mRequiredConstraintsOfInterest;
int satisfiedConstraints = 0;
private int mSatisfiedConstraintsOfInterest = 0;
// Set to true if doze constraint was satisfied due to app being whitelisted.
public boolean dozeWhitelisted;
// Set to true when the app is "active" per AppStateTracker
public boolean uidActive;
/**
* 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;
/**
* Flag for {@link #trackingControllers}: the quota controller is currently tracking this job.
*/
public static final int TRACKING_QUOTA = 1 << 6;
/**
* Bit mask of controllers that are currently tracking the job.
*/
private int trackingControllers;
/**
* Flag for {@link #mInternalFlags}: this job was scheduled when the app that owns the job
* service (not necessarily the caller) was in the foreground and the job has no time
* constraints, which makes it exempted from the battery saver job restriction.
*
* @hide
*/
public static final int INTERNAL_FLAG_HAS_FOREGROUND_EXEMPTION = 1 << 0;
/**
* Versatile, persistable flags for a job that's updated within the system server,
* as opposed to {@link JobInfo#flags} that's set by callers.
*/
private int mInternalFlags;
// These are filled in by controllers when preparing for execution.
public ArraySet<Uri> changedUris;
public ArraySet<String> changedAuthorities;
public Network network;
public int lastEvaluatedPriority;
// If non-null, this is work that has been enqueued for the job.
public ArrayList<JobWorkItem> pendingWork;
// If non-null, this is work that is currently being executed.
public ArrayList<JobWorkItem> 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;
/**
* Last time a job finished successfully for a periodic job, in the currentTimeMillis time,
* for dumpsys.
*/
private long mLastSuccessfulRunTime;
/**
* Last time a job finished unsuccessfully, in the currentTimeMillis time, for dumpsys.
*/
private long mLastFailedRunTime;
/**
* Transient: when a job is inflated from disk before we have a reliable RTC clock time,
* we retain the canonical (delay, deadline) scheduling tuple read out of the persistent
* store in UTC so that we can fix up the job's scheduling criteria once we get a good
* wall-clock time. If we have to persist the job again before the clock has been updated,
* we record these times again rather than calculating based on the earliest/latest elapsed
* time base figures.
*
* 'first' is the earliest/delay time, and 'second' is the latest/deadline time.
*/
private Pair<Long, Long> mPersistedUtcTimes;
/**
* For use only by ContentObserverController: state it is maintaining about content URIs
* being observed.
*/
ContentObserverController.JobInstance contentObserverJobInstance;
private long mTotalNetworkDownloadBytes = JobInfo.NETWORK_BYTES_UNKNOWN;
private long mTotalNetworkUploadBytes = JobInfo.NETWORK_BYTES_UNKNOWN;
/////// Booleans that track if a job is ready to run. They should be updated whenever dependent
/////// states change.
/**
* The deadline for the job has passed. This is only good for non-periodic jobs. A periodic job
* should only run if its constraints are satisfied.
* Computed as: NOT periodic AND has deadline constraint AND deadline constraint satisfied.
*/
private boolean mReadyDeadlineSatisfied;
/**
* The device isn't Dozing or this job will be in the foreground. This implicit constraint must
* be satisfied.
*/
private boolean mReadyNotDozing;
/**
* The job is not restricted from running in the background (due to Battery Saver). This
* implicit constraint must be satisfied.
*/
private boolean mReadyNotRestrictedInBg;
/** The job is within its quota based on its standby bucket. */
private boolean mReadyWithinQuota;
/** Provide a handle to the service that this job will be run on. */
public int getServiceToken() {
return callingUid;
}
/**
* Core constructor for JobStatus instances. All other ctors funnel down to this one.
*
* @param job The actual requested parameters for the job
* @param callingUid Identity of the app that is scheduling the job. This may not be the
* app in which the job is implemented; such as with sync jobs.
* @param sourcePackageName The package name of the app in which the job will run.
* @param sourceUserId The user in which the job will run
* @param standbyBucket The standby bucket that the source package is currently assigned to,
* cached here for speed of handling during runnability evaluations (and updated when bucket
* assignments are changed)
* @param heartbeat Timestamp of when the job was created, in the standby-related
* timebase.
* @param tag A string associated with the job for debugging/logging purposes.
* @param numFailures Count of how many times this job has requested a reschedule because
* its work was not yet finished.
* @param earliestRunTimeElapsedMillis Milestone: earliest point in time at which the job
* is to be considered runnable
* @param latestRunTimeElapsedMillis Milestone: point in time at which the job will be
* considered overdue
* @param lastSuccessfulRunTime When did we last run this job to completion?
* @param lastFailedRunTime When did we last run this job only to have it stop incomplete?
* @param internalFlags Non-API property flags about this job
*/
private JobStatus(JobInfo job, int callingUid, String sourcePackageName,
int sourceUserId, int standbyBucket, long heartbeat, String tag, int numFailures,
long earliestRunTimeElapsedMillis, long latestRunTimeElapsedMillis,
long lastSuccessfulRunTime, long lastFailedRunTime, int internalFlags) {
this.job = job;
this.callingUid = callingUid;
this.standbyBucket = standbyBucket;
this.baseHeartbeat = heartbeat;
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.mOriginalLatestRunTimeElapsedMillis = latestRunTimeElapsedMillis;
this.numFailures = numFailures;
int requiredConstraints = job.getConstraintFlags();
if (job.getRequiredNetwork() != null) {
requiredConstraints |= CONSTRAINT_CONNECTIVITY;
}
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;
mRequiredConstraintsOfInterest = requiredConstraints & CONSTRAINTS_OF_INTEREST;
mReadyNotDozing = (job.getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0;
mLastSuccessfulRunTime = lastSuccessfulRunTime;
mLastFailedRunTime = lastFailedRunTime;
mInternalFlags = internalFlags;
updateEstimatedNetworkBytesLocked();
if (job.getRequiredNetwork() != null) {
// Later, when we check if a given network satisfies the required
// network, we need to know the UID that is requesting it, so push
// our source UID into place.
job.getRequiredNetwork().networkCapabilities.setSingleUid(this.sourceUid);
}
}
/** Copy constructor: used specifically when cloning JobStatus objects for persistence,
* so we preserve RTC window bounds if the source object has them. */
public JobStatus(JobStatus jobStatus) {
this(jobStatus.getJob(), jobStatus.getUid(),
jobStatus.getSourcePackageName(), jobStatus.getSourceUserId(),
jobStatus.getStandbyBucket(), jobStatus.getBaseHeartbeat(),
jobStatus.getSourceTag(), jobStatus.getNumFailures(),
jobStatus.getEarliestRunTime(), jobStatus.getLatestRunTimeElapsed(),
jobStatus.getLastSuccessfulRunTime(), jobStatus.getLastFailedRunTime(),
jobStatus.getInternalFlags());
mPersistedUtcTimes = jobStatus.mPersistedUtcTimes;
if (jobStatus.mPersistedUtcTimes != null) {
if (DEBUG) {
Slog.i(TAG, "Cloning job with persisted run times", new RuntimeException("here"));
}
}
}
/**
* 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, and the associated
* standby bucket is whatever the OS thinks it should be at this moment.
*/
public JobStatus(JobInfo job, int callingUid, String sourcePkgName, int sourceUserId,
int standbyBucket, long baseHeartbeat, String sourceTag,
long earliestRunTimeElapsedMillis, long latestRunTimeElapsedMillis,
long lastSuccessfulRunTime, long lastFailedRunTime,
Pair<Long, Long> persistedExecutionTimesUTC,
int innerFlags) {
this(job, callingUid, sourcePkgName, sourceUserId,
standbyBucket, baseHeartbeat,
sourceTag, 0,
earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis,
lastSuccessfulRunTime, lastFailedRunTime, innerFlags);
// Only during initial inflation do we record the UTC-timebase execution bounds
// read from the persistent store. If we ever have to recreate the JobStatus on
// the fly, it means we're rescheduling the job; and this means that the calculated
// elapsed timebase bounds intrinsically become correct.
this.mPersistedUtcTimes = persistedExecutionTimesUTC;
if (persistedExecutionTimesUTC != null) {
if (DEBUG) {
Slog.i(TAG, "+ restored job with RTC times because of bad boot clock");
}
}
}
/** Create a new job to be rescheduled with the provided parameters. */
public JobStatus(JobStatus rescheduling, long newBaseHeartbeat,
long newEarliestRuntimeElapsedMillis,
long newLatestRuntimeElapsedMillis, int backoffAttempt,
long lastSuccessfulRunTime, long lastFailedRunTime) {
this(rescheduling.job, rescheduling.getUid(),
rescheduling.getSourcePackageName(), rescheduling.getSourceUserId(),
rescheduling.getStandbyBucket(), newBaseHeartbeat,
rescheduling.getSourceTag(), backoffAttempt, newEarliestRuntimeElapsedMillis,
newLatestRuntimeElapsedMillis,
lastSuccessfulRunTime, lastFailedRunTime, rescheduling.getInternalFlags());
}
/**
* Create a newly scheduled job.
* @param callingUid Uid of the package that scheduled this job.
* @param sourcePkg Package name of the app that will actually run the job. Null indicates
* that the calling package is the source.
* @param sourceUserId User id for whom this job is scheduled. -1 indicates this is same as the
* caller.
*/
public static JobStatus createFromJobInfo(JobInfo job, int callingUid, String sourcePkg,
int sourceUserId, String tag) {
final long elapsedNow = sElapsedRealtimeClock.millis();
final long earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis;
if (job.isPeriodic()) {
// Make sure period is in the interval [min_possible_period, max_possible_period].
final long period = Math.max(JobInfo.getMinPeriodMillis(),
Math.min(JobSchedulerService.MAX_ALLOWED_PERIOD_MS, job.getIntervalMillis()));
latestRunTimeElapsedMillis = elapsedNow + period;
earliestRunTimeElapsedMillis = latestRunTimeElapsedMillis
// Make sure flex is in the interval [min_possible_flex, period].
- Math.max(JobInfo.getMinFlexMillis(), Math.min(period, job.getFlexMillis()));
} else {
earliestRunTimeElapsedMillis = job.hasEarlyConstraint() ?
elapsedNow + job.getMinLatencyMillis() : NO_EARLIEST_RUNTIME;
latestRunTimeElapsedMillis = job.hasLateConstraint() ?
elapsedNow + job.getMaxExecutionDelayMillis() : NO_LATEST_RUNTIME;
}
String jobPackage = (sourcePkg != null) ? sourcePkg : job.getService().getPackageName();
int standbyBucket = JobSchedulerService.standbyBucketForPackage(jobPackage,
sourceUserId, elapsedNow);
JobSchedulerInternal js = LocalServices.getService(JobSchedulerInternal.class);
long currentHeartbeat = js != null
? js.baseHeartbeatForApp(jobPackage, sourceUserId, standbyBucket)
: 0;
return new JobStatus(job, callingUid, sourcePkg, sourceUserId,
standbyBucket, currentHeartbeat, tag, 0,
earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis,
0 /* lastSuccessfulRunTime */, 0 /* lastFailedRunTime */,
/*innerFlags=*/ 0);
}
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);
updateEstimatedNetworkBytesLocked();
}
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();
}
updateEstimatedNetworkBytesLocked();
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<JobWorkItem> 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;
incomingJob.updateEstimatedNetworkBytesLocked();
} else {
// We are completely stopping the job... need to clean up work.
ungrantWorkList(am, pendingWork);
pendingWork = null;
ungrantWorkList(am, executingWork);
executingWork = null;
}
updateEstimatedNetworkBytesLocked();
}
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 int getStandbyBucket() {
return standbyBucket;
}
public long getBaseHeartbeat() {
return baseHeartbeat;
}
public void setStandbyBucket(int newBucket) {
standbyBucket = newBucket;
}
// Called only by the standby monitoring code
public long getWhenStandbyDeferred() {
return whenStandbyDeferred;
}
// Called only by the standby monitoring code
public void setWhenStandbyDeferred(long now) {
whenStandbyDeferred = now;
}
/**
* Returns the first time this job was force batched, in the elapsed realtime timebase. Will be
* 0 if this job was never force batched.
*/
public long getFirstForceBatchedTimeElapsed() {
return mFirstForceBatchedTimeElapsed;
}
public void setFirstForceBatchedTimeElapsed(long now) {
mFirstForceBatchedTimeElapsed = now;
}
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();
}
public int getInternalFlags() {
return mInternalFlags;
}
public void addInternalFlags(int flags) {
mInternalFlags |= flags;
}
public int getSatisfiedConstraintFlags() {
return satisfiedConstraints;
}
public void maybeAddForegroundExemption(Predicate<Integer> uidForegroundChecker) {
// Jobs with time constraints shouldn't be exempted.
if (job.hasEarlyConstraint() || job.hasLateConstraint()) {
return;
}
// Already exempted, skip the foreground check.
if ((mInternalFlags & INTERNAL_FLAG_HAS_FOREGROUND_EXEMPTION) != 0) {
return;
}
if (uidForegroundChecker.test(getSourceUid())) {
addInternalFlags(INTERNAL_FLAG_HAS_FOREGROUND_EXEMPTION);
}
}
private void updateEstimatedNetworkBytesLocked() {
mTotalNetworkDownloadBytes = job.getEstimatedNetworkDownloadBytes();
mTotalNetworkUploadBytes = job.getEstimatedNetworkUploadBytes();
if (pendingWork != null) {
for (int i = 0; i < pendingWork.size(); i++) {
if (mTotalNetworkDownloadBytes != JobInfo.NETWORK_BYTES_UNKNOWN) {
// If any component of the job has unknown usage, we don't have a
// complete picture of what data will be used, and we have to treat the
// entire up/download as unknown.
long downloadBytes = pendingWork.get(i).getEstimatedNetworkDownloadBytes();
if (downloadBytes != JobInfo.NETWORK_BYTES_UNKNOWN) {
mTotalNetworkDownloadBytes += downloadBytes;
}
}
if (mTotalNetworkUploadBytes != JobInfo.NETWORK_BYTES_UNKNOWN) {
// If any component of the job has unknown usage, we don't have a
// complete picture of what data will be used, and we have to treat the
// entire up/download as unknown.
long uploadBytes = pendingWork.get(i).getEstimatedNetworkUploadBytes();
if (uploadBytes != JobInfo.NETWORK_BYTES_UNKNOWN) {
mTotalNetworkUploadBytes += uploadBytes;
}
}
}
}
}
public long getEstimatedNetworkDownloadBytes() {
return mTotalNetworkDownloadBytes;
}
public long getEstimatedNetworkUploadBytes() {
return mTotalNetworkUploadBytes;
}
/** Does this job have any sort of networking constraint? */
public boolean hasConnectivityConstraint() {
return (requiredConstraints&CONSTRAINT_CONNECTIVITY) != 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;
}
public long getOriginalLatestRunTimeElapsed() {
return mOriginalLatestRunTimeElapsedMillis;
}
public void setOriginalLatestRunTimeElapsed(long latestRunTimeElapsed) {
mOriginalLatestRunTimeElapsedMillis = latestRunTimeElapsed;
}
/**
* Return the fractional position of "now" within the "run time" window of
* this job.
* <p>
* For example, if the earliest run time was 10 minutes ago, and the latest
* run time is 30 minutes from now, this would return 0.25.
* <p>
* If the job has no window defined, returns 1. When only an earliest or
* latest time is defined, it's treated as an infinitely small window at
* that time.
*/
public float getFractionRunTime() {
final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
if (earliestRunTimeElapsedMillis == 0 && latestRunTimeElapsedMillis == Long.MAX_VALUE) {
return 1;
} else if (earliestRunTimeElapsedMillis == 0) {
return now >= latestRunTimeElapsedMillis ? 1 : 0;
} else if (latestRunTimeElapsedMillis == Long.MAX_VALUE) {
return now >= earliestRunTimeElapsedMillis ? 1 : 0;
} else {
if (now <= earliestRunTimeElapsedMillis) {
return 0;
} else if (now >= latestRunTimeElapsedMillis) {
return 1;
} else {
return (float) (now - earliestRunTimeElapsedMillis)
/ (float) (latestRunTimeElapsedMillis - earliestRunTimeElapsedMillis);
}
}
}
public Pair<Long, Long> getPersistedUtcTimes() {
return mPersistedUtcTimes;
}
public void clearPersistedUtcTimes() {
mPersistedUtcTimes = null;
}
/** @return true if the constraint was changed, false otherwise. */
boolean setChargingConstraintSatisfied(boolean state) {
return setConstraintSatisfied(CONSTRAINT_CHARGING, state);
}
/** @return true if the constraint was changed, false otherwise. */
boolean setBatteryNotLowConstraintSatisfied(boolean state) {
return setConstraintSatisfied(CONSTRAINT_BATTERY_NOT_LOW, state);
}
/** @return true if the constraint was changed, false otherwise. */
boolean setStorageNotLowConstraintSatisfied(boolean state) {
return setConstraintSatisfied(CONSTRAINT_STORAGE_NOT_LOW, state);
}
/** @return true if the constraint was changed, false otherwise. */
boolean setTimingDelayConstraintSatisfied(boolean state) {
return setConstraintSatisfied(CONSTRAINT_TIMING_DELAY, state);
}
/** @return true if the constraint was changed, false otherwise. */
boolean setDeadlineConstraintSatisfied(boolean state) {
if (setConstraintSatisfied(CONSTRAINT_DEADLINE, state)) {
// The constraint was changed. Update the ready flag.
mReadyDeadlineSatisfied = !job.isPeriodic() && hasDeadlineConstraint() && state;
return true;
}
return false;
}
/** @return true if the constraint was changed, false otherwise. */
boolean setIdleConstraintSatisfied(boolean state) {
return setConstraintSatisfied(CONSTRAINT_IDLE, state);
}
/** @return true if the constraint was changed, false otherwise. */
boolean setConnectivityConstraintSatisfied(boolean state) {
return setConstraintSatisfied(CONSTRAINT_CONNECTIVITY, state);
}
/** @return true if the constraint was changed, false otherwise. */
boolean setContentTriggerConstraintSatisfied(boolean state) {
return setConstraintSatisfied(CONSTRAINT_CONTENT_TRIGGER, state);
}
/** @return true if the constraint was changed, false otherwise. */
boolean setDeviceNotDozingConstraintSatisfied(boolean state, boolean whitelisted) {
dozeWhitelisted = whitelisted;
if (setConstraintSatisfied(CONSTRAINT_DEVICE_NOT_DOZING, state)) {
// The constraint was changed. Update the ready flag.
mReadyNotDozing = state || (job.getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0;
return true;
}
return false;
}
/** @return true if the constraint was changed, false otherwise. */
boolean setBackgroundNotRestrictedConstraintSatisfied(boolean state) {
if (setConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED, state)) {
// The constraint was changed. Update the ready flag.
mReadyNotRestrictedInBg = state;
return true;
}
return false;
}
/** @return true if the constraint was changed, false otherwise. */
boolean setQuotaConstraintSatisfied(boolean state) {
if (setConstraintSatisfied(CONSTRAINT_WITHIN_QUOTA, state)) {
// The constraint was changed. Update the ready flag.
mReadyWithinQuota = state;
return true;
}
return false;
}
/** @return true if the state was changed, false otherwise. */
boolean setUidActive(final boolean newActiveState) {
if (newActiveState != uidActive) {
uidActive = newActiveState;
return true;
}
return false; /* unchanged */
}
/** @return true if the constraint was changed, false otherwise. */
boolean setConstraintSatisfied(int constraint, boolean state) {
boolean old = (satisfiedConstraints&constraint) != 0;
if (old == state) {
return false;
}
satisfiedConstraints = (satisfiedConstraints&~constraint) | (state ? constraint : 0);
mSatisfiedConstraintsOfInterest = satisfiedConstraints & CONSTRAINTS_OF_INTEREST;
if (STATS_LOG_ENABLED && (STATSD_CONSTRAINTS_TO_LOG & constraint) != 0) {
StatsLog.write_non_chained(StatsLog.SCHEDULED_JOB_CONSTRAINT_CHANGED,
sourceUid, null, getBatteryName(), getProtoConstraint(constraint),
state ? StatsLog.SCHEDULED_JOB_CONSTRAINT_CHANGED__STATE__SATISFIED
: StatsLog.SCHEDULED_JOB_CONSTRAINT_CHANGED__STATE__UNSATISFIED);
}
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 long getLastSuccessfulRunTime() {
return mLastSuccessfulRunTime;
}
public long getLastFailedRunTime() {
return mLastFailedRunTime;
}
/**
* @return Whether or not this job is ready to run, based on its requirements.
*/
public boolean isReady() {
return isReady(mSatisfiedConstraintsOfInterest);
}
/**
* @return Whether or not this job would be ready to run if it had the specified constraint
* granted, based on its requirements.
*/
boolean wouldBeReadyWithConstraint(int constraint) {
boolean oldValue = false;
int satisfied = mSatisfiedConstraintsOfInterest;
switch (constraint) {
case CONSTRAINT_BACKGROUND_NOT_RESTRICTED:
oldValue = mReadyNotRestrictedInBg;
mReadyNotRestrictedInBg = true;
break;
case CONSTRAINT_DEADLINE:
oldValue = mReadyDeadlineSatisfied;
mReadyDeadlineSatisfied = true;
break;
case CONSTRAINT_DEVICE_NOT_DOZING:
oldValue = mReadyNotDozing;
mReadyNotDozing = true;
break;
case CONSTRAINT_WITHIN_QUOTA:
oldValue = mReadyWithinQuota;
mReadyWithinQuota = true;
break;
default:
satisfied |= constraint;
break;
}
boolean toReturn = isReady(satisfied);
switch (constraint) {
case CONSTRAINT_BACKGROUND_NOT_RESTRICTED:
mReadyNotRestrictedInBg = oldValue;
break;
case CONSTRAINT_DEADLINE:
mReadyDeadlineSatisfied = oldValue;
break;
case CONSTRAINT_DEVICE_NOT_DOZING:
mReadyNotDozing = oldValue;
break;
case CONSTRAINT_WITHIN_QUOTA:
mReadyWithinQuota = oldValue;
break;
}
return toReturn;
}
private boolean isReady(int satisfiedConstraints) {
// Quota constraints trumps all other constraints.
if (!mReadyWithinQuota) {
return false;
}
// Deadline constraint trumps other constraints besides quota (except for periodic jobs
// where deadline is an implementation detail. A periodic job should only run if its
// constraints are satisfied).
// DeviceNotDozing implicit constraint must be satisfied
// NotRestrictedInBackground implicit constraint must be satisfied
return mReadyNotDozing && mReadyNotRestrictedInBg && (mReadyDeadlineSatisfied
|| isConstraintsSatisfied(satisfiedConstraints));
}
static final int CONSTRAINTS_OF_INTEREST = CONSTRAINT_CHARGING | CONSTRAINT_BATTERY_NOT_LOW
| CONSTRAINT_STORAGE_NOT_LOW | CONSTRAINT_TIMING_DELAY | CONSTRAINT_CONNECTIVITY
| 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() {
return isConstraintsSatisfied(mSatisfiedConstraintsOfInterest);
}
private boolean isConstraintsSatisfied(int satisfiedConstraints) {
if (overrideState == OVERRIDE_FULL) {
// force override: the job is always runnable
return true;
}
int sat = satisfiedConstraints;
if (overrideState == OVERRIDE_SOFT) {
// override: pretend all 'soft' requirements are satisfied
sat |= (requiredConstraints & SOFT_OVERRIDE_CONSTRAINTS);
}
return (sat & mRequiredConstraintsOfInterest) == mRequiredConstraintsOfInterest;
}
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 = sElapsedRealtimeClock.millis();
sb.append(" TIME=");
formatRunTime(sb, earliestRunTimeElapsedMillis, NO_EARLIEST_RUNTIME, now);
sb.append(":");
formatRunTime(sb, latestRunTimeElapsedMillis, NO_LATEST_RUNTIME, now);
}
if (job.getRequiredNetwork() != null) {
sb.append(" NET");
}
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.isPeriodic()) {
sb.append(" PERIODIC");
}
if (job.isPersisted()) {
sb.append(" PERSISTED");
}
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();
}
/**
* Convenience function to dump data that identifies a job uniquely to proto. This is intended
* to mimic {@link #toShortString}.
*/
public void writeToShortProto(ProtoOutputStream proto, long fieldId) {
final long token = proto.start(fieldId);
proto.write(JobStatusShortInfoProto.CALLING_UID, callingUid);
proto.write(JobStatusShortInfoProto.JOB_ID, job.getId());
proto.write(JobStatusShortInfoProto.BATTERY_NAME, batteryName);
proto.end(token);
}
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_CONTENT_TRIGGER) != 0) {
pw.print(" CONTENT_TRIGGER");
}
if ((constraints&CONSTRAINT_DEVICE_NOT_DOZING) != 0) {
pw.print(" DEVICE_NOT_DOZING");
}
if ((constraints&CONSTRAINT_BACKGROUND_NOT_RESTRICTED) != 0) {
pw.print(" BACKGROUND_NOT_RESTRICTED");
}
if ((constraints & CONSTRAINT_WITHIN_QUOTA) != 0) {
pw.print(" WITHIN_QUOTA");
}
if (constraints != 0) {
pw.print(" [0x");
pw.print(Integer.toHexString(constraints));
pw.print("]");
}
}
/** Returns a {@link JobServerProtoEnums.Constraint} enum value for the given constraint. */
private int getProtoConstraint(int constraint) {
switch (constraint) {
case CONSTRAINT_BACKGROUND_NOT_RESTRICTED:
return JobServerProtoEnums.CONSTRAINT_BACKGROUND_NOT_RESTRICTED;
case CONSTRAINT_BATTERY_NOT_LOW:
return JobServerProtoEnums.CONSTRAINT_BATTERY_NOT_LOW;
case CONSTRAINT_CHARGING:
return JobServerProtoEnums.CONSTRAINT_CHARGING;
case CONSTRAINT_CONNECTIVITY:
return JobServerProtoEnums.CONSTRAINT_CONNECTIVITY;
case CONSTRAINT_CONTENT_TRIGGER:
return JobServerProtoEnums.CONSTRAINT_CONTENT_TRIGGER;
case CONSTRAINT_DEADLINE:
return JobServerProtoEnums.CONSTRAINT_DEADLINE;
case CONSTRAINT_DEVICE_NOT_DOZING:
return JobServerProtoEnums.CONSTRAINT_DEVICE_NOT_DOZING;
case CONSTRAINT_IDLE:
return JobServerProtoEnums.CONSTRAINT_IDLE;
case CONSTRAINT_STORAGE_NOT_LOW:
return JobServerProtoEnums.CONSTRAINT_STORAGE_NOT_LOW;
case CONSTRAINT_TIMING_DELAY:
return JobServerProtoEnums.CONSTRAINT_TIMING_DELAY;
case CONSTRAINT_WITHIN_QUOTA:
return JobServerProtoEnums.CONSTRAINT_WITHIN_QUOTA;
default:
return JobServerProtoEnums.CONSTRAINT_UNKNOWN;
}
}
/** Writes constraints to the given repeating proto field. */
void dumpConstraints(ProtoOutputStream proto, long fieldId, int constraints) {
if ((constraints & CONSTRAINT_CHARGING) != 0) {
proto.write(fieldId, JobServerProtoEnums.CONSTRAINT_CHARGING);
}
if ((constraints & CONSTRAINT_BATTERY_NOT_LOW) != 0) {
proto.write(fieldId, JobServerProtoEnums.CONSTRAINT_BATTERY_NOT_LOW);
}
if ((constraints & CONSTRAINT_STORAGE_NOT_LOW) != 0) {
proto.write(fieldId, JobServerProtoEnums.CONSTRAINT_STORAGE_NOT_LOW);
}
if ((constraints & CONSTRAINT_TIMING_DELAY) != 0) {
proto.write(fieldId, JobServerProtoEnums.CONSTRAINT_TIMING_DELAY);
}
if ((constraints & CONSTRAINT_DEADLINE) != 0) {
proto.write(fieldId, JobServerProtoEnums.CONSTRAINT_DEADLINE);
}
if ((constraints & CONSTRAINT_IDLE) != 0) {
proto.write(fieldId, JobServerProtoEnums.CONSTRAINT_IDLE);
}
if ((constraints & CONSTRAINT_CONNECTIVITY) != 0) {
proto.write(fieldId, JobServerProtoEnums.CONSTRAINT_CONNECTIVITY);
}
if ((constraints & CONSTRAINT_CONTENT_TRIGGER) != 0) {
proto.write(fieldId, JobServerProtoEnums.CONSTRAINT_CONTENT_TRIGGER);
}
if ((constraints & CONSTRAINT_DEVICE_NOT_DOZING) != 0) {
proto.write(fieldId, JobServerProtoEnums.CONSTRAINT_DEVICE_NOT_DOZING);
}
if ((constraints & CONSTRAINT_WITHIN_QUOTA) != 0) {
proto.write(fieldId, JobServerProtoEnums.CONSTRAINT_WITHIN_QUOTA);
}
if ((constraints & CONSTRAINT_BACKGROUND_NOT_RESTRICTED) != 0) {
proto.write(fieldId, JobServerProtoEnums.CONSTRAINT_BACKGROUND_NOT_RESTRICTED);
}
}
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 + " ");
}
}
private void dumpJobWorkItem(ProtoOutputStream proto, long fieldId, JobWorkItem work) {
final long token = proto.start(fieldId);
proto.write(JobStatusDumpProto.JobWorkItem.WORK_ID, work.getWorkId());
proto.write(JobStatusDumpProto.JobWorkItem.DELIVERY_COUNT, work.getDeliveryCount());
if (work.getIntent() != null) {
work.getIntent().writeToProto(proto, JobStatusDumpProto.JobWorkItem.INTENT);
}
Object grants = work.getGrants();
if (grants != null) {
((GrantedUriPermissions) grants).dump(proto, JobStatusDumpProto.JobWorkItem.URI_GRANTS);
}
proto.end(token);
}
/**
* Returns a bucket name based on the normalized bucket indices, not the AppStandby constants.
*/
String getBucketName() {
return bucketName(standbyBucket);
}
/**
* Returns a bucket name based on the normalized bucket indices, not the AppStandby constants.
*/
static String bucketName(int standbyBucket) {
switch (standbyBucket) {
case 0: return "ACTIVE";
case 1: return "WORKING_SET";
case 2: return "FREQUENT";
case 3: return "RARE";
case 4: return "NEVER";
default:
return "Unknown: " + standbyBucket;
}
}
// 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(JobInfo.getPriorityString(job.getPriority()));
}
if (job.getFlags() != 0) {
pw.print(prefix); pw.print(" Flags: ");
pw.println(Integer.toHexString(job.getFlags()));
}
if (getInternalFlags() != 0) {
pw.print(prefix); pw.print(" Internal flags: ");
pw.print(Integer.toHexString(getInternalFlags()));
if ((getInternalFlags()&INTERNAL_FLAG_HAS_FOREGROUND_EXEMPTION) != 0) {
pw.print(" HAS_FOREGROUND_EXEMPTION");
}
pw.println();
}
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.getRequiredNetwork() != null) {
pw.print(prefix); pw.print(" Network type: ");
pw.println(job.getRequiredNetwork());
}
if (mTotalNetworkDownloadBytes != JobInfo.NETWORK_BYTES_UNKNOWN) {
pw.print(prefix); pw.print(" Network download bytes: ");
pw.println(mTotalNetworkDownloadBytes);
}
if (mTotalNetworkUploadBytes != JobInfo.NETWORK_BYTES_UNKNOWN) {
pw.print(prefix); pw.print(" Network upload bytes: ");
pw.println(mTotalNetworkUploadBytes);
}
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 | CONSTRAINT_WITHIN_QUOTA) & ~satisfiedConstraints));
pw.println();
if (dozeWhitelisted) {
pw.print(prefix); pw.println("Doze whitelisted: true");
}
if (uidActive) {
pw.print(prefix); pw.println("Uid: active");
}
if (job.isExemptedFromAppStandby()) {
pw.print(prefix); pw.println("Is exempted from app standby");
}
}
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");
if ((trackingControllers & TRACKING_QUOTA) != 0) pw.print(" QUOTA");
pw.println();
}
pw.print(prefix); pw.println("Implicit constraints:");
pw.print(prefix); pw.print(" readyNotDozing: ");
pw.println(mReadyNotDozing);
pw.print(prefix); pw.print(" readyNotRestrictedInBg: ");
pw.println(mReadyNotRestrictedInBg);
if (!job.isPeriodic() && hasDeadlineConstraint()) {
pw.print(prefix); pw.print(" readyDeadlineSatisfied: ");
pw.println(mReadyDeadlineSatisfied);
}
if (changedAuthorities != null) {
pw.print(prefix); pw.println("Changed authorities:");
for (int i=0; i<changedAuthorities.size(); i++) {
pw.print(prefix); pw.print(" "); pw.println(changedAuthorities.valueAt(i));
}
if (changedUris != null) {
pw.print(prefix); pw.println("Changed URIs:");
for (int i=0; i<changedUris.size(); i++) {
pw.print(prefix); pw.print(" "); pw.println(changedUris.valueAt(i));
}
}
}
if (network != null) {
pw.print(prefix); pw.print("Network: "); pw.println(network);
}
if (pendingWork != null && pendingWork.size() > 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("Standby bucket: ");
pw.println(getBucketName());
if (standbyBucket > 0) {
pw.print(prefix); pw.print("Base heartbeat: ");
pw.println(baseHeartbeat);
}
if (whenStandbyDeferred != 0) {
pw.print(prefix); pw.print(" Deferred since: ");
TimeUtils.formatDuration(whenStandbyDeferred, elapsedRealtimeMillis, pw);
pw.println();
}
if (mFirstForceBatchedTimeElapsed != 0) {
pw.print(prefix);
pw.print(" Time since first force batch attempt: ");
TimeUtils.formatDuration(mFirstForceBatchedTimeElapsed, elapsedRealtimeMillis, pw);
pw.println();
}
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.print(", original latest=");
formatRunTime(pw, mOriginalLatestRunTimeElapsedMillis,
NO_LATEST_RUNTIME, elapsedRealtimeMillis);
pw.println();
if (numFailures != 0) {
pw.print(prefix); pw.print("Num failures: "); pw.println(numFailures);
}
if (mLastSuccessfulRunTime != 0) {
pw.print(prefix); pw.print("Last successful run: ");
pw.println(TimeMigrationUtils.formatMillisWithFixedFormat(mLastSuccessfulRunTime));
}
if (mLastFailedRunTime != 0) {
pw.print(prefix); pw.print("Last failed run: ");
pw.println(TimeMigrationUtils.formatMillisWithFixedFormat(mLastFailedRunTime));
}
}
public void dump(ProtoOutputStream proto, long fieldId, boolean full, long elapsedRealtimeMillis) {
final long token = proto.start(fieldId);
proto.write(JobStatusDumpProto.CALLING_UID, callingUid);
proto.write(JobStatusDumpProto.TAG, tag);
proto.write(JobStatusDumpProto.SOURCE_UID, getSourceUid());
proto.write(JobStatusDumpProto.SOURCE_USER_ID, getSourceUserId());
proto.write(JobStatusDumpProto.SOURCE_PACKAGE_NAME, getSourcePackageName());
proto.write(JobStatusDumpProto.INTERNAL_FLAGS, getInternalFlags());
if (full) {
final long jiToken = proto.start(JobStatusDumpProto.JOB_INFO);
job.getService().writeToProto(proto, JobStatusDumpProto.JobInfo.SERVICE);
proto.write(JobStatusDumpProto.JobInfo.IS_PERIODIC, job.isPeriodic());
proto.write(JobStatusDumpProto.JobInfo.PERIOD_INTERVAL_MS, job.getIntervalMillis());
proto.write(JobStatusDumpProto.JobInfo.PERIOD_FLEX_MS, job.getFlexMillis());
proto.write(JobStatusDumpProto.JobInfo.IS_PERSISTED, job.isPersisted());
proto.write(JobStatusDumpProto.JobInfo.PRIORITY, job.getPriority());
proto.write(JobStatusDumpProto.JobInfo.FLAGS, job.getFlags());
proto.write(JobStatusDumpProto.JobInfo.REQUIRES_CHARGING, job.isRequireCharging());
proto.write(JobStatusDumpProto.JobInfo.REQUIRES_BATTERY_NOT_LOW, job.isRequireBatteryNotLow());
proto.write(JobStatusDumpProto.JobInfo.REQUIRES_DEVICE_IDLE, job.isRequireDeviceIdle());
if (job.getTriggerContentUris() != null) {
for (int i = 0; i < job.getTriggerContentUris().length; i++) {
final long tcuToken = proto.start(JobStatusDumpProto.JobInfo.TRIGGER_CONTENT_URIS);
JobInfo.TriggerContentUri trig = job.getTriggerContentUris()[i];
proto.write(JobStatusDumpProto.JobInfo.TriggerContentUri.FLAGS, trig.getFlags());
Uri u = trig.getUri();
if (u != null) {
proto.write(JobStatusDumpProto.JobInfo.TriggerContentUri.URI, u.toString());
}
proto.end(tcuToken);
}
if (job.getTriggerContentUpdateDelay() >= 0) {
proto.write(JobStatusDumpProto.JobInfo.TRIGGER_CONTENT_UPDATE_DELAY_MS,
job.getTriggerContentUpdateDelay());
}
if (job.getTriggerContentMaxDelay() >= 0) {
proto.write(JobStatusDumpProto.JobInfo.TRIGGER_CONTENT_MAX_DELAY_MS,
job.getTriggerContentMaxDelay());
}
}
if (job.getExtras() != null && !job.getExtras().maybeIsEmpty()) {
job.getExtras().writeToProto(proto, JobStatusDumpProto.JobInfo.EXTRAS);
}
if (job.getTransientExtras() != null && !job.getTransientExtras().maybeIsEmpty()) {
job.getTransientExtras().writeToProto(proto, JobStatusDumpProto.JobInfo.TRANSIENT_EXTRAS);
}
if (job.getClipData() != null) {
job.getClipData().writeToProto(proto, JobStatusDumpProto.JobInfo.CLIP_DATA);
}
if (uriPerms != null) {
uriPerms.dump(proto, JobStatusDumpProto.JobInfo.GRANTED_URI_PERMISSIONS);
}
if (job.getRequiredNetwork() != null) {
job.getRequiredNetwork().writeToProto(proto, JobStatusDumpProto.JobInfo.REQUIRED_NETWORK);
}
if (mTotalNetworkDownloadBytes != JobInfo.NETWORK_BYTES_UNKNOWN) {
proto.write(JobStatusDumpProto.JobInfo.TOTAL_NETWORK_DOWNLOAD_BYTES,
mTotalNetworkDownloadBytes);
}
if (mTotalNetworkUploadBytes != JobInfo.NETWORK_BYTES_UNKNOWN) {
proto.write(JobStatusDumpProto.JobInfo.TOTAL_NETWORK_UPLOAD_BYTES,
mTotalNetworkUploadBytes);
}
proto.write(JobStatusDumpProto.JobInfo.MIN_LATENCY_MS, job.getMinLatencyMillis());
proto.write(JobStatusDumpProto.JobInfo.MAX_EXECUTION_DELAY_MS, job.getMaxExecutionDelayMillis());
final long bpToken = proto.start(JobStatusDumpProto.JobInfo.BACKOFF_POLICY);
proto.write(JobStatusDumpProto.JobInfo.Backoff.POLICY, job.getBackoffPolicy());
proto.write(JobStatusDumpProto.JobInfo.Backoff.INITIAL_BACKOFF_MS,
job.getInitialBackoffMillis());
proto.end(bpToken);
proto.write(JobStatusDumpProto.JobInfo.HAS_EARLY_CONSTRAINT, job.hasEarlyConstraint());
proto.write(JobStatusDumpProto.JobInfo.HAS_LATE_CONSTRAINT, job.hasLateConstraint());
proto.end(jiToken);
}
dumpConstraints(proto, JobStatusDumpProto.REQUIRED_CONSTRAINTS, requiredConstraints);
if (full) {
dumpConstraints(proto, JobStatusDumpProto.SATISFIED_CONSTRAINTS, satisfiedConstraints);
dumpConstraints(proto, JobStatusDumpProto.UNSATISFIED_CONSTRAINTS,
((requiredConstraints | CONSTRAINT_WITHIN_QUOTA) & ~satisfiedConstraints));
proto.write(JobStatusDumpProto.IS_DOZE_WHITELISTED, dozeWhitelisted);
proto.write(JobStatusDumpProto.IS_UID_ACTIVE, uidActive);
proto.write(JobStatusDumpProto.IS_EXEMPTED_FROM_APP_STANDBY,
job.isExemptedFromAppStandby());
}
// Tracking controllers
if ((trackingControllers&TRACKING_BATTERY) != 0) {
proto.write(JobStatusDumpProto.TRACKING_CONTROLLERS,
JobStatusDumpProto.TRACKING_BATTERY);
}
if ((trackingControllers&TRACKING_CONNECTIVITY) != 0) {
proto.write(JobStatusDumpProto.TRACKING_CONTROLLERS,
JobStatusDumpProto.TRACKING_CONNECTIVITY);
}
if ((trackingControllers&TRACKING_CONTENT) != 0) {
proto.write(JobStatusDumpProto.TRACKING_CONTROLLERS,
JobStatusDumpProto.TRACKING_CONTENT);
}
if ((trackingControllers&TRACKING_IDLE) != 0) {
proto.write(JobStatusDumpProto.TRACKING_CONTROLLERS,
JobStatusDumpProto.TRACKING_IDLE);
}
if ((trackingControllers&TRACKING_STORAGE) != 0) {
proto.write(JobStatusDumpProto.TRACKING_CONTROLLERS,
JobStatusDumpProto.TRACKING_STORAGE);
}
if ((trackingControllers&TRACKING_TIME) != 0) {
proto.write(JobStatusDumpProto.TRACKING_CONTROLLERS,
JobStatusDumpProto.TRACKING_TIME);
}
if ((trackingControllers & TRACKING_QUOTA) != 0) {
proto.write(JobStatusDumpProto.TRACKING_CONTROLLERS,
JobStatusDumpProto.TRACKING_QUOTA);
}
// Implicit constraints
final long icToken = proto.start(JobStatusDumpProto.IMPLICIT_CONSTRAINTS);
proto.write(JobStatusDumpProto.ImplicitConstraints.IS_NOT_DOZING, mReadyNotDozing);
proto.write(JobStatusDumpProto.ImplicitConstraints.IS_NOT_RESTRICTED_IN_BG,
mReadyNotRestrictedInBg);
proto.end(icToken);
if (changedAuthorities != null) {
for (int k = 0; k < changedAuthorities.size(); k++) {
proto.write(JobStatusDumpProto.CHANGED_AUTHORITIES, changedAuthorities.valueAt(k));
}
}
if (changedUris != null) {
for (int i = 0; i < changedUris.size(); i++) {
Uri u = changedUris.valueAt(i);
proto.write(JobStatusDumpProto.CHANGED_URIS, u.toString());
}
}
if (network != null) {
network.writeToProto(proto, JobStatusDumpProto.NETWORK);
}
if (pendingWork != null && pendingWork.size() > 0) {
for (int i = 0; i < pendingWork.size(); i++) {
dumpJobWorkItem(proto, JobStatusDumpProto.PENDING_WORK, pendingWork.get(i));
}
}
if (executingWork != null && executingWork.size() > 0) {
for (int i = 0; i < executingWork.size(); i++) {
dumpJobWorkItem(proto, JobStatusDumpProto.EXECUTING_WORK, executingWork.get(i));
}
}
proto.write(JobStatusDumpProto.STANDBY_BUCKET, standbyBucket);
proto.write(JobStatusDumpProto.ENQUEUE_DURATION_MS, elapsedRealtimeMillis - enqueueTime);
proto.write(JobStatusDumpProto.TIME_SINCE_FIRST_DEFERRAL_MS,
whenStandbyDeferred == 0 ? 0 : elapsedRealtimeMillis - whenStandbyDeferred);
proto.write(JobStatusDumpProto.TIME_SINCE_FIRST_FORCE_BATCH_ATTEMPT_MS,
mFirstForceBatchedTimeElapsed == 0
? 0 : elapsedRealtimeMillis - mFirstForceBatchedTimeElapsed);
if (earliestRunTimeElapsedMillis == NO_EARLIEST_RUNTIME) {
proto.write(JobStatusDumpProto.TIME_UNTIL_EARLIEST_RUNTIME_MS, 0);
} else {
proto.write(JobStatusDumpProto.TIME_UNTIL_EARLIEST_RUNTIME_MS,
earliestRunTimeElapsedMillis - elapsedRealtimeMillis);
}
if (latestRunTimeElapsedMillis == NO_LATEST_RUNTIME) {
proto.write(JobStatusDumpProto.TIME_UNTIL_LATEST_RUNTIME_MS, 0);
} else {
proto.write(JobStatusDumpProto.TIME_UNTIL_LATEST_RUNTIME_MS,
latestRunTimeElapsedMillis - elapsedRealtimeMillis);
}
proto.write(JobStatusDumpProto.NUM_FAILURES, numFailures);
proto.write(JobStatusDumpProto.LAST_SUCCESSFUL_RUN_TIME, mLastSuccessfulRunTime);
proto.write(JobStatusDumpProto.LAST_FAILED_RUN_TIME, mLastFailedRunTime);
proto.end(token);
}
}