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

Commit b4b8b9ce authored by Kweku Adams's avatar Kweku Adams
Browse files

Making ConnectivityController use remaining quota for job eligibility.

Have ConnectivityController use the app's remaining quota time to
determine if a job would complete in time instead of the maximum
execution time. This will prevent JobScheduler from starting a
connectivity job if there isn't enough remaining quota time to complete
the data transfer.

Bug: 133891979
Test: atest com.android.server.job.JobSchedulerServiceTest
Test: atest com.android.server.job.controllers.ConnectivityControllerTest
Change-Id: Ia4d2de7bdab52230ea4649532a8e5b4526e9f0fa
parent ee577653
Loading
Loading
Loading
Loading
+16 −1
Original line number Diff line number Diff line
@@ -187,6 +187,9 @@ public class JobSchedulerService extends com.android.server.SystemService
    private final StorageController mStorageController;
    /** Need directly for sending uid state changes */
    private final DeviceIdleJobsController mDeviceIdleJobsController;
    /** Needed to get remaining quota time. */
    private final QuotaController mQuotaController;

    /** Need directly for receiving thermal events */
    private IThermalService mThermalService;
    /** Thermal constraint. */
@@ -1338,7 +1341,8 @@ public class JobSchedulerService extends com.android.server.SystemService
        mControllers.add(new ContentObserverController(this));
        mDeviceIdleJobsController = new DeviceIdleJobsController(this);
        mControllers.add(mDeviceIdleJobsController);
        mControllers.add(new QuotaController(this));
        mQuotaController = new QuotaController(this);
        mControllers.add(mQuotaController);

        // If the job store determined that it can't yet reschedule persisted jobs,
        // we need to start watching the clock.
