From 57c9f5ceb713f30b9155ba0fc2d67c212ec08831 Mon Sep 17 00:00:00 2001 From: Kweku Adams Date: Wed, 9 Nov 2022 19:21:44 +0000 Subject: [PATCH] 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 --- .../java/android/app/JobSchedulerImpl.java | 11 +- .../java/android/app/job/IJobScheduler.aidl | 1 + .../java/android/app/job/JobScheduler.java | 139 +++++++++++++ .../server/job/JobConcurrencyManager.java | 5 +- .../server/job/JobSchedulerService.java | 191 ++++++++++++++++++ .../server/job/controllers/JobStatus.java | 103 ++++++++++ .../job/restrictions/JobRestriction.java | 25 ++- .../ThermalStatusRestriction.java | 2 + core/api/current.txt | 19 ++ 9 files changed, 485 insertions(+), 11 deletions(-) diff --git a/apex/jobscheduler/framework/java/android/app/JobSchedulerImpl.java b/apex/jobscheduler/framework/java/android/app/JobSchedulerImpl.java index 0c65b99305e56..2d3201aab133e 100644 --- a/apex/jobscheduler/framework/java/android/app/JobSchedulerImpl.java +++ b/apex/jobscheduler/framework/java/android/app/JobSchedulerImpl.java @@ -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 { diff --git a/apex/jobscheduler/framework/java/android/app/job/IJobScheduler.aidl b/apex/jobscheduler/framework/java/android/app/job/IJobScheduler.aidl index d2be32e219c99..bf29dc98887ab 100644 --- a/apex/jobscheduler/framework/java/android/app/job/IJobScheduler.aidl +++ b/apex/jobscheduler/framework/java/android/app/job/IJobScheduler.aidl @@ -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 getStartedJobs(); diff --git a/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java b/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java index 13b6652bb7517..659db9f7b6e3a 100644 --- a/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java +++ b/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java @@ -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. diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java index f9dd0b379cb24..47f68902e88f3 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java @@ -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); diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java index ee08f85cbcb62..9fb2af736cea6 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java @@ -363,6 +363,9 @@ public class JobSchedulerService extends com.android.server.SystemService @GuardedBy("mLock") private final ArraySet mChangedJobList = new ArraySet<>(); + @GuardedBy("mPendingJobReasonCache") // Use its own lock to avoid blocking JS processing + private final SparseArray 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 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 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(); diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java index da20684aaefb1..af8e7272605b5 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java @@ -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; diff --git a/apex/jobscheduler/service/java/com/android/server/job/restrictions/JobRestriction.java b/apex/jobscheduler/service/java/com/android/server/job/restrictions/JobRestriction.java index 4067541b46462..7aab67a00b1da 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/restrictions/JobRestriction.java +++ b/apex/jobscheduler/service/java/com/android/server/job/restrictions/JobRestriction.java @@ -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() { diff --git a/apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java b/apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java index ca2fd60a07b2a..830031e2f442f 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java +++ b/apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java @@ -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); } diff --git a/core/api/current.txt b/core/api/current.txt index e6c0d45b3e578..32183687ca210 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -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 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 }