Merge "Adds Flexibility Controller and its supporting JobStatus functions."

This commit is contained in:
Alex Bianchi
2022-07-21 15:55:23 +00:00
committed by Android (Google) Code Review
3 changed files with 657 additions and 2 deletions

View File

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

View File

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

View File

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