diff options
author | Kweku Adams <kwekua@google.com> | 2021-03-10 14:03:50 -0800 |
---|---|---|
committer | Kweku Adams <kwekua@google.com> | 2021-03-22 11:18:36 -0700 |
commit | 0aa5738423d2c7a8d1eabd5fc4757bdac5a959cc (patch) | |
tree | 8275686b9600efc44c05ef3de395675274d1d924 | |
parent | 672f46671394e2b838731ee2501c52e31b0d318e (diff) |
Expose job stop reasons.
Provide the various reasons a job could be stopped (via onStopJob) so
developers can react accordingly.
Bug: 171305774
Test: atest frameworks/base/services/tests/mockingservicestests/src/com/android/server/job
Test: atest frameworks/base/services/tests/servicestests/src/com/android/server/job
Test: atest CtsJobSchedulerTestCases
Change-Id: I5a19e74d3924de449cc5e4de27445e25cf8e25f5
21 files changed, 429 insertions, 116 deletions
diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreIdleJobService.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreIdleJobService.java index 999860fdf4da..e65abcfba3e4 100644 --- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreIdleJobService.java +++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreIdleJobService.java @@ -48,7 +48,8 @@ public class BlobStoreIdleJobService extends JobService { @Override public boolean onStopJob(final JobParameters params) { Slog.d(TAG, "Idle maintenance job is stopped; id=" + params.getJobId() - + ", reason=" + JobParameters.getReasonCodeDescription(params.getStopReason())); + + ", reason=" + + JobParameters.getLegacyReasonCodeDescription(params.getLegacyStopReason())); return false; } diff --git a/apex/jobscheduler/framework/java/android/app/job/JobParameters.java b/apex/jobscheduler/framework/java/android/app/job/JobParameters.java index 0d3e0016fca0..60f64757f65a 100644 --- a/apex/jobscheduler/framework/java/android/app/job/JobParameters.java +++ b/apex/jobscheduler/framework/java/android/app/job/JobParameters.java @@ -16,10 +16,14 @@ package android.app.job; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.ActivityManager; +import android.app.usage.UsageStatsManager; import android.compat.annotation.UnsupportedAppUsage; import android.content.ClipData; +import android.content.pm.PackageManager; import android.net.Network; import android.net.NetworkRequest; import android.net.Uri; @@ -30,6 +34,9 @@ import android.os.Parcelable; import android.os.PersistableBundle; import android.os.RemoteException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * Contains the parameters used to configure/identify your job. You do not create this object * yourself, instead it is handed in to your application by the System. @@ -82,7 +89,7 @@ public class JobParameters implements Parcelable { */ // TODO(142420609): make it @SystemApi for mainline @NonNull - public static String getReasonCodeDescription(int reasonCode) { + public static String getLegacyReasonCodeDescription(int reasonCode) { switch (reasonCode) { case REASON_CANCELED: return "canceled"; case REASON_CONSTRAINTS_NOT_SATISFIED: return "constraints"; @@ -96,12 +103,119 @@ public class JobParameters implements Parcelable { } /** @hide */ - // @SystemApi TODO make it a system api for mainline + // TODO: move current users of legacy reasons to new public reasons @NonNull public static int[] getJobStopReasonCodes() { return JOB_STOP_REASON_CODES; } + /** + * There is no reason the job is stopped. This is the value returned from the JobParameters + * object passed to {@link JobService#onStartJob(JobParameters)}. + */ + public static final int STOP_REASON_UNDEFINED = 0; + /** + * The job was cancelled directly by the app, either by calling + * {@link JobScheduler#cancel(int)}, {@link JobScheduler#cancelAll()}, or by scheduling a + * new job with the same job ID. + */ + public static final int STOP_REASON_CANCELLED_BY_APP = 1; + /** The job was stopped to run a higher priority job of the app. */ + public static final int STOP_REASON_PREEMPT = 2; + /** + * The job used up its maximum execution time and timed out. Each individual job has a maximum + * execution time limit, regardless of how much total quota the app has. See the note on + * {@link JobScheduler} for the execution time limits. + */ + public static final int STOP_REASON_TIMEOUT = 3; + /** + * The device state (eg. Doze, battery saver, memory usage, etc) requires JobScheduler stop this + * job. + */ + public static final int STOP_REASON_DEVICE_STATE = 4; + /** + * The requested battery-not-low constraint is no longer satisfied. + * + * @see JobInfo.Builder#setRequiresBatteryNotLow(boolean) + */ + public static final int STOP_REASON_CONSTRAINT_BATTERY_NOT_LOW = 5; + /** + * The requested charging constraint is no longer satisfied. + * + * @see JobInfo.Builder#setRequiresCharging(boolean) + */ + public static final int STOP_REASON_CONSTRAINT_CHARGING = 6; + /** + * The requested connectivity constraint is no longer satisfied. + * + * @see JobInfo.Builder#setRequiredNetwork(NetworkRequest) + * @see JobInfo.Builder#setRequiredNetworkType(int) + */ + public static final int STOP_REASON_CONSTRAINT_CONNECTIVITY = 7; + /** + * The requested idle constraint is no longer satisfied. + * + * @see JobInfo.Builder#setRequiresDeviceIdle(boolean) + */ + public static final int STOP_REASON_CONSTRAINT_DEVICE_IDLE = 8; + /** + * The requested storage-not-low constraint is no longer satisfied. + * + * @see JobInfo.Builder#setRequiresStorageNotLow(boolean) + */ + public static final int STOP_REASON_CONSTRAINT_STORAGE_NOT_LOW = 9; + /** + * The app has consumed all of its current quota. Each app is assigned a quota of how much + * it can run jobs within a certain time frame. The quota is informed, in part, by app standby + * buckets. Once an app has used up all of its quota, it won't be able to start jobs until + * quota is replenished, is changed, or is temporarily not applied. + * + * @see UsageStatsManager#getAppStandbyBucket() + */ + public static final int STOP_REASON_QUOTA = 10; + /** + * The app is restricted from running in the background. + * + * @see ActivityManager#isBackgroundRestricted() + * @see PackageManager#isInstantApp() + */ + public static final int STOP_REASON_BACKGROUND_RESTRICTION = 11; + /** + * The current standby bucket requires that the job stop now. + * + * @see UsageStatsManager#STANDBY_BUCKET_RESTRICTED + */ + public static final int STOP_REASON_APP_STANDBY = 12; + /** + * The user stopped the job. This can happen either through force-stop, or via adb shell + * commands. + */ + public static final int STOP_REASON_USER = 13; + /** The system is doing some processing that requires stopping this job. */ + public static final int STOP_REASON_SYSTEM_PROCESSING = 14; + + /** @hide */ + @IntDef(prefix = {"STOP_REASON_"}, value = { + STOP_REASON_UNDEFINED, + STOP_REASON_CANCELLED_BY_APP, + STOP_REASON_PREEMPT, + STOP_REASON_TIMEOUT, + STOP_REASON_DEVICE_STATE, + STOP_REASON_CONSTRAINT_BATTERY_NOT_LOW, + STOP_REASON_CONSTRAINT_CHARGING, + STOP_REASON_CONSTRAINT_CONNECTIVITY, + STOP_REASON_CONSTRAINT_DEVICE_IDLE, + STOP_REASON_CONSTRAINT_STORAGE_NOT_LOW, + STOP_REASON_QUOTA, + STOP_REASON_BACKGROUND_RESTRICTION, + STOP_REASON_APP_STANDBY, + STOP_REASON_USER, + STOP_REASON_SYSTEM_PROCESSING, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface StopReason { + } + @UnsupportedAppUsage private final int jobId; private final PersistableBundle extras; @@ -116,7 +230,8 @@ public class JobParameters implements Parcelable { private final String[] mTriggeredContentAuthorities; private final Network network; - private int stopReason; // Default value of stopReason is REASON_CANCELED + private int mStopReason = STOP_REASON_UNDEFINED; + private int mLegacyStopReason; // Default value of stopReason is REASON_CANCELED private String debugStopReason; // Human readable stop reason for debugging. /** @hide */ @@ -145,15 +260,23 @@ public class JobParameters implements Parcelable { } /** - * Reason onStopJob() was called on this job. - * @hide + * @return The reason {@link JobService#onStopJob(JobParameters)} was called on this job. Will + * be {@link #STOP_REASON_UNDEFINED} if {@link JobService#onStopJob(JobParameters)} has not + * yet been called. */ + @StopReason public int getStopReason() { - return stopReason; + return mStopReason; + } + + /** @hide */ + public int getLegacyStopReason() { + return mLegacyStopReason; } /** * Reason onStopJob() was called on this job. + * * @hide */ public String getDebugStopReason() { @@ -368,13 +491,16 @@ public class JobParameters implements Parcelable { } else { network = null; } - stopReason = in.readInt(); + mStopReason = in.readInt(); + mLegacyStopReason = in.readInt(); debugStopReason = in.readString(); } /** @hide */ - public void setStopReason(int reason, String debugStopReason) { - stopReason = reason; + public void setStopReason(@StopReason int reason, int legacyStopReason, + String debugStopReason) { + mStopReason = reason; + mLegacyStopReason = legacyStopReason; this.debugStopReason = debugStopReason; } @@ -406,7 +532,8 @@ public class JobParameters implements Parcelable { } else { dest.writeInt(0); } - dest.writeInt(stopReason); + dest.writeInt(mStopReason); + dest.writeInt(mLegacyStopReason); dest.writeString(debugStopReason); } diff --git a/apex/jobscheduler/framework/java/android/app/job/JobService.java b/apex/jobscheduler/framework/java/android/app/job/JobService.java index 0f3d299291c5..fa7a2d362ffa 100644 --- a/apex/jobscheduler/framework/java/android/app/job/JobService.java +++ b/apex/jobscheduler/framework/java/android/app/job/JobService.java @@ -139,19 +139,23 @@ public abstract class JobService extends Service { * Once this method is called, you no longer need to call * {@link #jobFinished(JobParameters, boolean)}. * - * <p>This will happen if the requirements specified at schedule time are no longer met. For + * <p>This may happen if the requirements specified at schedule time are no longer met. For * example you may have requested WiFi with * {@link android.app.job.JobInfo.Builder#setRequiredNetworkType(int)}, yet while your * job was executing the user toggled WiFi. Another example is if you had specified - * {@link android.app.job.JobInfo.Builder#setRequiresDeviceIdle(boolean)}, and the phone left its - * idle maintenance window. You are solely responsible for the behavior of your application - * upon receipt of this message; your app will likely start to misbehave if you ignore it. + * {@link android.app.job.JobInfo.Builder#setRequiresDeviceIdle(boolean)}, and the phone left + * its idle maintenance window. There are many other reasons a job can be stopped early besides + * constraints no longer being satisfied. {@link JobParameters#getStopReason()} will return the + * reason this method was called. You are solely responsible for the behavior of your + * application upon receipt of this message; your app will likely start to misbehave if you + * ignore it. * <p> * Once this method returns (or times out), the system releases the wakelock that it is holding * on behalf of the job.</p> * - * @param params The parameters identifying this job, as supplied to - * the job in the {@link #onStartJob(JobParameters)} callback. + * @param params The parameters identifying this job, similar to what was supplied to the job in + * the {@link #onStartJob(JobParameters)} callback, but with the stop reason + * included. * @return {@code true} to indicate to the JobManager whether you'd like to reschedule * this job based on the retry criteria provided at job creation-time; or {@code false} * to end the job entirely. Regardless of the value returned, your job must stop executing. diff --git a/apex/jobscheduler/framework/java/com/android/server/job/JobSchedulerInternal.java b/apex/jobscheduler/framework/java/com/android/server/job/JobSchedulerInternal.java index 7833a037463c..6ae91a0917a4 100644 --- a/apex/jobscheduler/framework/java/com/android/server/job/JobSchedulerInternal.java +++ b/apex/jobscheduler/framework/java/com/android/server/job/JobSchedulerInternal.java @@ -18,6 +18,7 @@ package com.android.server.job; import android.annotation.NonNull; import android.app.job.JobInfo; +import android.app.job.JobParameters; import android.util.proto.ProtoOutputStream; import java.util.List; @@ -36,7 +37,7 @@ public interface JobSchedulerInternal { /** * Cancel the jobs for a given uid (e.g. when app data is cleared) */ - void cancelJobsForUid(int uid, String reason); + void cancelJobsForUid(int uid, @JobParameters.StopReason int reason, String debugReason); /** * These are for activity manager to communicate to use what is currently performing backups. 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 b958c3f694bf..325be1b5c3a5 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java @@ -266,6 +266,8 @@ class JobConcurrencyManager { String[] mRecycledPreemptReasonForContext = new String[MAX_JOB_CONTEXTS_COUNT]; + int[] mRecycledPreemptReasonCodeForContext = new int[MAX_JOB_CONTEXTS_COUNT]; + String[] mRecycledShouldStopJobReason = new String[MAX_JOB_CONTEXTS_COUNT]; private final ArraySet<JobStatus> mRunningJobs = new ArraySet<>(); @@ -505,6 +507,7 @@ class JobConcurrencyManager { int[] preferredUidForContext = mRecycledPreferredUidForContext; int[] workTypeForContext = mRecycledWorkTypeForContext; String[] preemptReasonForContext = mRecycledPreemptReasonForContext; + int[] preemptReasonCodeForContext = mRecycledPreemptReasonCodeForContext; String[] shouldStopJobReason = mRecycledShouldStopJobReason; updateCounterConfigLocked(); @@ -528,6 +531,7 @@ class JobConcurrencyManager { slotChanged[i] = false; preferredUidForContext[i] = js.getPreferredUid(); preemptReasonForContext[i] = null; + preemptReasonCodeForContext[i] = JobParameters.STOP_REASON_UNDEFINED; shouldStopJobReason[i] = shouldStopRunningJobLocked(js); } if (DEBUG) { @@ -551,6 +555,7 @@ class JobConcurrencyManager { int allWorkTypes = getJobWorkTypes(nextPending); int workType = mWorkCountTracker.canJobStart(allWorkTypes); boolean startingJob = false; + int preemptReasonCode = JobParameters.STOP_REASON_UNDEFINED; String preemptReason = null; // TODO(141645789): rewrite this to look at empty contexts first so we don't // unnecessarily preempt @@ -582,6 +587,7 @@ class JobConcurrencyManager { // assign the new job to this context since we'll reassign when the // preempted job finally stops. preemptReason = reason; + preemptReasonCode = JobParameters.STOP_REASON_DEVICE_STATE; } continue; } @@ -597,6 +603,7 @@ class JobConcurrencyManager { minPriorityForPreemption = jobPriority; selectedContextId = j; preemptReason = "higher priority job found"; + preemptReasonCode = JobParameters.STOP_REASON_PREEMPT; // In this case, we're just going to preempt a low priority job, we're not // actually starting a job, so don't set startingJob. } @@ -604,6 +611,7 @@ class JobConcurrencyManager { if (selectedContextId != -1) { contextIdToJobMap[selectedContextId] = nextPending; slotChanged[selectedContextId] = true; + preemptReasonCodeForContext[selectedContextId] = preemptReasonCode; preemptReasonForContext[selectedContextId] = preemptReason; } if (startingJob) { @@ -631,8 +639,13 @@ class JobConcurrencyManager { } // preferredUid will be set to uid of currently running job. activeServices.get(i).cancelExecutingJobLocked( + preemptReasonCodeForContext[i], JobParameters.REASON_PREEMPT, preemptReasonForContext[i]); - preservePreferredUid = true; + // Only preserve the UID if we're preempting for the same UID. If we're stopping + // the job because something is pending (eg. EJs), then we shouldn't preserve + // the UID. + preservePreferredUid = + preemptReasonCodeForContext[i] == JobParameters.STOP_REASON_PREEMPT; } else { final JobStatus pendingJob = contextIdToJobMap[i]; if (DEBUG) { @@ -657,7 +670,8 @@ class JobConcurrencyManager { final JobStatus jobStatus = jsc.getRunningJobLocked(); if (jobStatus != null && !jsc.isWithinExecutionGuaranteeTime()) { - jsc.cancelExecutingJobLocked(JobParameters.REASON_TIMEOUT, debugReason); + jsc.cancelExecutingJobLocked(JobParameters.STOP_REASON_DEVICE_STATE, + JobParameters.REASON_TIMEOUT, debugReason); } } } diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobPackageTracker.java b/apex/jobscheduler/service/java/com/android/server/job/JobPackageTracker.java index 6ffac91d7098..02f912919878 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobPackageTracker.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobPackageTracker.java @@ -374,7 +374,7 @@ public final class JobPackageTracker { pw.print(pe.stopReasons.valueAt(k)); pw.print("x "); pw.print(JobParameters - .getReasonCodeDescription(pe.stopReasons.keyAt(k))); + .getLegacyReasonCodeDescription(pe.stopReasons.keyAt(k))); } pw.println(); } @@ -621,7 +621,7 @@ public final class JobPackageTracker { if (reason != null) { pw.print(mEventReasons[index]); } else { - pw.print(JobParameters.getReasonCodeDescription( + pw.print(JobParameters.getLegacyReasonCodeDescription( (mEventCmds[index] & EVENT_STOP_REASON_MASK) >> EVENT_STOP_REASON_SHIFT)); } 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 2b08ba554404..a041f8c0b512 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java @@ -746,8 +746,11 @@ public class JobSchedulerService extends com.android.server.SystemService Slog.d(TAG, "Removing jobs for package " + pkgName + " in user " + userId); } + // By the time we get here, the process should have already + // been stopped, so the app wouldn't get the stop reason, + // so just put USER instead of UNINSTALL or DISABLED. cancelJobsForPackageAndUid(pkgName, pkgUid, - "app disabled"); + JobParameters.STOP_REASON_USER, "app disabled"); } } catch (RemoteException|IllegalArgumentException e) { /* @@ -785,7 +788,11 @@ public class JobSchedulerService extends com.android.server.SystemService if (DEBUG) { Slog.d(TAG, "Removing jobs for uid: " + uidRemoved); } - cancelJobsForPackageAndUid(pkgName, uidRemoved, "app uninstalled"); + // By the time we get here, the process should have already + // been stopped, so the app wouldn't get the stop reason, + // so just put USER instead of UNINSTALL or DISABLED. + cancelJobsForPackageAndUid(pkgName, uidRemoved, + JobParameters.STOP_REASON_USER, "app uninstalled"); synchronized (mLock) { for (int c = 0; c < mControllers.size(); ++c) { mControllers.get(c).onAppRemovedLocked(pkgName, pkgUid); @@ -837,7 +844,8 @@ public class JobSchedulerService extends com.android.server.SystemService if (DEBUG) { Slog.d(TAG, "Removing jobs for pkg " + pkgName + " at uid " + pkgUid); } - cancelJobsForPackageAndUid(pkgName, pkgUid, "app force stopped"); + cancelJobsForPackageAndUid(pkgName, pkgUid, + JobParameters.STOP_REASON_USER, "app force stopped"); } } } @@ -924,8 +932,7 @@ public class JobSchedulerService extends com.android.server.SystemService final String servicePkg = job.getService().getPackageName(); if (job.isPersisted() && (packageName == null || packageName.equals(servicePkg))) { // Only limit schedule calls for persisted jobs scheduled by the app itself. - final String pkg = - packageName == null ? job.getService().getPackageName() : packageName; + final String pkg = packageName == null ? servicePkg : packageName; if (!mQuotaTracker.isWithinQuota(userId, pkg, QUOTA_TRACKER_SCHEDULE_PERSISTED_TAG)) { if (mQuotaTracker.isWithinQuota(userId, pkg, QUOTA_TRACKER_SCHEDULE_LOGGED)) { // Don't log too frequently @@ -972,14 +979,10 @@ public class JobSchedulerService extends com.android.server.SystemService mQuotaTracker.noteEvent(userId, pkg, QUOTA_TRACKER_SCHEDULE_PERSISTED_TAG); } - try { - if (ActivityManager.getService().isAppStartModeDisabled(uId, - job.getService().getPackageName())) { - Slog.w(TAG, "Not scheduling job " + uId + ":" + job.toString() - + " -- package not allowed to start"); - return JobScheduler.RESULT_FAILURE; - } - } catch (RemoteException e) { + if (mActivityManagerInternal.isAppStartModeDisabled(uId, servicePkg)) { + Slog.w(TAG, "Not scheduling job " + uId + ":" + job.toString() + + " -- package not allowed to start"); + return JobScheduler.RESULT_FAILURE; } synchronized (mLock) { @@ -1029,7 +1032,8 @@ public class JobSchedulerService extends com.android.server.SystemService if (toCancel != null) { // Implicitly replaces the existing job record with the new instance - cancelJobImplLocked(toCancel, jobStatus, "job rescheduled by app"); + cancelJobImplLocked(toCancel, jobStatus, JobParameters.STOP_REASON_CANCELLED_BY_APP, + "job rescheduled by app"); } else { startTrackingJobLocked(jobStatus, null); } @@ -1105,7 +1109,10 @@ public class JobSchedulerService extends com.android.server.SystemService final List<JobStatus> jobsForUser = mJobs.getJobsByUser(userHandle); for (int i=0; i<jobsForUser.size(); i++) { JobStatus toRemove = jobsForUser.get(i); - cancelJobImplLocked(toRemove, null, "user removed"); + // By the time we get here, the process should have already been stopped, so the + // app wouldn't get the stop reason, so just put USER instead of UNINSTALL. + cancelJobImplLocked(toRemove, null, JobParameters.STOP_REASON_USER, + "user removed"); } } } @@ -1117,7 +1124,8 @@ public class JobSchedulerService extends com.android.server.SystemService } } - void cancelJobsForPackageAndUid(String pkgName, int uid, String reason) { + void cancelJobsForPackageAndUid(String pkgName, int uid, @JobParameters.StopReason int reason, + String debugReason) { if ("android".equals(pkgName)) { Slog.wtfStack(TAG, "Can't cancel all jobs for system package"); return; @@ -1127,7 +1135,7 @@ public class JobSchedulerService extends com.android.server.SystemService for (int i = jobsForUid.size() - 1; i >= 0; i--) { final JobStatus job = jobsForUid.get(i); if (job.getSourcePackageName().equals(pkgName)) { - cancelJobImplLocked(job, null, reason); + cancelJobImplLocked(job, null, reason, debugReason); } } } @@ -1137,10 +1145,11 @@ public class JobSchedulerService extends com.android.server.SystemService * Entry point from client to cancel all jobs originating from their uid. * This will remove the job from the master list, and cancel the job if it was staged for * execution or being executed. - * @param uid Uid to check against for removal of a job. * + * @param uid Uid to check against for removal of a job. */ - public boolean cancelJobsForUid(int uid, String reason) { + public boolean cancelJobsForUid(int uid, @JobParameters.StopReason int reason, + String debugReason) { if (uid == Process.SYSTEM_UID) { Slog.wtfStack(TAG, "Can't cancel all jobs for system uid"); return false; @@ -1151,7 +1160,7 @@ public class JobSchedulerService extends com.android.server.SystemService final List<JobStatus> jobsForUid = mJobs.getJobsByUid(uid); for (int i=0; i<jobsForUid.size(); i++) { JobStatus toRemove = jobsForUid.get(i); - cancelJobImplLocked(toRemove, null, reason); + cancelJobImplLocked(toRemove, null, reason, debugReason); jobsCanceled = true; } } @@ -1162,15 +1171,17 @@ public class JobSchedulerService extends com.android.server.SystemService * Entry point from client to cancel the job corresponding to the jobId provided. * This will remove the job from the master list, and cancel the job if it was staged for * execution or being executed. - * @param uid Uid of the calling client. + * + * @param uid Uid of the calling client. * @param jobId Id of the job, provided at schedule-time. */ - public boolean cancelJob(int uid, int jobId, int callingUid) { + private boolean cancelJob(int uid, int jobId, int callingUid, + @JobParameters.StopReason int reason) { JobStatus toCancel; synchronized (mLock) { toCancel = mJobs.getJobByUidAndJobId(uid, jobId); if (toCancel != null) { - cancelJobImplLocked(toCancel, null, + cancelJobImplLocked(toCancel, null, reason, "cancel() called by app, callingUid=" + callingUid + " uid=" + uid + " jobId=" + jobId); } @@ -1184,7 +1195,8 @@ public class JobSchedulerService extends com.android.server.SystemService * {@code incomingJob} is non-null, it replaces {@code cancelled} in the store of * currently scheduled jobs. */ - private void cancelJobImplLocked(JobStatus cancelled, JobStatus incomingJob, String reason) { + private void cancelJobImplLocked(JobStatus cancelled, JobStatus incomingJob, + @JobParameters.StopReason int reason, String debugReason) { if (DEBUG) Slog.d(TAG, "CANCEL: " + cancelled.toShortString()); cancelled.unprepareLocked(); stopTrackingJobLocked(cancelled, incomingJob, true /* writeBack */); @@ -1193,7 +1205,8 @@ public class JobSchedulerService extends com.android.server.SystemService mJobPackageTracker.noteNonpending(cancelled); } // Cancel if running. - stopJobOnServiceContextLocked(cancelled, JobParameters.REASON_CANCELED, reason); + stopJobOnServiceContextLocked(cancelled, reason, JobParameters.REASON_CANCELED, + debugReason); // If this is a replacement, bring in the new version of the job if (incomingJob != null) { if (DEBUG) Slog.i(TAG, "Tracking replacement job " + incomingJob.toShortString()); @@ -1232,7 +1245,8 @@ public class JobSchedulerService extends com.android.server.SystemService JobServiceContext jsc = mActiveServices.get(i); final JobStatus executing = jsc.getRunningJobLocked(); if (executing != null && !executing.canRunInDoze()) { - jsc.cancelExecutingJobLocked(JobParameters.REASON_DEVICE_IDLE, + jsc.cancelExecutingJobLocked(JobParameters.STOP_REASON_DEVICE_STATE, + JobParameters.REASON_DEVICE_IDLE, "cancelled due to doze"); } } @@ -1430,7 +1444,8 @@ public class JobSchedulerService extends com.android.server.SystemService if (DEBUG) { Slog.v(TAG, " replacing " + oldJob + " with " + newJob); } - cancelJobImplLocked(oldJob, newJob, "deferred rtc calculation"); + cancelJobImplLocked(oldJob, newJob, JobParameters.STOP_REASON_SYSTEM_PROCESSING, + "deferred rtc calculation"); } } }; @@ -1550,12 +1565,13 @@ public class JobSchedulerService extends com.android.server.SystemService return removed; } - private boolean stopJobOnServiceContextLocked(JobStatus job, int reason, String debugReason) { + private boolean stopJobOnServiceContextLocked(JobStatus job, + @JobParameters.StopReason int reason, int legacyReason, String debugReason) { for (int i=0; i<mActiveServices.size(); i++) { JobServiceContext jsc = mActiveServices.get(i); final JobStatus executing = jsc.getRunningJobLocked(); if (executing != null && executing.matches(job.getUid(), job.getJobId())) { - jsc.cancelExecutingJobLocked(reason, debugReason); + jsc.cancelExecutingJobLocked(reason, legacyReason, debugReason); return true; } } @@ -1880,7 +1896,7 @@ public class JobSchedulerService extends com.android.server.SystemService queueReadyJobsForExecutionLocked(); break; case MSG_STOP_JOB: - cancelJobImplLocked((JobStatus) message.obj, null, + cancelJobImplLocked((JobStatus) message.obj, null, message.arg1, "app no longer allowed to run"); break; @@ -1895,7 +1911,9 @@ public class JobSchedulerService extends com.android.server.SystemService final boolean disabled = message.arg2 != 0; updateUidState(uid, ActivityManager.PROCESS_STATE_CACHED_EMPTY); if (disabled) { - cancelJobsForUid(uid, "uid gone"); + cancelJobsForUid(uid, + JobParameters.STOP_REASON_BACKGROUND_RESTRICTION, + "uid gone"); } synchronized (mLock) { mDeviceIdleJobsController.setUidActiveLocked(uid, false); @@ -1913,7 +1931,9 @@ public class JobSchedulerService extends com.android.server.SystemService final int uid = message.arg1; final boolean disabled = message.arg2 != 0; if (disabled) { - cancelJobsForUid(uid, "app uid idle"); + cancelJobsForUid(uid, + JobParameters.STOP_REASON_BACKGROUND_RESTRICTION, + "app uid idle"); } synchronized (mLock) { mDeviceIdleJobsController.setUidActiveLocked(uid, false); @@ -1965,10 +1985,12 @@ public class JobSchedulerService extends com.android.server.SystemService if (running.getEffectiveStandbyBucket() == RESTRICTED_INDEX && !running.areDynamicConstraintsSatisfied()) { serviceContext.cancelExecutingJobLocked( + running.getStopReason(), JobParameters.REASON_RESTRICTED_BUCKET, "cancelled due to restricted bucket"); } else { serviceContext.cancelExecutingJobLocked( + running.getStopReason(), JobParameters.REASON_CONSTRAINTS_NOT_SATISFIED, "cancelled due to unsatisfied constraints"); } @@ -1977,7 +1999,9 @@ public class JobSchedulerService extends com.android.server.SystemService if (restriction != null) { final int reason = restriction.getReason(); serviceContext.cancelExecutingJobLocked(reason, - "restricted due to " + JobParameters.getReasonCodeDescription(reason)); + restriction.getLegacyReason(), + "restricted due to " + JobParameters.getLegacyReasonCodeDescription( + reason)); } } } @@ -2058,15 +2082,14 @@ public class JobSchedulerService extends com.android.server.SystemService @Override public void accept(JobStatus job) { if (isReadyToBeExecutedLocked(job)) { - try { - if (ActivityManager.getService().isAppStartModeDisabled(job.getUid(), - job.getJob().getService().getPackageName())) { - Slog.w(TAG, "Aborting job " + job.getUid() + ":" - + job.getJob().toString() + " -- package not allowed to start"); - mHandler.obtainMessage(MSG_STOP_JOB, job).sendToTarget(); - return; - } - } catch (RemoteException e) { + if (mActivityManagerInternal.isAppStartModeDisabled(job.getUid(), + job.getJob().getService().getPackageName())) { + Slog.w(TAG, "Aborting job " + job.getUid() + ":" + + job.getJob().toString() + " -- package not allowed to start"); + mHandler.obtainMessage(MSG_STOP_JOB, + JobParameters.STOP_REASON_BACKGROUND_RESTRICTION, 0, job) + .sendToTarget(); + return; } final boolean shouldForceBatchJob; @@ -2276,7 +2299,7 @@ public class JobSchedulerService extends com.android.server.SystemService if (restriction != null) { if (DEBUG) { Slog.v(TAG, "areComponentsInPlaceLocked: " + job.toShortString() - + " restricted due to " + restriction.getReason()); + + " restricted due to " + restriction.getLegacyReason()); } return false; } @@ -2367,8 +2390,9 @@ public class JobSchedulerService extends com.android.server.SystemService } @Override - public void cancelJobsForUid(int uid, String reason) { - JobSchedulerService.this.cancelJobsForUid(uid, reason); + public void cancelJobsForUid(int uid, @JobParameters.StopReason int reason, + String debugReason) { + JobSchedulerService.this.cancelJobsForUid(uid, reason, debugReason); } @Override @@ -2706,6 +2730,7 @@ public class JobSchedulerService extends com.android.server.SystemService final long ident = Binder.clearCallingIdentity(); try { JobSchedulerService.this.cancelJobsForUid(uid, + JobParameters.STOP_REASON_CANCELLED_BY_APP, "cancelAll() called by app, callingUid=" + uid); } finally { Binder.restoreCallingIdentity(ident); @@ -2718,7 +2743,8 @@ public class JobSchedulerService extends com.android.server.SystemService final long ident = Binder.clearCallingIdentity(); try { - JobSchedulerService.this.cancelJob(uid, jobId, uid); + JobSchedulerService.this.cancelJob(uid, jobId, uid, + JobParameters.STOP_REASON_CANCELLED_BY_APP); } finally { Binder.restoreCallingIdentity(ident); } @@ -2924,12 +2950,13 @@ public class JobSchedulerService extends com.android.server.SystemService if (!hasJobId) { pw.println("Canceling all jobs for " + pkgName + " in user " + userId); - if (!cancelJobsForUid(pkgUid, "cancel shell command for package")) { + if (!cancelJobsForUid(pkgUid, JobParameters.STOP_REASON_USER, + "cancel shell command for package")) { pw.println("No matching jobs found."); } } else { pw.println("Canceling job " + pkgName + "/#" + jobId + " in user " + userId); - if (!cancelJob(pkgUid, jobId, Process.SHELL_UID)) { + if (!cancelJob(pkgUid, jobId, Process.SHELL_UID, JobParameters.STOP_REASON_USER)) { pw.println("No matching job found."); } } @@ -3164,8 +3191,9 @@ public class JobSchedulerService extends com.android.server.SystemService for (int i = mJobRestrictions.size() - 1; i >= 0; i--) { final JobRestriction restriction = mJobRestrictions.get(i); if (restriction.isJobRestricted(job)) { - final int reason = restriction.getReason(); - pw.print(" " + JobParameters.getReasonCodeDescription(reason)); + final int reason = restriction.getLegacyReason(); + pw.print(" "); + pw.print(JobParameters.getLegacyReasonCodeDescription(reason)); } } } else { @@ -3430,7 +3458,7 @@ public class JobSchedulerService extends com.android.server.SystemService final long restrictionsToken = proto.start( JobSchedulerServiceDumpProto.RegisteredJob.RESTRICTIONS); proto.write(JobSchedulerServiceDumpProto.JobRestriction.REASON, - restriction.getReason()); + restriction.getLegacyReason()); proto.write(JobSchedulerServiceDumpProto.JobRestriction.IS_RESTRICTING, restriction.isJobRestricted(job)); proto.end(restrictionsToken); diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java index 9ef46df7dac5..e8bcbfbb2c58 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java @@ -354,8 +354,9 @@ public final class JobServiceContext implements ServiceConnection { /** Called externally when a job that was scheduled for execution should be cancelled. */ @GuardedBy("mLock") - void cancelExecutingJobLocked(int reason, @NonNull String debugReason) { - doCancelLocked(reason, debugReason); + void cancelExecutingJobLocked(@JobParameters.StopReason int reason, + int legacyStopReason, @NonNull String debugReason) { + doCancelLocked(reason, legacyStopReason, debugReason); } int getPreferredUid() { @@ -387,7 +388,8 @@ public final class JobServiceContext implements ServiceConnection { && (pkgName == null || pkgName.equals(executing.getSourcePackageName())) && (!matchJobId || jobId == executing.getJobId())) { if (mVerb == VERB_EXECUTING) { - mParams.setStopReason(JobParameters.REASON_TIMEOUT, reason); + mParams.setStopReason(JobParameters.STOP_REASON_TIMEOUT, + JobParameters.REASON_TIMEOUT, reason); sendStopMessageLocked("force timeout from shell"); return true; } @@ -614,7 +616,8 @@ public final class JobServiceContext implements ServiceConnection { } @GuardedBy("mLock") - private void doCancelLocked(int stopReasonCode, @Nullable String debugReason) { + private void doCancelLocked(@JobParameters.StopReason int stopReasonCode, int legacyStopReason, + @Nullable String debugReason) { if (mVerb == VERB_FINISHED) { if (DEBUG) { Slog.d(TAG, @@ -622,8 +625,8 @@ public final class JobServiceContext implements ServiceConnection { } return; } - mParams.setStopReason(stopReasonCode, debugReason); - if (stopReasonCode == JobParameters.REASON_PREEMPT) { + mParams.setStopReason(stopReasonCode, legacyStopReason, debugReason); + if (legacyStopReason == JobParameters.REASON_PREEMPT) { mPreferredUid = mRunningJob != null ? mRunningJob.getUid() : NO_PREFERRED_UID; } @@ -781,7 +784,8 @@ public final class JobServiceContext implements ServiceConnection { // Not an error - client ran out of time. Slog.i(TAG, "Client timed out while executing (no jobFinished received)." + " Sending onStop: " + getRunningJobNameLocked()); - mParams.setStopReason(JobParameters.REASON_TIMEOUT, "client timed out"); + mParams.setStopReason(JobParameters.STOP_REASON_TIMEOUT, + JobParameters.REASON_TIMEOUT, "client timed out"); sendStopMessageLocked("timeout while executing"); } else { // We've given the app the minimum execution time. See if we should stop it or @@ -790,7 +794,11 @@ public final class JobServiceContext implements ServiceConnection { if (reason != null) { Slog.i(TAG, "Stopping client after min execution time: " + getRunningJobNameLocked() + " because " + reason); - mParams.setStopReason(JobParameters.REASON_TIMEOUT, reason); + // Tell the developer we're stopping the job due to device state instead + // of timeout since all of the reasons could equate to "the system needs + // the resources the app is currently using." + mParams.setStopReason(JobParameters.STOP_REASON_DEVICE_STATE, + JobParameters.REASON_TIMEOUT, reason); sendStopMessageLocked(reason); } else { Slog.i(TAG, "Letting " + getRunningJobNameLocked() @@ -844,12 +852,12 @@ public final class JobServiceContext implements ServiceConnection { } applyStoppedReasonLocked(reason); completedJob = mRunningJob; - final int stopReason = mParams.getStopReason(); - mJobPackageTracker.noteInactive(completedJob, stopReason, reason); + final int legacyStopReason = mParams.getLegacyStopReason(); + mJobPackageTracker.noteInactive(completedJob, legacyStopReason, reason); FrameworkStatsLog.write_non_chained(FrameworkStatsLog.SCHEDULED_JOB_STATE_CHANGED, completedJob.getSourceUid(), null, completedJob.getBatteryName(), FrameworkStatsLog.SCHEDULED_JOB_STATE_CHANGED__STATE__FINISHED, - stopReason, completedJob.getStandbyBucket(), completedJob.getJobId(), + legacyStopReason, completedJob.getStandbyBucket(), completedJob.getJobId(), completedJob.hasChargingConstraint(), completedJob.hasBatteryNotLowConstraint(), completedJob.hasStorageNotLowConstraint(), @@ -860,7 +868,7 @@ public final class JobServiceContext implements ServiceConnection { completedJob.hasContentTriggerConstraint()); try { mBatteryStats.noteJobFinish(mRunningJob.getBatteryName(), mRunningJob.getSourceUid(), - stopReason); + legacyStopReason); } catch (RemoteException e) { // Whatever. } @@ -879,7 +887,7 @@ public final class JobServiceContext implements ServiceConnection { service = null; mAvailable = true; removeOpTimeOutLocked(); - mCompletedListener.onJobCompletedLocked(completedJob, stopReason, reschedule); + mCompletedListener.onJobCompletedLocked(completedJob, legacyStopReason, reschedule); mJobConcurrencyManager.onJobCompletedLocked(this, completedJob, workType); } diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java index a230b23f03a4..548a1ac14391 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java @@ -210,7 +210,8 @@ public final class BackgroundJobsController extends StateController { jobStatus.maybeLogBucketMismatch(); } boolean didChange = - jobStatus.setBackgroundNotRestrictedConstraintSatisfied(nowElapsed, canRun); + jobStatus.setBackgroundNotRestrictedConstraintSatisfied(nowElapsed, canRun, + !mAppStateTracker.isRunAnyInBackgroundAppOpsAllowed(uid, packageName)); didChange |= jobStatus.setUidActive(isActive); return didChange; } 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 bad8dc1ad1cb..659cfa715352 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 @@ -24,6 +24,7 @@ import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock; import android.app.AppGlobals; import android.app.job.JobInfo; +import android.app.job.JobParameters; import android.app.job.JobWorkItem; import android.content.ClipData; import android.content.ComponentName; @@ -353,6 +354,9 @@ public final class JobStatus { */ private long mLastFailedRunTime; + /** Whether or not the app is background restricted by the user (FAS). */ + private boolean mIsUserBgRestricted; + /** * 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 @@ -409,6 +413,9 @@ public final class JobStatus { /** The job's dynamic requirements have been satisfied. */ private boolean mReadyDynamicSatisfied; + /** The reason a job most recently went from ready to not ready. */ + private int mReasonReadyToUnready = JobParameters.STOP_REASON_UNDEFINED; + /** Provide a handle to the service that this job will be run on. */ public int getServiceToken() { return callingUid; @@ -1042,6 +1049,11 @@ public final class JobStatus { mOriginalLatestRunTimeElapsedMillis = latestRunTimeElapsed; } + @JobParameters.StopReason + public int getStopReason() { + return mReasonReadyToUnready; + } + /** * Return the fractional position of "now" within the "run time" window of * this job. @@ -1172,7 +1184,9 @@ public final class JobStatus { } /** @return true if the constraint was changed, false otherwise. */ - boolean setBackgroundNotRestrictedConstraintSatisfied(final long nowElapsed, boolean state) { + boolean setBackgroundNotRestrictedConstraintSatisfied(final long nowElapsed, boolean state, + boolean isUserBgRestricted) { + mIsUserBgRestricted = isUserBgRestricted; if (setConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED, nowElapsed, state)) { // The constraint was changed. Update the ready flag. mReadyNotRestrictedInBg = state; @@ -1226,6 +1240,7 @@ public final class JobStatus { "Constraint " + constraint + " is " + (!state ? "NOT " : "") + "satisfied for " + toShortString()); } + final boolean wasReady = !state && isReady(); satisfiedConstraints = (satisfiedConstraints&~constraint) | (state ? constraint : 0); mSatisfiedConstraintsOfInterest = satisfiedConstraints & CONSTRAINTS_OF_INTEREST; mReadyDynamicSatisfied = mDynamicConstraints != 0 @@ -1244,9 +1259,81 @@ public final class JobStatus { mConstraintChangeHistoryIndex = (mConstraintChangeHistoryIndex + 1) % NUM_CONSTRAINT_CHANGE_HISTORY; + // Can't use isReady() directly since "cache booleans" haven't updated yet. + final boolean isReady = readinessStatusWithConstraint(constraint, state); + if (wasReady && !isReady) { + mReasonReadyToUnready = constraintToStopReason(constraint); + } else if (!wasReady && isReady) { + mReasonReadyToUnready = JobParameters.STOP_REASON_UNDEFINED; + } + return true; } + @JobParameters.StopReason + private int constraintToStopReason(int constraint) { + switch (constraint) { + case CONSTRAINT_BATTERY_NOT_LOW: + if ((requiredConstraints & constraint) != 0) { + // The developer requested this constraint, so it makes sense to return the + // explicit constraint reason. + return JobParameters.STOP_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 JobParameters.STOP_REASON_APP_STANDBY; + case CONSTRAINT_CHARGING: + if ((requiredConstraints & constraint) != 0) { + // The developer requested this constraint, so it makes sense to return the + // explicit constraint reason. + return JobParameters.STOP_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 JobParameters.STOP_REASON_APP_STANDBY; + case CONSTRAINT_CONNECTIVITY: + return JobParameters.STOP_REASON_CONSTRAINT_CONNECTIVITY; + case CONSTRAINT_IDLE: + if ((requiredConstraints & constraint) != 0) { + // The developer requested this constraint, so it makes sense to return the + // explicit constraint reason. + return JobParameters.STOP_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 JobParameters.STOP_REASON_APP_STANDBY; + case CONSTRAINT_STORAGE_NOT_LOW: + return JobParameters.STOP_REASON_CONSTRAINT_STORAGE_NOT_LOW; + + case CONSTRAINT_BACKGROUND_NOT_RESTRICTED: + // The BACKGROUND_NOT_RESTRICTED constraint could be dissatisfied 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 + // are background restricted have their jobs stopped, 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 JobParameters.STOP_REASON_BACKGROUND_RESTRICTION; + } + return JobParameters.STOP_REASON_DEVICE_STATE; + case CONSTRAINT_DEVICE_NOT_DOZING: + return JobParameters.STOP_REASON_DEVICE_STATE; + + case CONSTRAINT_WITHIN_QUOTA: + case CONSTRAINT_WITHIN_EXPEDITED_QUOTA: + return JobParameters.STOP_REASON_QUOTA; + + // These should never be stop reasons since they can never go from true to false. + case CONSTRAINT_CONTENT_TRIGGER: + case CONSTRAINT_DEADLINE: + case CONSTRAINT_TIMING_DELAY: + default: + Slog.wtf(TAG, "Unsupported constraint (" + constraint + ") --stop reason mapping"); + return JobParameters.STOP_REASON_UNDEFINED; + } + } + boolean isConstraintSatisfied(int constraint) { return (satisfiedConstraints&constraint) != 0; } @@ -1330,33 +1417,42 @@ public final class JobStatus { * granted, based on its requirements. */ boolean wouldBeReadyWithConstraint(int constraint) { + return readinessStatusWithConstraint(constraint, true); + } + + private boolean readinessStatusWithConstraint(int constraint, boolean value) { boolean oldValue = false; int satisfied = mSatisfiedConstraintsOfInterest; switch (constraint) { case CONSTRAINT_BACKGROUND_NOT_RESTRICTED: oldValue = mReadyNotRestrictedInBg; - mReadyNotRestrictedInBg = true; + mReadyNotRestrictedInBg = value; break; case CONSTRAINT_DEADLINE: oldValue = mReadyDeadlineSatisfied; - mReadyDeadlineSatisfied = true; + mReadyDeadlineSatisfied = value; break; case CONSTRAINT_DEVICE_NOT_DOZING: oldValue = mReadyNotDozing; - mReadyNotDozing = true; + mReadyNotDozing = value; break; case CONSTRAINT_WITHIN_QUOTA: oldValue = mReadyWithinQuota; - mReadyWithinQuota = true; + mReadyWithinQuota = value; break; case CONSTRAINT_WITHIN_EXPEDITED_QUOTA: oldValue = mReadyWithinExpeditedQuota; - mReadyWithinExpeditedQuota = true; + mReadyWithinExpeditedQuota = value; break; default: - satisfied |= constraint; + if (value) { + satisfied |= constraint; + } else { + satisfied &= ~constraint; + } mReadyDynamicSatisfied = mDynamicConstraints != 0 && mDynamicConstraints == (satisfied & mDynamicConstraints); + break; } 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 ac59f9542e99..2962b1017315 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 @@ -17,6 +17,7 @@ package com.android.server.job.restrictions; import android.app.job.JobInfo; +import android.app.job.JobParameters; import android.util.IndentingPrintWriter; import android.util.proto.ProtoOutputStream; @@ -26,9 +27,8 @@ 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 reason (from {@link - * android.app.job.JobParameters#JOB_STOP_REASON_CODES}), which could be retrieved using {@link - * #getReason()}. + * Every restriction is associated with exactly one stop reason, which could be retrieved using + * {@link #getReason()} (and the legacy reason via {@link #getLegacyReason()}). * Note, that this is not taken into account for the jobs that have priority * {@link JobInfo#PRIORITY_FOREGROUND_APP} or higher. */ @@ -36,10 +36,13 @@ public abstract class JobRestriction { final JobSchedulerService mService; private final int mReason; + private final int mLegacyReason; - JobRestriction(JobSchedulerService service, int reason) { + JobRestriction(JobSchedulerService service, @JobParameters.StopReason int reason, + int legacyReason) { mService = service; mReason = reason; + mLegacyReason = legacyReason; } /** @@ -66,7 +69,12 @@ public abstract class JobRestriction { public abstract void dumpConstants(ProtoOutputStream proto); /** @return reason code for the Restriction. */ + @JobParameters.StopReason public final int getReason() { return mReason; } + + public final int getLegacyReason() { + return mLegacyReason; + } } 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 954a5b8bdaa8..8b699e9b04c3 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 @@ -34,7 +34,7 @@ public class ThermalStatusRestriction extends JobRestriction { private PowerManager mPowerManager; public ThermalStatusRestriction(JobSchedulerService service) { - super(service, JobParameters.REASON_DEVICE_THERMAL); + super(service, JobParameters.STOP_REASON_DEVICE_STATE, JobParameters.REASON_DEVICE_THERMAL); } @Override diff --git a/core/api/current.txt b/core/api/current.txt index 3b26ce6fcd5d..32f991dbed5f 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -7946,6 +7946,7 @@ package android.app.job { method @NonNull public android.os.PersistableBundle getExtras(); method public int getJobId(); method @Nullable public android.net.Network getNetwork(); + method public int getStopReason(); method @NonNull public android.os.Bundle getTransientExtras(); method @Nullable public String[] getTriggeredContentAuthorities(); method @Nullable public android.net.Uri[] getTriggeredContentUris(); @@ -7954,6 +7955,21 @@ package android.app.job { method public boolean isOverrideDeadlineExpired(); method public void writeToParcel(android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.app.job.JobParameters> CREATOR; + field public static final int STOP_REASON_APP_STANDBY = 12; // 0xc + field public static final int STOP_REASON_BACKGROUND_RESTRICTION = 11; // 0xb + field public static final int STOP_REASON_CANCELLED_BY_APP = 1; // 0x1 + field public static final int STOP_REASON_CONSTRAINT_BATTERY_NOT_LOW = 5; // 0x5 + field public static final int STOP_REASON_CONSTRAINT_CHARGING = 6; // 0x6 + field public static final int STOP_REASON_CONSTRAINT_CONNECTIVITY = 7; // 0x7 + field public static final int STOP_REASON_CONSTRAINT_DEVICE_IDLE = 8; // 0x8 + field public static final int STOP_REASON_CONSTRAINT_STORAGE_NOT_LOW = 9; // 0x9 + field public static final int STOP_REASON_DEVICE_STATE = 4; // 0x4 + field public static final int STOP_REASON_PREEMPT = 2; // 0x2 + field public static final int STOP_REASON_QUOTA = 10; // 0xa + field public static final int STOP_REASON_SYSTEM_PROCESSING = 14; // 0xe + field public static final int STOP_REASON_TIMEOUT = 3; // 0x3 + field public static final int STOP_REASON_UNDEFINED = 0; // 0x0 + field public static final int STOP_REASON_USER = 13; // 0xd } public abstract class JobScheduler { diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java index fa6472ee4a79..4674aa2d120e 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -6030,7 +6030,7 @@ public abstract class BatteryStats implements Parcelable { pw.print(":"); for (int it=0; it<types.size(); it++) { pw.print(" "); - pw.print(JobParameters.getReasonCodeDescription(types.keyAt(it))); + pw.print(JobParameters.getLegacyReasonCodeDescription(types.keyAt(it))); pw.print("("); pw.print(types.valueAt(it)); pw.print("x)"); diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index c4548a3070a7..492759f7c73d 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -182,6 +182,7 @@ import android.app.PropertyInvalidatedCache; import android.app.WaitResult; import android.app.backup.BackupManager.OperationType; import android.app.backup.IBackupManager; +import android.app.job.JobParameters; import android.app.usage.UsageEvents; import android.app.usage.UsageEvents.Event; import android.app.usage.UsageStatsManager; @@ -3490,7 +3491,9 @@ public class ActivityManagerService extends IActivityManager.Stub // Clear its scheduled jobs JobSchedulerInternal js = LocalServices.getService(JobSchedulerInternal.class); - js.cancelJobsForUid(appInfo.uid, "clear data"); + // Clearing data is akin to uninstalling. The app is force stopped before we + // get to this point, so the reason won't be checked by the app. + js.cancelJobsForUid(appInfo.uid, JobParameters.STOP_REASON_USER, "clear data"); // Clear its pending alarms AlarmManagerInternal ami = LocalServices.getService(AlarmManagerInternal.class); diff --git a/services/core/java/com/android/server/content/SyncJobService.java b/services/core/java/com/android/server/content/SyncJobService.java index aaf9cbc168af..1f4606104ab8 100644 --- a/services/core/java/com/android/server/content/SyncJobService.java +++ b/services/core/java/com/android/server/content/SyncJobService.java @@ -119,7 +119,7 @@ public class SyncJobService extends JobService { public boolean onStopJob(JobParameters params) { if (Log.isLoggable(TAG, Log.VERBOSE)) { Slog.v(TAG, "onStopJob called " + params.getJobId() + ", reason: " - + params.getStopReason()); + + params.getLegacyStopReason()); } final SyncOperation op = SyncOperation.maybeCreateFromJobExtras(params.getExtras()); if (op == null) { @@ -161,9 +161,9 @@ public class SyncJobService extends JobService { m.obj = op; // Reschedule if this job was NOT explicitly canceled. - m.arg1 = params.getStopReason() != JobParameters.REASON_CANCELED ? 1 : 0; + m.arg1 = params.getLegacyStopReason() != JobParameters.REASON_CANCELED ? 1 : 0; // Apply backoff only if stop is called due to timeout. - m.arg2 = params.getStopReason() == JobParameters.REASON_TIMEOUT ? 1 : 0; + m.arg2 = params.getLegacyStopReason() == JobParameters.REASON_TIMEOUT ? 1 : 0; SyncManager.sendMessage(m); return false; @@ -204,7 +204,8 @@ public class SyncJobService extends JobService { return "job:null"; } else { return "job:#" + params.getJobId() + ":" - + "sr=[" + params.getStopReason() + "/" + params.getDebugStopReason() + "]:" + + "sr=[" + params.getLegacyStopReason() + + "/" + params.getDebugStopReason() + "]:" + SyncOperation.maybeCreateFromJobExtras(params.getExtras()); } } diff --git a/services/core/java/com/android/server/timezone/TimeZoneUpdateIdler.java b/services/core/java/com/android/server/timezone/TimeZoneUpdateIdler.java index a7767a4fbd66..23e3eba68c76 100644 --- a/services/core/java/com/android/server/timezone/TimeZoneUpdateIdler.java +++ b/services/core/java/com/android/server/timezone/TimeZoneUpdateIdler.java @@ -16,8 +16,6 @@ package com.android.server.timezone; -import com.android.server.LocalServices; - import android.app.job.JobInfo; import android.app.job.JobParameters; import android.app.job.JobScheduler; @@ -26,6 +24,8 @@ import android.content.ComponentName; import android.content.Context; import android.util.Slog; +import com.android.server.LocalServices; + /** * A JobService used to trigger time zone rules update work when a device falls idle. */ @@ -55,7 +55,7 @@ public final class TimeZoneUpdateIdler extends JobService { @Override public boolean onStopJob(JobParameters params) { // Reschedule if stopped unless it was cancelled due to unschedule(). - boolean reschedule = params.getStopReason() != JobParameters.REASON_CANCELED; + boolean reschedule = params.getStopReason() != JobParameters.STOP_REASON_CANCELLED_BY_APP; Slog.d(TAG, "onStopJob() called: Reschedule=" + reschedule); return reschedule; } diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java index 7925b69852ba..0fcda819d83c 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java @@ -660,15 +660,19 @@ public class JobStatusTest { new JobInfo.Builder(101, new ComponentName("foo", "bar")).build()); markImplicitConstraintsSatisfied(job, false); - job.setBackgroundNotRestrictedConstraintSatisfied(sElapsedRealtimeClock.millis(), false); + job.setBackgroundNotRestrictedConstraintSatisfied( + sElapsedRealtimeClock.millis(), false, false); assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_BACKGROUND_NOT_RESTRICTED)); - job.setBackgroundNotRestrictedConstraintSatisfied(sElapsedRealtimeClock.millis(), true); + job.setBackgroundNotRestrictedConstraintSatisfied( + sElapsedRealtimeClock.millis(), true, false); assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_BACKGROUND_NOT_RESTRICTED)); markImplicitConstraintsSatisfied(job, true); - job.setBackgroundNotRestrictedConstraintSatisfied(sElapsedRealtimeClock.millis(), false); + job.setBackgroundNotRestrictedConstraintSatisfied( + sElapsedRealtimeClock.millis(), false, false); assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_BACKGROUND_NOT_RESTRICTED)); - job.setBackgroundNotRestrictedConstraintSatisfied(sElapsedRealtimeClock.millis(), true); + job.setBackgroundNotRestrictedConstraintSatisfied( + sElapsedRealtimeClock.millis(), true, false); assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_BACKGROUND_NOT_RESTRICTED)); } @@ -677,7 +681,7 @@ public class JobStatusTest { job.setDeviceNotDozingConstraintSatisfied( sElapsedRealtimeClock.millis(), isSatisfied, false); job.setBackgroundNotRestrictedConstraintSatisfied( - sElapsedRealtimeClock.millis(), isSatisfied); + sElapsedRealtimeClock.millis(), isSatisfied, false); } private static JobStatus createJobStatus(long earliestRunTimeElapsedMillis, diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java index f73af535f452..ee1a4f4b3578 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java @@ -384,7 +384,8 @@ public class QuotaControllerTest { // Make sure Doze and background-not-restricted don't affect tests. js.setDeviceNotDozingConstraintSatisfied(/* nowElapsed */ sElapsedRealtimeClock.millis(), /* state */ true, /* allowlisted */false); - js.setBackgroundNotRestrictedConstraintSatisfied(sElapsedRealtimeClock.millis(), true); + js.setBackgroundNotRestrictedConstraintSatisfied( + sElapsedRealtimeClock.millis(), true, false); return js; } diff --git a/services/tests/servicestests/src/com/android/server/job/BackgroundRestrictionsTest.java b/services/tests/servicestests/src/com/android/server/job/BackgroundRestrictionsTest.java index a7b32ac5c387..68a6e6017bc6 100644 --- a/services/tests/servicestests/src/com/android/server/job/BackgroundRestrictionsTest.java +++ b/services/tests/servicestests/src/com/android/server/job/BackgroundRestrictionsTest.java @@ -96,7 +96,7 @@ public class BackgroundRestrictionsTest { case ACTION_JOB_STOPPED: mTestJobStatus.running = false; mTestJobStatus.jobId = params.getJobId(); - mTestJobStatus.stopReason = params.getStopReason(); + mTestJobStatus.stopReason = params.getLegacyStopReason(); break; } } diff --git a/services/tests/servicestests/src/com/android/server/job/MockPriorityJobService.java b/services/tests/servicestests/src/com/android/server/job/MockPriorityJobService.java index 3ea86f2e9ac0..87881bf839cc 100644 --- a/services/tests/servicestests/src/com/android/server/job/MockPriorityJobService.java +++ b/services/tests/servicestests/src/com/android/server/job/MockPriorityJobService.java @@ -47,7 +47,7 @@ public class MockPriorityJobService extends JobService { int reason = params.getStopReason(); int event = TestEnvironment.EVENT_STOP_JOB; Log.d(TAG, "stop reason: " + String.valueOf(reason)); - if (reason == JobParameters.REASON_PREEMPT) { + if (reason == JobParameters.STOP_REASON_PREEMPT) { event = TestEnvironment.EVENT_PREEMPT_JOB; Log.d(TAG, "preempted " + String.valueOf(params.getJobId())); } |