Merge "Place additional constraints on restricted jobs."

This commit is contained in:
Kweku Adams
2020-01-22 21:38:34 +00:00
committed by Android (Google) Code Review
12 changed files with 432 additions and 45 deletions

View File

@@ -48,6 +48,13 @@ public class JobParameters implements Parcelable {
public static final int REASON_DEVICE_IDLE = JobProtoEnums.STOP_REASON_DEVICE_IDLE; // 4.
/** @hide */
public static final int REASON_DEVICE_THERMAL = JobProtoEnums.STOP_REASON_DEVICE_THERMAL; // 5.
/**
* The job is in the {@link android.app.usage.UsageStatsManager#STANDBY_BUCKET_RESTRICTED}
* bucket.
*
* @hide
*/
public static final int REASON_RESTRAINED = JobProtoEnums.STOP_REASON_RESTRAINED; // 6.
/**
* All the stop reason codes. This should be regarded as an immutable array at runtime.
@@ -65,6 +72,7 @@ public class JobParameters implements Parcelable {
REASON_TIMEOUT,
REASON_DEVICE_IDLE,
REASON_DEVICE_THERMAL,
REASON_RESTRAINED,
};
/**
@@ -80,6 +88,7 @@ public class JobParameters implements Parcelable {
case REASON_TIMEOUT: return "timeout";
case REASON_DEVICE_IDLE: return "device_idle";
case REASON_DEVICE_THERMAL: return "thermal";
case REASON_RESTRAINED: return "restrained";
default: return "unknown:" + reason;
}
}

View File

@@ -101,6 +101,7 @@ import com.android.server.job.controllers.DeviceIdleJobsController;
import com.android.server.job.controllers.IdleController;
import com.android.server.job.controllers.JobStatus;
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.TimeController;
@@ -241,6 +242,11 @@ public class JobSchedulerService extends com.android.server.SystemService
/** List of controllers that will notify this service of updates to jobs. */
final List<StateController> mControllers;
/**
* List of controllers that will apply to all jobs in the RESTRICTED bucket. This is a subset of
* {@link #mControllers}.
*/
private final List<RestrictingController> mRestrictiveControllers;
/** Need direct access to this for testing. */
private final BatteryController mBatteryController;
/** Need direct access to this for testing. */
@@ -313,6 +319,9 @@ public class JobSchedulerService extends com.android.server.SystemService
public static final int FREQUENT_INDEX = 2;
public static final int RARE_INDEX = 3;
public static final int NEVER_INDEX = 4;
// Putting RESTRICTED_INDEX after NEVER_INDEX to make it easier for proto dumping
// (ScheduledJobStateChanged and JobStatusDumpProto).
public static final int RESTRICTED_INDEX = 5;
// -- Pre-allocated temporaries only for use in assignJobsToContextsLocked --
@@ -1367,6 +1376,40 @@ public class JobSchedulerService extends com.android.server.SystemService
}
}
@Override
public void onRestrictedBucketChanged(List<JobStatus> jobs) {
final int len = jobs.size();
if (len == 0) {
Slog.wtf(TAG, "onRestrictedBucketChanged called with no jobs");
return;
}
synchronized (mLock) {
for (int i = 0; i < len; ++i) {
JobStatus js = jobs.get(i);
for (int j = mRestrictiveControllers.size() - 1; j >= 0; --j) {
// Effective standby bucket can change after this in some situations so use
// the real bucket so that the job is tracked by the controllers.
if (js.getStandbyBucket() == RESTRICTED_INDEX) {
js.addDynamicConstraint(JobStatus.CONSTRAINT_BATTERY_NOT_LOW);
js.addDynamicConstraint(JobStatus.CONSTRAINT_CHARGING);
js.addDynamicConstraint(JobStatus.CONSTRAINT_CONNECTIVITY);
js.addDynamicConstraint(JobStatus.CONSTRAINT_IDLE);
mRestrictiveControllers.get(j).startTrackingRestrictedJobLocked(js);
} else {
js.removeDynamicConstraint(JobStatus.CONSTRAINT_BATTERY_NOT_LOW);
js.removeDynamicConstraint(JobStatus.CONSTRAINT_CHARGING);
js.removeDynamicConstraint(JobStatus.CONSTRAINT_CONNECTIVITY);
js.removeDynamicConstraint(JobStatus.CONSTRAINT_IDLE);
mRestrictiveControllers.get(j).stopTrackingRestrictedJobLocked(js);
}
}
}
}
mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
}
void reportActiveLocked() {
// active is true if pending queue contains jobs OR some job is running.
boolean active = mPendingJobs.size() > 0;
@@ -1443,9 +1486,11 @@ public class JobSchedulerService extends com.android.server.SystemService
// Create the controllers.
mControllers = new ArrayList<StateController>();
mControllers.add(new ConnectivityController(this));
final ConnectivityController connectivityController = new ConnectivityController(this);
mControllers.add(connectivityController);
mControllers.add(new TimeController(this));
mControllers.add(new IdleController(this));
final IdleController idleController = new IdleController(this);
mControllers.add(idleController);
mBatteryController = new BatteryController(this);
mControllers.add(mBatteryController);
mStorageController = new StorageController(this);
@@ -1457,6 +1502,11 @@ public class JobSchedulerService extends com.android.server.SystemService
mQuotaController = new QuotaController(this);
mControllers.add(mQuotaController);
mRestrictiveControllers = new ArrayList<>();
mRestrictiveControllers.add(mBatteryController);
mRestrictiveControllers.add(connectivityController);
mRestrictiveControllers.add(idleController);
// Create restrictions
mJobRestrictions = new ArrayList<>();
mJobRestrictions.add(new ThermalStatusRestriction(this));
@@ -2127,11 +2177,13 @@ public class JobSchedulerService extends com.android.server.SystemService
}
} catch (RemoteException e) {
}
if (mConstants.MIN_READY_NON_ACTIVE_JOBS_COUNT > 1
// Restricted jobs must always be batched
if (job.getEffectiveStandbyBucket() == RESTRICTED_INDEX
|| (mConstants.MIN_READY_NON_ACTIVE_JOBS_COUNT > 1
&& job.getEffectiveStandbyBucket() != ACTIVE_INDEX
&& (job.getFirstForceBatchedTimeElapsed() == 0
|| sElapsedRealtimeClock.millis() - job.getFirstForceBatchedTimeElapsed()
< mConstants.MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS)) {
< mConstants.MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS))) {
// Force batching non-ACTIVE jobs. Don't include them in the other counts.
forceBatchedCount++;
if (job.getFirstForceBatchedTimeElapsed() == 0) {
@@ -2538,11 +2590,19 @@ public class JobSchedulerService extends com.android.server.SystemService
public static int standbyBucketToBucketIndex(int bucket) {
// Normalize AppStandby constants to indices into our bookkeeping
if (bucket == UsageStatsManager.STANDBY_BUCKET_NEVER) return NEVER_INDEX;
else if (bucket > UsageStatsManager.STANDBY_BUCKET_FREQUENT) return RARE_INDEX;
else if (bucket > UsageStatsManager.STANDBY_BUCKET_WORKING_SET) return FREQUENT_INDEX;
else if (bucket > UsageStatsManager.STANDBY_BUCKET_ACTIVE) return WORKING_INDEX;
else return ACTIVE_INDEX;
if (bucket == UsageStatsManager.STANDBY_BUCKET_NEVER) {
return NEVER_INDEX;
} else if (bucket > UsageStatsManager.STANDBY_BUCKET_RARE) {
return RESTRICTED_INDEX;
} else if (bucket > UsageStatsManager.STANDBY_BUCKET_FREQUENT) {
return RARE_INDEX;
} else if (bucket > UsageStatsManager.STANDBY_BUCKET_WORKING_SET) {
return FREQUENT_INDEX;
} else if (bucket > UsageStatsManager.STANDBY_BUCKET_ACTIVE) {
return WORKING_INDEX;
} else {
return ACTIVE_INDEX;
}
}
// Static to support external callers

View File

@@ -16,8 +16,12 @@
package com.android.server.job;
import android.annotation.NonNull;
import com.android.server.job.controllers.JobStatus;
import java.util.List;
/**
* Interface through which a {@link com.android.server.job.controllers.StateController} informs
* the {@link com.android.server.job.JobSchedulerService} that there are some tasks potentially
@@ -39,4 +43,10 @@ public interface StateChangedListener {
public void onRunJobNow(JobStatus jobStatus);
public void onDeviceIdleStateChanged(boolean deviceIdle);
/**
* Called when these jobs are added or removed from the
* {@link android.app.usage.UsageStatsManager#STANDBY_BUCKET_RESTRICTED} bucket.
*/
void onRestrictedBucketChanged(@NonNull List<JobStatus> jobs);
}

View File

@@ -43,7 +43,7 @@ import java.util.function.Predicate;
* be charging when it's been plugged in for more than two minutes, and the system has broadcast
* ACTION_BATTERY_OK.
*/
public final class BatteryController extends StateController {
public final class BatteryController extends RestrictingController {
private static final String TAG = "JobScheduler.Battery";
private static final boolean DEBUG = JobSchedulerService.DEBUG
|| Log.isLoggable(TAG, Log.DEBUG);
@@ -72,6 +72,11 @@ public final class BatteryController extends StateController {
}
}
@Override
public void startTrackingRestrictedJobLocked(JobStatus jobStatus) {
maybeStartTrackingJobLocked(jobStatus, null);
}
@Override
public void maybeStopTrackingJobLocked(JobStatus taskStatus, JobStatus incomingJob, boolean forUpdate) {
if (taskStatus.clearTrackingController(JobStatus.TRACKING_BATTERY)) {
@@ -79,6 +84,13 @@ public final class BatteryController extends StateController {
}
}
@Override
public void stopTrackingRestrictedJobLocked(JobStatus jobStatus) {
if (!jobStatus.hasPowerConstraint()) {
maybeStopTrackingJobLocked(jobStatus, null, false);
}
}
private void maybeReportNewChargingStateLocked() {
final boolean stablePower = mChargeTracker.isOnStablePower();
final boolean batteryNotLow = mChargeTracker.isBatteryNotLow();

View File

@@ -20,6 +20,8 @@ import static android.net.NetworkCapabilities.LINK_BANDWIDTH_UNSPECIFIED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
import static com.android.server.job.JobSchedulerService.RESTRICTED_INDEX;
import android.app.job.JobInfo;
import android.net.ConnectivityManager;
import android.net.ConnectivityManager.NetworkCallback;
@@ -63,7 +65,7 @@ import java.util.function.Predicate;
*
* Test: atest com.android.server.job.controllers.ConnectivityControllerTest
*/
public final class ConnectivityController extends StateController implements
public final class ConnectivityController extends RestrictingController implements
ConnectivityManager.OnNetworkActiveListener {
private static final String TAG = "JobScheduler.Connectivity";
private static final boolean DEBUG = JobSchedulerService.DEBUG
@@ -138,8 +140,22 @@ public final class ConnectivityController extends StateController implements
}
}
@Override
public void startTrackingRestrictedJobLocked(JobStatus jobStatus) {
// Don't need to start tracking the job. If the job needed network, it would already be
// tracked.
updateConstraintsSatisfied(jobStatus);
}
@Override
public void stopTrackingRestrictedJobLocked(JobStatus jobStatus) {
// Shouldn't stop tracking the job here. If the job was tracked, it still needs network,
// even after being unrestricted.
updateConstraintsSatisfied(jobStatus);
}
/**
* Returns true if the job's requested network is available. This DOES NOT necesarilly mean
* Returns true if the job's requested network is available. This DOES NOT necessarily mean
* that the UID has been granted access to the network.
*/
public boolean isNetworkAvailable(JobStatus job) {
@@ -353,14 +369,24 @@ public final class ConnectivityController extends StateController implements
private static boolean isStrictSatisfied(JobStatus jobStatus, Network network,
NetworkCapabilities capabilities, Constants constants) {
return jobStatus.getJob().getRequiredNetwork().networkCapabilities
.satisfiedByNetworkCapabilities(capabilities);
final NetworkCapabilities required;
// A restricted job that's out of quota MUST use an unmetered network.
if (jobStatus.getEffectiveStandbyBucket() == RESTRICTED_INDEX
&& !jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)) {
required = new NetworkCapabilities(
jobStatus.getJob().getRequiredNetwork().networkCapabilities)
.addCapability(NET_CAPABILITY_NOT_METERED);
} else {
required = jobStatus.getJob().getRequiredNetwork().networkCapabilities;
}
return required.satisfiedByNetworkCapabilities(capabilities);
}
private static boolean isRelaxedSatisfied(JobStatus jobStatus, Network network,
NetworkCapabilities capabilities, Constants constants) {
// Only consider doing this for prefetching jobs
if (!jobStatus.getJob().isPrefetch()) {
// Only consider doing this for unrestricted prefetching jobs
if (!jobStatus.getJob().isPrefetch() || jobStatus.getStandbyBucket() == RESTRICTED_INDEX) {
return false;
}

View File

@@ -32,7 +32,15 @@ import com.android.server.job.controllers.idle.IdlenessTracker;
import java.util.function.Predicate;
public final class IdleController extends StateController implements IdlenessListener {
/**
* Simple controller that tracks whether the device is idle or not. Idleness depends on the device
* type and is not related to device-idle (Doze mode) despite the similar naming.
*
* @see CarIdlenessTracker
* @see DeviceIdlenessTracker
* @see IdlenessTracker
*/
public final class IdleController extends RestrictingController implements IdlenessListener {
private static final String TAG = "JobScheduler.IdleController";
// Policy: we decide that we're "idle" if the device has been unused /
// screen off or dreaming or wireless charging dock idle for at least this long
@@ -56,6 +64,11 @@ public final class IdleController extends StateController implements IdlenessLis
}
}
@Override
public void startTrackingRestrictedJobLocked(JobStatus jobStatus) {
maybeStartTrackingJobLocked(jobStatus, null);
}
@Override
public void maybeStopTrackingJobLocked(JobStatus taskStatus, JobStatus incomingJob,
boolean forUpdate) {
@@ -64,6 +77,13 @@ public final class IdleController extends StateController implements IdlenessLis
}
}
@Override
public void stopTrackingRestrictedJobLocked(JobStatus jobStatus) {
if (!jobStatus.hasIdleConstraint()) {
maybeStopTrackingJobLocked(jobStatus, null, false);
}
}
/**
* State-change notifications from the idleness tracker
*/

View File

@@ -69,13 +69,14 @@ public final class JobStatus {
public static final long NO_LATEST_RUNTIME = Long.MAX_VALUE;
public static final long NO_EARLIEST_RUNTIME = 0L;
static final int CONSTRAINT_CHARGING = JobInfo.CONSTRAINT_FLAG_CHARGING; // 1 < 0
static final int CONSTRAINT_IDLE = JobInfo.CONSTRAINT_FLAG_DEVICE_IDLE; // 1 << 2
static final int CONSTRAINT_BATTERY_NOT_LOW = JobInfo.CONSTRAINT_FLAG_BATTERY_NOT_LOW; // 1 << 1
public static final int CONSTRAINT_CHARGING = JobInfo.CONSTRAINT_FLAG_CHARGING; // 1 < 0
public static final int CONSTRAINT_IDLE = JobInfo.CONSTRAINT_FLAG_DEVICE_IDLE; // 1 << 2
public static final int CONSTRAINT_BATTERY_NOT_LOW =
JobInfo.CONSTRAINT_FLAG_BATTERY_NOT_LOW; // 1 << 1
static final int CONSTRAINT_STORAGE_NOT_LOW = JobInfo.CONSTRAINT_FLAG_STORAGE_NOT_LOW; // 1 << 3
static final int CONSTRAINT_TIMING_DELAY = 1<<31;
static final int CONSTRAINT_DEADLINE = 1<<30;
static final int CONSTRAINT_CONNECTIVITY = 1<<28;
public static final int CONSTRAINT_CONNECTIVITY = 1 << 28;
static final int CONSTRAINT_CONTENT_TRIGGER = 1<<26;
static final int CONSTRAINT_DEVICE_NOT_DOZING = 1 << 25; // Implicit constraint
static final int CONSTRAINT_WITHIN_QUOTA = 1 << 24; // Implicit constraint
@@ -117,7 +118,7 @@ public final class JobStatus {
/** The minimum possible update delay is 1/2 second. */
public static final long MIN_TRIGGER_UPDATE_DELAY = 500;
/** If not specified, trigger maxumum delay is 2 minutes. */
/** If not specified, trigger maximum delay is 2 minutes. */
public static final long DEFAULT_TRIGGER_MAX_DELAY = 2*60*1000;
/** The minimum possible update delay is 1 second. */
@@ -188,6 +189,11 @@ public final class JobStatus {
private final int mRequiredConstraintsOfInterest;
int satisfiedConstraints = 0;
private int mSatisfiedConstraintsOfInterest = 0;
/**
* Set of constraints that must be satisfied for the job if/because it's in the RESTRICTED
* bucket.
*/
private int mDynamicConstraints = 0;
// Set to true if doze constraint was satisfied due to app being whitelisted.
public boolean dozeWhitelisted;
@@ -328,6 +334,9 @@ public final class JobStatus {
/** The job is within its quota based on its standby bucket. */
private boolean mReadyWithinQuota;
/** The job's dynamic requirements have been satisfied. */
private boolean mReadyDynamicSatisfied;
/** Provide a handle to the service that this job will be run on. */
public int getServiceToken() {
return callingUid;
@@ -410,6 +419,7 @@ public final class JobStatus {
this.requiredConstraints = requiredConstraints;
mRequiredConstraintsOfInterest = requiredConstraints & CONSTRAINTS_OF_INTEREST;
mReadyNotDozing = (job.getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0;
mReadyDynamicSatisfied = true;
mLastSuccessfulRunTime = lastSuccessfulRunTime;
mLastFailedRunTime = lastFailedRunTime;
@@ -830,41 +840,54 @@ public final class JobStatus {
/** Does this job have any sort of networking constraint? */
public boolean hasConnectivityConstraint() {
// No need to check mDynamicConstraints since connectivity will only be in that list if
// it's already in the requiredConstraints list.
return (requiredConstraints&CONSTRAINT_CONNECTIVITY) != 0;
}
public boolean hasChargingConstraint() {
return (requiredConstraints&CONSTRAINT_CHARGING) != 0;
return hasConstraint(CONSTRAINT_CHARGING);
}
public boolean hasBatteryNotLowConstraint() {
return (requiredConstraints&CONSTRAINT_BATTERY_NOT_LOW) != 0;
return hasConstraint(CONSTRAINT_BATTERY_NOT_LOW);
}
public boolean hasPowerConstraint() {
return (requiredConstraints&(CONSTRAINT_CHARGING|CONSTRAINT_BATTERY_NOT_LOW)) != 0;
/** Returns true if the job requires charging OR battery not low. */
boolean hasPowerConstraint() {
return hasConstraint(CONSTRAINT_CHARGING | CONSTRAINT_BATTERY_NOT_LOW);
}
public boolean hasStorageNotLowConstraint() {
return (requiredConstraints&CONSTRAINT_STORAGE_NOT_LOW) != 0;
return hasConstraint(CONSTRAINT_STORAGE_NOT_LOW);
}
public boolean hasTimingDelayConstraint() {
return (requiredConstraints&CONSTRAINT_TIMING_DELAY) != 0;
return hasConstraint(CONSTRAINT_TIMING_DELAY);
}
public boolean hasDeadlineConstraint() {
return (requiredConstraints&CONSTRAINT_DEADLINE) != 0;
return hasConstraint(CONSTRAINT_DEADLINE);
}
public boolean hasIdleConstraint() {
return (requiredConstraints&CONSTRAINT_IDLE) != 0;
return hasConstraint(CONSTRAINT_IDLE);
}
public boolean hasContentTriggerConstraint() {
// No need to check mDynamicConstraints since content trigger will only be in that list if
// it's already in the requiredConstraints list.
return (requiredConstraints&CONSTRAINT_CONTENT_TRIGGER) != 0;
}
/**
* Checks both {@link #requiredConstraints} and {@link #mDynamicConstraints} to see if this job
* requires the specified constraint.
*/
private boolean hasConstraint(int constraint) {
return (requiredConstraints & constraint) != 0 || (mDynamicConstraints & constraint) != 0;
}
public long getTriggerContentUpdateDelay() {
long time = job.getTriggerContentUpdateDelay();
if (time < 0) {
@@ -1033,6 +1056,8 @@ public final class JobStatus {
}
satisfiedConstraints = (satisfiedConstraints&~constraint) | (state ? constraint : 0);
mSatisfiedConstraintsOfInterest = satisfiedConstraints & CONSTRAINTS_OF_INTEREST;
mReadyDynamicSatisfied =
mDynamicConstraints == (satisfiedConstraints & mDynamicConstraints);
if (STATS_LOG_ENABLED && (STATSD_CONSTRAINTS_TO_LOG & constraint) != 0) {
StatsLog.write_non_chained(StatsLog.SCHEDULED_JOB_CONSTRAINT_CHANGED,
sourceUid, null, getBatteryName(), getProtoConstraint(constraint),
@@ -1058,6 +1083,43 @@ public final class JobStatus {
trackingControllers |= which;
}
/**
* Indicates that this job cannot run without the specified constraint. This is evaluated
* separately from the job's explicitly requested constraints and MUST be satisfied before
* the job can run if the app doesn't have quota.
*
*/
public void addDynamicConstraint(int constraint) {
if (constraint == CONSTRAINT_WITHIN_QUOTA) {
Slog.wtf(TAG, "Tried to set quota as a dynamic constraint");
return;
}
// Connectivity and content trigger are special since they're only valid to add if the
// job has requested network or specific content URIs. Adding these constraints to jobs
// that don't need them doesn't make sense.
if ((constraint == CONSTRAINT_CONNECTIVITY && !hasConnectivityConstraint())
|| (constraint == CONSTRAINT_CONTENT_TRIGGER && !hasContentTriggerConstraint())) {
return;
}
mDynamicConstraints |= constraint;
mReadyDynamicSatisfied =
mDynamicConstraints == (satisfiedConstraints & mDynamicConstraints);
}
/**
* Removes a dynamic constraint from a job, meaning that the requirement is not required for
* the job to run (if the job itself hasn't requested the constraint. This is separate from
* the job's explicitly requested constraints and does not remove those requested constraints.
*
*/
public void removeDynamicConstraint(int constraint) {
mDynamicConstraints &= ~constraint;
mReadyDynamicSatisfied =
mDynamicConstraints == (satisfiedConstraints & mDynamicConstraints);
}
public long getLastSuccessfulRunTime() {
return mLastSuccessfulRunTime;
}
@@ -1099,6 +1161,8 @@ public final class JobStatus {
break;
default:
satisfied |= constraint;
mReadyDynamicSatisfied =
mDynamicConstraints == (satisfied & mDynamicConstraints);
break;
}
@@ -1117,24 +1181,29 @@ public final class JobStatus {
case CONSTRAINT_WITHIN_QUOTA:
mReadyWithinQuota = oldValue;
break;
default:
mReadyDynamicSatisfied =
mDynamicConstraints == (satisfiedConstraints & mDynamicConstraints);
break;
}
return toReturn;
}
private boolean isReady(int satisfiedConstraints) {
// Quota constraints trumps all other constraints.
if (!mReadyWithinQuota) {
// Quota and dynamic constraints trump all other constraints.
if (!mReadyWithinQuota && !mReadyDynamicSatisfied) {
return false;
}
// Deadline constraint trumps other constraints besides quota (except for periodic jobs
// where deadline is an implementation detail. A periodic job should only run if its
// constraints are satisfied).
// Deadline constraint trumps other constraints besides quota and dynamic (except for
// periodic jobs where deadline is an implementation detail. A periodic job should only
// run if its constraints are satisfied).
// DeviceNotDozing implicit constraint must be satisfied
// NotRestrictedInBackground implicit constraint must be satisfied
return mReadyNotDozing && mReadyNotRestrictedInBg && (mReadyDeadlineSatisfied
|| isConstraintsSatisfied(satisfiedConstraints));
}
/** All constraints besides implicit and deadline. */
static final int CONSTRAINTS_OF_INTEREST = CONSTRAINT_CHARGING | CONSTRAINT_BATTERY_NOT_LOW
| CONSTRAINT_STORAGE_NOT_LOW | CONSTRAINT_TIMING_DELAY | CONSTRAINT_CONNECTIVITY
| CONSTRAINT_IDLE | CONSTRAINT_CONTENT_TRIGGER;
@@ -1441,6 +1510,8 @@ public final class JobStatus {
case 2: return "FREQUENT";
case 3: return "RARE";
case 4: return "NEVER";
case 5:
return "RESTRICTED";
default:
return "Unknown: " + standbyBucket;
}
@@ -1560,6 +1631,10 @@ public final class JobStatus {
pw.print(prefix); pw.print("Required constraints:");
dumpConstraints(pw, requiredConstraints);
pw.println();
pw.print(prefix);
pw.print("Dynamic constraints:");
dumpConstraints(pw, mDynamicConstraints);
pw.println();
if (full) {
pw.print(prefix); pw.print("Satisfied constraints:");
dumpConstraints(pw, satisfiedConstraints);
@@ -1599,6 +1674,9 @@ public final class JobStatus {
pw.print(prefix); pw.print(" readyDeadlineSatisfied: ");
pw.println(mReadyDeadlineSatisfied);
}
pw.print(prefix);
pw.print(" readyDynamicSatisfied: ");
pw.println(mReadyDynamicSatisfied);
if (changedAuthorities != null) {
pw.print(prefix); pw.println("Changed authorities:");
@@ -1760,6 +1838,7 @@ public final class JobStatus {
}
dumpConstraints(proto, JobStatusDumpProto.REQUIRED_CONSTRAINTS, requiredConstraints);
dumpConstraints(proto, JobStatusDumpProto.DYNAMIC_CONSTRAINTS, mDynamicConstraints);
if (full) {
dumpConstraints(proto, JobStatusDumpProto.SATISFIED_CONSTRAINTS, satisfiedConstraints);
dumpConstraints(proto, JobStatusDumpProto.UNSATISFIED_CONSTRAINTS,
@@ -1807,6 +1886,8 @@ public final class JobStatus {
mReadyNotRestrictedInBg);
// mReadyDeadlineSatisfied isn't an implicit constraint...and can be determined from other
// field values.
proto.write(JobStatusDumpProto.ImplicitConstraints.IS_DYNAMIC_SATISFIED,
mReadyDynamicSatisfied);
proto.end(icToken);
if (changedAuthorities != null) {

View File

@@ -24,6 +24,7 @@ import static com.android.server.job.JobSchedulerService.ACTIVE_INDEX;
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.RESTRICTED_INDEX;
import static com.android.server.job.JobSchedulerService.WORKING_INDEX;
import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
@@ -424,7 +425,9 @@ public final class QuotaController extends StateController {
QcConstants.DEFAULT_WINDOW_SIZE_ACTIVE_MS,
QcConstants.DEFAULT_WINDOW_SIZE_WORKING_MS,
QcConstants.DEFAULT_WINDOW_SIZE_FREQUENT_MS,
QcConstants.DEFAULT_WINDOW_SIZE_RARE_MS
QcConstants.DEFAULT_WINDOW_SIZE_RARE_MS,
0, // NEVER
QcConstants.DEFAULT_WINDOW_SIZE_RESTRICTED_MS
};
/** The maximum period any bucket can have. */
@@ -441,7 +444,9 @@ public final class QuotaController extends StateController {
QcConstants.DEFAULT_MAX_JOB_COUNT_ACTIVE,
QcConstants.DEFAULT_MAX_JOB_COUNT_WORKING,
QcConstants.DEFAULT_MAX_JOB_COUNT_FREQUENT,
QcConstants.DEFAULT_MAX_JOB_COUNT_RARE
QcConstants.DEFAULT_MAX_JOB_COUNT_RARE,
0, // NEVER
QcConstants.DEFAULT_MAX_JOB_COUNT_RESTRICTED
};
/**
@@ -455,7 +460,9 @@ public final class QuotaController extends StateController {
QcConstants.DEFAULT_MAX_SESSION_COUNT_ACTIVE,
QcConstants.DEFAULT_MAX_SESSION_COUNT_WORKING,
QcConstants.DEFAULT_MAX_SESSION_COUNT_FREQUENT,
QcConstants.DEFAULT_MAX_SESSION_COUNT_RARE
QcConstants.DEFAULT_MAX_SESSION_COUNT_RARE,
0, // NEVER
QcConstants.DEFAULT_MAX_SESSION_COUNT_RESTRICTED,
};
/**
@@ -648,7 +655,11 @@ public final class QuotaController extends StateController {
// Quota constraint is not enforced while charging.
if (mChargeTracker.isCharging()) {
return true;
// Restricted jobs require additional constraints when charging, so don't immediately
// mark quota as free when charging.
if (standbyBucket != RESTRICTED_INDEX) {
return true;
}
}
ExecutionStats stats = getExecutionStatsLocked(userId, packageName, standbyBucket);
@@ -1105,14 +1116,37 @@ public final class QuotaController extends StateController {
}
}
private class TimerChargingUpdateFunctor implements Consumer<Timer> {
private long mNowElapsed;
private boolean mIsCharging;
private void setStatus(long nowElapsed, boolean isCharging) {
mNowElapsed = nowElapsed;
mIsCharging = isCharging;
}
@Override
public void accept(Timer timer) {
if (JobSchedulerService.standbyBucketForPackage(timer.mPkg.packageName,
timer.mPkg.userId, mNowElapsed) != RESTRICTED_INDEX) {
// Restricted jobs need additional constraints even when charging, so don't
// immediately say that quota is free.
timer.onStateChangedLocked(mNowElapsed, mIsCharging);
}
}
}
private final TimerChargingUpdateFunctor
mTimerChargingUpdateFunctor = new TimerChargingUpdateFunctor();
private void handleNewChargingStateLocked() {
final long nowElapsed = sElapsedRealtimeClock.millis();
final boolean isCharging = mChargeTracker.isCharging();
mTimerChargingUpdateFunctor.setStatus(sElapsedRealtimeClock.millis(),
mChargeTracker.isCharging());
if (DEBUG) {
Slog.d(TAG, "handleNewChargingStateLocked: " + isCharging);
Slog.d(TAG, "handleNewChargingStateLocked: " + mChargeTracker.isCharging());
}
// Deal with Timers first.
mPkgTimers.forEach((t) -> t.onStateChangedLocked(nowElapsed, isCharging));
mPkgTimers.forEach(mTimerChargingUpdateFunctor);
// Now update jobs.
maybeUpdateAllConstraintsLocked();
}
@@ -1555,7 +1589,10 @@ public final class QuotaController extends StateController {
}
private boolean shouldTrackLocked() {
return !mChargeTracker.isCharging() && !mForegroundUids.get(mUid);
final int standbyBucket = JobSchedulerService.standbyBucketForPackage(mPkg.packageName,
mPkg.userId, sElapsedRealtimeClock.millis());
return (standbyBucket == RESTRICTED_INDEX || !mChargeTracker.isCharging())
&& !mForegroundUids.get(mUid);
}
void onStateChangedLocked(long nowElapsed, boolean isQuotaFree) {
@@ -1670,6 +1707,7 @@ public final class QuotaController extends StateController {
Slog.i(TAG, "Moving pkg " + string(userId, packageName) + " to bucketIndex "
+ bucketIndex);
}
List<JobStatus> restrictedChanges = new ArrayList<>();
synchronized (mLock) {
ArraySet<JobStatus> jobs = mTrackedJobs.get(userId, packageName);
if (jobs == null || jobs.size() == 0) {
@@ -1677,6 +1715,13 @@ public final class QuotaController extends StateController {
}
for (int i = jobs.size() - 1; i >= 0; i--) {
JobStatus js = jobs.valueAt(i);
// Effective standby bucket can change after this in some situations so
// use the real bucket so that the job is tracked by the controllers.
if ((bucketIndex == RESTRICTED_INDEX
|| js.getStandbyBucket() == RESTRICTED_INDEX)
&& bucketIndex != js.getStandbyBucket()) {
restrictedChanges.add(js);
}
js.setStandbyBucket(bucketIndex);
}
Timer timer = mPkgTimers.get(userId, packageName);
@@ -1687,6 +1732,9 @@ public final class QuotaController extends StateController {
mStateChangedListener.onControllerStateChanged();
}
}
if (restrictedChanges.size() > 0) {
mStateChangedListener.onRestrictedBucketChanged(restrictedChanges);
}
});
}
}
@@ -1863,11 +1911,13 @@ public final class QuotaController extends StateController {
private static final String KEY_WINDOW_SIZE_WORKING_MS = "window_size_working_ms";
private static final String KEY_WINDOW_SIZE_FREQUENT_MS = "window_size_frequent_ms";
private static final String KEY_WINDOW_SIZE_RARE_MS = "window_size_rare_ms";
private static final String KEY_WINDOW_SIZE_RESTRICTED_MS = "window_size_restricted_ms";
private static final String KEY_MAX_EXECUTION_TIME_MS = "max_execution_time_ms";
private static final String KEY_MAX_JOB_COUNT_ACTIVE = "max_job_count_active";
private static final String KEY_MAX_JOB_COUNT_WORKING = "max_job_count_working";
private static final String KEY_MAX_JOB_COUNT_FREQUENT = "max_job_count_frequent";
private static final String KEY_MAX_JOB_COUNT_RARE = "max_job_count_rare";
private static final String KEY_MAX_JOB_COUNT_RESTRICTED = "max_job_count_restricted";
private static final String KEY_RATE_LIMITING_WINDOW_MS = "rate_limiting_window_ms";
private static final String KEY_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW =
"max_job_count_per_rate_limiting_window";
@@ -1875,6 +1925,8 @@ public final class QuotaController extends StateController {
private static final String KEY_MAX_SESSION_COUNT_WORKING = "max_session_count_working";
private static final String KEY_MAX_SESSION_COUNT_FREQUENT = "max_session_count_frequent";
private static final String KEY_MAX_SESSION_COUNT_RARE = "max_session_count_rare";
private static final String KEY_MAX_SESSION_COUNT_RESTRICTED =
"max_session_count_restricted";
private static final String KEY_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW =
"max_session_count_per_rate_limiting_window";
private static final String KEY_TIMING_SESSION_COALESCING_DURATION_MS =
@@ -1892,6 +1944,8 @@ public final class QuotaController extends StateController {
8 * 60 * 60 * 1000L; // 8 hours
private static final long DEFAULT_WINDOW_SIZE_RARE_MS =
24 * 60 * 60 * 1000L; // 24 hours
private static final long DEFAULT_WINDOW_SIZE_RESTRICTED_MS =
24 * 60 * 60 * 1000L; // 24 hours
private static final long DEFAULT_MAX_EXECUTION_TIME_MS =
4 * HOUR_IN_MILLIS;
private static final long DEFAULT_RATE_LIMITING_WINDOW_MS =
@@ -1905,6 +1959,7 @@ public final class QuotaController extends StateController {
(int) (25.0 * DEFAULT_WINDOW_SIZE_FREQUENT_MS / HOUR_IN_MILLIS);
private static final int DEFAULT_MAX_JOB_COUNT_RARE = // 48/window = 2/hr = 16/session
(int) (2.0 * DEFAULT_WINDOW_SIZE_RARE_MS / HOUR_IN_MILLIS);
private static final int DEFAULT_MAX_JOB_COUNT_RESTRICTED = 10;
private static final int DEFAULT_MAX_SESSION_COUNT_ACTIVE =
75; // 450/hr
private static final int DEFAULT_MAX_SESSION_COUNT_WORKING =
@@ -1913,6 +1968,7 @@ public final class QuotaController extends StateController {
8; // 1/hr
private static final int DEFAULT_MAX_SESSION_COUNT_RARE =
3; // .125/hr
private static final int DEFAULT_MAX_SESSION_COUNT_RESTRICTED = 1; // 1/day
private static final int DEFAULT_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW = 20;
private static final long DEFAULT_TIMING_SESSION_COALESCING_DURATION_MS = 5000; // 5 seconds
@@ -1953,6 +2009,13 @@ public final class QuotaController extends StateController {
*/
public long WINDOW_SIZE_RARE_MS = DEFAULT_WINDOW_SIZE_RARE_MS;
/**
* The quota window size of the particular standby bucket. Apps in this standby bucket are
* expected to run only {@link #ALLOWED_TIME_PER_PERIOD_MS} within the past
* WINDOW_SIZE_MS.
*/
public long WINDOW_SIZE_RESTRICTED_MS = DEFAULT_WINDOW_SIZE_RESTRICTED_MS;
/**
* The maximum amount of time an app can have its jobs running within a 24 hour window.
*/
@@ -1982,6 +2045,12 @@ public final class QuotaController extends StateController {
*/
public int MAX_JOB_COUNT_RARE = DEFAULT_MAX_JOB_COUNT_RARE;
/**
* The maximum number of jobs an app can run within this particular standby bucket's
* window size.
*/
public int MAX_JOB_COUNT_RESTRICTED = DEFAULT_MAX_JOB_COUNT_RESTRICTED;
/** The period of time used to rate limit recently run jobs. */
public long RATE_LIMITING_WINDOW_MS = DEFAULT_RATE_LIMITING_WINDOW_MS;
@@ -2015,6 +2084,12 @@ public final class QuotaController extends StateController {
*/
public int MAX_SESSION_COUNT_RARE = DEFAULT_MAX_SESSION_COUNT_RARE;
/**
* The maximum number of {@link TimingSession}s an app can run within this particular
* standby bucket's window size.
*/
public int MAX_SESSION_COUNT_RESTRICTED = DEFAULT_MAX_SESSION_COUNT_RESTRICTED;
/**
* The maximum number of {@link TimingSession}s that can run within the past
* {@link #ALLOWED_TIME_PER_PERIOD_MS}.
@@ -2087,6 +2162,8 @@ public final class QuotaController extends StateController {
KEY_WINDOW_SIZE_FREQUENT_MS, DEFAULT_WINDOW_SIZE_FREQUENT_MS);
WINDOW_SIZE_RARE_MS = mParser.getDurationMillis(
KEY_WINDOW_SIZE_RARE_MS, DEFAULT_WINDOW_SIZE_RARE_MS);
WINDOW_SIZE_RESTRICTED_MS = mParser.getDurationMillis(
KEY_WINDOW_SIZE_RESTRICTED_MS, DEFAULT_WINDOW_SIZE_RESTRICTED_MS);
MAX_EXECUTION_TIME_MS = mParser.getDurationMillis(
KEY_MAX_EXECUTION_TIME_MS, DEFAULT_MAX_EXECUTION_TIME_MS);
MAX_JOB_COUNT_ACTIVE = mParser.getInt(
@@ -2097,6 +2174,8 @@ public final class QuotaController extends StateController {
KEY_MAX_JOB_COUNT_FREQUENT, DEFAULT_MAX_JOB_COUNT_FREQUENT);
MAX_JOB_COUNT_RARE = mParser.getInt(
KEY_MAX_JOB_COUNT_RARE, DEFAULT_MAX_JOB_COUNT_RARE);
MAX_JOB_COUNT_RESTRICTED = mParser.getInt(
KEY_MAX_JOB_COUNT_RESTRICTED, DEFAULT_MAX_JOB_COUNT_RESTRICTED);
RATE_LIMITING_WINDOW_MS = mParser.getLong(
KEY_RATE_LIMITING_WINDOW_MS, DEFAULT_RATE_LIMITING_WINDOW_MS);
MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW = mParser.getInt(
@@ -2110,6 +2189,8 @@ public final class QuotaController extends StateController {
KEY_MAX_SESSION_COUNT_FREQUENT, DEFAULT_MAX_SESSION_COUNT_FREQUENT);
MAX_SESSION_COUNT_RARE = mParser.getInt(
KEY_MAX_SESSION_COUNT_RARE, DEFAULT_MAX_SESSION_COUNT_RARE);
MAX_SESSION_COUNT_RESTRICTED = mParser.getInt(
KEY_MAX_SESSION_COUNT_RESTRICTED, DEFAULT_MAX_SESSION_COUNT_RESTRICTED);
MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW = mParser.getInt(
KEY_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW,
DEFAULT_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW);
@@ -2173,6 +2254,13 @@ public final class QuotaController extends StateController {
mBucketPeriodsMs[RARE_INDEX] = newRarePeriodMs;
changed = true;
}
// Fit in the range [allowed time (10 mins), 1 week].
long newRestrictedPeriodMs = Math.max(mAllowedTimePerPeriodMs,
Math.min(7 * 24 * 60 * MINUTE_IN_MILLIS, WINDOW_SIZE_RESTRICTED_MS));
if (mBucketPeriodsMs[RESTRICTED_INDEX] != newRestrictedPeriodMs) {
mBucketPeriodsMs[RESTRICTED_INDEX] = newRestrictedPeriodMs;
changed = true;
}
long newRateLimitingWindowMs = Math.min(MAX_PERIOD_MS,
Math.max(MIN_RATE_LIMITING_WINDOW_MS, RATE_LIMITING_WINDOW_MS));
if (mRateLimitingWindowMs != newRateLimitingWindowMs) {
@@ -2206,6 +2294,12 @@ public final class QuotaController extends StateController {
mMaxBucketJobCounts[RARE_INDEX] = newRareMaxJobCount;
changed = true;
}
int newRestrictedMaxJobCount = Math.max(MIN_BUCKET_JOB_COUNT,
MAX_JOB_COUNT_RESTRICTED);
if (mMaxBucketJobCounts[RESTRICTED_INDEX] != newRestrictedMaxJobCount) {
mMaxBucketJobCounts[RESTRICTED_INDEX] = newRestrictedMaxJobCount;
changed = true;
}
int newMaxSessionCountPerRateLimitPeriod = Math.max(
MIN_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW,
MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW);
@@ -2237,6 +2331,11 @@ public final class QuotaController extends StateController {
mMaxBucketSessionCounts[RARE_INDEX] = newRareMaxSessionCount;
changed = true;
}
int newRestrictedMaxSessionCount = Math.max(0, MAX_SESSION_COUNT_RESTRICTED);
if (mMaxBucketSessionCounts[RESTRICTED_INDEX] != newRestrictedMaxSessionCount) {
mMaxBucketSessionCounts[RESTRICTED_INDEX] = newRestrictedMaxSessionCount;
changed = true;
}
long newSessionCoalescingDurationMs = Math.min(15 * MINUTE_IN_MILLIS,
Math.max(0, TIMING_SESSION_COALESCING_DURATION_MS));
if (mTimingSessionCoalescingDurationMs != newSessionCoalescingDurationMs) {
@@ -2266,11 +2365,13 @@ public final class QuotaController extends StateController {
pw.printPair(KEY_WINDOW_SIZE_WORKING_MS, WINDOW_SIZE_WORKING_MS).println();
pw.printPair(KEY_WINDOW_SIZE_FREQUENT_MS, WINDOW_SIZE_FREQUENT_MS).println();
pw.printPair(KEY_WINDOW_SIZE_RARE_MS, WINDOW_SIZE_RARE_MS).println();
pw.printPair(KEY_WINDOW_SIZE_RESTRICTED_MS, WINDOW_SIZE_RESTRICTED_MS).println();
pw.printPair(KEY_MAX_EXECUTION_TIME_MS, MAX_EXECUTION_TIME_MS).println();
pw.printPair(KEY_MAX_JOB_COUNT_ACTIVE, MAX_JOB_COUNT_ACTIVE).println();
pw.printPair(KEY_MAX_JOB_COUNT_WORKING, MAX_JOB_COUNT_WORKING).println();
pw.printPair(KEY_MAX_JOB_COUNT_FREQUENT, MAX_JOB_COUNT_FREQUENT).println();
pw.printPair(KEY_MAX_JOB_COUNT_RARE, MAX_JOB_COUNT_RARE).println();
pw.printPair(KEY_MAX_JOB_COUNT_RESTRICTED, MAX_JOB_COUNT_RESTRICTED).println();
pw.printPair(KEY_RATE_LIMITING_WINDOW_MS, RATE_LIMITING_WINDOW_MS).println();
pw.printPair(KEY_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW,
MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW).println();
@@ -2278,6 +2379,7 @@ public final class QuotaController extends StateController {
pw.printPair(KEY_MAX_SESSION_COUNT_WORKING, MAX_SESSION_COUNT_WORKING).println();
pw.printPair(KEY_MAX_SESSION_COUNT_FREQUENT, MAX_SESSION_COUNT_FREQUENT).println();
pw.printPair(KEY_MAX_SESSION_COUNT_RARE, MAX_SESSION_COUNT_RARE).println();
pw.printPair(KEY_MAX_SESSION_COUNT_RESTRICTED, MAX_SESSION_COUNT_RESTRICTED).println();
pw.printPair(KEY_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW,
MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW).println();
pw.printPair(KEY_TIMING_SESSION_COALESCING_DURATION_MS,
@@ -2297,6 +2399,8 @@ public final class QuotaController extends StateController {
proto.write(ConstantsProto.QuotaController.FREQUENT_WINDOW_SIZE_MS,
WINDOW_SIZE_FREQUENT_MS);
proto.write(ConstantsProto.QuotaController.RARE_WINDOW_SIZE_MS, WINDOW_SIZE_RARE_MS);
proto.write(ConstantsProto.QuotaController.RESTRICTED_WINDOW_SIZE_MS,
WINDOW_SIZE_RESTRICTED_MS);
proto.write(ConstantsProto.QuotaController.MAX_EXECUTION_TIME_MS,
MAX_EXECUTION_TIME_MS);
proto.write(ConstantsProto.QuotaController.MAX_JOB_COUNT_ACTIVE, MAX_JOB_COUNT_ACTIVE);
@@ -2305,6 +2409,8 @@ public final class QuotaController extends StateController {
proto.write(ConstantsProto.QuotaController.MAX_JOB_COUNT_FREQUENT,
MAX_JOB_COUNT_FREQUENT);
proto.write(ConstantsProto.QuotaController.MAX_JOB_COUNT_RARE, MAX_JOB_COUNT_RARE);
proto.write(ConstantsProto.QuotaController.MAX_JOB_COUNT_RESTRICTED,
MAX_JOB_COUNT_RESTRICTED);
proto.write(ConstantsProto.QuotaController.RATE_LIMITING_WINDOW_MS,
RATE_LIMITING_WINDOW_MS);
proto.write(ConstantsProto.QuotaController.MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW,
@@ -2317,6 +2423,8 @@ public final class QuotaController extends StateController {
MAX_SESSION_COUNT_FREQUENT);
proto.write(ConstantsProto.QuotaController.MAX_SESSION_COUNT_RARE,
MAX_SESSION_COUNT_RARE);
proto.write(ConstantsProto.QuotaController.MAX_SESSION_COUNT_RESTRICTED,
MAX_SESSION_COUNT_RESTRICTED);
proto.write(ConstantsProto.QuotaController.MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW,
MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW);
proto.write(ConstantsProto.QuotaController.TIMING_SESSION_COALESCING_DURATION_MS,

View File

@@ -0,0 +1,41 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.job.controllers;
import com.android.server.job.JobSchedulerService;
/**
* Controller that can also handle jobs in the
* {@link android.app.usage.UsageStatsManager#STANDBY_BUCKET_RESTRICTED} bucket.
*/
public abstract class RestrictingController extends StateController {
RestrictingController(JobSchedulerService service) {
super(service);
}
/**
* Start tracking a job that has been added to the
* {@link android.app.usage.UsageStatsManager#STANDBY_BUCKET_RESTRICTED} bucket.
*/
public abstract void startTrackingRestrictedJobLocked(JobStatus jobStatus);
/**
* Stop tracking a job that has been removed from the
* {@link android.app.usage.UsageStatsManager#STANDBY_BUCKET_RESTRICTED} bucket.
*/
public abstract void stopTrackingRestrictedJobLocked(JobStatus jobStatus);
}

View File

@@ -810,6 +810,7 @@ message ScheduledJobStateChanged {
FREQUENT = 2;
RARE = 3;
NEVER = 4;
RESTRICTED = 5;
}
optional Bucket standby_bucket = 5 [default = UNKNOWN];

View File

@@ -34,4 +34,5 @@ enum StopReasonEnum {
STOP_REASON_TIMEOUT = 3;
STOP_REASON_DEVICE_IDLE = 4;
STOP_REASON_DEVICE_THERMAL = 5;
STOP_REASON_RESTRAINED = 6;
}

View File

@@ -282,6 +282,10 @@ message ConstantsProto {
// expected to run only {@link QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS} within the past
// WINDOW_SIZE_MS.
optional int64 rare_window_size_ms = 6;
// The quota window size of the particular standby bucket. Apps in this standby bucket are
// expected to run only {@link QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS} within the past
// WINDOW_SIZE_MS.
optional int64 restricted_window_size_ms = 20;
// The maximum amount of time an app can have its jobs running within a 24 hour window.
optional int64 max_execution_time_ms = 7;
// The maximum number of jobs an app can run within this particular standby bucket's
@@ -296,6 +300,9 @@ message ConstantsProto {
// The maximum number of jobs an app can run within this particular standby bucket's
// window size.
optional int32 max_job_count_rare = 11;
// The maximum number of jobs an app can run within this particular standby bucket's
// window size.
optional int32 max_job_count_restricted = 21;
// The period of time used to rate limit recently run jobs.
optional int32 rate_limiting_window_ms = 19;
// The maximum number of jobs that should be allowed to run in the past
@@ -313,12 +320,17 @@ message ConstantsProto {
// The maximum number of timing sessions an app can run within this particular standby
// bucket's window size.
optional int32 max_session_count_rare = 16;
// The maximum number of timing sessions an app can run within this particular standby
// bucket's window size.
optional int32 max_session_count_restricted = 22;
// The maximum number of timing sessions that should be allowed to run in the past
// rate_limiting_window_ms.
optional int32 max_session_count_per_rate_limiting_window = 17;
// Treat two distinct {@link TimingSession}s as the same if they start and end within this
// amount of time of each other.
optional int64 timing_session_coalescing_duration_ms = 18;
// Next tag: 23
}
optional QuotaController quota_controller = 24;
@@ -943,6 +955,9 @@ message JobStatusDumpProto {
optional JobInfo job_info = 6;
repeated ConstraintEnum required_constraints = 7;
// Dynamic constraints are additional constraints imposed by the system that MUST be met before
// the app can run if the app does not have quota.
repeated ConstraintEnum dynamic_constraints = 31;
repeated ConstraintEnum satisfied_constraints = 8;
repeated ConstraintEnum unsatisfied_constraints = 9;
optional bool is_doze_whitelisted = 10;
@@ -956,6 +971,8 @@ message JobStatusDumpProto {
// Battery Saver). This implicit constraint must be satisfied for the
// job to run.
optional bool is_not_restricted_in_bg = 2;
// True if dynamic constraints have been satisfied.
optional bool is_dynamic_satisfied = 3;
}
optional ImplicitConstraints implicit_constraints = 25;
@@ -998,6 +1015,7 @@ message JobStatusDumpProto {
FREQUENT = 2;
RARE = 3;
NEVER = 4;
RESTRICTED = 5;
}
optional Bucket standby_bucket = 17;
optional bool is_exempted_from_app_standby = 27;
@@ -1028,7 +1046,7 @@ message JobStatusDumpProto {
// was no attempt.
optional int64 time_since_first_force_batch_attempt_ms = 29;
// Next tag: 31
// Next tag: 32
}
// Dump from com.android.server.job.JobConcurrencyManager.