@@ -2397,6 +2401,17 @@ public class JobSchedulerService extends com.android.server.SystemService
        return isComponentUsable(job);
    }

    /** Returns the maximum amount of time this job could run for. */
    public long getMaxJobExecutionTimeMs(JobStatus job) {
        synchronized (mLock) {
            if (mConstants.USE_HEARTBEATS) {
                return JobServiceContext.EXECUTING_TIMESLICE_MILLIS;
            }
            return Math.min(mQuotaController.getMaxJobExecutionTimeMsLocked(job),
                    JobServiceContext.EXECUTING_TIMESLICE_MILLIS);
        }
    }

    /**
     * Reconcile jobs in the pending queue against available execution contexts.
     * A controller can force a job into the pending queue even if it's already running, but
+26 −10
Original line number Diff line number Diff line
@@ -88,8 +88,11 @@ public final class ConnectivityController extends StateController implements
    @GuardedBy("mLock")
    private final ArraySet<Network> mAvailableNetworks = new ArraySet<>();

    private boolean mUseQuotaLimit;

    private static final int MSG_DATA_SAVER_TOGGLED = 0;
    private static final int MSG_UID_RULES_CHANGES = 1;
    private static final int MSG_REEVALUATE_JOBS = 2;

    private final Handler mHandler;

@@ -107,6 +110,8 @@ public final class ConnectivityController extends StateController implements
        mConnManager.registerNetworkCallback(request, mNetworkCallback);

        mNetPolicyManager.registerListener(mNetPolicyListener);

        mUseQuotaLimit = !mConstants.USE_HEARTBEATS;
    }

    @GuardedBy("mLock")
@@ -149,6 +154,10 @@ public final class ConnectivityController extends StateController implements
            }
            mRequestedWhitelistJobs.clear();
        }
        if (mUseQuotaLimit == mConstants.USE_HEARTBEATS) {
            mUseQuotaLimit = !mConstants.USE_HEARTBEATS;
            mHandler.obtainMessage(MSG_REEVALUATE_JOBS).sendToTarget();
        }
    }

    /**
@@ -318,9 +327,12 @@ public final class ConnectivityController extends StateController implements
     * connection, it would take 10.4 minutes, and has no chance of succeeding
     * before the job times out, so we'd be insane to try running it.
     */
    @SuppressWarnings("unused")
    private static boolean isInsane(JobStatus jobStatus, Network network,
    private boolean isInsane(JobStatus jobStatus, Network network,
            NetworkCapabilities capabilities, Constants constants) {
        final long maxJobExecutionTimeMs = mUseQuotaLimit
                ? mService.getMaxJobExecutionTimeMs(jobStatus)
                : JobServiceContext.EXECUTING_TIMESLICE_MILLIS;

        final long downloadBytes = jobStatus.getEstimatedNetworkDownloadBytes();
        if (downloadBytes != JobInfo.NETWORK_BYTES_UNKNOWN) {
            final long bandwidth = capabilities.getLinkDownstreamBandwidthKbps();
@@ -329,10 +341,11 @@ public final class ConnectivityController extends StateController implements
                // Divide by 8 to convert bits to bytes.
                final long estimatedMillis = ((downloadBytes * DateUtils.SECOND_IN_MILLIS)
                        / (DataUnit.KIBIBYTES.toBytes(bandwidth) / 8));
                if (estimatedMillis > JobServiceContext.EXECUTING_TIMESLICE_MILLIS) {
                if (estimatedMillis > maxJobExecutionTimeMs) {
                    // If we'd never finish before the timeout, we'd be insane!
                    Slog.w(TAG, "Estimated " + downloadBytes + " download bytes over " + bandwidth
                            + " kbps network would take " + estimatedMillis + "ms; that's insane!");
                            + " kbps network would take " + estimatedMillis + "ms and job has "
                            + maxJobExecutionTimeMs + "ms to run; that's insane!");
                    return true;
                }
            }
@@ -346,10 +359,11 @@ public final class ConnectivityController extends StateController implements
                // Divide by 8 to convert bits to bytes.
                final long estimatedMillis = ((uploadBytes * DateUtils.SECOND_IN_MILLIS)
                        / (DataUnit.KIBIBYTES.toBytes(bandwidth) / 8));
                if (estimatedMillis > JobServiceContext.EXECUTING_TIMESLICE_MILLIS) {
                if (estimatedMillis > maxJobExecutionTimeMs) {
                    // If we'd never finish before the timeout, we'd be insane!
                    Slog.w(TAG, "Estimated " + uploadBytes + " upload bytes over " + bandwidth
                            + " kbps network would take " + estimatedMillis + "ms; that's insane!");
                            + " kbps network would take " + estimatedMillis + "ms and job has "
                            + maxJobExecutionTimeMs + "ms to run; that's insane!");
                    return true;
                }
            }
@@ -358,7 +372,6 @@ public final class ConnectivityController extends StateController implements
        return false;
    }

    @SuppressWarnings("unused")
    private static boolean isCongestionDelayed(JobStatus jobStatus, Network network,
            NetworkCapabilities capabilities, Constants constants) {
        // If network is congested, and job is less than 50% through the
@@ -370,14 +383,12 @@ public final class ConnectivityController extends StateController implements
        }
    }

    @SuppressWarnings("unused")
    private static boolean isStrictSatisfied(JobStatus jobStatus, Network network,
            NetworkCapabilities capabilities, Constants constants) {
        return jobStatus.getJob().getRequiredNetwork().networkCapabilities
                .satisfiedByNetworkCapabilities(capabilities);
    }

    @SuppressWarnings("unused")
    private static boolean isRelaxedSatisfied(JobStatus jobStatus, Network network,
            NetworkCapabilities capabilities, Constants constants) {
        // Only consider doing this for prefetching jobs
@@ -398,7 +409,7 @@ public final class ConnectivityController extends StateController implements
    }

    @VisibleForTesting
    static boolean isSatisfied(JobStatus jobStatus, Network network,
    boolean isSatisfied(JobStatus jobStatus, Network network,
            NetworkCapabilities capabilities, Constants constants) {
        // Zeroth, we gotta have a network to think about being satisfied
        if (network == null || capabilities == null) return false;
@@ -594,6 +605,9 @@ public final class ConnectivityController extends StateController implements
                    case MSG_UID_RULES_CHANGES:
                        updateTrackedJobs(msg.arg1, null);
                        break;
                    case MSG_REEVALUATE_JOBS:
                        updateTrackedJobs(-1, null);
                        break;
                }
            }
        }
