Merge "Place additional constraints on restricted jobs."
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -810,6 +810,7 @@ message ScheduledJobStateChanged {
|
||||
FREQUENT = 2;
|
||||
RARE = 3;
|
||||
NEVER = 4;
|
||||
RESTRICTED = 5;
|
||||
}
|
||||
optional Bucket standby_bucket = 5 [default = UNKNOWN];
|
||||
|
||||
|
||||
@@ -34,4 +34,5 @@ enum StopReasonEnum {
|
||||
STOP_REASON_TIMEOUT = 3;
|
||||
STOP_REASON_DEVICE_IDLE = 4;
|
||||
STOP_REASON_DEVICE_THERMAL = 5;
|
||||
STOP_REASON_RESTRAINED = 6;
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user