From fc9bdda8161899d798b4024d17f42aba8ad92b46 Mon Sep 17 00:00:00 2001 From: Kweku Adams Date: Tue, 3 Aug 2021 10:38:18 -0700 Subject: 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 --- .../android/server/job/JobSchedulerService.java | 71 ++++++++++++++++++++-- .../com/android/server/job/JobServiceContext.java | 38 ++++++++++++ .../job/controllers/ConnectivityController.java | 3 +- .../android/server/job/controllers/JobStatus.java | 5 +- .../server/job/controllers/QuotaController.java | 17 +++++- .../server/job/controllers/TareController.java | 43 +++++++++++++ .../server/job/controllers/JobStatusTest.java | 1 + .../job/controllers/QuotaControllerTest.java | 2 + 8 files changed, 170 insertions(+), 10 deletions(-) diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java index f0745f3bad00..26237c41f5dc 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java @@ -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,9 +1181,12 @@ 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)) { - return JobScheduler.RESULT_FAILURE; + 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. @@ -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)); } } diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java index 986b2d9c6194..f485211732f5 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java @@ -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(); } diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java index 321c0b334a54..607ed3cd6a35 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java @@ -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); diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java index 844a480529af..3801885c9781 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java @@ -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; } diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java index 36afac8fec78..4ea46ebb752d 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java @@ -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 predicate) { + pw.println("Is enabled: " + mIsEnabled); pw.println("Is charging: " + mChargeTracker.isChargingLocked()); pw.println("Current elapsed time: " + sElapsedRealtimeClock.millis()); pw.println(); diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/TareController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/TareController.java index 401646d0aac2..be3a3ee2921b 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/TareController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/TareController.java @@ -27,6 +27,7 @@ import android.util.Slog; import android.util.SparseArrayMap; import com.android.internal.annotations.GuardedBy; +import com.android.server.JobSchedulerBackgroundThread; import com.android.server.LocalServices; import com.android.server.job.JobSchedulerService; import com.android.server.tare.EconomyManagerInternal; @@ -170,6 +171,7 @@ public class TareController extends StateController { mBackgroundJobsController = backgroundJobsController; mConnectivityController = connectivityController; mEconomyManagerInternal = LocalServices.getService(EconomyManagerInternal.class); + mIsEnabled = mConstants.USE_TARE_POLICY; } @Override @@ -237,6 +239,32 @@ public class TareController extends StateController { } } + @Override + @GuardedBy("mLock") + public void onConstantsUpdatedLocked() { + if (mIsEnabled != mConstants.USE_TARE_POLICY) { + mIsEnabled = mConstants.USE_TARE_POLICY; + // Update job bookkeeping out of band. + JobSchedulerBackgroundThread.getHandler().post(() -> { + synchronized (mLock) { + final long nowElapsed = sElapsedRealtimeClock.millis(); + mService.getJobStore().forEachJob((jobStatus) -> { + if (!mIsEnabled) { + jobStatus.setTareWealthConstraintSatisfied(nowElapsed, true); + setExpeditedTareApproved(jobStatus, nowElapsed, true); + } else { + jobStatus.setTareWealthConstraintSatisfied( + nowElapsed, hasEnoughWealthLocked(jobStatus)); + setExpeditedTareApproved(jobStatus, nowElapsed, + jobStatus.isRequestedExpeditedJob() + && canAffordExpeditedBillLocked(jobStatus)); + } + }); + } + }); + } + } + @GuardedBy("mLock") public boolean canScheduleEJ(@NonNull JobStatus jobStatus) { if (!mIsEnabled) { @@ -245,6 +273,21 @@ public class TareController extends StateController { return canAffordBillLocked(jobStatus, BILL_JOB_START_EXPEDITED); } + @GuardedBy("mLock") + public long getMaxJobExecutionTimeMsLocked(@NonNull JobStatus jobStatus) { + if (!mIsEnabled) { + return mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS; + } + if (jobStatus.shouldTreatAsExpeditedJob()) { + return mEconomyManagerInternal.getMaxDurationMs( + jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(), + BILL_JOB_RUNNING_EXPEDITED); + } + return mEconomyManagerInternal.getMaxDurationMs( + jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(), + BILL_JOB_RUNNING_DEFAULT); + } + @GuardedBy("mLock") private void addJobToBillList(@NonNull JobStatus jobStatus, @NonNull ActionBill bill) { final int userId = jobStatus.getSourceUserId(); diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java index 0fcda819d83c..6a2556035470 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java @@ -678,6 +678,7 @@ public class JobStatusTest { private void markImplicitConstraintsSatisfied(JobStatus job, boolean isSatisfied) { job.setQuotaConstraintSatisfied(sElapsedRealtimeClock.millis(), isSatisfied); + job.setTareWealthConstraintSatisfied(sElapsedRealtimeClock.millis(), isSatisfied); job.setDeviceNotDozingConstraintSatisfied( sElapsedRealtimeClock.millis(), isSatisfied, false); job.setBackgroundNotRestrictedConstraintSatisfied( diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java index dd70c874eb86..473815923056 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java @@ -389,6 +389,8 @@ public class QuotaControllerTest { /* state */ true, /* allowlisted */false); js.setBackgroundNotRestrictedConstraintSatisfied( sElapsedRealtimeClock.millis(), true, false); + js.setTareWealthConstraintSatisfied(sElapsedRealtimeClock.millis(), true); + js.setExpeditedJobTareApproved(sElapsedRealtimeClock.millis(), true); return js; } -- cgit v1.2.3-59-g8ed1b