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

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

Integrate JobScheduler with TARE.

1. Send job events to EconomyManager.
2. Integrate TareController with the rest of the JS flow and toggle
   between TareController and QuotaController based on the settings
   flags.

Bug: 158300259
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: Ia1e504dcb4e77f02b5f29a459701e090826736f2
parent f8dd1735
Loading
Loading
Loading
Loading
+65 −6
Original line number Diff line number Diff line
@@ -40,6 +40,7 @@ import android.app.usage.UsageStatsManager;
import android.app.usage.UsageStatsManagerInternal;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -50,6 +51,7 @@ import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.PackageManagerInternal;
import android.content.pm.ParceledListSlice;
import android.content.pm.ServiceInfo;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.BatteryStats;
import android.os.BatteryStatsInternal;
@@ -66,6 +68,7 @@ import android.os.SystemClock;
import android.os.UserHandle;
import android.os.WorkSource;
import android.provider.DeviceConfig;
import android.provider.Settings;
import android.text.format.DateUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -106,6 +109,7 @@ import com.android.server.job.controllers.QuotaController;
import com.android.server.job.controllers.RestrictingController;
import com.android.server.job.controllers.StateController;
import com.android.server.job.controllers.StorageController;
import com.android.server.job.controllers.TareController;
import com.android.server.job.controllers.TimeController;
import com.android.server.job.restrictions.JobRestriction;
import com.android.server.job.restrictions.ThermalStatusRestriction;
@@ -250,6 +254,8 @@ public class JobSchedulerService extends com.android.server.SystemService
    private final DeviceIdleJobsController mDeviceIdleJobsController;
    /** Needed to get remaining quota time. */
    private final QuotaController mQuotaController;
    /** Needed to get max execution time and expedited-job allowance. */
    private final TareController mTareController;
    /**
     * List of restrictions.
     * Note: do not add to or remove from this list at runtime except in the constructor, because we
@@ -344,14 +350,40 @@ public class JobSchedulerService extends com.android.server.SystemService
    // (ScheduledJobStateChanged and JobStatusDumpProto).
    public static final int RESTRICTED_INDEX = 5;

    private class ConstantsObserver implements DeviceConfig.OnPropertiesChangedListener {
    private class ConstantsObserver extends ContentObserver
            implements DeviceConfig.OnPropertiesChangedListener {
        private final ContentResolver mContentResolver;

        ConstantsObserver(Handler handler, Context context) {
            super(handler);
            mContentResolver = context.getContentResolver();
        }

        public void start() {
            DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_JOB_SCHEDULER,
                    JobSchedulerBackgroundThread.getExecutor(), this);
            mContentResolver.registerContentObserver(
                    Settings.Global.getUriFor(Settings.Global.ENABLE_TARE), false, this);
            // Load all the constants.
            synchronized (mLock) {
                mConstants.updateSettingsConstantsLocked(mContentResolver);
            }
            onPropertiesChanged(DeviceConfig.getProperties(DeviceConfig.NAMESPACE_JOB_SCHEDULER));
        }

        @Override
        public void onChange(boolean selfChange) {
            synchronized (mLock) {
                if (mConstants.updateSettingsConstantsLocked(mContentResolver)) {
                    for (int controller = 0; controller < mControllers.size(); controller++) {
                        final StateController sc = mControllers.get(controller);
                        sc.onConstantsUpdatedLocked();
                    }
                    onControllerStateChanged(null);
                }
            }
        }

        @Override
        public void onPropertiesChanged(DeviceConfig.Properties properties) {
            boolean apiQuotaScheduleUpdated = false;
@@ -482,6 +514,7 @@ public class JobSchedulerService extends com.android.server.SystemService
        public static final long DEFAULT_RUNTIME_MIN_GUARANTEE_MS = 10 * MINUTE_IN_MILLIS;
        @VisibleForTesting
        public static final long DEFAULT_RUNTIME_MIN_EJ_GUARANTEE_MS = 3 * MINUTE_IN_MILLIS;
        private static final boolean DEFAULT_USE_TARE_POLICY = false;

        /**
         * Minimum # of non-ACTIVE jobs for which the JMS will be happy running some work early.
@@ -559,6 +592,11 @@ public class JobSchedulerService extends com.android.server.SystemService
         */
        public long RUNTIME_MIN_EJ_GUARANTEE_MS = DEFAULT_RUNTIME_MIN_EJ_GUARANTEE_MS;

        /**
         * If true, use TARE policy for job limiting. If false, use quotas.
         */
        public boolean USE_TARE_POLICY = DEFAULT_USE_TARE_POLICY;

        private void updateBatchingConstantsLocked() {
            MIN_READY_NON_ACTIVE_JOBS_COUNT = DeviceConfig.getInt(
                    DeviceConfig.NAMESPACE_JOB_SCHEDULER,
@@ -637,6 +675,17 @@ public class JobSchedulerService extends com.android.server.SystemService
                            DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS));
        }

        private boolean updateSettingsConstantsLocked(ContentResolver contentResolver) {
            boolean changed = false;
            final boolean isTareEnabled = Settings.Global.getInt(contentResolver,
                    Settings.Global.ENABLE_TARE, Settings.Global.DEFAULT_ENABLE_TARE) == 1;
            if (USE_TARE_POLICY != isTareEnabled) {
                USE_TARE_POLICY = isTareEnabled;
                changed = true;
            }
            return changed;
        }

        void dump(IndentingPrintWriter pw) {
            pw.println("Settings:");
            pw.increaseIndent();
@@ -665,6 +714,8 @@ public class JobSchedulerService extends com.android.server.SystemService
            pw.print(KEY_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS, RUNTIME_FREE_QUOTA_MAX_LIMIT_MS)
                    .println();

            pw.print(Settings.Global.ENABLE_TARE, USE_TARE_POLICY).println();

            pw.decreaseIndent();
        }

