Add an API to query pending job reason.
With Data Transfer jobs, an app may want to tell the user why the transfer isn't running. This adds a simple API that lets the app query why a job may isn't running. It will only return one reason even if there are multiple reasons the job isn't running. Bug: 255371817 Test: atest CtsJobSchedulerTestCases:JobSchedulingTest Change-Id: Ie86079c63a94312007406a43c4883e18d39a00ab
This commit is contained in:
@@ -36,7 +36,7 @@ import java.util.List;
|
||||
* Note android.app.job is the better package to put this class, but we can't move it there
|
||||
* because that'd break robolectric. Grr.
|
||||
*
|
||||
* @hide
|
||||
* @hide
|
||||
*/
|
||||
public class JobSchedulerImpl extends JobScheduler {
|
||||
IJobScheduler mBinder;
|
||||
@@ -108,6 +108,15 @@ public class JobSchedulerImpl extends JobScheduler {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPendingJobReason(int jobId) {
|
||||
try {
|
||||
return mBinder.getPendingJobReason(jobId);
|
||||
} catch (RemoteException e) {
|
||||
return PENDING_JOB_REASON_UNDEFINED;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canRunLongJobs() {
|
||||
try {
|
||||
|
||||
@@ -33,6 +33,7 @@ interface IJobScheduler {
|
||||
void cancelAll();
|
||||
ParceledListSlice getAllPendingJobs();
|
||||
JobInfo getPendingJob(int jobId);
|
||||
int getPendingJobReason(int jobId);
|
||||
boolean canRunLongJobs(String packageName);
|
||||
boolean hasRunLongJobsPermission(String packageName, int userId);
|
||||
List<JobInfo> getStartedJobs();
|
||||
|
||||
@@ -23,10 +23,14 @@ import android.annotation.RequiresPermission;
|
||||
import android.annotation.SystemApi;
|
||||
import android.annotation.SystemService;
|
||||
import android.annotation.UserIdInt;
|
||||
import android.app.ActivityManager;
|
||||
import android.app.usage.UsageStatsManager;
|
||||
import android.compat.annotation.ChangeId;
|
||||
import android.compat.annotation.EnabledAfter;
|
||||
import android.content.ClipData;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.NetworkRequest;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.PersistableBundle;
|
||||
@@ -133,6 +137,132 @@ public abstract class JobScheduler {
|
||||
*/
|
||||
public static final int RESULT_SUCCESS = 1;
|
||||
|
||||
/** The job doesn't exist. */
|
||||
public static final int PENDING_JOB_REASON_INVALID_JOB_ID = -2;
|
||||
/** The job is currently running and is therefore not pending. */
|
||||
public static final int PENDING_JOB_REASON_EXECUTING = -1;
|
||||
/**
|
||||
* There is no known reason why the job is pending.
|
||||
* If additional reasons are added on newer Android versions, the system may return this reason
|
||||
* to apps whose target SDK is not high enough to expect that reason.
|
||||
*/
|
||||
public static final int PENDING_JOB_REASON_UNDEFINED = 0;
|
||||
/**
|
||||
* The app is in a state that prevents the job from running
|
||||
* (eg. the {@link JobService} component is disabled).
|
||||
*/
|
||||
public static final int PENDING_JOB_REASON_APP = 1;
|
||||
/**
|
||||
* The current standby bucket prevents the job from running.
|
||||
*
|
||||
* @see UsageStatsManager#STANDBY_BUCKET_RESTRICTED
|
||||
*/
|
||||
public static final int PENDING_JOB_REASON_APP_STANDBY = 2;
|
||||
/**
|
||||
* The app is restricted from running in the background.
|
||||
*
|
||||
* @see ActivityManager#isBackgroundRestricted()
|
||||
* @see PackageManager#isInstantApp()
|
||||
*/
|
||||
public static final int PENDING_JOB_REASON_BACKGROUND_RESTRICTION = 3;
|
||||
/**
|
||||
* The requested battery-not-low constraint is not satisfied.
|
||||
*
|
||||
* @see JobInfo.Builder#setRequiresBatteryNotLow(boolean)
|
||||
*/
|
||||
public static final int PENDING_JOB_REASON_CONSTRAINT_BATTERY_NOT_LOW = 4;
|
||||
/**
|
||||
* The requested charging constraint is not satisfied.
|
||||
*
|
||||
* @see JobInfo.Builder#setRequiresCharging(boolean)
|
||||
*/
|
||||
public static final int PENDING_JOB_REASON_CONSTRAINT_CHARGING = 5;
|
||||
/**
|
||||
* The requested connectivity constraint is not satisfied.
|
||||
*
|
||||
* @see JobInfo.Builder#setRequiredNetwork(NetworkRequest)
|
||||
* @see JobInfo.Builder#setRequiredNetworkType(int)
|
||||
*/
|
||||
public static final int PENDING_JOB_REASON_CONSTRAINT_CONNECTIVITY = 6;
|
||||
/**
|
||||
* The requested content trigger constraint is not satisfied.
|
||||
*
|
||||
* @see JobInfo.Builder#addTriggerContentUri(JobInfo.TriggerContentUri)
|
||||
*/
|
||||
public static final int PENDING_JOB_REASON_CONSTRAINT_CONTENT_TRIGGER = 7;
|
||||
/**
|
||||
* The requested idle constraint is not satisfied.
|
||||
*
|
||||
* @see JobInfo.Builder#setRequiresDeviceIdle(boolean)
|
||||
*/
|
||||
public static final int PENDING_JOB_REASON_CONSTRAINT_DEVICE_IDLE = 8;
|
||||
/**
|
||||
* The minimum latency has not transpired.
|
||||
*
|
||||
* @see JobInfo.Builder#setMinimumLatency(long)
|
||||
*/
|
||||
public static final int PENDING_JOB_REASON_CONSTRAINT_MINIMUM_LATENCY = 9;
|
||||
/**
|
||||
* The system's estimate of when the app will be launched is far away enough to warrant delaying
|
||||
* this job.
|
||||
*
|
||||
* @see JobInfo#isPrefetch()
|
||||
* @see JobInfo.Builder#setPrefetch(boolean)
|
||||
*/
|
||||
public static final int PENDING_JOB_REASON_CONSTRAINT_PREFETCH = 10;
|
||||
/**
|
||||
* The requested storage-not-low constraint is not satisfied.
|
||||
*
|
||||
* @see JobInfo.Builder#setRequiresStorageNotLow(boolean)
|
||||
*/
|
||||
public static final int PENDING_JOB_REASON_CONSTRAINT_STORAGE_NOT_LOW = 11;
|
||||
/**
|
||||
* The job is being deferred due to the device state (eg. Doze, battery saver, memory usage,
|
||||
* thermal status, etc.).
|
||||
*/
|
||||
public static final int PENDING_JOB_REASON_DEVICE_STATE = 12;
|
||||
/**
|
||||
* JobScheduler thinks it can defer this job to a more optimal running time.
|
||||
*/
|
||||
public static final int PENDING_JOB_REASON_JOB_SCHEDULER_OPTIMIZATION = 13;
|
||||
/**
|
||||
* The app has consumed all of its current quota.
|
||||
*
|
||||
* @see UsageStatsManager#getAppStandbyBucket()
|
||||
* @see JobParameters#STOP_REASON_QUOTA
|
||||
*/
|
||||
public static final int PENDING_JOB_REASON_QUOTA = 14;
|
||||
/**
|
||||
* JobScheduler is respecting one of the user's actions (eg. force stop or adb shell commands)
|
||||
* to defer this job.
|
||||
*/
|
||||
public static final int PENDING_JOB_REASON_USER = 15;
|
||||
|
||||
/** @hide */
|
||||
@IntDef(prefix = {"PENDING_JOB_REASON_"}, value = {
|
||||
PENDING_JOB_REASON_UNDEFINED,
|
||||
PENDING_JOB_REASON_APP,
|
||||
PENDING_JOB_REASON_APP_STANDBY,
|
||||
PENDING_JOB_REASON_BACKGROUND_RESTRICTION,
|
||||
PENDING_JOB_REASON_CONSTRAINT_BATTERY_NOT_LOW,
|
||||
PENDING_JOB_REASON_CONSTRAINT_CHARGING,
|
||||
PENDING_JOB_REASON_CONSTRAINT_CONNECTIVITY,
|
||||
PENDING_JOB_REASON_CONSTRAINT_CONTENT_TRIGGER,
|
||||
PENDING_JOB_REASON_CONSTRAINT_DEVICE_IDLE,
|
||||
PENDING_JOB_REASON_CONSTRAINT_MINIMUM_LATENCY,
|
||||
PENDING_JOB_REASON_CONSTRAINT_PREFETCH,
|
||||
PENDING_JOB_REASON_CONSTRAINT_STORAGE_NOT_LOW,
|
||||
PENDING_JOB_REASON_DEVICE_STATE,
|
||||
PENDING_JOB_REASON_EXECUTING,
|
||||
PENDING_JOB_REASON_INVALID_JOB_ID,
|
||||
PENDING_JOB_REASON_JOB_SCHEDULER_OPTIMIZATION,
|
||||
PENDING_JOB_REASON_QUOTA,
|
||||
PENDING_JOB_REASON_USER,
|
||||
})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface PendingJobReason {
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule a job to be executed. Will replace any currently scheduled job with the same
|
||||
* ID with the new information in the {@link JobInfo}. If a job with the given ID is currently
|
||||
@@ -249,6 +379,15 @@ public abstract class JobScheduler {
|
||||
*/
|
||||
public abstract @Nullable JobInfo getPendingJob(int jobId);
|
||||
|
||||
/**
|
||||
* Returns a reason why the job is pending and not currently executing. If there are multiple
|
||||
* reasons why a job may be pending, this will only return one of them.
|
||||
*/
|
||||
@PendingJobReason
|
||||
public int getPendingJobReason(int jobId) {
|
||||
return PENDING_JOB_REASON_UNDEFINED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if the calling app currently holds the
|
||||
* {@link android.Manifest.permission#RUN_LONG_JOBS} permission, allowing it to run long jobs.
|
||||
|
||||
@@ -1175,7 +1175,7 @@ class JobConcurrencyManager {
|
||||
|
||||
if (jobStatus != null && !jsc.isWithinExecutionGuaranteeTime()
|
||||
&& restriction.isJobRestricted(jobStatus)) {
|
||||
jsc.cancelExecutingJobLocked(restriction.getReason(),
|
||||
jsc.cancelExecutingJobLocked(restriction.getStopReason(),
|
||||
restriction.getInternalReason(),
|
||||
JobParameters.getInternalReasonCodeDescription(
|
||||
restriction.getInternalReason()));
|
||||
@@ -1208,7 +1208,7 @@ class JobConcurrencyManager {
|
||||
final JobRestriction restriction = mService.checkIfRestricted(running);
|
||||
if (restriction != null) {
|
||||
final int internalReasonCode = restriction.getInternalReason();
|
||||
serviceContext.cancelExecutingJobLocked(restriction.getReason(),
|
||||
serviceContext.cancelExecutingJobLocked(restriction.getStopReason(),
|
||||
internalReasonCode,
|
||||
"restricted due to "
|
||||
+ JobParameters.getInternalReasonCodeDescription(
|
||||
@@ -1324,6 +1324,7 @@ class JobConcurrencyManager {
|
||||
mActivePkgStats.add(
|
||||
jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(),
|
||||
packageStats);
|
||||
mService.resetPendingJobReasonCache(jobStatus);
|
||||
}
|
||||
if (mService.getPendingJobQueue().remove(jobStatus)) {
|
||||
mService.mJobPackageTracker.noteNonpending(jobStatus);
|
||||
|
||||
@@ -363,6 +363,9 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
@GuardedBy("mLock")
|
||||
private final ArraySet<JobStatus> mChangedJobList = new ArraySet<>();
|
||||
|
||||
@GuardedBy("mPendingJobReasonCache") // Use its own lock to avoid blocking JS processing
|
||||
private final SparseArray<SparseIntArray> mPendingJobReasonCache = new SparseArray<>();
|
||||
|
||||
/**
|
||||
* Named indices into standby bucket arrays, for clarity in referring to
|
||||
* specific buckets' bookkeeping.
|
||||
@@ -1357,6 +1360,134 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
}
|
||||
}
|
||||
|
||||
@JobScheduler.PendingJobReason
|
||||
private int getPendingJobReason(int uid, int jobId) {
|
||||
int reason;
|
||||
// Some apps may attempt to query this frequently, so cache the reason under a separate lock
|
||||
// so that the rest of JS processing isn't negatively impacted.
|
||||
synchronized (mPendingJobReasonCache) {
|
||||
SparseIntArray jobIdToReason = mPendingJobReasonCache.get(uid);
|
||||
if (jobIdToReason != null) {
|
||||
reason = jobIdToReason.get(jobId, JobScheduler.PENDING_JOB_REASON_UNDEFINED);
|
||||
if (reason != JobScheduler.PENDING_JOB_REASON_UNDEFINED) {
|
||||
return reason;
|
||||
}
|
||||
}
|
||||
}
|
||||
synchronized (mLock) {
|
||||
reason = getPendingJobReasonLocked(uid, jobId);
|
||||
if (DEBUG) {
|
||||
Slog.v(TAG, "getPendingJobReason(" + uid + "," + jobId + ")=" + reason);
|
||||
}
|
||||
}
|
||||
synchronized (mPendingJobReasonCache) {
|
||||
SparseIntArray jobIdToReason = mPendingJobReasonCache.get(uid);
|
||||
if (jobIdToReason == null) {
|
||||
jobIdToReason = new SparseIntArray();
|
||||
mPendingJobReasonCache.put(uid, jobIdToReason);
|
||||
}
|
||||
jobIdToReason.put(jobId, reason);
|
||||
}
|
||||
return reason;
|
||||
}
|
||||
|
||||
@JobScheduler.PendingJobReason
|
||||
@GuardedBy("mLock")
|
||||
private int getPendingJobReasonLocked(int uid, int jobId) {
|
||||
// Very similar code to isReadyToBeExecutedLocked.
|
||||
|
||||
JobStatus job = mJobs.getJobByUidAndJobId(uid, jobId);
|
||||
if (job == null) {
|
||||
// Job doesn't exist.
|
||||
return JobScheduler.PENDING_JOB_REASON_INVALID_JOB_ID;
|
||||
}
|
||||
|
||||
if (isCurrentlyRunningLocked(job)) {
|
||||
return JobScheduler.PENDING_JOB_REASON_EXECUTING;
|
||||
}
|
||||
|
||||
final boolean jobReady = job.isReady();
|
||||
|
||||
if (DEBUG) {
|
||||
Slog.v(TAG, "getPendingJobReasonLocked: " + job.toShortString()
|
||||
+ " ready=" + jobReady);
|
||||
}
|
||||
|
||||
if (!jobReady) {
|
||||
return job.getPendingJobReason();
|
||||
}
|
||||
|
||||
final boolean userStarted = areUsersStartedLocked(job);
|
||||
|
||||
if (DEBUG) {
|
||||
Slog.v(TAG, "getPendingJobReasonLocked: " + job.toShortString()
|
||||
+ " userStarted=" + userStarted);
|
||||
}
|
||||
if (!userStarted) {
|
||||
return JobScheduler.PENDING_JOB_REASON_USER;
|
||||
}
|
||||
|
||||
final boolean backingUp = mBackingUpUids.get(job.getSourceUid());
|
||||
if (DEBUG) {
|
||||
Slog.v(TAG, "getPendingJobReasonLocked: " + job.toShortString()
|
||||
+ " backingUp=" + backingUp);
|
||||
}
|
||||
|
||||
if (backingUp) {
|
||||
// TODO: Should we make a special reason for this?
|
||||
return JobScheduler.PENDING_JOB_REASON_APP;
|
||||
}
|
||||
|
||||
JobRestriction restriction = checkIfRestricted(job);
|
||||
if (DEBUG) {
|
||||
Slog.v(TAG, "getPendingJobReasonLocked: " + job.toShortString()
|
||||
+ " restriction=" + restriction);
|
||||
}
|
||||
if (restriction != null) {
|
||||
return restriction.getPendingReason();
|
||||
}
|
||||
|
||||
// The following can be a little more expensive (especially jobActive, since we need to
|
||||
// go through the array of all potentially active jobs), so we are doing them
|
||||
// later... but still before checking with the package manager!
|
||||
final boolean jobPending = mPendingJobQueue.contains(job);
|
||||
|
||||
|
||||
if (DEBUG) {
|
||||
Slog.v(TAG, "getPendingJobReasonLocked: " + job.toShortString()
|
||||
+ " pending=" + jobPending);
|
||||
}
|
||||
|
||||
if (jobPending) {
|
||||
// We haven't started the job for some reason. Presumably, there are too many jobs
|
||||
// running.
|
||||
return JobScheduler.PENDING_JOB_REASON_DEVICE_STATE;
|
||||
}
|
||||
|
||||
final boolean jobActive = mConcurrencyManager.isJobRunningLocked(job);
|
||||
|
||||
if (DEBUG) {
|
||||
Slog.v(TAG, "getPendingJobReasonLocked: " + job.toShortString()
|
||||
+ " active=" + jobActive);
|
||||
}
|
||||
if (jobActive) {
|
||||
return JobScheduler.PENDING_JOB_REASON_UNDEFINED;
|
||||
}
|
||||
|
||||
// Validate that the defined package+service is still present & viable.
|
||||
final boolean componentUsable = isComponentUsable(job);
|
||||
|
||||
if (DEBUG) {
|
||||
Slog.v(TAG, "getPendingJobReasonLocked: " + job.toShortString()
|
||||
+ " componentUsable=" + componentUsable);
|
||||
}
|
||||
if (!componentUsable) {
|
||||
return JobScheduler.PENDING_JOB_REASON_APP;
|
||||
}
|
||||
|
||||
return JobScheduler.PENDING_JOB_REASON_UNDEFINED;
|
||||
}
|
||||
|
||||
public JobInfo getPendingJob(int uid, int jobId) {
|
||||
synchronized (mLock) {
|
||||
ArraySet<JobStatus> jobs = mJobs.getJobsByUid(uid);
|
||||
@@ -1389,6 +1520,9 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
synchronized (mLock) {
|
||||
mJobs.removeJobsOfUnlistedUsers(umi.getUserIds());
|
||||
}
|
||||
synchronized (mPendingJobReasonCache) {
|
||||
mPendingJobReasonCache.clear();
|
||||
}
|
||||
}
|
||||
|
||||
private void cancelJobsForPackageAndUidLocked(String pkgName, int uid,
|
||||
@@ -1874,6 +2008,8 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
jobStatus.enqueueTime = sElapsedRealtimeClock.millis();
|
||||
final boolean update = lastJob != null;
|
||||
mJobs.add(jobStatus);
|
||||
// Clear potentially cached INVALID_JOB_ID reason.
|
||||
resetPendingJobReasonCache(jobStatus);
|
||||
if (mReadyToRock) {
|
||||
for (int i = 0; i < mControllers.size(); i++) {
|
||||
StateController controller = mControllers.get(i);
|
||||
@@ -1895,6 +2031,13 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
// Deal with any remaining work items in the old job.
|
||||
jobStatus.stopTrackingJobLocked(incomingJob);
|
||||
|
||||
synchronized (mPendingJobReasonCache) {
|
||||
SparseIntArray reasonCache = mPendingJobReasonCache.get(jobStatus.getUid());
|
||||
if (reasonCache != null) {
|
||||
reasonCache.delete(jobStatus.getJobId());
|
||||
}
|
||||
}
|
||||
|
||||
// Remove from store as well as controllers.
|
||||
final boolean removed = mJobs.remove(jobStatus, removeFromPersisted);
|
||||
if (!removed) {
|
||||
@@ -1917,6 +2060,16 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
return removed;
|
||||
}
|
||||
|
||||
/** Remove the pending job reason for this job from the cache. */
|
||||
void resetPendingJobReasonCache(@NonNull JobStatus jobStatus) {
|
||||
synchronized (mPendingJobReasonCache) {
|
||||
final SparseIntArray reasons = mPendingJobReasonCache.get(jobStatus.getUid());
|
||||
if (reasons != null) {
|
||||
reasons.delete(jobStatus.getJobId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Return {@code true} if the specified job is currently executing. */
|
||||
@GuardedBy("mLock")
|
||||
public boolean isCurrentlyRunningLocked(JobStatus job) {
|
||||
@@ -2210,11 +2363,20 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
public void onControllerStateChanged(@Nullable ArraySet<JobStatus> changedJobs) {
|
||||
if (changedJobs == null) {
|
||||
mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
|
||||
synchronized (mPendingJobReasonCache) {
|
||||
mPendingJobReasonCache.clear();
|
||||
}
|
||||
} else if (changedJobs.size() > 0) {
|
||||
synchronized (mLock) {
|
||||
mChangedJobList.addAll(changedJobs);
|
||||
}
|
||||
mHandler.obtainMessage(MSG_CHECK_CHANGED_JOB_LIST).sendToTarget();
|
||||
synchronized (mPendingJobReasonCache) {
|
||||
for (int i = changedJobs.size() - 1; i >= 0; --i) {
|
||||
final JobStatus job = changedJobs.valueAt(i);
|
||||
resetPendingJobReasonCache(job);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2568,6 +2730,23 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "maybeQueueReadyJobsForExecutionLocked: Not running anything.");
|
||||
}
|
||||
final int numRunnableJobs = runnableJobs.size();
|
||||
if (numRunnableJobs > 0) {
|
||||
synchronized (mPendingJobReasonCache) {
|
||||
for (int i = 0; i < numRunnableJobs; ++i) {
|
||||
final JobStatus job = runnableJobs.get(i);
|
||||
SparseIntArray reasons = mPendingJobReasonCache.get(job.getUid());
|
||||
if (reasons == null) {
|
||||
reasons = new SparseIntArray();
|
||||
mPendingJobReasonCache.put(job.getUid(), reasons);
|
||||
}
|
||||
// We're force batching these jobs, so consider it an optimization
|
||||
// policy reason.
|
||||
reasons.put(job.getJobId(),
|
||||
JobScheduler.PENDING_JOB_REASON_JOB_SCHEDULER_OPTIMIZATION);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Be ready for next time
|
||||
@@ -3369,6 +3548,18 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPendingJobReason(int jobId) throws RemoteException {
|
||||
final int uid = Binder.getCallingUid();
|
||||
|
||||
final long ident = Binder.clearCallingIdentity();
|
||||
try {
|
||||
return JobSchedulerService.this.getPendingJobReason(uid, jobId);
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(ident);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public JobInfo getPendingJob(int jobId) throws RemoteException {
|
||||
final int uid = Binder.getCallingUid();
|
||||
|
||||
@@ -32,6 +32,7 @@ import android.annotation.ElapsedRealtimeLong;
|
||||
import android.app.AppGlobals;
|
||||
import android.app.job.JobInfo;
|
||||
import android.app.job.JobParameters;
|
||||
import android.app.job.JobScheduler;
|
||||
import android.app.job.JobWorkItem;
|
||||
import android.content.ClipData;
|
||||
import android.content.ComponentName;
|
||||
@@ -106,6 +107,13 @@ public final class JobStatus {
|
||||
static final int CONSTRAINT_BACKGROUND_NOT_RESTRICTED = 1 << 22; // Implicit constraint
|
||||
static final int CONSTRAINT_FLEXIBLE = 1 << 21; // Implicit constraint
|
||||
|
||||
private static final int IMPLICIT_CONSTRAINTS = 0
|
||||
| CONSTRAINT_BACKGROUND_NOT_RESTRICTED
|
||||
| CONSTRAINT_DEVICE_NOT_DOZING
|
||||
| CONSTRAINT_FLEXIBLE
|
||||
| CONSTRAINT_TARE_WEALTH
|
||||
| CONSTRAINT_WITHIN_QUOTA;
|
||||
|
||||
// The following set of dynamic constraints are for specific use cases (as explained in their
|
||||
// relative naming and comments). Right now, they apply different constraints, which is fine,
|
||||
// but if in the future, we have overlapping dynamic constraint sets, removing one constraint
|
||||
@@ -1613,6 +1621,101 @@ public final class JobStatus {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If {@link #isReady()} returns false, this will return a single reason why the job isn't
|
||||
* ready. If {@link #isReady()} returns true, this will return
|
||||
* {@link JobScheduler#PENDING_JOB_REASON_UNDEFINED}.
|
||||
*/
|
||||
@JobScheduler.PendingJobReason
|
||||
public int getPendingJobReason() {
|
||||
final int unsatisfiedConstraints = ~satisfiedConstraints
|
||||
& (requiredConstraints | mDynamicConstraints | IMPLICIT_CONSTRAINTS);
|
||||
if ((CONSTRAINT_BACKGROUND_NOT_RESTRICTED & unsatisfiedConstraints) != 0) {
|
||||
// The BACKGROUND_NOT_RESTRICTED constraint could be unsatisfied either because
|
||||
// the app is background restricted, or because we're restricting background work
|
||||
// in battery saver. Assume that background restriction is the reason apps that
|
||||
// jobs are not ready, and battery saver otherwise.
|
||||
// This has the benefit of being consistent for background restricted apps
|
||||
// (they'll always get BACKGROUND_RESTRICTION) as the reason, regardless of
|
||||
// battery saver state.
|
||||
if (mIsUserBgRestricted) {
|
||||
return JobScheduler.PENDING_JOB_REASON_BACKGROUND_RESTRICTION;
|
||||
}
|
||||
return JobScheduler.PENDING_JOB_REASON_DEVICE_STATE;
|
||||
}
|
||||
if ((CONSTRAINT_BATTERY_NOT_LOW & unsatisfiedConstraints) != 0) {
|
||||
if ((CONSTRAINT_BATTERY_NOT_LOW & requiredConstraints) != 0) {
|
||||
// The developer requested this constraint, so it makes sense to return the
|
||||
// explicit constraint reason.
|
||||
return JobScheduler.PENDING_JOB_REASON_CONSTRAINT_BATTERY_NOT_LOW;
|
||||
}
|
||||
// Hard-coding right now since the current dynamic constraint sets don't overlap
|
||||
// TODO: return based on active dynamic constraint sets when they start overlapping
|
||||
return JobScheduler.PENDING_JOB_REASON_APP_STANDBY;
|
||||
}
|
||||
if ((CONSTRAINT_CHARGING & unsatisfiedConstraints) != 0) {
|
||||
if ((CONSTRAINT_CHARGING & requiredConstraints) != 0) {
|
||||
// The developer requested this constraint, so it makes sense to return the
|
||||
// explicit constraint reason.
|
||||
return JobScheduler.PENDING_JOB_REASON_CONSTRAINT_CHARGING;
|
||||
}
|
||||
// Hard-coding right now since the current dynamic constraint sets don't overlap
|
||||
// TODO: return based on active dynamic constraint sets when they start overlapping
|
||||
return JobScheduler.PENDING_JOB_REASON_APP_STANDBY;
|
||||
}
|
||||
if ((CONSTRAINT_CONNECTIVITY & unsatisfiedConstraints) != 0) {
|
||||
return JobScheduler.PENDING_JOB_REASON_CONSTRAINT_CONNECTIVITY;
|
||||
}
|
||||
if ((CONSTRAINT_CONTENT_TRIGGER & unsatisfiedConstraints) != 0) {
|
||||
return JobScheduler.PENDING_JOB_REASON_CONSTRAINT_CONTENT_TRIGGER;
|
||||
}
|
||||
if ((CONSTRAINT_DEVICE_NOT_DOZING & unsatisfiedConstraints) != 0) {
|
||||
return JobScheduler.PENDING_JOB_REASON_DEVICE_STATE;
|
||||
}
|
||||
if ((CONSTRAINT_FLEXIBLE & unsatisfiedConstraints) != 0) {
|
||||
return JobScheduler.PENDING_JOB_REASON_JOB_SCHEDULER_OPTIMIZATION;
|
||||
}
|
||||
if ((CONSTRAINT_IDLE & unsatisfiedConstraints) != 0) {
|
||||
if ((CONSTRAINT_IDLE & requiredConstraints) != 0) {
|
||||
// The developer requested this constraint, so it makes sense to return the
|
||||
// explicit constraint reason.
|
||||
return JobScheduler.PENDING_JOB_REASON_CONSTRAINT_DEVICE_IDLE;
|
||||
}
|
||||
// Hard-coding right now since the current dynamic constraint sets don't overlap
|
||||
// TODO: return based on active dynamic constraint sets when they start overlapping
|
||||
return JobScheduler.PENDING_JOB_REASON_APP_STANDBY;
|
||||
}
|
||||
if ((CONSTRAINT_PREFETCH & unsatisfiedConstraints) != 0) {
|
||||
return JobScheduler.PENDING_JOB_REASON_CONSTRAINT_PREFETCH;
|
||||
}
|
||||
if ((CONSTRAINT_STORAGE_NOT_LOW & unsatisfiedConstraints) != 0) {
|
||||
return JobScheduler.PENDING_JOB_REASON_CONSTRAINT_STORAGE_NOT_LOW;
|
||||
}
|
||||
if ((CONSTRAINT_TARE_WEALTH & unsatisfiedConstraints) != 0) {
|
||||
return JobScheduler.PENDING_JOB_REASON_QUOTA;
|
||||
}
|
||||
if ((CONSTRAINT_TIMING_DELAY & unsatisfiedConstraints) != 0) {
|
||||
return JobScheduler.PENDING_JOB_REASON_CONSTRAINT_MINIMUM_LATENCY;
|
||||
}
|
||||
if ((CONSTRAINT_WITHIN_QUOTA & unsatisfiedConstraints) != 0) {
|
||||
return JobScheduler.PENDING_JOB_REASON_QUOTA;
|
||||
}
|
||||
|
||||
if (getEffectiveStandbyBucket() == NEVER_INDEX) {
|
||||
Slog.wtf(TAG, "App in NEVER bucket querying pending job reason");
|
||||
// The user hasn't officially launched this app.
|
||||
return JobScheduler.PENDING_JOB_REASON_USER;
|
||||
}
|
||||
if (serviceProcessName != null) {
|
||||
return JobScheduler.PENDING_JOB_REASON_APP;
|
||||
}
|
||||
|
||||
if (!isReady()) {
|
||||
Slog.wtf(TAG, "Unknown reason job isn't ready");
|
||||
}
|
||||
return JobScheduler.PENDING_JOB_REASON_UNDEFINED;
|
||||
}
|
||||
|
||||
/** @return whether or not the @param constraint is satisfied */
|
||||
public boolean isConstraintSatisfied(int constraint) {
|
||||
return (satisfiedConstraints&constraint) != 0;
|
||||
|
||||
@@ -18,6 +18,7 @@ package com.android.server.job.restrictions;
|
||||
|
||||
import android.app.job.JobInfo;
|
||||
import android.app.job.JobParameters;
|
||||
import android.app.job.JobScheduler;
|
||||
import android.util.IndentingPrintWriter;
|
||||
import android.util.proto.ProtoOutputStream;
|
||||
|
||||
@@ -28,20 +29,23 @@ import com.android.server.job.controllers.JobStatus;
|
||||
* Used by {@link JobSchedulerService} to impose additional restrictions regarding whether jobs
|
||||
* should be scheduled or not based on the state of the system/device.
|
||||
* Every restriction is associated with exactly one stop reason, which could be retrieved using
|
||||
* {@link #getReason()} (and the internal reason via {@link #getInternalReason()}).
|
||||
* {@link #getStopReason()}, one pending reason (retrievable via {@link #getPendingReason()},
|
||||
* (and the internal reason via {@link #getInternalReason()}).
|
||||
* Note, that this is not taken into account for the jobs that have
|
||||
* {@link JobInfo#BIAS_FOREGROUND_SERVICE} bias or higher.
|
||||
*/
|
||||
public abstract class JobRestriction {
|
||||
|
||||
final JobSchedulerService mService;
|
||||
private final int mReason;
|
||||
private final int mStopReason;
|
||||
private final int mPendingReason;
|
||||
private final int mInternalReason;
|
||||
|
||||
JobRestriction(JobSchedulerService service, @JobParameters.StopReason int reason,
|
||||
int internalReason) {
|
||||
protected JobRestriction(JobSchedulerService service, @JobParameters.StopReason int stopReason,
|
||||
@JobScheduler.PendingJobReason int pendingReason, int internalReason) {
|
||||
mService = service;
|
||||
mReason = reason;
|
||||
mPendingReason = pendingReason;
|
||||
mStopReason = stopReason;
|
||||
mInternalReason = internalReason;
|
||||
}
|
||||
|
||||
@@ -70,10 +74,15 @@ public abstract class JobRestriction {
|
||||
public void dumpConstants(ProtoOutputStream proto) {
|
||||
}
|
||||
|
||||
/** @return reason code for the Restriction. */
|
||||
@JobScheduler.PendingJobReason
|
||||
public final int getPendingReason() {
|
||||
return mPendingReason;
|
||||
}
|
||||
|
||||
/** @return stop reason code for the Restriction. */
|
||||
@JobParameters.StopReason
|
||||
public final int getReason() {
|
||||
return mReason;
|
||||
public final int getStopReason() {
|
||||
return mStopReason;
|
||||
}
|
||||
|
||||
public final int getInternalReason() {
|
||||
|
||||
@@ -18,6 +18,7 @@ package com.android.server.job.restrictions;
|
||||
|
||||
import android.app.job.JobInfo;
|
||||
import android.app.job.JobParameters;
|
||||
import android.app.job.JobScheduler;
|
||||
import android.os.PowerManager;
|
||||
import android.os.PowerManager.OnThermalStatusChangedListener;
|
||||
import android.util.IndentingPrintWriter;
|
||||
@@ -42,6 +43,7 @@ public class ThermalStatusRestriction extends JobRestriction {
|
||||
|
||||
public ThermalStatusRestriction(JobSchedulerService service) {
|
||||
super(service, JobParameters.STOP_REASON_DEVICE_STATE,
|
||||
JobScheduler.PENDING_JOB_REASON_DEVICE_STATE,
|
||||
JobParameters.INTERNAL_STOP_REASON_DEVICE_THERMAL);
|
||||
}
|
||||
|
||||
|
||||
@@ -8484,7 +8484,26 @@ package android.app.job {
|
||||
method public abstract int enqueue(@NonNull android.app.job.JobInfo, @NonNull android.app.job.JobWorkItem);
|
||||
method @NonNull public abstract java.util.List<android.app.job.JobInfo> getAllPendingJobs();
|
||||
method @Nullable public abstract android.app.job.JobInfo getPendingJob(int);
|
||||
method public int getPendingJobReason(int);
|
||||
method public abstract int schedule(@NonNull android.app.job.JobInfo);
|
||||
field public static final int PENDING_JOB_REASON_APP = 1; // 0x1
|
||||
field public static final int PENDING_JOB_REASON_APP_STANDBY = 2; // 0x2
|
||||
field public static final int PENDING_JOB_REASON_BACKGROUND_RESTRICTION = 3; // 0x3
|
||||
field public static final int PENDING_JOB_REASON_CONSTRAINT_BATTERY_NOT_LOW = 4; // 0x4
|
||||
field public static final int PENDING_JOB_REASON_CONSTRAINT_CHARGING = 5; // 0x5
|
||||
field public static final int PENDING_JOB_REASON_CONSTRAINT_CONNECTIVITY = 6; // 0x6
|
||||
field public static final int PENDING_JOB_REASON_CONSTRAINT_CONTENT_TRIGGER = 7; // 0x7
|
||||
field public static final int PENDING_JOB_REASON_CONSTRAINT_DEVICE_IDLE = 8; // 0x8
|
||||
field public static final int PENDING_JOB_REASON_CONSTRAINT_MINIMUM_LATENCY = 9; // 0x9
|
||||
field public static final int PENDING_JOB_REASON_CONSTRAINT_PREFETCH = 10; // 0xa
|
||||
field public static final int PENDING_JOB_REASON_CONSTRAINT_STORAGE_NOT_LOW = 11; // 0xb
|
||||
field public static final int PENDING_JOB_REASON_DEVICE_STATE = 12; // 0xc
|
||||
field public static final int PENDING_JOB_REASON_EXECUTING = -1; // 0xffffffff
|
||||
field public static final int PENDING_JOB_REASON_INVALID_JOB_ID = -2; // 0xfffffffe
|
||||
field public static final int PENDING_JOB_REASON_JOB_SCHEDULER_OPTIMIZATION = 13; // 0xd
|
||||
field public static final int PENDING_JOB_REASON_QUOTA = 14; // 0xe
|
||||
field public static final int PENDING_JOB_REASON_UNDEFINED = 0; // 0x0
|
||||
field public static final int PENDING_JOB_REASON_USER = 15; // 0xf
|
||||
field public static final int RESULT_FAILURE = 0; // 0x0
|
||||
field public static final int RESULT_SUCCESS = 1; // 0x1
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user