Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d2c6a356 authored by Kweku Adams's avatar Kweku Adams Committed by Android (Google) Code Review
Browse files

Merge "Demote jobs if the user stops the app."

parents 2a893219 9fc560f1
Loading
Loading
Loading
Loading
+9 −3
Original line number Diff line number Diff line
@@ -16,6 +16,8 @@

package com.android.server.job;

import android.app.job.JobParameters;

import com.android.server.job.controllers.JobStatus;

/**
@@ -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);
}
+30 −7
Original line number Diff line number Diff line
@@ -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);
            }
        }
    }

@@ -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();

@@ -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++;
@@ -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());
@@ -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);
@@ -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
+4 −2
Original line number Diff line number Diff line
@@ -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) {
@@ -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(),
@@ -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);
    }

+7 −2
Original line number Diff line number Diff line
@@ -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
@@ -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;
    }

    /**
+49 −2
Original line number Diff line number Diff line
@@ -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;
@@ -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;
@@ -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());
@@ -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());
@@ -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
@@ -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);
@@ -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

@@ -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

@@ -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

@@ -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;
@@ -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);
@@ -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