@@ -1130,10 +1181,13 @@ public class JobSchedulerService extends com.android.server.SystemService
            JobStatus jobStatus = JobStatus.createFromJobInfo(job, uId, packageName, userId, tag);

            // Return failure early if expedited job quota used up.
            if (jobStatus.isRequestedExpeditedJob()
                    && !mQuotaController.isWithinEJQuotaLocked(jobStatus)) {
            if (jobStatus.isRequestedExpeditedJob()) {
                if ((mConstants.USE_TARE_POLICY && !mTareController.canScheduleEJ(jobStatus))
                        || (!mConstants.USE_TARE_POLICY
                        && !mQuotaController.isWithinEJQuotaLocked(jobStatus))) {
                    return JobScheduler.RESULT_FAILURE;
                }
            }

            // Give exemption if the source is in the foreground just now.
            // Note if it's a sync job, this method is called on the handler so it's not exactly
@@ -1474,7 +1528,7 @@ public class JobSchedulerService extends com.android.server.SystemService

        mHandler = new JobHandler(context.getMainLooper());
        mConstants = new Constants();
        mConstantsObserver = new ConstantsObserver();
        mConstantsObserver = new ConstantsObserver(mHandler, context);
        mJobSchedulerStub = new JobSchedulerStub();

        mConcurrencyManager = new JobConcurrencyManager(this);
@@ -1519,6 +1573,9 @@ public class JobSchedulerService extends com.android.server.SystemService
                new QuotaController(this, backgroundJobsController, connectivityController);
        mControllers.add(mQuotaController);
        mControllers.add(new ComponentController(this));
        mTareController =
                new TareController(this, backgroundJobsController, connectivityController);
        mControllers.add(mTareController);

        mRestrictiveControllers = new ArrayList<>();
        mRestrictiveControllers.add(mBatteryController);
@@ -2560,7 +2617,9 @@ public class JobSchedulerService extends com.android.server.SystemService
    public long getMaxJobExecutionTimeMs(JobStatus job) {
        synchronized (mLock) {
            return Math.min(mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
                    mQuotaController.getMaxJobExecutionTimeMsLocked(job));
                    mConstants.USE_TARE_POLICY
                            ? mTareController.getMaxJobExecutionTimeMsLocked(job)
                            : mQuotaController.getMaxJobExecutionTimeMsLocked(job));
        }
    }

+38 −0
Original line number Diff line number Diff line
@@ -54,6 +54,9 @@ import com.android.internal.util.FrameworkStatsLog;
import com.android.server.EventLogTags;
import com.android.server.LocalServices;
import com.android.server.job.controllers.JobStatus;
import com.android.server.tare.EconomicPolicy;
import com.android.server.tare.EconomyManagerInternal;
import com.android.server.tare.JobSchedulerEconomicPolicy;

