Merge "Adds Flexibility Controller and its supporting JobStatus functions."
This commit is contained in:
@@ -0,0 +1,312 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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 static android.text.format.DateUtils.HOUR_IN_MILLIS;
|
||||
import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
|
||||
|
||||
import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
|
||||
|
||||
import android.annotation.ElapsedRealtimeLong;
|
||||
import android.annotation.NonNull;
|
||||
import android.app.job.JobInfo;
|
||||
import android.content.Context;
|
||||
import android.os.Looper;
|
||||
import android.os.UserHandle;
|
||||
import android.util.ArraySet;
|
||||
import android.util.IndentingPrintWriter;
|
||||
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.server.JobSchedulerBackgroundThread;
|
||||
import com.android.server.job.JobSchedulerService;
|
||||
import com.android.server.utils.AlarmQueue;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* Controller that tracks the number of flexible constraints being actively satisfied.
|
||||
* Drops constraint for TOP apps and lowers number of required constraints with time.
|
||||
*
|
||||
* TODO: Plug in to other controllers (b/239047584), handle prefetch (b/238887951)
|
||||
*/
|
||||
public final class FlexibilityController extends StateController {
|
||||
/**
|
||||
* List of all potential flexible constraints
|
||||
*/
|
||||
@VisibleForTesting
|
||||
static final int FLEXIBLE_CONSTRAINTS = JobStatus.CONSTRAINT_BATTERY_NOT_LOW
|
||||
| JobStatus.CONSTRAINT_CHARGING
|
||||
| JobStatus.CONSTRAINT_CONNECTIVITY
|
||||
| JobStatus.CONSTRAINT_IDLE;
|
||||
|
||||
/** Hard cutoff to remove flexible constraints */
|
||||
private static final long DEADLINE_PROXIMITY_LIMIT_MS = 15 * MINUTE_IN_MILLIS;
|
||||
|
||||
/**
|
||||
* Keeps track of what flexible constraints are satisfied at the moment.
|
||||
* Is updated by the other controllers.
|
||||
*/
|
||||
private int mSatisfiedFlexibleConstraints;
|
||||
|
||||
@VisibleForTesting
|
||||
@GuardedBy("mLock")
|
||||
final FlexibilityTracker mFlexibilityTracker;
|
||||
|
||||
private final FlexibilityAlarmQueue mFlexibilityAlarmQueue;
|
||||
private final long mMinTimeBetweenAlarmsMs = MINUTE_IN_MILLIS;
|
||||
|
||||
/**
|
||||
* The percent of a Jobs lifecycle to drop number of required constraints.
|
||||
* mPercentToDropConstraints[i] denotes that at x% of a Jobs lifecycle,
|
||||
* the controller should have i+1 constraints dropped.
|
||||
*/
|
||||
private final int[] mPercentToDropConstraints = {50, 60, 70, 80};
|
||||
|
||||
/** The default deadline that all flexible constraints should be dropped by. */
|
||||
private final long mDefaultFlexibleDeadline = 72 * HOUR_IN_MILLIS;
|
||||
|
||||
public FlexibilityController(JobSchedulerService service) {
|
||||
super(service);
|
||||
mFlexibilityTracker = new FlexibilityTracker(FLEXIBLE_CONSTRAINTS);
|
||||
mFlexibilityAlarmQueue = new FlexibilityAlarmQueue(
|
||||
mContext, JobSchedulerBackgroundThread.get().getLooper());
|
||||
}
|
||||
|
||||
/**
|
||||
* StateController interface
|
||||
*/
|
||||
@Override
|
||||
public void maybeStartTrackingJobLocked(JobStatus js, JobStatus lastJob) {
|
||||
if (js.hasFlexibilityConstraint()) {
|
||||
mFlexibilityTracker.add(js);
|
||||
js.setTrackingController(JobStatus.TRACKING_FLEXIBILITY);
|
||||
final long nowElapsed = sElapsedRealtimeClock.millis();
|
||||
js.setFlexibilityConstraintSatisfied(nowElapsed, isFlexibilitySatisfiedLocked(js));
|
||||
mFlexibilityAlarmQueue.addAlarm(js, getNextConstraintDropTimeElapsed(js));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void maybeStopTrackingJobLocked(JobStatus js, JobStatus incomingJob, boolean forUpdate) {
|
||||
if (js.clearTrackingController(JobStatus.TRACKING_FLEXIBILITY)) {
|
||||
mFlexibilityAlarmQueue.removeAlarmForKey(js);
|
||||
mFlexibilityTracker.remove(js);
|
||||
}
|
||||
}
|
||||
|
||||
/** Checks if the flexibility constraint is actively satisfied for a given job. */
|
||||
@VisibleForTesting
|
||||
boolean isFlexibilitySatisfiedLocked(JobStatus js) {
|
||||
synchronized (mLock) {
|
||||
return mService.getUidBias(js.getUid()) == JobInfo.BIAS_TOP_APP
|
||||
|| mService.isCurrentlyRunningLocked(js)
|
||||
|| getNumSatisfiedRequiredConstraintsLocked(js)
|
||||
>= js.getNumRequiredFlexibleConstraints();
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
@GuardedBy("mLock")
|
||||
int getNumSatisfiedRequiredConstraintsLocked(JobStatus js) {
|
||||
return Integer.bitCount(js.getFlexibleConstraints() & mSatisfiedFlexibleConstraints);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the controller's constraint to a given state.
|
||||
* Changes flexibility constraint satisfaction for affected jobs.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
void setConstraintSatisfied(int constraint, boolean state) {
|
||||
synchronized (mLock) {
|
||||
final boolean old = (mSatisfiedFlexibleConstraints & constraint) != 0;
|
||||
if (old == state) {
|
||||
return;
|
||||
}
|
||||
|
||||
final int prevSatisfied = Integer.bitCount(mSatisfiedFlexibleConstraints);
|
||||
mSatisfiedFlexibleConstraints =
|
||||
(mSatisfiedFlexibleConstraints & ~constraint) | (state ? constraint : 0);
|
||||
final int curSatisfied = Integer.bitCount(mSatisfiedFlexibleConstraints);
|
||||
|
||||
// Only the max of the number of required flexible constraints will need to be updated
|
||||
// The rest did not have a change in state and are still satisfied or unsatisfied.
|
||||
final int numConstraintsToUpdate = Math.max(curSatisfied, prevSatisfied);
|
||||
|
||||
final ArraySet<JobStatus> jobs = mFlexibilityTracker.getJobsByNumRequiredConstraints(
|
||||
numConstraintsToUpdate);
|
||||
final long nowElapsed = sElapsedRealtimeClock.millis();
|
||||
|
||||
for (int i = 0; i < jobs.size(); i++) {
|
||||
JobStatus js = jobs.valueAt(i);
|
||||
js.setFlexibilityConstraintSatisfied(nowElapsed, isFlexibilitySatisfiedLocked(js));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Checks if the given constraint is satisfied in the flexibility controller. */
|
||||
@VisibleForTesting
|
||||
boolean isConstraintSatisfied(int constraint) {
|
||||
return (mSatisfiedFlexibleConstraints & constraint) != 0;
|
||||
}
|
||||
|
||||
/** The elapsed time that marks when the next constraint should be dropped. */
|
||||
@VisibleForTesting
|
||||
@ElapsedRealtimeLong
|
||||
long getNextConstraintDropTimeElapsed(JobStatus js) {
|
||||
final long earliest = js.getEarliestRunTime() == JobStatus.NO_EARLIEST_RUNTIME
|
||||
? js.enqueueTime : js.getEarliestRunTime();
|
||||
final long latest = js.getLatestRunTimeElapsed() == JobStatus.NO_LATEST_RUNTIME
|
||||
? earliest + mDefaultFlexibleDeadline
|
||||
: js.getLatestRunTimeElapsed();
|
||||
final int percent = mPercentToDropConstraints[js.getNumDroppedFlexibleConstraints()];
|
||||
final long percentInTime = ((latest - earliest) * percent) / 100;
|
||||
return earliest + percentInTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
@GuardedBy("mLock")
|
||||
public void onUidBiasChangedLocked(int uid, int prevBias, int newBias) {
|
||||
if (prevBias != JobInfo.BIAS_TOP_APP && newBias != JobInfo.BIAS_TOP_APP) {
|
||||
return;
|
||||
}
|
||||
final long nowElapsed = sElapsedRealtimeClock.millis();
|
||||
List<JobStatus> jobsByUid = mService.getJobStore().getJobsByUid(uid);
|
||||
for (int i = 0; i < jobsByUid.size(); i++) {
|
||||
JobStatus js = jobsByUid.get(i);
|
||||
if (js.hasFlexibilityConstraint()) {
|
||||
js.setFlexibilityConstraintSatisfied(nowElapsed, isFlexibilitySatisfiedLocked(js));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
class FlexibilityTracker {
|
||||
final ArrayList<ArraySet<JobStatus>> mTrackedJobs;
|
||||
|
||||
FlexibilityTracker(int flexibleConstraints) {
|
||||
mTrackedJobs = new ArrayList<>();
|
||||
int numFlexibleConstraints = Integer.bitCount(flexibleConstraints);
|
||||
for (int i = 0; i <= numFlexibleConstraints; i++) {
|
||||
mTrackedJobs.add(new ArraySet<JobStatus>());
|
||||
}
|
||||
}
|
||||
|
||||
/** Gets every tracked job with a given number of required constraints. */
|
||||
public ArraySet<JobStatus> getJobsByNumRequiredConstraints(int numRequired) {
|
||||
return mTrackedJobs.get(numRequired - 1);
|
||||
}
|
||||
|
||||
/** adds a JobStatus object based on number of required flexible constraints. */
|
||||
public void add(JobStatus js) {
|
||||
if (js.getNumRequiredFlexibleConstraints() <= 0) {
|
||||
return;
|
||||
}
|
||||
mTrackedJobs.get(js.getNumRequiredFlexibleConstraints() - 1).add(js);
|
||||
}
|
||||
|
||||
/** Removes a JobStatus object. */
|
||||
public void remove(JobStatus js) {
|
||||
if (js.getNumRequiredFlexibleConstraints() == 0) {
|
||||
return;
|
||||
}
|
||||
mTrackedJobs.get(js.getNumRequiredFlexibleConstraints() - 1).remove(js);
|
||||
}
|
||||
|
||||
/** Returns all tracked jobs. */
|
||||
public ArrayList<ArraySet<JobStatus>> getArrayList() {
|
||||
return mTrackedJobs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjusts number of required flexible constraints and sorts it into the tracker.
|
||||
* Returns false if the job status's number of flexible constraints is now 0.
|
||||
* Jobs with 0 required flexible constraints are removed from the tracker.
|
||||
*/
|
||||
public boolean adjustJobsRequiredConstraints(JobStatus js, int n) {
|
||||
remove(js);
|
||||
js.adjustNumRequiredFlexibleConstraints(n);
|
||||
final long nowElapsed = sElapsedRealtimeClock.millis();
|
||||
js.setFlexibilityConstraintSatisfied(nowElapsed, isFlexibilitySatisfiedLocked(js));
|
||||
if (js.getNumRequiredFlexibleConstraints() <= 0) {
|
||||
maybeStopTrackingJobLocked(js, null, false);
|
||||
return false;
|
||||
}
|
||||
add(js);
|
||||
return true;
|
||||
}
|
||||
|
||||
public void dump(IndentingPrintWriter pw, Predicate<JobStatus> predicate) {
|
||||
for (int i = 0; i < mTrackedJobs.size(); i++) {
|
||||
ArraySet<JobStatus> jobs = mTrackedJobs.get(i);
|
||||
for (int j = 0; j < mTrackedJobs.size(); j++) {
|
||||
final JobStatus js = jobs.valueAt(j);
|
||||
if (!predicate.test(js)) {
|
||||
continue;
|
||||
}
|
||||
pw.print("#");
|
||||
js.printUniqueId(pw);
|
||||
pw.print(" from ");
|
||||
UserHandle.formatUid(pw, js.getSourceUid());
|
||||
pw.println();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class FlexibilityAlarmQueue extends AlarmQueue<JobStatus> {
|
||||
private FlexibilityAlarmQueue(Context context, Looper looper) {
|
||||
super(context, looper, "*job.flexibility_check*",
|
||||
"Flexible Constraint Check", false, mMinTimeBetweenAlarmsMs);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isForUser(@NonNull JobStatus js, int userId) {
|
||||
return js.getSourceUserId() == userId;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void processExpiredAlarms(@NonNull ArraySet<JobStatus> expired) {
|
||||
synchronized (mLock) {
|
||||
JobStatus js;
|
||||
for (int i = 0; i < expired.size(); i++) {
|
||||
js = expired.valueAt(i);
|
||||
long time = getNextConstraintDropTimeElapsed(js);
|
||||
if (js.getLatestRunTimeElapsed() - time < DEADLINE_PROXIMITY_LIMIT_MS) {
|
||||
mFlexibilityTracker.adjustJobsRequiredConstraints(js,
|
||||
-js.getNumRequiredFlexibleConstraints());
|
||||
continue;
|
||||
}
|
||||
if (mFlexibilityTracker.adjustJobsRequiredConstraints(js, -1)) {
|
||||
mFlexibilityAlarmQueue.addAlarm(js, time);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@GuardedBy("mLock")
|
||||
public void dumpControllerStateLocked(IndentingPrintWriter pw, Predicate<JobStatus> predicate) {
|
||||
pw.println("# Constraints Satisfied: " + Integer.bitCount(mSatisfiedFlexibleConstraints));
|
||||
pw.println();
|
||||
|
||||
mFlexibilityTracker.dump(pw, predicate);
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,9 @@
|
||||
|
||||
package com.android.server.job.controllers;
|
||||
|
||||
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
|
||||
import static android.text.format.DateUtils.HOUR_IN_MILLIS;
|
||||
|
||||
import static com.android.server.job.JobSchedulerService.ACTIVE_INDEX;
|
||||
import static com.android.server.job.JobSchedulerService.EXEMPTED_INDEX;
|
||||
import static com.android.server.job.JobSchedulerService.NEVER_INDEX;
|
||||
@@ -99,6 +102,7 @@ public final class JobStatus {
|
||||
static final int CONSTRAINT_WITHIN_QUOTA = 1 << 24; // Implicit constraint
|
||||
static final int CONSTRAINT_PREFETCH = 1 << 23;
|
||||
static final int CONSTRAINT_BACKGROUND_NOT_RESTRICTED = 1 << 22; // Implicit constraint
|
||||
static final int CONSTRAINT_FLEXIBLE = 1 << 21; // Implicit constraint
|
||||
|
||||
// The following set of dynamic constraints are for specific use cases (as explained in their
|
||||
// relative naming and comments). Right now, they apply different constraints, which is fine,
|
||||
@@ -117,6 +121,22 @@ public final class JobStatus {
|
||||
| CONSTRAINT_CONNECTIVITY
|
||||
| CONSTRAINT_IDLE;
|
||||
|
||||
/**
|
||||
* The set of constraints that are required to satisfy flexible constraints.
|
||||
* Constraints explicitly requested by the job will not be added to the set.
|
||||
*/
|
||||
private int mFlexibleConstraints;
|
||||
|
||||
/**
|
||||
* Keeps track of how many flexible constraints must be satisfied for the job to execute.
|
||||
*/
|
||||
private int mNumRequiredFlexibleConstraints;
|
||||
|
||||
/**
|
||||
* Number of required flexible constraints that have been dropped.
|
||||
*/
|
||||
private int mNumDroppedFlexibleConstraints;
|
||||
|
||||
/**
|
||||
* The additional set of dynamic constraints that must be met if this is an expedited job that
|
||||
* had a long enough run while the device was Dozing or in battery saver.
|
||||
@@ -304,6 +324,12 @@ public final class JobStatus {
|
||||
*/
|
||||
public static final int TRACKING_QUOTA = 1 << 6;
|
||||
|
||||
/**
|
||||
* Flag for {@link #trackingControllers}: the flexibility controller is currently tracking this
|
||||
* job.
|
||||
*/
|
||||
public static final int TRACKING_FLEXIBILITY = 1 << 7;
|
||||
|
||||
/**
|
||||
* Bit mask of controllers that are currently tracking the job.
|
||||
*/
|
||||
@@ -318,6 +344,8 @@ public final class JobStatus {
|
||||
*/
|
||||
public static final int INTERNAL_FLAG_HAS_FOREGROUND_EXEMPTION = 1 << 0;
|
||||
|
||||
/** Minimum difference between start and end time to have flexible constraint */
|
||||
private static final long MIN_WINDOW_FOR_FLEXIBILITY_MS = HOUR_IN_MILLIS;
|
||||
/**
|
||||
* Versatile, persistable flags for a job that's updated within the system server,
|
||||
* as opposed to {@link JobInfo#flags} that's set by callers.
|
||||
@@ -525,6 +553,33 @@ public final class JobStatus {
|
||||
}
|
||||
}
|
||||
mHasExemptedMediaUrisOnly = exemptedMediaUrisOnly;
|
||||
|
||||
if (isRequestedExpeditedJob()
|
||||
|| ((latestRunTimeElapsedMillis - earliestRunTimeElapsedMillis)
|
||||
< MIN_WINDOW_FOR_FLEXIBILITY_MS)
|
||||
|| job.isPrefetch()) {
|
||||
mFlexibleConstraints = 0;
|
||||
} else {
|
||||
if ((requiredConstraints & CONSTRAINT_CHARGING) == 0) {
|
||||
mFlexibleConstraints |= CONSTRAINT_CHARGING;
|
||||
}
|
||||
if ((requiredConstraints & CONSTRAINT_BATTERY_NOT_LOW) == 0) {
|
||||
mFlexibleConstraints |= CONSTRAINT_BATTERY_NOT_LOW;
|
||||
}
|
||||
if ((requiredConstraints & CONSTRAINT_IDLE) == 0) {
|
||||
mFlexibleConstraints |= CONSTRAINT_IDLE;
|
||||
}
|
||||
if (job.getRequiredNetwork() != null
|
||||
&& !job.getRequiredNetwork().hasCapability(NET_CAPABILITY_NOT_METERED)) {
|
||||
mFlexibleConstraints |= CONSTRAINT_CONNECTIVITY;
|
||||
}
|
||||
}
|
||||
if (mFlexibleConstraints != 0) {
|
||||
// TODO(b/239047584): Uncomment once Flexibility Controller is plugged in.
|
||||
// requiredConstraints |= CONSTRAINT_FLEXIBLE;
|
||||
mNumRequiredFlexibleConstraints = Integer.bitCount(mFlexibleConstraints);
|
||||
}
|
||||
|
||||
this.requiredConstraints = requiredConstraints;
|
||||
mRequiredConstraintsOfInterest = requiredConstraints & CONSTRAINTS_OF_INTEREST;
|
||||
addDynamicConstraints(dynamicConstraints);
|
||||
@@ -1082,12 +1137,35 @@ public final class JobStatus {
|
||||
return hasConstraint(CONSTRAINT_IDLE);
|
||||
}
|
||||
|
||||
/** Returns true if the job has a prefetch constraint */
|
||||
public boolean hasPrefetchConstraint() {
|
||||
return hasConstraint(CONSTRAINT_PREFETCH);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/** Returns true if the job has flexible job constraints enabled */
|
||||
public boolean hasFlexibilityConstraint() {
|
||||
return (requiredConstraints & CONSTRAINT_FLEXIBLE) != 0;
|
||||
}
|
||||
|
||||
/** Returns the number of flexible job constraints required to be satisfied to execute */
|
||||
public int getNumRequiredFlexibleConstraints() {
|
||||
return mNumRequiredFlexibleConstraints;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of required flexible job constraints that have been dropped with time.
|
||||
* The lower this number is the easier it is for the flexibility constraint to be satisfied.
|
||||
*/
|
||||
public int getNumDroppedFlexibleConstraints() {
|
||||
return mNumDroppedFlexibleConstraints;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks both {@link #requiredConstraints} and {@link #mDynamicConstraints} to see if this job
|
||||
* requires the specified constraint.
|
||||
@@ -1128,6 +1206,10 @@ public final class JobStatus {
|
||||
return mOriginalLatestRunTimeElapsedMillis;
|
||||
}
|
||||
|
||||
public int getFlexibleConstraints() {
|
||||
return mFlexibleConstraints;
|
||||
}
|
||||
|
||||
public void setOriginalLatestRunTimeElapsed(long latestRunTimeElapsed) {
|
||||
mOriginalLatestRunTimeElapsedMillis = latestRunTimeElapsed;
|
||||
}
|
||||
@@ -1301,6 +1383,11 @@ public final class JobStatus {
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @return true if the constraint was changed, false otherwise. */
|
||||
boolean setFlexibilityConstraintSatisfied(final long nowElapsed, boolean state) {
|
||||
return setConstraintSatisfied(CONSTRAINT_FLEXIBLE, nowElapsed, state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether or not this job is approved to be treated as an expedited job based on quota
|
||||
* policy.
|
||||
@@ -1490,6 +1577,18 @@ public final class JobStatus {
|
||||
trackingControllers |= which;
|
||||
}
|
||||
|
||||
/** Adjusts the number of required flexible constraints by the given number */
|
||||
public void adjustNumRequiredFlexibleConstraints(int adjustment) {
|
||||
mNumRequiredFlexibleConstraints += adjustment;
|
||||
if (mNumRequiredFlexibleConstraints < 0) {
|
||||
mNumRequiredFlexibleConstraints = 0;
|
||||
}
|
||||
mNumDroppedFlexibleConstraints -= adjustment;
|
||||
if (mNumDroppedFlexibleConstraints < 0) {
|
||||
mNumDroppedFlexibleConstraints = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add additional constraints to prevent this job from running when doze or battery saver are
|
||||
* active.
|
||||
@@ -1650,12 +1749,14 @@ public final class JobStatus {
|
||||
/** 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 | CONSTRAINT_PREFETCH;
|
||||
| CONSTRAINT_IDLE | CONSTRAINT_CONTENT_TRIGGER | CONSTRAINT_PREFETCH
|
||||
| CONSTRAINT_FLEXIBLE;
|
||||
|
||||
// Soft override covers all non-"functional" constraints
|
||||
static final int SOFT_OVERRIDE_CONSTRAINTS =
|
||||
CONSTRAINT_CHARGING | CONSTRAINT_BATTERY_NOT_LOW | CONSTRAINT_STORAGE_NOT_LOW
|
||||
| CONSTRAINT_TIMING_DELAY | CONSTRAINT_IDLE | CONSTRAINT_PREFETCH;
|
||||
| CONSTRAINT_TIMING_DELAY | CONSTRAINT_IDLE | CONSTRAINT_PREFETCH
|
||||
| CONSTRAINT_FLEXIBLE;
|
||||
|
||||
/** Returns true whenever all dynamically set constraints are satisfied. */
|
||||
public boolean areDynamicConstraintsSatisfied() {
|
||||
|
||||
@@ -0,0 +1,242 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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 static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
|
||||
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.app.AlarmManager;
|
||||
import android.app.job.JobInfo;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManagerInternal;
|
||||
import android.os.Looper;
|
||||
import android.util.ArraySet;
|
||||
|
||||
import com.android.server.LocalServices;
|
||||
import com.android.server.job.JobSchedulerService;
|
||||
import com.android.server.job.JobStore;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoSession;
|
||||
import org.mockito.quality.Strictness;
|
||||
|
||||
import java.time.Clock;
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class FlexibilityControllerTest {
|
||||
private static final String SOURCE_PACKAGE = "com.android.frameworks.mockingservicestests";
|
||||
private static final int SOURCE_USER_ID = 0;
|
||||
|
||||
private MockitoSession mMockingSession;
|
||||
private FlexibilityController mFlexibilityController;
|
||||
@Mock
|
||||
private AlarmManager mAlarmManager;
|
||||
@Mock
|
||||
private Context mContext;
|
||||
@Mock
|
||||
private JobSchedulerService mJobSchedulerService;
|
||||
private JobStore mJobStore;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
mMockingSession = mockitoSession()
|
||||
.initMocks(this)
|
||||
.strictness(Strictness.LENIENT)
|
||||
.mockStatic(LocalServices.class)
|
||||
.startMocking();
|
||||
// Called in StateController constructor.
|
||||
when(mJobSchedulerService.getTestableContext()).thenReturn(mContext);
|
||||
when(mJobSchedulerService.getLock()).thenReturn(mJobSchedulerService);
|
||||
when(mJobSchedulerService.getConstants()).thenReturn(
|
||||
mock(JobSchedulerService.Constants.class));
|
||||
// Called in FlexibilityController constructor.
|
||||
when(mContext.getMainLooper()).thenReturn(Looper.getMainLooper());
|
||||
when(mContext.getSystemService(Context.ALARM_SERVICE)).thenReturn(mAlarmManager);
|
||||
//used to get jobs by UID
|
||||
mJobStore = JobStore.initAndGetForTesting(mContext, mContext.getFilesDir());
|
||||
when(mJobSchedulerService.getJobStore()).thenReturn(mJobStore);
|
||||
// Used in JobStatus.
|
||||
doReturn(mock(PackageManagerInternal.class))
|
||||
.when(() -> LocalServices.getService(PackageManagerInternal.class));
|
||||
// Freeze the clocks at a moment in time
|
||||
JobSchedulerService.sSystemClock =
|
||||
Clock.fixed(Instant.ofEpochMilli(100L), ZoneOffset.UTC);
|
||||
JobSchedulerService.sElapsedRealtimeClock =
|
||||
Clock.fixed(Instant.ofEpochMilli(100L), ZoneOffset.UTC);
|
||||
// Initialize real objects.
|
||||
mFlexibilityController = new FlexibilityController(mJobSchedulerService);
|
||||
}
|
||||
|
||||
@After
|
||||
public void teardown() {
|
||||
if (mMockingSession != null) {
|
||||
mMockingSession.finishMocking();
|
||||
}
|
||||
}
|
||||
|
||||
private static JobInfo.Builder createJob(int id) {
|
||||
return new JobInfo.Builder(id, new ComponentName("foo", "bar"));
|
||||
}
|
||||
|
||||
private JobStatus createJobStatus(String testTag, JobInfo.Builder job) {
|
||||
JobInfo jobInfo = job.build();
|
||||
return JobStatus.createFromJobInfo(
|
||||
jobInfo, 1000, SOURCE_PACKAGE, SOURCE_USER_ID, testTag);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetNextConstraintDropTimeElapsed() {
|
||||
long nextTimeToDropNumConstraints;
|
||||
|
||||
// no delay, deadline
|
||||
JobInfo.Builder jb = createJob(0).setOverrideDeadline(1000);
|
||||
JobStatus js = createJobStatus("time", jb);
|
||||
js.enqueueTime = 100L;
|
||||
|
||||
assertEquals(0, js.getEarliestRunTime());
|
||||
assertEquals(1100L, js.getLatestRunTimeElapsed());
|
||||
assertEquals(100L, js.enqueueTime);
|
||||
|
||||
nextTimeToDropNumConstraints = mFlexibilityController.getNextConstraintDropTimeElapsed(js);
|
||||
assertEquals(600L, nextTimeToDropNumConstraints);
|
||||
js.adjustNumRequiredFlexibleConstraints(-1);
|
||||
nextTimeToDropNumConstraints = mFlexibilityController.getNextConstraintDropTimeElapsed(js);
|
||||
assertEquals(700L, nextTimeToDropNumConstraints);
|
||||
js.adjustNumRequiredFlexibleConstraints(-1);
|
||||
nextTimeToDropNumConstraints = mFlexibilityController.getNextConstraintDropTimeElapsed(js);
|
||||
assertEquals(800L, nextTimeToDropNumConstraints);
|
||||
|
||||
// delay, no deadline
|
||||
jb = createJob(0).setMinimumLatency(800000L);
|
||||
js = createJobStatus("time", jb);
|
||||
js.enqueueTime = 100L;
|
||||
|
||||
nextTimeToDropNumConstraints = mFlexibilityController.getNextConstraintDropTimeElapsed(js);
|
||||
assertEquals(130400100, nextTimeToDropNumConstraints);
|
||||
js.adjustNumRequiredFlexibleConstraints(-1);
|
||||
nextTimeToDropNumConstraints = mFlexibilityController.getNextConstraintDropTimeElapsed(js);
|
||||
assertEquals(156320100L, nextTimeToDropNumConstraints);
|
||||
js.adjustNumRequiredFlexibleConstraints(-1);
|
||||
nextTimeToDropNumConstraints = mFlexibilityController.getNextConstraintDropTimeElapsed(js);
|
||||
assertEquals(182240100L, nextTimeToDropNumConstraints);
|
||||
|
||||
// no delay, no deadline
|
||||
jb = createJob(0);
|
||||
js = createJobStatus("time", jb);
|
||||
js.enqueueTime = 100L;
|
||||
|
||||
nextTimeToDropNumConstraints = mFlexibilityController.getNextConstraintDropTimeElapsed(js);
|
||||
assertEquals(129600100, nextTimeToDropNumConstraints);
|
||||
js.adjustNumRequiredFlexibleConstraints(-1);
|
||||
nextTimeToDropNumConstraints = mFlexibilityController.getNextConstraintDropTimeElapsed(js);
|
||||
assertEquals(155520100L, nextTimeToDropNumConstraints);
|
||||
js.adjustNumRequiredFlexibleConstraints(-1);
|
||||
nextTimeToDropNumConstraints = mFlexibilityController.getNextConstraintDropTimeElapsed(js);
|
||||
assertEquals(181440100L, nextTimeToDropNumConstraints);
|
||||
|
||||
// delay, deadline
|
||||
jb = createJob(0).setOverrideDeadline(1100).setMinimumLatency(100);
|
||||
js = createJobStatus("time", jb);
|
||||
js.enqueueTime = 100L;
|
||||
|
||||
nextTimeToDropNumConstraints = mFlexibilityController.getNextConstraintDropTimeElapsed(js);
|
||||
assertEquals(700L, nextTimeToDropNumConstraints);
|
||||
js.adjustNumRequiredFlexibleConstraints(-1);
|
||||
nextTimeToDropNumConstraints = mFlexibilityController.getNextConstraintDropTimeElapsed(js);
|
||||
assertEquals(800L, nextTimeToDropNumConstraints);
|
||||
js.adjustNumRequiredFlexibleConstraints(-1);
|
||||
nextTimeToDropNumConstraints = mFlexibilityController.getNextConstraintDropTimeElapsed(js);
|
||||
assertEquals(900L, nextTimeToDropNumConstraints);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWontStopJobFromRunning() {
|
||||
JobStatus js = createJobStatus("testWontStopJobFromRunning", createJob(101));
|
||||
// Stop satisfied constraints from causing a false positive.
|
||||
js.adjustNumRequiredFlexibleConstraints(100);
|
||||
synchronized (mFlexibilityController.mLock) {
|
||||
when(mJobSchedulerService.isCurrentlyRunningLocked(js)).thenReturn(true);
|
||||
assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(js));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFlexibilityTracker() {
|
||||
FlexibilityController.FlexibilityTracker flexTracker =
|
||||
mFlexibilityController.new
|
||||
FlexibilityTracker(FlexibilityController.FLEXIBLE_CONSTRAINTS);
|
||||
|
||||
JobStatus[] jobs = new JobStatus[4];
|
||||
JobInfo.Builder jb;
|
||||
for (int i = 0; i < jobs.length; i++) {
|
||||
jb = createJob(i);
|
||||
if (i > 0) {
|
||||
jb.setRequiresDeviceIdle(true);
|
||||
}
|
||||
if (i > 1) {
|
||||
jb.setRequiresBatteryNotLow(true);
|
||||
}
|
||||
if (i > 2) {
|
||||
jb.setRequiresCharging(true);
|
||||
}
|
||||
jobs[i] = createJobStatus("", jb);
|
||||
flexTracker.add(jobs[i]);
|
||||
|
||||
}
|
||||
|
||||
ArrayList<ArraySet<JobStatus>> trackedJobs = flexTracker.getArrayList();
|
||||
assertEquals(1, trackedJobs.get(0).size());
|
||||
assertEquals(1, trackedJobs.get(1).size());
|
||||
assertEquals(1, trackedJobs.get(2).size());
|
||||
assertEquals(0, trackedJobs.get(3).size());
|
||||
|
||||
flexTracker.adjustJobsRequiredConstraints(jobs[0], -1);
|
||||
assertEquals(1, trackedJobs.get(0).size());
|
||||
assertEquals(2, trackedJobs.get(1).size());
|
||||
assertEquals(0, trackedJobs.get(2).size());
|
||||
assertEquals(0, trackedJobs.get(3).size());
|
||||
|
||||
flexTracker.adjustJobsRequiredConstraints(jobs[0], -1);
|
||||
assertEquals(2, trackedJobs.get(0).size());
|
||||
assertEquals(1, trackedJobs.get(1).size());
|
||||
assertEquals(0, trackedJobs.get(2).size());
|
||||
assertEquals(0, trackedJobs.get(3).size());
|
||||
|
||||
flexTracker.adjustJobsRequiredConstraints(jobs[0], -1);
|
||||
assertEquals(1, trackedJobs.get(0).size());
|
||||
assertEquals(1, trackedJobs.get(1).size());
|
||||
assertEquals(0, trackedJobs.get(2).size());
|
||||
assertEquals(0, trackedJobs.get(3).size());
|
||||
|
||||
flexTracker.remove(jobs[1]);
|
||||
assertEquals(1, trackedJobs.get(0).size());
|
||||
assertEquals(0, trackedJobs.get(1).size());
|
||||
assertEquals(0, trackedJobs.get(2).size());
|
||||
assertEquals(0, trackedJobs.get(3).size());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user