Loading apex/jobscheduler/service/java/com/android/server/job/JobCompletedListener.java +9 −3 Original line number Diff line number Diff line Loading @@ -16,6 +16,8 @@ package com.android.server.job; import android.app.job.JobParameters; import com.android.server.job.controllers.JobStatus; /** Loading @@ -26,8 +28,12 @@ public interface JobCompletedListener { /** * Callback for when a job is completed. * * @param stopReason The stop reason provided to JobParameters. * @param stopReason The stop reason returned from * {@link JobParameters#getStopReason()}. * @param internalStopReason The stop reason returned from * {@link JobParameters#getInternalStopReasonCode()}. * @param needsReschedule Whether the implementing class should reschedule this job. */ void onJobCompletedLocked(JobStatus jobStatus, int stopReason, boolean needsReschedule); void onJobCompletedLocked(JobStatus jobStatus, int stopReason, int internalStopReason, boolean needsReschedule); } apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +30 −7 Original line number Diff line number Diff line Loading @@ -1695,10 +1695,28 @@ public class JobSchedulerService extends com.android.server.SystemService } private void stopUserVisibleJobsInternal(@NonNull String packageName, int userId) { final int packageUid = mLocalPM.getPackageUid(packageName, 0, userId); if (packageUid < 0) { Slog.wtf(TAG, "Asked to stop jobs of an unknown package"); return; } synchronized (mLock) { mConcurrencyManager.stopUserVisibleJobsLocked(userId, packageName, JobParameters.STOP_REASON_USER, JobParameters.INTERNAL_STOP_REASON_USER_UI_STOP); final ArraySet<JobStatus> jobs = mJobs.getJobsByUid(packageUid); for (int i = jobs.size() - 1; i >= 0; i--) { final JobStatus job = jobs.valueAt(i); // For now, demote all jobs of the app. However, if the app was only doing work // on behalf of another app and the user wanted just that work to stop, this // unfairly penalizes any other jobs that may be scheduled. // For example, if apps A & B ask app C to do something (thus A & B are "source" // and C is "calling"), but only A's work was under way and the user wanted // to stop only that work, B's jobs would be demoted as well. // TODO(255768978): make it possible to demote only the relevant subset of jobs job.addInternalFlags(JobStatus.INTERNAL_FLAG_DEMOTED_BY_USER); } } } Loading Loading @@ -2352,7 +2370,7 @@ public class JobSchedulerService extends com.android.server.SystemService */ @VisibleForTesting JobStatus getRescheduleJobForFailureLocked(JobStatus failureToReschedule, int internalStopReason) { @JobParameters.StopReason int stopReason, int internalStopReason) { final long elapsedNowMillis = sElapsedRealtimeClock.millis(); final JobInfo job = failureToReschedule.getJob(); Loading @@ -2360,9 +2378,11 @@ public class JobSchedulerService extends com.android.server.SystemService int numFailures = failureToReschedule.getNumFailures(); int numSystemStops = failureToReschedule.getNumSystemStops(); // We should back off slowly if JobScheduler keeps stopping the job, // but back off immediately if the issue appeared to be the app's fault. // but back off immediately if the issue appeared to be the app's fault // or the user stopped the job somehow. if (internalStopReason == JobParameters.INTERNAL_STOP_REASON_SUCCESSFUL_FINISH || internalStopReason == JobParameters.INTERNAL_STOP_REASON_TIMEOUT) { || internalStopReason == JobParameters.INTERNAL_STOP_REASON_TIMEOUT || stopReason == JobParameters.STOP_REASON_USER) { numFailures++; } else { numSystemStops++; Loading Loading @@ -2393,11 +2413,14 @@ public class JobSchedulerService extends com.android.server.SystemService } delayMillis = Math.min(delayMillis, JobInfo.MAX_BACKOFF_DELAY_MILLIS); // TODO(255767350): demote all jobs to regular for user stops so they don't keep privileges JobStatus newJob = new JobStatus(failureToReschedule, elapsedNowMillis + delayMillis, JobStatus.NO_LATEST_RUNTIME, numFailures, numSystemStops, failureToReschedule.getLastSuccessfulRunTime(), sSystemClock.millis()); if (stopReason == JobParameters.STOP_REASON_USER) { // Demote all jobs to regular for user stops so they don't keep privileges. newJob.addInternalFlags(JobStatus.INTERNAL_FLAG_DEMOTED_BY_USER); } if (job.isPeriodic()) { newJob.setOriginalLatestRunTimeElapsed( failureToReschedule.getOriginalLatestRunTimeElapsed()); Loading Loading @@ -2518,8 +2541,8 @@ public class JobSchedulerService extends com.android.server.SystemService * @param needsReschedule Whether the implementing class should reschedule this job. */ @Override public void onJobCompletedLocked(JobStatus jobStatus, int debugStopReason, boolean needsReschedule) { public void onJobCompletedLocked(JobStatus jobStatus, @JobParameters.StopReason int stopReason, int debugStopReason, boolean needsReschedule) { if (DEBUG) { Slog.d(TAG, "Completed " + jobStatus + ", reason=" + debugStopReason + ", reschedule=" + needsReschedule); Loading @@ -2546,7 +2569,7 @@ public class JobSchedulerService extends com.android.server.SystemService // job so we can transfer any appropriate state over from the previous job when // we stop it. final JobStatus rescheduledJob = needsReschedule ? getRescheduleJobForFailureLocked(jobStatus, debugStopReason) : null; ? getRescheduleJobForFailureLocked(jobStatus, stopReason, debugStopReason) : null; if (rescheduledJob != null && !rescheduledJob.shouldTreatAsUserInitiatedJob() && (debugStopReason == JobParameters.INTERNAL_STOP_REASON_TIMEOUT Loading apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java +4 −2 Original line number Diff line number Diff line Loading @@ -1194,6 +1194,7 @@ public final class JobServiceContext implements ServiceConnection { applyStoppedReasonLocked(reason); completedJob = mRunningJob; final int internalStopReason = mParams.getInternalStopReasonCode(); final int stopReason = mParams.getStopReason(); mPreviousJobHadSuccessfulFinish = (internalStopReason == JobParameters.INTERNAL_STOP_REASON_SUCCESSFUL_FINISH); if (!mPreviousJobHadSuccessfulFinish) { Loading @@ -1214,7 +1215,7 @@ public final class JobServiceContext implements ServiceConnection { completedJob.hasContentTriggerConstraint(), completedJob.isRequestedExpeditedJob(), completedJob.startedAsExpeditedJob, mParams.getStopReason(), stopReason, completedJob.getJob().isPrefetch(), completedJob.getJob().getPriority(), completedJob.getEffectivePriority(), Loading Loading @@ -1267,7 +1268,8 @@ public final class JobServiceContext implements ServiceConnection { if (completedJob.isUserVisibleJob()) { mService.informObserversOfUserVisibleJobChange(this, completedJob, false); } mCompletedListener.onJobCompletedLocked(completedJob, internalStopReason, reschedule); mCompletedListener.onJobCompletedLocked(completedJob, stopReason, internalStopReason, reschedule); mJobConcurrencyManager.onJobCompletedLocked(this, completedJob, workType); } Loading apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java +7 −2 Original line number Diff line number Diff line Loading @@ -373,6 +373,11 @@ public final class JobStatus { * @hide */ public static final int INTERNAL_FLAG_HAS_FOREGROUND_EXEMPTION = 1 << 0; /** * Flag for {@link #mInternalFlags}: this job was stopped by the user for some reason * and is thus considered demoted from whatever privileged state it had in the past. */ public static final int INTERNAL_FLAG_DEMOTED_BY_USER = 1 << 1; /** Minimum difference between start and end time to have flexible constraint */ @VisibleForTesting Loading Loading @@ -1380,8 +1385,8 @@ public final class JobStatus { * for any reason. */ public boolean shouldTreatAsUserInitiatedJob() { // TODO(248386641): update implementation to handle loss of privilege return getJob().isUserInitiated(); return getJob().isUserInitiated() && (getInternalFlags() & INTERNAL_FLAG_DEMOTED_BY_USER) == 0; } /** Loading services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java +49 −2 Original line number Diff line number Diff line Loading @@ -30,6 +30,7 @@ import static com.android.server.job.JobSchedulerService.RARE_INDEX; import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; Loading Loading @@ -370,11 +371,12 @@ public class JobSchedulerServiceTest { } /** * Confirm that {@link JobSchedulerService#getRescheduleJobForFailureLocked(JobStatus, int)} * Confirm that * {@link JobSchedulerService#getRescheduleJobForFailureLocked(JobStatus, int, int)} * returns a job with the correct delay and deadline constraints. */ @Test public void testGetRescheduleJobForFailure() { public void testGetRescheduleJobForFailure_timingCalculations() { final long nowElapsed = sElapsedRealtimeClock.millis(); final long initialBackoffMs = MINUTE_IN_MILLIS; mService.mConstants.SYSTEM_STOP_TO_FAILURE_RATIO = 3; Loading @@ -387,15 +389,18 @@ public class JobSchedulerServiceTest { // failure = 0, systemStop = 1 JobStatus rescheduledJob = mService.getRescheduleJobForFailureLocked(originalJob, JobParameters.STOP_REASON_DEVICE_STATE, JobParameters.INTERNAL_STOP_REASON_DEVICE_THERMAL); assertEquals(nowElapsed + initialBackoffMs, rescheduledJob.getEarliestRunTime()); assertEquals(JobStatus.NO_LATEST_RUNTIME, rescheduledJob.getLatestRunTimeElapsed()); // failure = 0, systemStop = 2 rescheduledJob = mService.getRescheduleJobForFailureLocked(rescheduledJob, JobParameters.STOP_REASON_DEVICE_STATE, JobParameters.INTERNAL_STOP_REASON_PREEMPT); // failure = 0, systemStop = 3 rescheduledJob = mService.getRescheduleJobForFailureLocked(rescheduledJob, JobParameters.STOP_REASON_CONSTRAINT_CHARGING, JobParameters.INTERNAL_STOP_REASON_CONSTRAINTS_NOT_SATISFIED); assertEquals(nowElapsed + initialBackoffMs, rescheduledJob.getEarliestRunTime()); assertEquals(JobStatus.NO_LATEST_RUNTIME, rescheduledJob.getLatestRunTimeElapsed()); Loading @@ -403,6 +408,7 @@ public class JobSchedulerServiceTest { // failure = 0, systemStop = 2 * SYSTEM_STOP_TO_FAILURE_RATIO for (int i = 0; i < mService.mConstants.SYSTEM_STOP_TO_FAILURE_RATIO; ++i) { rescheduledJob = mService.getRescheduleJobForFailureLocked(rescheduledJob, JobParameters.STOP_REASON_SYSTEM_PROCESSING, JobParameters.INTERNAL_STOP_REASON_RTC_UPDATED); } assertEquals(nowElapsed + 2 * initialBackoffMs, rescheduledJob.getEarliestRunTime()); Loading @@ -410,17 +416,51 @@ public class JobSchedulerServiceTest { // failure = 1, systemStop = 2 * SYSTEM_STOP_TO_FAILURE_RATIO rescheduledJob = mService.getRescheduleJobForFailureLocked(rescheduledJob, JobParameters.STOP_REASON_TIMEOUT, JobParameters.INTERNAL_STOP_REASON_TIMEOUT); assertEquals(nowElapsed + 3 * initialBackoffMs, rescheduledJob.getEarliestRunTime()); assertEquals(JobStatus.NO_LATEST_RUNTIME, rescheduledJob.getLatestRunTimeElapsed()); // failure = 2, systemStop = 2 * SYSTEM_STOP_TO_FAILURE_RATIO rescheduledJob = mService.getRescheduleJobForFailureLocked(rescheduledJob, JobParameters.STOP_REASON_UNDEFINED, JobParameters.INTERNAL_STOP_REASON_SUCCESSFUL_FINISH); assertEquals(nowElapsed + 4 * initialBackoffMs, rescheduledJob.getEarliestRunTime()); assertEquals(JobStatus.NO_LATEST_RUNTIME, rescheduledJob.getLatestRunTimeElapsed()); } /** * Confirm that * {@link JobSchedulerService#getRescheduleJobForFailureLocked(JobStatus, int, int)} * returns a job that is correctly marked as demoted by the user. */ @Test public void testGetRescheduleJobForFailure_userDemotion() { JobStatus originalJob = createJobStatus("testGetRescheduleJobForFailure", createJobInfo()); assertEquals(0, originalJob.getInternalFlags() & JobStatus.INTERNAL_FLAG_DEMOTED_BY_USER); // Reschedule for a non-user reason JobStatus rescheduledJob = mService.getRescheduleJobForFailureLocked(originalJob, JobParameters.STOP_REASON_DEVICE_STATE, JobParameters.INTERNAL_STOP_REASON_DEVICE_THERMAL); assertEquals(0, rescheduledJob.getInternalFlags() & JobStatus.INTERNAL_FLAG_DEMOTED_BY_USER); // Reschedule for a user reason rescheduledJob = mService.getRescheduleJobForFailureLocked(rescheduledJob, JobParameters.STOP_REASON_USER, JobParameters.INTERNAL_STOP_REASON_USER_UI_STOP); assertNotEquals(0, rescheduledJob.getInternalFlags() & JobStatus.INTERNAL_FLAG_DEMOTED_BY_USER); // Reschedule a previously demoted job for a non-user reason rescheduledJob = mService.getRescheduleJobForFailureLocked(rescheduledJob, JobParameters.STOP_REASON_CONSTRAINT_CHARGING, JobParameters.INTERNAL_STOP_REASON_CONSTRAINTS_NOT_SATISFIED); assertNotEquals(0, rescheduledJob.getInternalFlags() & JobStatus.INTERNAL_FLAG_DEMOTED_BY_USER); } /** * Confirm that {@link JobSchedulerService#getRescheduleJobForPeriodic(JobStatus)} returns a job * with the correct delay and deadline constraints if the periodic job is scheduled with the Loading Loading @@ -731,6 +771,7 @@ public class JobSchedulerServiceTest { JobStatus job = createJobStatus("testGetRescheduleJobForPeriodic_insideWindow_failedJob", createJobInfo().setPeriodic(HOUR_IN_MILLIS)); JobStatus failedJob = mService.getRescheduleJobForFailureLocked(job, JobParameters.STOP_REASON_UNDEFINED, JobParameters.INTERNAL_STOP_REASON_SUCCESSFUL_FINISH); JobStatus rescheduledJob = mService.getRescheduleJobForPeriodic(failedJob); Loading @@ -739,6 +780,7 @@ public class JobSchedulerServiceTest { advanceElapsedClock(5 * MINUTE_IN_MILLIS); // now + 5 minutes failedJob = mService.getRescheduleJobForFailureLocked(job, JobParameters.STOP_REASON_UNDEFINED, JobParameters.INTERNAL_STOP_REASON_SUCCESSFUL_FINISH); advanceElapsedClock(5 * MINUTE_IN_MILLIS); // now + 10 minutes Loading @@ -748,6 +790,7 @@ public class JobSchedulerServiceTest { advanceElapsedClock(35 * MINUTE_IN_MILLIS); // now + 45 minutes failedJob = mService.getRescheduleJobForFailureLocked(job, JobParameters.STOP_REASON_UNDEFINED, JobParameters.INTERNAL_STOP_REASON_SUCCESSFUL_FINISH); advanceElapsedClock(10 * MINUTE_IN_MILLIS); // now + 55 minutes Loading @@ -759,6 +802,7 @@ public class JobSchedulerServiceTest { advanceElapsedClock(2 * MINUTE_IN_MILLIS); // now + 57 minutes failedJob = mService.getRescheduleJobForFailureLocked(job, JobParameters.STOP_REASON_UNDEFINED, JobParameters.INTERNAL_STOP_REASON_SUCCESSFUL_FINISH); advanceElapsedClock(2 * MINUTE_IN_MILLIS); // now + 59 minutes Loading Loading @@ -856,6 +900,7 @@ public class JobSchedulerServiceTest { JobStatus job = createJobStatus("testGetRescheduleJobForPeriodic_outsideWindow_failedJob", createJobInfo().setPeriodic(HOUR_IN_MILLIS)); JobStatus failedJob = mService.getRescheduleJobForFailureLocked(job, JobParameters.STOP_REASON_UNDEFINED, JobParameters.INTERNAL_STOP_REASON_SUCCESSFUL_FINISH); long now = sElapsedRealtimeClock.millis(); long nextWindowStartTime = now + HOUR_IN_MILLIS; Loading Loading @@ -893,6 +938,7 @@ public class JobSchedulerServiceTest { "testGetRescheduleJobForPeriodic_outsideWindow_flex_failedJob", createJobInfo().setPeriodic(HOUR_IN_MILLIS, 30 * MINUTE_IN_MILLIS)); JobStatus failedJob = mService.getRescheduleJobForFailureLocked(job, JobParameters.STOP_REASON_UNDEFINED, JobParameters.INTERNAL_STOP_REASON_SUCCESSFUL_FINISH); // First window starts 30 minutes from now. advanceElapsedClock(30 * MINUTE_IN_MILLIS); Loading Loading @@ -935,6 +981,7 @@ public class JobSchedulerServiceTest { "testGetRescheduleJobForPeriodic_outsideWindow_flex_failedJob_longPeriod", createJobInfo().setPeriodic(7 * DAY_IN_MILLIS, 9 * HOUR_IN_MILLIS)); JobStatus failedJob = mService.getRescheduleJobForFailureLocked(job, JobParameters.STOP_REASON_UNDEFINED, JobParameters.INTERNAL_STOP_REASON_SUCCESSFUL_FINISH); // First window starts 6.625 days from now. advanceElapsedClock(6 * DAY_IN_MILLIS + 15 * HOUR_IN_MILLIS); Loading Loading
apex/jobscheduler/service/java/com/android/server/job/JobCompletedListener.java +9 −3 Original line number Diff line number Diff line Loading @@ -16,6 +16,8 @@ package com.android.server.job; import android.app.job.JobParameters; import com.android.server.job.controllers.JobStatus; /** Loading @@ -26,8 +28,12 @@ public interface JobCompletedListener { /** * Callback for when a job is completed. * * @param stopReason The stop reason provided to JobParameters. * @param stopReason The stop reason returned from * {@link JobParameters#getStopReason()}. * @param internalStopReason The stop reason returned from * {@link JobParameters#getInternalStopReasonCode()}. * @param needsReschedule Whether the implementing class should reschedule this job. */ void onJobCompletedLocked(JobStatus jobStatus, int stopReason, boolean needsReschedule); void onJobCompletedLocked(JobStatus jobStatus, int stopReason, int internalStopReason, boolean needsReschedule); }
apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +30 −7 Original line number Diff line number Diff line Loading @@ -1695,10 +1695,28 @@ public class JobSchedulerService extends com.android.server.SystemService } private void stopUserVisibleJobsInternal(@NonNull String packageName, int userId) { final int packageUid = mLocalPM.getPackageUid(packageName, 0, userId); if (packageUid < 0) { Slog.wtf(TAG, "Asked to stop jobs of an unknown package"); return; } synchronized (mLock) { mConcurrencyManager.stopUserVisibleJobsLocked(userId, packageName, JobParameters.STOP_REASON_USER, JobParameters.INTERNAL_STOP_REASON_USER_UI_STOP); final ArraySet<JobStatus> jobs = mJobs.getJobsByUid(packageUid); for (int i = jobs.size() - 1; i >= 0; i--) { final JobStatus job = jobs.valueAt(i); // For now, demote all jobs of the app. However, if the app was only doing work // on behalf of another app and the user wanted just that work to stop, this // unfairly penalizes any other jobs that may be scheduled. // For example, if apps A & B ask app C to do something (thus A & B are "source" // and C is "calling"), but only A's work was under way and the user wanted // to stop only that work, B's jobs would be demoted as well. // TODO(255768978): make it possible to demote only the relevant subset of jobs job.addInternalFlags(JobStatus.INTERNAL_FLAG_DEMOTED_BY_USER); } } } Loading Loading @@ -2352,7 +2370,7 @@ public class JobSchedulerService extends com.android.server.SystemService */ @VisibleForTesting JobStatus getRescheduleJobForFailureLocked(JobStatus failureToReschedule, int internalStopReason) { @JobParameters.StopReason int stopReason, int internalStopReason) { final long elapsedNowMillis = sElapsedRealtimeClock.millis(); final JobInfo job = failureToReschedule.getJob(); Loading @@ -2360,9 +2378,11 @@ public class JobSchedulerService extends com.android.server.SystemService int numFailures = failureToReschedule.getNumFailures(); int numSystemStops = failureToReschedule.getNumSystemStops(); // We should back off slowly if JobScheduler keeps stopping the job, // but back off immediately if the issue appeared to be the app's fault. // but back off immediately if the issue appeared to be the app's fault // or the user stopped the job somehow. if (internalStopReason == JobParameters.INTERNAL_STOP_REASON_SUCCESSFUL_FINISH || internalStopReason == JobParameters.INTERNAL_STOP_REASON_TIMEOUT) { || internalStopReason == JobParameters.INTERNAL_STOP_REASON_TIMEOUT || stopReason == JobParameters.STOP_REASON_USER) { numFailures++; } else { numSystemStops++; Loading Loading @@ -2393,11 +2413,14 @@ public class JobSchedulerService extends com.android.server.SystemService } delayMillis = Math.min(delayMillis, JobInfo.MAX_BACKOFF_DELAY_MILLIS); // TODO(255767350): demote all jobs to regular for user stops so they don't keep privileges JobStatus newJob = new JobStatus(failureToReschedule, elapsedNowMillis + delayMillis, JobStatus.NO_LATEST_RUNTIME, numFailures, numSystemStops, failureToReschedule.getLastSuccessfulRunTime(), sSystemClock.millis()); if (stopReason == JobParameters.STOP_REASON_USER) { // Demote all jobs to regular for user stops so they don't keep privileges. newJob.addInternalFlags(JobStatus.INTERNAL_FLAG_DEMOTED_BY_USER); } if (job.isPeriodic()) { newJob.setOriginalLatestRunTimeElapsed( failureToReschedule.getOriginalLatestRunTimeElapsed()); Loading Loading @@ -2518,8 +2541,8 @@ public class JobSchedulerService extends com.android.server.SystemService * @param needsReschedule Whether the implementing class should reschedule this job. */ @Override public void onJobCompletedLocked(JobStatus jobStatus, int debugStopReason, boolean needsReschedule) { public void onJobCompletedLocked(JobStatus jobStatus, @JobParameters.StopReason int stopReason, int debugStopReason, boolean needsReschedule) { if (DEBUG) { Slog.d(TAG, "Completed " + jobStatus + ", reason=" + debugStopReason + ", reschedule=" + needsReschedule); Loading @@ -2546,7 +2569,7 @@ public class JobSchedulerService extends com.android.server.SystemService // job so we can transfer any appropriate state over from the previous job when // we stop it. final JobStatus rescheduledJob = needsReschedule ? getRescheduleJobForFailureLocked(jobStatus, debugStopReason) : null; ? getRescheduleJobForFailureLocked(jobStatus, stopReason, debugStopReason) : null; if (rescheduledJob != null && !rescheduledJob.shouldTreatAsUserInitiatedJob() && (debugStopReason == JobParameters.INTERNAL_STOP_REASON_TIMEOUT Loading
apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java +4 −2 Original line number Diff line number Diff line Loading @@ -1194,6 +1194,7 @@ public final class JobServiceContext implements ServiceConnection { applyStoppedReasonLocked(reason); completedJob = mRunningJob; final int internalStopReason = mParams.getInternalStopReasonCode(); final int stopReason = mParams.getStopReason(); mPreviousJobHadSuccessfulFinish = (internalStopReason == JobParameters.INTERNAL_STOP_REASON_SUCCESSFUL_FINISH); if (!mPreviousJobHadSuccessfulFinish) { Loading @@ -1214,7 +1215,7 @@ public final class JobServiceContext implements ServiceConnection { completedJob.hasContentTriggerConstraint(), completedJob.isRequestedExpeditedJob(), completedJob.startedAsExpeditedJob, mParams.getStopReason(), stopReason, completedJob.getJob().isPrefetch(), completedJob.getJob().getPriority(), completedJob.getEffectivePriority(), Loading Loading @@ -1267,7 +1268,8 @@ public final class JobServiceContext implements ServiceConnection { if (completedJob.isUserVisibleJob()) { mService.informObserversOfUserVisibleJobChange(this, completedJob, false); } mCompletedListener.onJobCompletedLocked(completedJob, internalStopReason, reschedule); mCompletedListener.onJobCompletedLocked(completedJob, stopReason, internalStopReason, reschedule); mJobConcurrencyManager.onJobCompletedLocked(this, completedJob, workType); } Loading
apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java +7 −2 Original line number Diff line number Diff line Loading @@ -373,6 +373,11 @@ public final class JobStatus { * @hide */ public static final int INTERNAL_FLAG_HAS_FOREGROUND_EXEMPTION = 1 << 0; /** * Flag for {@link #mInternalFlags}: this job was stopped by the user for some reason * and is thus considered demoted from whatever privileged state it had in the past. */ public static final int INTERNAL_FLAG_DEMOTED_BY_USER = 1 << 1; /** Minimum difference between start and end time to have flexible constraint */ @VisibleForTesting Loading Loading @@ -1380,8 +1385,8 @@ public final class JobStatus { * for any reason. */ public boolean shouldTreatAsUserInitiatedJob() { // TODO(248386641): update implementation to handle loss of privilege return getJob().isUserInitiated(); return getJob().isUserInitiated() && (getInternalFlags() & INTERNAL_FLAG_DEMOTED_BY_USER) == 0; } /** Loading
services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java +49 −2 Original line number Diff line number Diff line Loading @@ -30,6 +30,7 @@ import static com.android.server.job.JobSchedulerService.RARE_INDEX; import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; Loading Loading @@ -370,11 +371,12 @@ public class JobSchedulerServiceTest { } /** * Confirm that {@link JobSchedulerService#getRescheduleJobForFailureLocked(JobStatus, int)} * Confirm that * {@link JobSchedulerService#getRescheduleJobForFailureLocked(JobStatus, int, int)} * returns a job with the correct delay and deadline constraints. */ @Test public void testGetRescheduleJobForFailure() { public void testGetRescheduleJobForFailure_timingCalculations() { final long nowElapsed = sElapsedRealtimeClock.millis(); final long initialBackoffMs = MINUTE_IN_MILLIS; mService.mConstants.SYSTEM_STOP_TO_FAILURE_RATIO = 3; Loading @@ -387,15 +389,18 @@ public class JobSchedulerServiceTest { // failure = 0, systemStop = 1 JobStatus rescheduledJob = mService.getRescheduleJobForFailureLocked(originalJob, JobParameters.STOP_REASON_DEVICE_STATE, JobParameters.INTERNAL_STOP_REASON_DEVICE_THERMAL); assertEquals(nowElapsed + initialBackoffMs, rescheduledJob.getEarliestRunTime()); assertEquals(JobStatus.NO_LATEST_RUNTIME, rescheduledJob.getLatestRunTimeElapsed()); // failure = 0, systemStop = 2 rescheduledJob = mService.getRescheduleJobForFailureLocked(rescheduledJob, JobParameters.STOP_REASON_DEVICE_STATE, JobParameters.INTERNAL_STOP_REASON_PREEMPT); // failure = 0, systemStop = 3 rescheduledJob = mService.getRescheduleJobForFailureLocked(rescheduledJob, JobParameters.STOP_REASON_CONSTRAINT_CHARGING, JobParameters.INTERNAL_STOP_REASON_CONSTRAINTS_NOT_SATISFIED); assertEquals(nowElapsed + initialBackoffMs, rescheduledJob.getEarliestRunTime()); assertEquals(JobStatus.NO_LATEST_RUNTIME, rescheduledJob.getLatestRunTimeElapsed()); Loading @@ -403,6 +408,7 @@ public class JobSchedulerServiceTest { // failure = 0, systemStop = 2 * SYSTEM_STOP_TO_FAILURE_RATIO for (int i = 0; i < mService.mConstants.SYSTEM_STOP_TO_FAILURE_RATIO; ++i) { rescheduledJob = mService.getRescheduleJobForFailureLocked(rescheduledJob, JobParameters.STOP_REASON_SYSTEM_PROCESSING, JobParameters.INTERNAL_STOP_REASON_RTC_UPDATED); } assertEquals(nowElapsed + 2 * initialBackoffMs, rescheduledJob.getEarliestRunTime()); Loading @@ -410,17 +416,51 @@ public class JobSchedulerServiceTest { // failure = 1, systemStop = 2 * SYSTEM_STOP_TO_FAILURE_RATIO rescheduledJob = mService.getRescheduleJobForFailureLocked(rescheduledJob, JobParameters.STOP_REASON_TIMEOUT, JobParameters.INTERNAL_STOP_REASON_TIMEOUT); assertEquals(nowElapsed + 3 * initialBackoffMs, rescheduledJob.getEarliestRunTime()); assertEquals(JobStatus.NO_LATEST_RUNTIME, rescheduledJob.getLatestRunTimeElapsed()); // failure = 2, systemStop = 2 * SYSTEM_STOP_TO_FAILURE_RATIO rescheduledJob = mService.getRescheduleJobForFailureLocked(rescheduledJob, JobParameters.STOP_REASON_UNDEFINED, JobParameters.INTERNAL_STOP_REASON_SUCCESSFUL_FINISH); assertEquals(nowElapsed + 4 * initialBackoffMs, rescheduledJob.getEarliestRunTime()); assertEquals(JobStatus.NO_LATEST_RUNTIME, rescheduledJob.getLatestRunTimeElapsed()); } /** * Confirm that * {@link JobSchedulerService#getRescheduleJobForFailureLocked(JobStatus, int, int)} * returns a job that is correctly marked as demoted by the user. */ @Test public void testGetRescheduleJobForFailure_userDemotion() { JobStatus originalJob = createJobStatus("testGetRescheduleJobForFailure", createJobInfo()); assertEquals(0, originalJob.getInternalFlags() & JobStatus.INTERNAL_FLAG_DEMOTED_BY_USER); // Reschedule for a non-user reason JobStatus rescheduledJob = mService.getRescheduleJobForFailureLocked(originalJob, JobParameters.STOP_REASON_DEVICE_STATE, JobParameters.INTERNAL_STOP_REASON_DEVICE_THERMAL); assertEquals(0, rescheduledJob.getInternalFlags() & JobStatus.INTERNAL_FLAG_DEMOTED_BY_USER); // Reschedule for a user reason rescheduledJob = mService.getRescheduleJobForFailureLocked(rescheduledJob, JobParameters.STOP_REASON_USER, JobParameters.INTERNAL_STOP_REASON_USER_UI_STOP); assertNotEquals(0, rescheduledJob.getInternalFlags() & JobStatus.INTERNAL_FLAG_DEMOTED_BY_USER); // Reschedule a previously demoted job for a non-user reason rescheduledJob = mService.getRescheduleJobForFailureLocked(rescheduledJob, JobParameters.STOP_REASON_CONSTRAINT_CHARGING, JobParameters.INTERNAL_STOP_REASON_CONSTRAINTS_NOT_SATISFIED); assertNotEquals(0, rescheduledJob.getInternalFlags() & JobStatus.INTERNAL_FLAG_DEMOTED_BY_USER); } /** * Confirm that {@link JobSchedulerService#getRescheduleJobForPeriodic(JobStatus)} returns a job * with the correct delay and deadline constraints if the periodic job is scheduled with the Loading Loading @@ -731,6 +771,7 @@ public class JobSchedulerServiceTest { JobStatus job = createJobStatus("testGetRescheduleJobForPeriodic_insideWindow_failedJob", createJobInfo().setPeriodic(HOUR_IN_MILLIS)); JobStatus failedJob = mService.getRescheduleJobForFailureLocked(job, JobParameters.STOP_REASON_UNDEFINED, JobParameters.INTERNAL_STOP_REASON_SUCCESSFUL_FINISH); JobStatus rescheduledJob = mService.getRescheduleJobForPeriodic(failedJob); Loading @@ -739,6 +780,7 @@ public class JobSchedulerServiceTest { advanceElapsedClock(5 * MINUTE_IN_MILLIS); // now + 5 minutes failedJob = mService.getRescheduleJobForFailureLocked(job, JobParameters.STOP_REASON_UNDEFINED, JobParameters.INTERNAL_STOP_REASON_SUCCESSFUL_FINISH); advanceElapsedClock(5 * MINUTE_IN_MILLIS); // now + 10 minutes Loading @@ -748,6 +790,7 @@ public class JobSchedulerServiceTest { advanceElapsedClock(35 * MINUTE_IN_MILLIS); // now + 45 minutes failedJob = mService.getRescheduleJobForFailureLocked(job, JobParameters.STOP_REASON_UNDEFINED, JobParameters.INTERNAL_STOP_REASON_SUCCESSFUL_FINISH); advanceElapsedClock(10 * MINUTE_IN_MILLIS); // now + 55 minutes Loading @@ -759,6 +802,7 @@ public class JobSchedulerServiceTest { advanceElapsedClock(2 * MINUTE_IN_MILLIS); // now + 57 minutes failedJob = mService.getRescheduleJobForFailureLocked(job, JobParameters.STOP_REASON_UNDEFINED, JobParameters.INTERNAL_STOP_REASON_SUCCESSFUL_FINISH); advanceElapsedClock(2 * MINUTE_IN_MILLIS); // now + 59 minutes Loading Loading @@ -856,6 +900,7 @@ public class JobSchedulerServiceTest { JobStatus job = createJobStatus("testGetRescheduleJobForPeriodic_outsideWindow_failedJob", createJobInfo().setPeriodic(HOUR_IN_MILLIS)); JobStatus failedJob = mService.getRescheduleJobForFailureLocked(job, JobParameters.STOP_REASON_UNDEFINED, JobParameters.INTERNAL_STOP_REASON_SUCCESSFUL_FINISH); long now = sElapsedRealtimeClock.millis(); long nextWindowStartTime = now + HOUR_IN_MILLIS; Loading Loading @@ -893,6 +938,7 @@ public class JobSchedulerServiceTest { "testGetRescheduleJobForPeriodic_outsideWindow_flex_failedJob", createJobInfo().setPeriodic(HOUR_IN_MILLIS, 30 * MINUTE_IN_MILLIS)); JobStatus failedJob = mService.getRescheduleJobForFailureLocked(job, JobParameters.STOP_REASON_UNDEFINED, JobParameters.INTERNAL_STOP_REASON_SUCCESSFUL_FINISH); // First window starts 30 minutes from now. advanceElapsedClock(30 * MINUTE_IN_MILLIS); Loading Loading @@ -935,6 +981,7 @@ public class JobSchedulerServiceTest { "testGetRescheduleJobForPeriodic_outsideWindow_flex_failedJob_longPeriod", createJobInfo().setPeriodic(7 * DAY_IN_MILLIS, 9 * HOUR_IN_MILLIS)); JobStatus failedJob = mService.getRescheduleJobForFailureLocked(job, JobParameters.STOP_REASON_UNDEFINED, JobParameters.INTERNAL_STOP_REASON_SUCCESSFUL_FINISH); // First window starts 6.625 days from now. advanceElapsedClock(6 * DAY_IN_MILLIS + 15 * HOUR_IN_MILLIS); Loading