/**
 * Handles client binding and lifecycle of a job. Jobs execute one at a time on an instance of this
@@ -107,6 +110,7 @@ public final class JobServiceContext implements ServiceConnection {
    private final Context mContext;
    private final Object mLock;
    private final IBatteryStats mBatteryStats;
    private final EconomyManagerInternal mEconomyManagerInternal;
    private final JobPackageTracker mJobPackageTracker;
    private final PowerManager mPowerManager;
    private PowerManager.WakeLock mWakeLock;
@@ -211,6 +215,7 @@ public final class JobServiceContext implements ServiceConnection {
        mLock = service.getLock();
        mService = service;
        mBatteryStats = batteryStats;
        mEconomyManagerInternal = LocalServices.getService(EconomyManagerInternal.class);
        mJobPackageTracker = tracker;
        mCallbackHandler = new JobServiceHandler(looper);
        mJobConcurrencyManager = concurrencyManager;
@@ -288,6 +293,11 @@ public final class JobServiceContext implements ServiceConnection {
            mWakeLock.setReferenceCounted(false);
            mWakeLock.acquire();

            // Note the start when we try to bind so that the app is charged for some processing
            // even if binding fails.
            mEconomyManagerInternal.noteInstantaneousEvent(
                    job.getSourceUserId(), job.getSourcePackageName(),
                    getStartActionId(job), String.valueOf(job.getJobId()));
            mVerb = VERB_BINDING;
            scheduleOpTimeOutLocked();
            final Intent intent = new Intent().setComponent(job.getServiceComponent());
@@ -350,6 +360,9 @@ public final class JobServiceContext implements ServiceConnection {
            } catch (RemoteException e) {
                // Whatever.
            }
            mEconomyManagerInternal.noteOngoingEventStarted(
                    job.getSourceUserId(), job.getSourcePackageName(),
                    getRunningActionId(job), String.valueOf(job.getJobId()));
            final String jobPackage = job.getSourcePackageName();
            final int jobUserId = job.getSourceUserId();
            UsageStatsManagerInternal usageStats =
@@ -363,6 +376,22 @@ public final class JobServiceContext implements ServiceConnection {
        }
    }

    @EconomicPolicy.AppAction
    private static int getStartActionId(@NonNull JobStatus job) {
        if (job.startedAsExpeditedJob || job.shouldTreatAsExpeditedJob()) {
            return JobSchedulerEconomicPolicy.ACTION_JOB_MAX_START;
        }
        return JobSchedulerEconomicPolicy.ACTION_JOB_DEFAULT_START;
    }

    @EconomicPolicy.AppAction
    private static int getRunningActionId(@NonNull JobStatus job) {
        if (job.startedAsExpeditedJob || job.shouldTreatAsExpeditedJob()) {
            return JobSchedulerEconomicPolicy.ACTION_JOB_MAX_RUNNING;
        }
        return JobSchedulerEconomicPolicy.ACTION_JOB_DEFAULT_RUNNING;
    }

    /**
     * Used externally to query the running job. Will return null if there is no job running.
     */
@@ -946,6 +975,15 @@ public final class JobServiceContext implements ServiceConnection {
        } catch (RemoteException e) {
            // Whatever.
        }
        mEconomyManagerInternal.noteOngoingEventStopped(
                mRunningJob.getSourceUserId(), mRunningJob.getSourcePackageName(),
                getRunningActionId(mRunningJob), String.valueOf(mRunningJob.getJobId()));
        if (mParams.getStopReason() == JobParameters.STOP_REASON_TIMEOUT) {
            mEconomyManagerInternal.noteInstantaneousEvent(
                    mRunningJob.getSourceUserId(), mRunningJob.getSourcePackageName(),
                    JobSchedulerEconomicPolicy.ACTION_JOB_TIMEOUT,
                    String.valueOf(mRunningJob.getJobId()));
        }
        if (mWakeLock != null) {
            mWakeLock.release();
        }
