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

Commit 2bbce862 authored by Xin Guan's avatar Xin Guan Committed by Android (Google) Code Review
Browse files

Merge "JobScheduler: Enable quota optimization overrides" into main

parents 6a2e69fe bc6275a5
Loading
Loading
Loading
Loading
+46 −7
Original line number Diff line number Diff line
@@ -36,10 +36,14 @@ import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.AlarmManager;
import android.app.UidObserver;
import android.app.compat.CompatChanges;
import android.app.job.JobInfo;
import android.app.usage.UsageEvents;
import android.app.usage.UsageStatsManagerInternal;
import android.app.usage.UsageStatsManagerInternal.UsageEventListener;
import android.compat.annotation.ChangeId;
import android.compat.annotation.Disabled;
import android.compat.annotation.Overridable;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
@@ -132,6 +136,27 @@ public final class QuotaController extends StateController {
        return (int) (val ^ (val >>> 32));
    }

    /**
     * When enabled this change id overrides the default quota policy enforcement to the jobs
     * running in the foreground process state.
     */
    // TODO: b/379681266 - Might need some refactoring for a better app-compat strategy.
    @VisibleForTesting
    @ChangeId
    @Disabled // Disabled by default
    @Overridable // The change can be overridden in user build
    static final long OVERRIDE_QUOTA_ENFORCEMENT_TO_FGS_JOBS = 341201311L;

    /**
     * When enabled this change id overrides the default quota policy enforcement policy
     * the jobs started when app was in the TOP state.
     */
    @VisibleForTesting
    @ChangeId
    @Disabled // Disabled by default
    @Overridable // The change can be overridden in user build.
    static final long OVERRIDE_QUOTA_ENFORCEMENT_TO_TOP_STARTED_JOBS = 374323858L;

    @VisibleForTesting
    static class ExecutionStats {
        /**
@@ -622,7 +647,9 @@ public final class QuotaController extends StateController {
        }

        final int uid = jobStatus.getSourceUid();
        if (!Flags.enforceQuotaPolicyToTopStartedJobs() && mTopAppCache.get(uid)) {
        if ((!Flags.enforceQuotaPolicyToTopStartedJobs()
                || CompatChanges.isChangeEnabled(OVERRIDE_QUOTA_ENFORCEMENT_TO_TOP_STARTED_JOBS,
                        uid)) && mTopAppCache.get(uid)) {
            if (DEBUG) {
                Slog.d(TAG, jobStatus.toShortString() + " is top started job");
            }
@@ -659,7 +686,9 @@ public final class QuotaController extends StateController {
                timer.stopTrackingJob(jobStatus);
            }
        }
        if (!Flags.enforceQuotaPolicyToTopStartedJobs()) {
        if (!Flags.enforceQuotaPolicyToTopStartedJobs()
                || CompatChanges.isChangeEnabled(OVERRIDE_QUOTA_ENFORCEMENT_TO_TOP_STARTED_JOBS,
                        jobStatus.getSourceUid())) {
            mTopStartedJobs.remove(jobStatus);
        }
    }
@@ -772,7 +801,13 @@ public final class QuotaController extends StateController {

    /** @return true if the job was started while the app was in the TOP state. */
    private boolean isTopStartedJobLocked(@NonNull final JobStatus jobStatus) {
        return !Flags.enforceQuotaPolicyToTopStartedJobs() && mTopStartedJobs.contains(jobStatus);
        if (!Flags.enforceQuotaPolicyToTopStartedJobs()
                || CompatChanges.isChangeEnabled(OVERRIDE_QUOTA_ENFORCEMENT_TO_TOP_STARTED_JOBS,
                        jobStatus.getSourceUid())) {
            return mTopStartedJobs.contains(jobStatus);
        }

        return false;
    }

    /** Returns the maximum amount of time this job could run for. */
@@ -2634,9 +2669,13 @@ public final class QuotaController extends StateController {
    }

    @VisibleForTesting
    int getProcessStateQuotaFreeThreshold() {
        return Flags.enforceQuotaPolicyToFgsJobs() ? ActivityManager.PROCESS_STATE_BOUND_TOP :
                ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
    int getProcessStateQuotaFreeThreshold(int uid) {
        if (Flags.enforceQuotaPolicyToFgsJobs()
                && !CompatChanges.isChangeEnabled(OVERRIDE_QUOTA_ENFORCEMENT_TO_FGS_JOBS, uid)) {
            return ActivityManager.PROCESS_STATE_BOUND_TOP;
        }

        return ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
    }

    private class QcHandler extends Handler {
@@ -2776,7 +2815,7 @@ public final class QuotaController extends StateController {
                                isQuotaFree = true;
                            } else {
                                final boolean reprocess;
                                if (procState <= getProcessStateQuotaFreeThreshold()) {
                                if (procState <= getProcessStateQuotaFreeThreshold(uid)) {
                                    reprocess = !mForegroundUids.get(uid);
                                    mForegroundUids.put(uid, true);
                                    isQuotaFree = true;
+103 −1
Original line number Diff line number Diff line
@@ -65,6 +65,7 @@ import android.app.job.JobInfo;
import android.app.usage.UsageEvents;
import android.app.usage.UsageStatsManager;
import android.app.usage.UsageStatsManagerInternal;
import android.compat.testing.PlatformCompatChangeRule;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ApplicationInfo;
@@ -103,10 +104,13 @@ import com.android.server.job.controllers.QuotaController.TimedEvent;
import com.android.server.job.controllers.QuotaController.TimingSession;
import com.android.server.usage.AppStandbyInternal;

import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;

import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestRule;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.ArgumentMatchers;
@@ -135,6 +139,9 @@ public class QuotaControllerTest {
    private static final int SOURCE_USER_ID = 0;

    @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();

    @Rule
    public TestRule compatChangeRule = new PlatformCompatChangeRule();
    private QuotaController mQuotaController;
    private QuotaController.QcConstants mQcConstants;
    private JobSchedulerService.Constants mConstants = new JobSchedulerService.Constants();
@@ -303,7 +310,7 @@ public class QuotaControllerTest {

    private int getProcessStateQuotaFreeThreshold() {
        synchronized (mQuotaController.mLock) {
            return mQuotaController.getProcessStateQuotaFreeThreshold();
            return mQuotaController.getProcessStateQuotaFreeThreshold(mSourceUid);
        }
    }

@@ -5197,6 +5204,101 @@ public class QuotaControllerTest {
        assertFalse(jobBg2.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
    }

    @Test
    @EnableCompatChanges({QuotaController.OVERRIDE_QUOTA_ENFORCEMENT_TO_TOP_STARTED_JOBS,
            QuotaController.OVERRIDE_QUOTA_ENFORCEMENT_TO_FGS_JOBS})
    @RequiresFlagsEnabled({Flags.FLAG_ENFORCE_QUOTA_POLICY_TO_TOP_STARTED_JOBS,
            Flags.FLAG_ENFORCE_QUOTA_POLICY_TO_FGS_JOBS})
    public void testTracking_OutOfQuota_ForegroundAndBackground_CompactChangeOverrides() {
        setDischarging();

        JobStatus jobBg = createJobStatus("testTracking_OutOfQuota_ForegroundAndBackground", 1);
        JobStatus jobTop = createJobStatus("testTracking_OutOfQuota_ForegroundAndBackground", 2);
        trackJobs(jobBg, jobTop);
        setStandbyBucket(WORKING_INDEX, jobTop, jobBg); // 2 hour window
        // Now the package only has 20 seconds to run.
        final long remainingTimeMs = 20 * SECOND_IN_MILLIS;
        mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
                createTimingSession(
                        JobSchedulerService.sElapsedRealtimeClock.millis() - HOUR_IN_MILLIS,
                        10 * MINUTE_IN_MILLIS - remainingTimeMs, 1), false);

        InOrder inOrder = inOrder(mJobSchedulerService);

        // UID starts out inactive.
        setProcessState(ActivityManager.PROCESS_STATE_SERVICE);
        // Start the job.
        synchronized (mQuotaController.mLock) {
            mQuotaController.prepareForExecutionLocked(jobBg);
        }
        advanceElapsedClock(remainingTimeMs / 2);
        // New job starts after UID is in the foreground. Since the app is now in the foreground, it
        // should continue to have remainingTimeMs / 2 time remaining.
        setProcessState(ActivityManager.PROCESS_STATE_TOP);
        synchronized (mQuotaController.mLock) {
            mQuotaController.prepareForExecutionLocked(jobTop);
        }
        advanceElapsedClock(remainingTimeMs);

        // Wait for some extra time to allow for job processing.
        inOrder.verify(mJobSchedulerService,
                        timeout(remainingTimeMs + 2 * SECOND_IN_MILLIS).times(0))
                .onControllerStateChanged(argThat(jobs -> jobs.size() > 0));
        synchronized (mQuotaController.mLock) {
            assertEquals(remainingTimeMs / 2,
                    mQuotaController.getRemainingExecutionTimeLocked(jobBg));
            assertEquals(remainingTimeMs / 2,
                    mQuotaController.getRemainingExecutionTimeLocked(jobTop));
        }
        // Go to a background state.
        setProcessState(ActivityManager.PROCESS_STATE_TOP_SLEEPING);
        advanceElapsedClock(remainingTimeMs / 2 + 1);
        // Only Bg job will be changed from in-quota to out-of-quota.
        inOrder.verify(mJobSchedulerService,
                        timeout(remainingTimeMs / 2 + 2 * SECOND_IN_MILLIS).times(1))
                .onControllerStateChanged(argThat(jobs -> jobs.size() == 1));
        // Top job should still be allowed to run.
        assertFalse(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
        assertTrue(jobTop.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));

        // New jobs to run.
        JobStatus jobBg2 = createJobStatus("testTracking_OutOfQuota_ForegroundAndBackground", 3);
        JobStatus jobTop2 = createJobStatus("testTracking_OutOfQuota_ForegroundAndBackground", 4);
        JobStatus jobFg = createJobStatus("testTracking_OutOfQuota_ForegroundAndBackground", 5);
        setStandbyBucket(WORKING_INDEX, jobBg2, jobTop2, jobFg);

        advanceElapsedClock(20 * SECOND_IN_MILLIS);
        setProcessState(ActivityManager.PROCESS_STATE_TOP);
        inOrder.verify(mJobSchedulerService, timeout(SECOND_IN_MILLIS).times(1))
                .onControllerStateChanged(argThat(jobs -> jobs.size() == 1));
        trackJobs(jobFg, jobTop);
        synchronized (mQuotaController.mLock) {
            mQuotaController.prepareForExecutionLocked(jobTop);
        }
        assertTrue(jobTop.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
        assertTrue(jobFg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
        assertTrue(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));

        // App still in foreground so everything should be in quota.
        advanceElapsedClock(20 * SECOND_IN_MILLIS);
        setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
        assertTrue(jobTop.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
        assertTrue(jobFg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
        assertTrue(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));

        advanceElapsedClock(20 * SECOND_IN_MILLIS);
        setProcessState(ActivityManager.PROCESS_STATE_SERVICE);
        inOrder.verify(mJobSchedulerService, timeout(SECOND_IN_MILLIS).times(1))
                .onControllerStateChanged(argThat(jobs -> jobs.size() == 2));
        // App is now in background and out of quota. Fg should now change to out of quota since it
        // wasn't started. Top should remain in quota since it started when the app was in TOP.
        assertTrue(jobTop.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
        assertFalse(jobFg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
        assertFalse(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
        trackJobs(jobBg2);
        assertFalse(jobBg2.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
    }

    /**
     * Tests that TOP jobs are stopped when an app runs out of quota.
     */