@@ -603,6 +617,8 @@ public final class ConnectivityController extends StateController implements
    @Override
    public void dumpControllerStateLocked(IndentingPrintWriter pw,
            Predicate<JobStatus> predicate) {
        pw.print("mUseQuotaLimit="); pw.println(mUseQuotaLimit);

        if (mRequestedWhitelistJobs.size() > 0) {
            pw.print("Requested standby exceptions:");
            for (int i = 0; i < mRequestedWhitelistJobs.size(); i++) {
+13 −0
Original line number Diff line number Diff line
@@ -68,6 +68,7 @@ import com.android.internal.util.IndentingPrintWriter;
import com.android.server.LocalServices;
import com.android.server.job.ConstantsProto;
import com.android.server.job.JobSchedulerService;
import com.android.server.job.JobServiceContext;
import com.android.server.job.StateControllerProto;

import java.util.ArrayList;
@@ -737,6 +738,18 @@ public final class QuotaController extends StateController {
        return mTopStartedJobs.contains(jobStatus);
    }

    /** Returns the maximum amount of time this job could run for. */
    public long getMaxJobExecutionTimeMsLocked(@NonNull final JobStatus jobStatus) {
        // If quota is currently "free", then the job can run for the full amount of time.
        if (mChargeTracker.isCharging()
                || mInParole
                || isTopStartedJobLocked(jobStatus)
                || isUidInForeground(jobStatus.getSourceUid())) {
            return JobServiceContext.EXECUTING_TIMESLICE_MILLIS;
        }
        return getRemainingExecutionTimeLocked(jobStatus);
    }

    /**
     * Returns an appropriate standby bucket for the job, taking into account any standby
     * exemptions.
+52 −16
Original line number Diff line number Diff line
@@ -59,6 +59,7 @@ import android.util.DataUnit;
import com.android.server.LocalServices;
import com.android.server.job.JobSchedulerService;
import com.android.server.job.JobSchedulerService.Constants;
import com.android.server.job.JobServiceContext;
import com.android.server.net.NetworkPolicyManagerInternal;

import org.junit.Before;
@@ -138,22 +139,53 @@ public class ConnectivityControllerTest {
                        DataUnit.MEBIBYTES.toBytes(1))
                .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);

        final ConnectivityController controller = new ConnectivityController(mService);
        when(mService.getMaxJobExecutionTimeMs(any()))
                .thenReturn(JobServiceContext.EXECUTING_TIMESLICE_MILLIS);

        // Slow network is too slow
        assertFalse(ConnectivityController.isSatisfied(createJobStatus(job), net,
        assertFalse(controller.isSatisfied(createJobStatus(job), net,
                createCapabilities().setLinkUpstreamBandwidthKbps(1)
                        .setLinkDownstreamBandwidthKbps(1), mConstants));
        // Slow downstream
        assertFalse(ConnectivityController.isSatisfied(createJobStatus(job), net,
        assertFalse(controller.isSatisfied(createJobStatus(job), net,
                createCapabilities().setLinkUpstreamBandwidthKbps(1024)
                        .setLinkDownstreamBandwidthKbps(1), mConstants));
        // Slow upstream
        assertFalse(ConnectivityController.isSatisfied(createJobStatus(job), net,
        assertFalse(controller.isSatisfied(createJobStatus(job), net,
                createCapabilities().setLinkUpstreamBandwidthKbps(1)
                        .setLinkDownstreamBandwidthKbps(1024), mConstants));
        // Fast network looks great
        assertTrue(ConnectivityController.isSatisfied(createJobStatus(job), net,
        assertTrue(controller.isSatisfied(createJobStatus(job), net,
                createCapabilities().setLinkUpstreamBandwidthKbps(1024)
                        .setLinkDownstreamBandwidthKbps(1024), mConstants));
        // Slow network still good given time
        assertTrue(controller.isSatisfied(createJobStatus(job), net,
                createCapabilities().setLinkUpstreamBandwidthKbps(130)
                        .setLinkDownstreamBandwidthKbps(130), mConstants));

        when(mService.getMaxJobExecutionTimeMs(any())).thenReturn(60_000L);

        // Slow network is too slow
        assertFalse(controller.isSatisfied(createJobStatus(job), net,
                createCapabilities().setLinkUpstreamBandwidthKbps(1)
                        .setLinkDownstreamBandwidthKbps(1), mConstants));
        // Slow downstream
        assertFalse(controller.isSatisfied(createJobStatus(job), net,
                createCapabilities().setLinkUpstreamBandwidthKbps(137)
                        .setLinkDownstreamBandwidthKbps(1), mConstants));
        // Slow upstream
        assertFalse(controller.isSatisfied(createJobStatus(job), net,
                createCapabilities().setLinkUpstreamBandwidthKbps(1)
                        .setLinkDownstreamBandwidthKbps(137), mConstants));
        // Network good enough
        assertTrue(controller.isSatisfied(createJobStatus(job), net,
                createCapabilities().setLinkUpstreamBandwidthKbps(137)
                        .setLinkDownstreamBandwidthKbps(137), mConstants));
        // Network slightly too slow given reduced time
        assertFalse(controller.isSatisfied(createJobStatus(job), net,
                createCapabilities().setLinkUpstreamBandwidthKbps(130)
                        .setLinkDownstreamBandwidthKbps(130), mConstants));
    }

    @Test
@@ -166,21 +198,23 @@ public class ConnectivityControllerTest {
        final JobStatus early = createJobStatus(job, now - 1000, now + 2000);
        final JobStatus late = createJobStatus(job, now - 2000, now + 1000);

        final ConnectivityController controller = new ConnectivityController(mService);

        // Uncongested network is whenever
        {
            final Network net = new Network(101);
            final NetworkCapabilities caps = createCapabilities()
                    .addCapability(NET_CAPABILITY_NOT_CONGESTED);
            assertTrue(ConnectivityController.isSatisfied(early, net, caps, mConstants));
            assertTrue(ConnectivityController.isSatisfied(late, net, caps, mConstants));
            assertTrue(controller.isSatisfied(early, net, caps, mConstants));
            assertTrue(controller.isSatisfied(late, net, caps, mConstants));
        }

        // Congested network is more selective
        {
            final Network net = new Network(101);
            final NetworkCapabilities caps = createCapabilities();
            assertFalse(ConnectivityController.isSatisfied(early, net, caps, mConstants));
            assertTrue(ConnectivityController.isSatisfied(late, net, caps, mConstants));
            assertFalse(controller.isSatisfied(early, net, caps, mConstants));
            assertTrue(controller.isSatisfied(late, net, caps, mConstants));
        }
    }

@@ -198,16 +232,18 @@ public class ConnectivityControllerTest {
        final JobStatus earlyPrefetch = createJobStatus(job, now - 1000, now + 2000);
        final JobStatus latePrefetch = createJobStatus(job, now - 2000, now + 1000);

        final ConnectivityController controller = new ConnectivityController(mService);

        // Unmetered network is whenever
        {
            final Network net = new Network(101);
            final NetworkCapabilities caps = createCapabilities()
                    .addCapability(NET_CAPABILITY_NOT_CONGESTED)
                    .addCapability(NET_CAPABILITY_NOT_METERED);
            assertTrue(ConnectivityController.isSatisfied(early, net, caps, mConstants));
            assertTrue(ConnectivityController.isSatisfied(late, net, caps, mConstants));
            assertTrue(ConnectivityController.isSatisfied(earlyPrefetch, net, caps, mConstants));
            assertTrue(ConnectivityController.isSatisfied(latePrefetch, net, caps, mConstants));
            assertTrue(controller.isSatisfied(early, net, caps, mConstants));
            assertTrue(controller.isSatisfied(late, net, caps, mConstants));
            assertTrue(controller.isSatisfied(earlyPrefetch, net, caps, mConstants));
            assertTrue(controller.isSatisfied(latePrefetch, net, caps, mConstants));
        }

        // Metered network is only when prefetching and late
@@ -215,10 +251,10 @@ public class ConnectivityControllerTest {
            final Network net = new Network(101);
            final NetworkCapabilities caps = createCapabilities()
                    .addCapability(NET_CAPABILITY_NOT_CONGESTED);
            assertFalse(ConnectivityController.isSatisfied(early, net, caps, mConstants));
            assertFalse(ConnectivityController.isSatisfied(late, net, caps, mConstants));
            assertFalse(ConnectivityController.isSatisfied(earlyPrefetch, net, caps, mConstants));
            assertTrue(ConnectivityController.isSatisfied(latePrefetch, net, caps, mConstants));
            assertFalse(controller.isSatisfied(early, net, caps, mConstants));
            assertFalse(controller.isSatisfied(late, net, caps, mConstants));
            assertFalse(controller.isSatisfied(earlyPrefetch, net, caps, mConstants));
            assertTrue(controller.isSatisfied(latePrefetch, net, caps, mConstants));
        }
    }

+33 −0
Original line number Diff line number Diff line
@@ -28,6 +28,7 @@ import static com.android.server.job.JobSchedulerService.FREQUENT_INDEX;
import static com.android.server.job.JobSchedulerService.NEVER_INDEX;
import static com.android.server.job.JobSchedulerService.RARE_INDEX;
import static com.android.server.job.JobSchedulerService.WORKING_INDEX;
import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -73,6 +74,7 @@ import androidx.test.runner.AndroidJUnit4;
import com.android.server.LocalServices;
import com.android.server.job.JobSchedulerService;
import com.android.server.job.JobSchedulerService.Constants;
import com.android.server.job.JobServiceContext;
import com.android.server.job.JobStore;
import com.android.server.job.controllers.QuotaController.ExecutionStats;
import com.android.server.job.controllers.QuotaController.TimingSession;
@@ -975,6 +977,37 @@ public class QuotaControllerTest {
        assertEquals(expectedStats, newStatsRare);
    }

    @Test
    public void testGetMaxJobExecutionTimeLocked() {
        mQuotaController.saveTimingSession(0, SOURCE_PACKAGE,
                createTimingSession(sElapsedRealtimeClock.millis() - (6 * MINUTE_IN_MILLIS),
                        3 * MINUTE_IN_MILLIS, 5));
        JobStatus job = createJobStatus("testGetMaxJobExecutionTimeLocked", 0);
        job.setStandbyBucket(RARE_INDEX);

        setCharging();
        assertEquals(JobServiceContext.EXECUTING_TIMESLICE_MILLIS,
                mQuotaController.getMaxJobExecutionTimeMsLocked((job)));

        setDischarging();
        setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
        assertEquals(JobServiceContext.EXECUTING_TIMESLICE_MILLIS,
                mQuotaController.getMaxJobExecutionTimeMsLocked((job)));

        // Top-started job
        setProcessState(ActivityManager.PROCESS_STATE_TOP);
        mQuotaController.maybeStartTrackingJobLocked(job, null);
        mQuotaController.prepareForExecutionLocked(job);
        setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
        assertEquals(JobServiceContext.EXECUTING_TIMESLICE_MILLIS,
                mQuotaController.getMaxJobExecutionTimeMsLocked((job)));
        mQuotaController.maybeStopTrackingJobLocked(job, null, false);

        setProcessState(ActivityManager.PROCESS_STATE_RECEIVER);
        assertEquals(7 * MINUTE_IN_MILLIS,
                mQuotaController.getMaxJobExecutionTimeMsLocked(job));
    }

    /**
     * Test getTimeUntilQuotaConsumedLocked when the determination is based within the bucket
     * window.