+2 −1
Original line number Diff line number Diff line
@@ -661,7 +661,8 @@ public final class ConnectivityController extends RestrictingController implemen
            NetworkCapabilities capabilities, Constants constants) {
        // A restricted job that's out of quota MUST use an unmetered network.
        if (jobStatus.getEffectiveStandbyBucket() == RESTRICTED_INDEX
                && !jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)) {
                && (!jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)
                || !jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_TARE_WEALTH))) {
            final NetworkCapabilities.Builder builder =
                    copyCapabilities(jobStatus.getJob().getRequiredNetwork());
            builder.addCapability(NET_CAPABILITY_NOT_METERED);
+3 −2
Original line number Diff line number Diff line
@@ -1141,7 +1141,7 @@ public final class JobStatus {
     * treated as an expedited job.
     */
    public boolean shouldTreatAsExpeditedJob() {
        return mExpeditedQuotaApproved && isRequestedExpeditedJob();
        return mExpeditedQuotaApproved && mExpeditedTareApproved && isRequestedExpeditedJob();
    }

    /**
@@ -1564,7 +1564,8 @@ public final class JobStatus {
        // sessions (exempt from dynamic restrictions), we need the additional check to ensure
        // that NEVER jobs don't run.
        // TODO: cleanup quota and standby bucket management so we don't need the additional checks
        if ((!mReadyWithinQuota && !mReadyDynamicSatisfied && !shouldTreatAsExpeditedJob())
        if (((!mReadyWithinQuota || !mReadyTareWealth)
                && !mReadyDynamicSatisfied && !shouldTreatAsExpeditedJob())
                || getEffectiveStandbyBucket() == NEVER_INDEX) {
            return false;
        }
+16 −1
Original line number Diff line number Diff line
@@ -372,6 +372,9 @@ public final class QuotaController extends StateController {
    private final BackgroundJobsController mBackgroundJobsController;
    private final ConnectivityController mConnectivityController;

    @GuardedBy("mLock")
    private boolean mIsEnabled;

    /** How much time each app will have to run jobs within their standby bucket window. */
    private long mAllowedTimePerPeriodMs = QcConstants.DEFAULT_ALLOWED_TIME_PER_PERIOD_MS;

@@ -590,6 +593,7 @@ public final class QuotaController extends StateController {
        mQcConstants = new QcConstants();
        mBackgroundJobsController = backgroundJobsController;
        mConnectivityController = connectivityController;
        mIsEnabled = !mConstants.USE_TARE_POLICY;

        // Set up the app standby bucketing tracker
        AppStandbyInternal appStandby = LocalServices.getService(AppStandbyInternal.class);
@@ -837,6 +841,9 @@ public final class QuotaController extends StateController {
    /** @return true if the job is within expedited job quota. */
    @GuardedBy("mLock")
    public boolean isWithinEJQuotaLocked(@NonNull final JobStatus jobStatus) {
        if (!mIsEnabled) {
            return true;
        }
        if (isQuotaFreeLocked(jobStatus.getEffectiveStandbyBucket())) {
            return true;
        }
@@ -884,6 +891,9 @@ public final class QuotaController extends StateController {

    @VisibleForTesting
    boolean isWithinQuotaLocked(@NonNull final JobStatus jobStatus) {
        if (!mIsEnabled) {
            return true;
        }
        final int standbyBucket = jobStatus.getEffectiveStandbyBucket();
        // A job is within quota if one of the following is true:
        //   1. it was started while the app was in the TOP state
@@ -910,6 +920,9 @@ public final class QuotaController extends StateController {
    @GuardedBy("mLock")
    boolean isWithinQuotaLocked(final int userId, @NonNull final String packageName,
            final int standbyBucket) {
        if (!mIsEnabled) {
            return true;
        }
        if (standbyBucket == NEVER_INDEX) return false;

        if (isQuotaFreeLocked(standbyBucket)) return true;
@@ -2980,7 +2993,8 @@ public final class QuotaController extends StateController {

    @Override
    public void onConstantsUpdatedLocked() {
        if (mQcConstants.mShouldReevaluateConstraints) {
        if (mQcConstants.mShouldReevaluateConstraints || mIsEnabled == mConstants.USE_TARE_POLICY) {
            mIsEnabled = !mConstants.USE_TARE_POLICY;
            // Update job bookkeeping out of band.
            JobSchedulerBackgroundThread.getHandler().post(() -> {
                synchronized (mLock) {
@@ -4120,6 +4134,7 @@ public final class QuotaController extends StateController {
    @Override
    public void dumpControllerStateLocked(final IndentingPrintWriter pw,
            final Predicate<JobStatus> predicate) {
        pw.println("Is enabled: " + mIsEnabled);
        pw.println("Is charging: " + mChargeTracker.isChargingLocked());
        pw.println("Current elapsed time: " + sElapsedRealtimeClock.millis());
        pw.println();
Loading