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
This commit is contained in:
Kweku Adams
2021-08-03 10:38:18 -07:00
parent f8dd173528
commit fc9bdda816
8 changed files with 170 additions and 10 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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