summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Kweku Adams <kwekua@google.com> 2021-02-08 23:53:04 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2021-02-08 23:53:04 +0000
commit076669f905517c3637066dfa3caf6821e400358c (patch)
treeeb1127c5eb1fdeab3f9cc64ba8b35bf75200f2d1
parent5ac0513b61ceb76f6b67f67a6dee7be4e0d2d09b (diff)
parent31b53cfe9395bdbe7a1b7a0f0b69b48d89058f98 (diff)
Merge "Add concurrency restriction at the USER level" into sc-dev
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java204
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java1
-rw-r--r--core/res/res/values/config.xml5
-rw-r--r--core/res/res/values/symbols.xml2
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java188
-rw-r--r--services/tests/servicestests/src/com/android/server/job/GracePeriodObserverTest.java106
-rw-r--r--services/tests/servicestests/src/com/android/server/job/WorkCountTrackerTest.java251
-rw-r--r--services/tests/servicestests/src/com/android/server/job/WorkTypeConfigTest.java102
8 files changed, 774 insertions, 85 deletions
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
index 18856f782b7d..edf2ce3c92cb 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
@@ -16,33 +16,43 @@
package com.android.server.job;
+import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
+
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
+import android.app.UserSwitchObserver;
import android.app.job.JobInfo;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.UserInfo;
import android.os.Handler;
import android.os.PowerManager;
import android.os.RemoteException;
+import android.os.UserHandle;
import android.provider.DeviceConfig;
import android.util.ArraySet;
import android.util.IndentingPrintWriter;
import android.util.Pair;
import android.util.Slog;
import android.util.SparseIntArray;
+import android.util.SparseLongArray;
import android.util.TimeUtils;
import android.util.proto.ProtoOutputStream;
+import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.procstats.ProcessStats;
import com.android.internal.util.StatLogger;
import com.android.server.JobSchedulerBackgroundThread;
+import com.android.server.LocalServices;
import com.android.server.job.controllers.JobStatus;
import com.android.server.job.controllers.StateController;
+import com.android.server.pm.UserManagerInternal;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -66,12 +76,14 @@ class JobConcurrencyManager {
static final int WORK_TYPE_NONE = 0;
static final int WORK_TYPE_TOP = 1 << 0;
static final int WORK_TYPE_BG = 1 << 1;
- private static final int NUM_WORK_TYPES = 2;
+ static final int WORK_TYPE_BGUSER = 1 << 2;
+ private static final int NUM_WORK_TYPES = 3;
@IntDef(prefix = {"WORK_TYPE_"}, flag = true, value = {
WORK_TYPE_NONE,
WORK_TYPE_TOP,
- WORK_TYPE_BG
+ WORK_TYPE_BG,
+ WORK_TYPE_BGUSER
})
@Retention(RetentionPolicy.SOURCE)
public @interface WorkType {
@@ -98,22 +110,26 @@ class JobConcurrencyManager {
// defaultMin
List.of(Pair.create(WORK_TYPE_TOP, 2), Pair.create(WORK_TYPE_BG, 2)),
// defaultMax
- List.of(Pair.create(WORK_TYPE_BG, 6))),
+ List.of(Pair.create(WORK_TYPE_BG, 6), Pair.create(WORK_TYPE_BGUSER, 4))
+ ),
new WorkTypeConfig("screen_on_moderate", 8,
// defaultMin
List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_BG, 2)),
// defaultMax
- List.of(Pair.create(WORK_TYPE_BG, 4))),
+ List.of(Pair.create(WORK_TYPE_BG, 4), Pair.create(WORK_TYPE_BGUSER, 2))
+ ),
new WorkTypeConfig("screen_on_low", 5,
// defaultMin
List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_BG, 1)),
// defaultMax
- List.of(Pair.create(WORK_TYPE_BG, 1))),
+ List.of(Pair.create(WORK_TYPE_BG, 1), Pair.create(WORK_TYPE_BGUSER, 1))
+ ),
new WorkTypeConfig("screen_on_critical", 5,
// defaultMin
List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_BG, 1)),
// defaultMax
- List.of(Pair.create(WORK_TYPE_BG, 1)))
+ List.of(Pair.create(WORK_TYPE_BG, 1), Pair.create(WORK_TYPE_BGUSER, 1))
+ )
);
private static final WorkConfigLimitsPerMemoryTrimLevel CONFIG_LIMITS_SCREEN_OFF =
new WorkConfigLimitsPerMemoryTrimLevel(
@@ -121,22 +137,26 @@ class JobConcurrencyManager {
// defaultMin
List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_BG, 2)),
// defaultMax
- List.of(Pair.create(WORK_TYPE_BG, 6))),
+ List.of(Pair.create(WORK_TYPE_BG, 6), Pair.create(WORK_TYPE_BGUSER, 4))
+ ),
new WorkTypeConfig("screen_off_moderate", 10,
// defaultMin
List.of(Pair.create(WORK_TYPE_TOP, 6), Pair.create(WORK_TYPE_BG, 2)),
// defaultMax
- List.of(Pair.create(WORK_TYPE_BG, 4))),
+ List.of(Pair.create(WORK_TYPE_BG, 4), Pair.create(WORK_TYPE_BGUSER, 2))
+ ),
new WorkTypeConfig("screen_off_low", 5,
// defaultMin
List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_BG, 1)),
// defaultMax
- List.of(Pair.create(WORK_TYPE_BG, 1))),
+ List.of(Pair.create(WORK_TYPE_BG, 1), Pair.create(WORK_TYPE_BGUSER, 1))
+ ),
new WorkTypeConfig("screen_off_critical", 5,
// defaultMin
List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_BG, 1)),
// defaultMax
- List.of(Pair.create(WORK_TYPE_BG, 1)))
+ List.of(Pair.create(WORK_TYPE_BG, 1), Pair.create(WORK_TYPE_BGUSER, 1))
+ )
);
/**
@@ -171,6 +191,10 @@ class JobConcurrencyManager {
"assignJobsToContexts",
"refreshSystemState",
});
+ @VisibleForTesting
+ GracePeriodObserver mGracePeriodObserver;
+ @VisibleForTesting
+ boolean mShouldRestrictBgUser;
interface Stats {
int ASSIGN_JOBS_TO_CONTEXTS = 0;
@@ -182,9 +206,13 @@ class JobConcurrencyManager {
JobConcurrencyManager(JobSchedulerService service) {
mService = service;
mLock = mService.mLock;
- mContext = service.getContext();
+ mContext = service.getTestableContext();
mHandler = JobSchedulerBackgroundThread.getHandler();
+
+ mGracePeriodObserver = new GracePeriodObserver(mContext);
+ mShouldRestrictBgUser = mContext.getResources().getBoolean(
+ R.bool.config_jobSchedulerRestrictBackgroundUser);
}
public void onSystemReady() {
@@ -193,10 +221,18 @@ class JobConcurrencyManager {
final IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_ON);
filter.addAction(Intent.ACTION_SCREEN_OFF);
mContext.registerReceiver(mReceiver, filter);
+ try {
+ ActivityManager.getService().registerUserSwitchObserver(mGracePeriodObserver, TAG);
+ } catch (RemoteException e) {
+ }
onInteractiveStateChanged(mPowerManager.isInteractive());
}
+ void onUserRemoved(int userId) {
+ mGracePeriodObserver.onUserRemoved(userId);
+ }
+
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -224,7 +260,7 @@ class JobConcurrencyManager {
Slog.d(TAG, "Interactive: " + interactive);
}
- final long nowRealtime = JobSchedulerService.sElapsedRealtimeClock.millis();
+ final long nowRealtime = sElapsedRealtimeClock.millis();
if (interactive) {
mLastScreenOnRealtime = nowRealtime;
mEffectiveInteractiveState = true;
@@ -261,7 +297,7 @@ class JobConcurrencyManager {
if (mLastScreenOnRealtime > mLastScreenOffRealtime) {
return;
}
- final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+ final long now = sElapsedRealtimeClock.millis();
if ((mLastScreenOffRealtime + mScreenOffAdjustmentDelayMs) > now) {
return;
}
@@ -723,6 +759,10 @@ class JobConcurrencyManager {
pw.print(mLastMemoryTrimLevel);
pw.println();
+ pw.print("User Grace Period: ");
+ pw.print(mGracePeriodObserver.mGracePeriodExpiration);
+ pw.println();
+
mStatLogger.dump(pw);
} finally {
pw.decreaseIndent();
@@ -748,14 +788,44 @@ class JobConcurrencyManager {
proto.end(token);
}
+ /**
+ * Decides whether a job is from the current foreground user or the equivalent.
+ */
+ @VisibleForTesting
+ boolean shouldRunAsFgUserJob(JobStatus job) {
+ if (!mShouldRestrictBgUser) return true;
+ int userId = job.getSourceUserId();
+ UserManagerInternal um = LocalServices.getService(UserManagerInternal.class);
+ UserInfo userInfo = um.getUserInfo(userId);
+
+ // If the user has a parent user (e.g. a work profile of another user), the user should be
+ // treated equivalent as its parent user.
+ if (userInfo.profileGroupId != UserInfo.NO_PROFILE_GROUP_ID
+ && userInfo.profileGroupId != userId) {
+ userId = userInfo.profileGroupId;
+ userInfo = um.getUserInfo(userId);
+ }
+
+ int currentUser = LocalServices.getService(ActivityManagerInternal.class)
+ .getCurrentUserId();
+ // A user is treated as foreground user if any of the followings is true:
+ // 1. The user is current user
+ // 2. The user is primary user
+ // 3. The user's grace period has not expired
+ return currentUser == userId || userInfo.isPrimary()
+ || mGracePeriodObserver.isWithinGracePeriodForUser(userId);
+ }
+
int getJobWorkTypes(@NonNull JobStatus js) {
int classification = 0;
// TODO(171305774): create dedicated work type for EJ and FGS
if (js.lastEvaluatedPriority >= JobInfo.PRIORITY_TOP_APP
|| js.shouldTreatAsExpeditedJob()) {
classification |= WORK_TYPE_TOP;
- } else {
+ } else if (shouldRunAsFgUserJob(js)) {
classification |= WORK_TYPE_BG;
+ } else {
+ classification |= WORK_TYPE_BGUSER;
}
return classification;
}
@@ -766,8 +836,12 @@ class JobConcurrencyManager {
CONFIG_KEY_PREFIX_CONCURRENCY + "max_total_";
private static final String KEY_PREFIX_MAX_TOP = CONFIG_KEY_PREFIX_CONCURRENCY + "max_top_";
private static final String KEY_PREFIX_MAX_BG = CONFIG_KEY_PREFIX_CONCURRENCY + "max_bg_";
+ private static final String KEY_PREFIX_MAX_BGUSER =
+ CONFIG_KEY_PREFIX_CONCURRENCY + "max_bguser_";
private static final String KEY_PREFIX_MIN_TOP = CONFIG_KEY_PREFIX_CONCURRENCY + "min_top_";
private static final String KEY_PREFIX_MIN_BG = CONFIG_KEY_PREFIX_CONCURRENCY + "min_bg_";
+ private static final String KEY_PREFIX_MIN_BGUSER =
+ CONFIG_KEY_PREFIX_CONCURRENCY + "min_bguser_";
private final String mConfigIdentifier;
private int mMaxTotal;
@@ -815,6 +889,10 @@ class JobConcurrencyManager {
properties.getInt(KEY_PREFIX_MAX_BG + mConfigIdentifier,
mDefaultMaxAllowedSlots.get(WORK_TYPE_BG, mMaxTotal))));
mMaxAllowedSlots.put(WORK_TYPE_BG, maxBg);
+ final int maxBgUser = Math.max(1, Math.min(mMaxTotal,
+ properties.getInt(KEY_PREFIX_MAX_BGUSER + mConfigIdentifier,
+ mDefaultMaxAllowedSlots.get(WORK_TYPE_BGUSER, mMaxTotal))));
+ mMaxAllowedSlots.put(WORK_TYPE_BGUSER, maxBgUser);
int remaining = mMaxTotal;
mMinReservedSlots.clear();
@@ -829,6 +907,12 @@ class JobConcurrencyManager {
properties.getInt(KEY_PREFIX_MIN_BG + mConfigIdentifier,
mDefaultMinReservedSlots.get(WORK_TYPE_BG))));
mMinReservedSlots.put(WORK_TYPE_BG, minBg);
+ remaining -= minBg;
+ // Ensure bg user is in the range [0, min(maxBgUser, remaining)]
+ final int minBgUser = Math.max(0, Math.min(Math.min(maxBgUser, remaining),
+ properties.getInt(KEY_PREFIX_MIN_BGUSER + mConfigIdentifier,
+ mDefaultMinReservedSlots.get(WORK_TYPE_BGUSER, 0))));
+ mMinReservedSlots.put(WORK_TYPE_BGUSER, minBgUser);
}
int getMaxTotal() {
@@ -853,6 +937,10 @@ class JobConcurrencyManager {
.println();
pw.print(KEY_PREFIX_MAX_BG + mConfigIdentifier, mMaxAllowedSlots.get(WORK_TYPE_BG))
.println();
+ pw.print(KEY_PREFIX_MIN_BGUSER + mConfigIdentifier,
+ mMinReservedSlots.get(WORK_TYPE_BGUSER)).println();
+ pw.print(KEY_PREFIX_MAX_BGUSER + mConfigIdentifier,
+ mMaxAllowedSlots.get(WORK_TYPE_BGUSER)).println();
}
}
@@ -873,6 +961,58 @@ class JobConcurrencyManager {
}
/**
+ * This class keeps the track of when a user's grace period expires.
+ */
+ @VisibleForTesting
+ static class GracePeriodObserver extends UserSwitchObserver {
+ // Key is UserId and Value is the time when grace period expires
+ @VisibleForTesting
+ final SparseLongArray mGracePeriodExpiration = new SparseLongArray();
+ private int mCurrentUserId;
+ @VisibleForTesting
+ int mGracePeriod;
+ private final UserManagerInternal mUserManagerInternal;
+ final Object mLock = new Object();
+
+
+ GracePeriodObserver(Context context) {
+ mCurrentUserId = LocalServices.getService(ActivityManagerInternal.class)
+ .getCurrentUserId();
+ mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
+ mGracePeriod = Math.max(0, context.getResources().getInteger(
+ R.integer.config_jobSchedulerUserGracePeriod));
+ }
+
+ @Override
+ public void onUserSwitchComplete(int newUserId) {
+ final long expiration = sElapsedRealtimeClock.millis() + mGracePeriod;
+ synchronized (mLock) {
+ if (mCurrentUserId != UserHandle.USER_NULL
+ && mUserManagerInternal.exists(mCurrentUserId)) {
+ mGracePeriodExpiration.append(mCurrentUserId, expiration);
+ }
+ mGracePeriodExpiration.delete(newUserId);
+ mCurrentUserId = newUserId;
+ }
+ }
+
+ void onUserRemoved(int userId) {
+ synchronized (mLock) {
+ mGracePeriodExpiration.delete(userId);
+ }
+ }
+
+ @VisibleForTesting
+ public boolean isWithinGracePeriodForUser(int userId) {
+ synchronized (mLock) {
+ return userId == mCurrentUserId
+ || sElapsedRealtimeClock.millis()
+ < mGracePeriodExpiration.get(userId, Long.MAX_VALUE);
+ }
+ }
+ }
+
+ /**
* This class decides, taking into account the current {@link WorkTypeConfig} and how many jobs
* are running/pending, how many more job can start.
*
@@ -900,12 +1040,16 @@ class JobConcurrencyManager {
mConfigNumReservedSlots.put(WORK_TYPE_TOP,
workTypeConfig.getMinReserved(WORK_TYPE_TOP));
mConfigNumReservedSlots.put(WORK_TYPE_BG, workTypeConfig.getMinReserved(WORK_TYPE_BG));
+ mConfigNumReservedSlots.put(WORK_TYPE_BGUSER,
+ workTypeConfig.getMinReserved(WORK_TYPE_BGUSER));
mConfigAbsoluteMaxSlots.put(WORK_TYPE_TOP, workTypeConfig.getMax(WORK_TYPE_TOP));
mConfigAbsoluteMaxSlots.put(WORK_TYPE_BG, workTypeConfig.getMax(WORK_TYPE_BG));
+ mConfigAbsoluteMaxSlots.put(WORK_TYPE_BGUSER, workTypeConfig.getMax(WORK_TYPE_BGUSER));
mNumUnspecialized = mConfigMaxTotal;
mNumUnspecialized -= mConfigNumReservedSlots.get(WORK_TYPE_TOP);
mNumUnspecialized -= mConfigNumReservedSlots.get(WORK_TYPE_BG);
+ mNumUnspecialized -= mConfigNumReservedSlots.get(WORK_TYPE_BGUSER);
mNumUnspecializedRemaining = mConfigMaxTotal;
for (int i = mNumRunningJobs.size() - 1; i >= 0; --i) {
mNumUnspecializedRemaining -= Math.max(mNumRunningJobs.valueAt(i),
@@ -937,6 +1081,9 @@ class JobConcurrencyManager {
if ((workTypes & WORK_TYPE_BG) == WORK_TYPE_BG) {
mNumPendingJobs.put(WORK_TYPE_BG, mNumPendingJobs.get(WORK_TYPE_BG) + 1);
}
+ if ((workTypes & WORK_TYPE_BGUSER) == WORK_TYPE_BGUSER) {
+ mNumPendingJobs.put(WORK_TYPE_BGUSER, mNumPendingJobs.get(WORK_TYPE_BGUSER) + 1);
+ }
}
void stageJob(@WorkType int workType) {
@@ -1029,6 +1176,11 @@ class JobConcurrencyManager {
int resBg = Math.min(mConfigNumReservedSlots.get(WORK_TYPE_BG), numBg);
mNumActuallyReservedSlots.put(WORK_TYPE_BG, resBg);
mNumUnspecialized -= resBg;
+ final int numBgUser = mNumRunningJobs.get(WORK_TYPE_BGUSER)
+ + mNumPendingJobs.get(WORK_TYPE_BGUSER);
+ int resBgUser = Math.min(mConfigNumReservedSlots.get(WORK_TYPE_BGUSER), numBgUser);
+ mNumActuallyReservedSlots.put(WORK_TYPE_BGUSER, resBgUser);
+ mNumUnspecialized -= resBgUser;
mNumUnspecializedRemaining = mNumUnspecialized;
// Account for already running jobs after we've assigned the minimum number of slots.
@@ -1048,6 +1200,14 @@ class JobConcurrencyManager {
resBg += unspecializedAssigned;
mNumUnspecializedRemaining -= extraRunning;
}
+ extraRunning = (mNumRunningJobs.get(WORK_TYPE_BGUSER) - resBgUser);
+ if (extraRunning > 0) {
+ unspecializedAssigned = Math.max(0,
+ Math.min(mConfigAbsoluteMaxSlots.get(WORK_TYPE_BGUSER) - resBgUser,
+ extraRunning));
+ resBgUser += unspecializedAssigned;
+ mNumUnspecializedRemaining -= extraRunning;
+ }
// Assign remaining unspecialized based on ranking.
unspecializedAssigned = Math.max(0,
@@ -1060,6 +1220,12 @@ class JobConcurrencyManager {
Math.min(mConfigAbsoluteMaxSlots.get(WORK_TYPE_BG), numBg) - resBg));
mNumActuallyReservedSlots.put(WORK_TYPE_BG, resBg + unspecializedAssigned);
mNumUnspecializedRemaining -= unspecializedAssigned;
+ unspecializedAssigned = Math.max(0,
+ Math.min(mNumUnspecializedRemaining,
+ Math.min(mConfigAbsoluteMaxSlots.get(WORK_TYPE_BGUSER), numBgUser)
+ - resBgUser));
+ mNumActuallyReservedSlots.put(WORK_TYPE_BGUSER, resBgUser + unspecializedAssigned);
+ mNumUnspecializedRemaining -= unspecializedAssigned;
}
int canJobStart(int workTypes) {
@@ -1081,6 +1247,16 @@ class JobConcurrencyManager {
return WORK_TYPE_BG;
}
}
+ if ((workTypes & WORK_TYPE_BGUSER) == WORK_TYPE_BGUSER) {
+ final int maxAllowed = Math.min(
+ mConfigAbsoluteMaxSlots.get(WORK_TYPE_BGUSER),
+ mNumActuallyReservedSlots.get(WORK_TYPE_BGUSER)
+ + mNumUnspecializedRemaining);
+ if (mNumRunningJobs.get(WORK_TYPE_BGUSER) + mNumStartingJobs.get(WORK_TYPE_BGUSER)
+ < maxAllowed) {
+ return WORK_TYPE_BGUSER;
+ }
+ }
return WORK_TYPE_NONE;
}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index 7ce867c6c850..bfc153f5f2f7 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -743,6 +743,7 @@ public class JobSchedulerService extends com.android.server.SystemService
mControllers.get(c).onUserRemovedLocked(userId);
}
}
+ mConcurrencyManager.onUserRemoved(userId);
} else if (Intent.ACTION_QUERY_PACKAGE_RESTART.equals(action)) {
// Has this package scheduled any jobs, such that we will take action
// if it were to be force-stopped?
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 25e7951efe28..8c5f454d204d 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3477,6 +3477,11 @@
<!-- The alarm window (in milliseconds) that JobScheduler uses to enter the idle state -->
<integer name="config_jobSchedulerIdleWindowSlop">300000</integer>
+ <!-- If true, jobs from background user will be restricted -->
+ <bool name="config_jobSchedulerRestrictBackgroundUser">false</bool>
+ <!-- The length of grace period after user becomes background user -->
+ <integer name="config_jobSchedulerUserGracePeriod">60000</integer>
+
<!-- If true, all guest users created on the device will be ephemeral. -->
<bool name="config_guestUserEphemeral">false</bool>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 9a165bb6fc30..c41b78e4a680 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2680,6 +2680,8 @@
<java-symbol type="integer" name="config_jobSchedulerInactivityIdleThreshold" />
<java-symbol type="integer" name="config_jobSchedulerIdleWindowSlop" />
+ <java-symbol type="bool" name="config_jobSchedulerRestrictBackgroundUser" />
+ <java-symbol type="integer" name="config_jobSchedulerUserGracePeriod" />
<java-symbol type="style" name="Animation.ImmersiveModeConfirmation" />
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java
new file mode 100644
index 000000000000..35ac8979d46a
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2021 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;
+
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManagerInternal;
+import android.app.job.JobInfo;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.UserInfo;
+import android.content.res.Resources;
+import android.os.UserHandle;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.R;
+import com.android.server.LocalServices;
+import com.android.server.job.JobConcurrencyManager.GracePeriodObserver;
+import com.android.server.job.controllers.JobStatus;
+import com.android.server.pm.UserManagerInternal;
+
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public final class JobConcurrencyManagerTest {
+ private static final int UNAVAILABLE_USER = 0;
+ private JobConcurrencyManager mJobConcurrencyManager;
+ private UserManagerInternal mUserManagerInternal;
+ private ActivityManagerInternal mActivityManagerInternal;
+ private int mNextUserId;
+ private GracePeriodObserver mGracePeriodObserver;
+ private Context mContext;
+ private Resources mResources;
+
+ @BeforeClass
+ public static void setUpOnce() {
+ LocalServices.addService(UserManagerInternal.class, mock(UserManagerInternal.class));
+ LocalServices.addService(
+ ActivityManagerInternal.class, mock(ActivityManagerInternal.class));
+ }
+
+ @AfterClass
+ public static void tearDownOnce() {
+ LocalServices.removeServiceForTest(UserManagerInternal.class);
+ LocalServices.removeServiceForTest(ActivityManagerInternal.class);
+ }
+
+ @Before
+ public void setUp() {
+ final JobSchedulerService jobSchedulerService = mock(JobSchedulerService.class);
+ mContext = mock(Context.class);
+ mResources = mock(Resources.class);
+ doReturn(true).when(mResources).getBoolean(
+ R.bool.config_jobSchedulerRestrictBackgroundUser);
+ when(mContext.getResources()).thenReturn(mResources);
+ doReturn(mContext).when(jobSchedulerService).getTestableContext();
+ mJobConcurrencyManager = new JobConcurrencyManager(jobSchedulerService);
+ mGracePeriodObserver = mock(GracePeriodObserver.class);
+ mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
+ mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
+ mNextUserId = 10;
+ mJobConcurrencyManager.mGracePeriodObserver = mGracePeriodObserver;
+ }
+
+ @Test
+ public void testShouldRunAsFgUserJob_currentUser() {
+ assertTrue(mJobConcurrencyManager.shouldRunAsFgUserJob(
+ createJob(createCurrentUser(false))));
+ }
+
+ @Test
+ public void testShouldRunAsFgUserJob_currentProfile() {
+ assertTrue(mJobConcurrencyManager.shouldRunAsFgUserJob(
+ createJob(createCurrentUser(true))));
+ }
+
+ @Test
+ public void testShouldRunAsFgUserJob_primaryUser() {
+ assertTrue(mJobConcurrencyManager.shouldRunAsFgUserJob(
+ createJob(createPrimaryUser(false))));
+ }
+
+ @Test
+ public void testShouldRunAsFgUserJob_primaryProfile() {
+ assertTrue(mJobConcurrencyManager.shouldRunAsFgUserJob(
+ createJob(createPrimaryUser(true))));
+ }
+
+ @Test
+ public void testShouldRunAsFgUserJob_UnexpiredUser() {
+ assertTrue(mJobConcurrencyManager.shouldRunAsFgUserJob(
+ createJob(createUnexpiredUser(false))));
+ }
+
+ @Test
+ public void testShouldRunAsFgUserJob_UnexpiredProfile() {
+ assertTrue(mJobConcurrencyManager.shouldRunAsFgUserJob(
+ createJob(createUnexpiredUser(true))));
+ }
+
+ @Test
+ public void testShouldRunAsFgUserJob_restrictedUser() {
+ assertFalse(mJobConcurrencyManager.shouldRunAsFgUserJob(
+ createJob(createRestrictedUser(false))));
+ }
+
+ @Test
+ public void testShouldRunAsFgUserJob_restrictedProfile() {
+ assertFalse(mJobConcurrencyManager.shouldRunAsFgUserJob(
+ createJob(createRestrictedUser(true))));
+ }
+
+ private UserInfo createCurrentUser(boolean isProfile) {
+ final UserInfo ui = createNewUser();
+ doReturn(ui.id).when(mActivityManagerInternal).getCurrentUserId();
+ return isProfile ? createNewProfile(ui) : ui;
+ }
+
+ private UserInfo createPrimaryUser(boolean isProfile) {
+ final UserInfo ui = createNewUser();
+ doReturn(true).when(ui).isPrimary();
+ return isProfile ? createNewProfile(ui) : ui;
+ }
+
+ private UserInfo createUnexpiredUser(boolean isProfile) {
+ final UserInfo ui = createNewUser();
+ doReturn(true).when(mGracePeriodObserver).isWithinGracePeriodForUser(ui.id);
+ return isProfile ? createNewProfile(ui) : ui;
+ }
+
+ private UserInfo createRestrictedUser(boolean isProfile) {
+ final UserInfo ui = createNewUser();
+ doReturn(UNAVAILABLE_USER).when(mActivityManagerInternal).getCurrentUserId();
+ doReturn(false).when(ui).isPrimary();
+ doReturn(false).when(mGracePeriodObserver).isWithinGracePeriodForUser(ui.id);
+ return isProfile ? createNewProfile(ui) : ui;
+ }
+
+ private UserInfo createNewProfile(UserInfo parent) {
+ final UserInfo ui = createNewUser();
+ parent.profileGroupId = parent.id;
+ ui.profileGroupId = parent.id;
+ doReturn(true).when(ui).isProfile();
+ return ui;
+ }
+
+ private UserInfo createNewUser() {
+ final UserInfo ui = mock(UserInfo.class);
+ ui.id = mNextUserId++;
+ doReturn(ui).when(mUserManagerInternal).getUserInfo(ui.id);
+ ui.profileGroupId = UserInfo.NO_PROFILE_GROUP_ID;
+ return ui;
+ }
+
+ private static JobStatus createJob(UserInfo userInfo) {
+ JobStatus jobStatus = JobStatus.createFromJobInfo(
+ new JobInfo.Builder(1, new ComponentName("foo", "bar")).build(),
+ userInfo.id * UserHandle.PER_USER_RANGE,
+ null, userInfo.id, "JobConcurrencyManagerTest");
+ return jobStatus;
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/job/GracePeriodObserverTest.java b/services/tests/servicestests/src/com/android/server/job/GracePeriodObserverTest.java
new file mode 100644
index 000000000000..1915b8c36892
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/job/GracePeriodObserverTest.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2021 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;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+
+import android.app.ActivityManagerInternal;
+import android.content.Context;
+import android.os.RemoteException;
+import android.os.SystemClock;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.LocalServices;
+import com.android.server.job.JobConcurrencyManager.GracePeriodObserver;
+import com.android.server.pm.UserManagerInternal;
+
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.time.Clock;
+import java.time.Duration;
+import java.time.ZoneOffset;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public final class GracePeriodObserverTest {
+ private GracePeriodObserver mGracePeriodObserver;
+ private UserManagerInternal mUserManagerInternal;
+ private static final int FIRST_USER = 0;
+
+ @BeforeClass
+ public static void setUpOnce() {
+ UserManagerInternal userManagerInternal = mock(UserManagerInternal.class);
+ LocalServices.addService(UserManagerInternal.class, userManagerInternal);
+ ActivityManagerInternal activityManagerInternal = mock(ActivityManagerInternal.class);
+ LocalServices.addService(ActivityManagerInternal.class, activityManagerInternal);
+ }
+
+ @AfterClass
+ public static void tearDownOnce() {
+ LocalServices.removeServiceForTest(UserManagerInternal.class);
+ LocalServices.removeServiceForTest(ActivityManagerInternal.class);
+ }
+
+ @Before
+ public void setUp() {
+ final Context context = ApplicationProvider.getApplicationContext();
+ JobSchedulerService.sElapsedRealtimeClock =
+ Clock.fixed(SystemClock.elapsedRealtimeClock().instant(), ZoneOffset.UTC);
+ doReturn(FIRST_USER)
+ .when(LocalServices.getService(ActivityManagerInternal.class)).getCurrentUserId();
+ mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
+ doReturn(true).when(mUserManagerInternal).exists(FIRST_USER);
+ mGracePeriodObserver = new GracePeriodObserver(context);
+ }
+
+ @Test
+ public void testGracePeriod() throws RemoteException {
+ final int oldUser = FIRST_USER;
+ final int newUser = 10;
+ doReturn(true).when(mUserManagerInternal).exists(newUser);
+ mGracePeriodObserver.onUserSwitchComplete(newUser);
+ assertTrue(mGracePeriodObserver.isWithinGracePeriodForUser(oldUser));
+ JobSchedulerService.sElapsedRealtimeClock =
+ Clock.offset(JobSchedulerService.sElapsedRealtimeClock,
+ Duration.ofMillis(mGracePeriodObserver.mGracePeriod));
+ assertFalse(mGracePeriodObserver.isWithinGracePeriodForUser(oldUser));
+ }
+
+ @Test
+ public void testCleanUp() throws RemoteException {
+ final int removedUser = FIRST_USER;
+ final int newUser = 10;
+ mGracePeriodObserver.onUserSwitchComplete(newUser);
+
+ final int sizeBefore = mGracePeriodObserver.mGracePeriodExpiration.size();
+ doReturn(false).when(mUserManagerInternal).exists(removedUser);
+
+ mGracePeriodObserver.onUserRemoved(removedUser);
+ assertEquals(sizeBefore - 1, mGracePeriodObserver.mGracePeriodExpiration.size());
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/job/WorkCountTrackerTest.java b/services/tests/servicestests/src/com/android/server/job/WorkCountTrackerTest.java
index 263cf48a8a18..9c781922bf2e 100644
--- a/services/tests/servicestests/src/com/android/server/job/WorkCountTrackerTest.java
+++ b/services/tests/servicestests/src/com/android/server/job/WorkCountTrackerTest.java
@@ -17,6 +17,7 @@
package com.android.server.job;
import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_BG;
+import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_BGUSER;
import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_NONE;
import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_TOP;
@@ -63,17 +64,27 @@ public class WorkCountTrackerTest {
public final SparseIntArray running = new SparseIntArray();
public final SparseIntArray pending = new SparseIntArray();
- public void maybeEnqueueJobs(double startRatio, double fgJobRatio) {
+ public void maybeEnqueueJobs(double startRatio, double fgJobRatio, double fgUserJobRatio) {
+ // fgUserJobRatio should always be at least fgJobRatio, otherwise no WORK_TYPE_BG will
+ // be enqueued.
while (mRandom.nextDouble() < startRatio) {
- if (mRandom.nextDouble() < fgJobRatio) {
+ final double random = mRandom.nextDouble();
+ if (random < fgJobRatio) {
pending.put(WORK_TYPE_TOP, pending.get(WORK_TYPE_TOP) + 1);
- } else {
+ } else if (random < fgUserJobRatio) {
pending.put(WORK_TYPE_BG, pending.get(WORK_TYPE_BG) + 1);
+ } else {
+ pending.put(WORK_TYPE_BGUSER, pending.get(WORK_TYPE_BGUSER) + 1);
}
}
}
public void maybeFinishJobs(double stopRatio) {
+ for (int i = running.get(WORK_TYPE_BGUSER); i > 0; i--) {
+ if (mRandom.nextDouble() < stopRatio) {
+ running.put(WORK_TYPE_BGUSER, running.get(WORK_TYPE_BGUSER) - 1);
+ }
+ }
for (int i = running.get(WORK_TYPE_BG); i > 0; i--) {
if (mRandom.nextDouble() < stopRatio) {
running.put(WORK_TYPE_BG, running.get(WORK_TYPE_BG) - 1);
@@ -120,8 +131,11 @@ public class WorkCountTrackerTest {
while ((jobs.pending.get(WORK_TYPE_TOP) > 0
&& mWorkCountTracker.canJobStart(WORK_TYPE_TOP) != WORK_TYPE_NONE)
|| (jobs.pending.get(WORK_TYPE_BG) > 0
- && mWorkCountTracker.canJobStart(WORK_TYPE_BG) != WORK_TYPE_NONE)) {
+ && mWorkCountTracker.canJobStart(WORK_TYPE_BG) != WORK_TYPE_NONE)
+ || (jobs.pending.get(WORK_TYPE_BGUSER) > 0
+ && mWorkCountTracker.canJobStart(WORK_TYPE_BGUSER) != WORK_TYPE_NONE)) {
final boolean isStartingFg = mRandom.nextBoolean();
+ final boolean isStartingFgUser = mRandom.nextBoolean();
if (isStartingFg) {
if (jobs.pending.get(WORK_TYPE_TOP) > 0
@@ -131,7 +145,7 @@ public class WorkCountTrackerTest {
mWorkCountTracker.stageJob(WORK_TYPE_TOP);
mWorkCountTracker.onJobStarted(WORK_TYPE_TOP);
}
- } else {
+ } else if (isStartingFgUser) {
if (jobs.pending.get(WORK_TYPE_BG) > 0
&& mWorkCountTracker.canJobStart(WORK_TYPE_BG) != WORK_TYPE_NONE) {
jobs.pending.put(WORK_TYPE_BG, jobs.pending.get(WORK_TYPE_BG) - 1);
@@ -139,6 +153,14 @@ public class WorkCountTrackerTest {
mWorkCountTracker.stageJob(WORK_TYPE_BG);
mWorkCountTracker.onJobStarted(WORK_TYPE_BG);
}
+ } else {
+ if (jobs.pending.get(WORK_TYPE_BGUSER) > 0
+ && mWorkCountTracker.canJobStart(WORK_TYPE_BGUSER) != WORK_TYPE_NONE) {
+ jobs.pending.put(WORK_TYPE_BGUSER, jobs.pending.get(WORK_TYPE_BGUSER) - 1);
+ jobs.running.put(WORK_TYPE_BGUSER, jobs.running.get(WORK_TYPE_BGUSER) + 1);
+ mWorkCountTracker.stageJob(WORK_TYPE_BGUSER);
+ mWorkCountTracker.onJobStarted(WORK_TYPE_BGUSER);
+ }
}
}
}
@@ -149,10 +171,10 @@ public class WorkCountTrackerTest {
private void checkRandom(Jobs jobs, int numTests, int totalMax,
@NonNull List<Pair<Integer, Integer>> minLimits,
@NonNull List<Pair<Integer, Integer>> maxLimits,
- double startRatio, double fgJobRatio, double stopRatio) {
+ double startRatio, double fgJobRatio, double fgUserJobRatio, double stopRatio) {
for (int i = 0; i < numTests; i++) {
jobs.maybeFinishJobs(stopRatio);
- jobs.maybeEnqueueJobs(startRatio, fgJobRatio);
+ jobs.maybeEnqueueJobs(startRatio, fgJobRatio, fgUserJobRatio);
recount(jobs, totalMax, minLimits, maxLimits);
startPendingJobs(jobs);
@@ -182,14 +204,21 @@ public class WorkCountTrackerTest {
final int numTests = 5000;
final int totalMax = 6;
- final List<Pair<Integer, Integer>> maxLimits = List.of(Pair.create(WORK_TYPE_BG, 4));
- final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_BG, 2));
+ final List<Pair<Integer, Integer>> maxLimits =
+ List.of(Pair.create(WORK_TYPE_BG, 4), Pair.create(WORK_TYPE_BGUSER, 2));
+ final List<Pair<Integer, Integer>> minLimits =
+ List.of(Pair.create(WORK_TYPE_BG, 2), Pair.create(WORK_TYPE_BGUSER, 1));
final double stopRatio = 0.1;
+ // WorkType probabilities:
+ // WORK_TYPE_TOP -- 50%
+ // WORK_TYPE_BG -- 50%
+ // WORK_TYPE_BGUSER -- 0%
final double fgJobRatio = 0.5;
+ final double fgUserJobRatio = 1;
final double startRatio = 0.1;
checkRandom(jobs, numTests, totalMax, minLimits, maxLimits,
- startRatio, fgJobRatio, stopRatio);
+ startRatio, fgJobRatio, fgUserJobRatio, stopRatio);
}
@Test
@@ -198,14 +227,20 @@ public class WorkCountTrackerTest {
final int numTests = 5000;
final int totalMax = 2;
- final List<Pair<Integer, Integer>> maxLimits = List.of(Pair.create(WORK_TYPE_BG, 2));
+ final List<Pair<Integer, Integer>> maxLimits =
+ List.of(Pair.create(WORK_TYPE_BG, 2), Pair.create(WORK_TYPE_BGUSER, 1));
final List<Pair<Integer, Integer>> minLimits = List.of();
final double stopRatio = 0.5;
+ // WorkType probabilities:
+ // WORK_TYPE_TOP -- 50%
+ // WORK_TYPE_BG -- 50%
+ // WORK_TYPE_BGUSER -- 0%
final double fgJobRatio = 0.5;
+ final double fgUserJobRatio = 1;
final double startRatio = 0.5;
checkRandom(jobs, numTests, totalMax, minLimits, maxLimits,
- startRatio, fgJobRatio, stopRatio);
+ startRatio, fgJobRatio, fgUserJobRatio, stopRatio);
}
@Test
@@ -214,14 +249,20 @@ public class WorkCountTrackerTest {
final int numTests = 5000;
final int totalMax = 2;
- final List<Pair<Integer, Integer>> maxLimits = List.of(Pair.create(WORK_TYPE_BG, 2));
+ final List<Pair<Integer, Integer>> maxLimits =
+ List.of(Pair.create(WORK_TYPE_BG, 2), Pair.create(WORK_TYPE_BGUSER, 1));
final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_BG, 2));
final double stopRatio = 0.5;
- final double fgJobRatio = 0.5;
+ // WorkType probabilities:
+ // WORK_TYPE_TOP -- 33%
+ // WORK_TYPE_BG -- 33%
+ // WORK_TYPE_BGUSER -- 33%
+ final double fgJobRatio = 1 / 3.0;
+ final double fgUserJobRatio = 2 / 3.0;
final double startRatio = 0.5;
checkRandom(jobs, numTests, totalMax, minLimits, maxLimits,
- startRatio, fgJobRatio, stopRatio);
+ startRatio, fgJobRatio, fgUserJobRatio, stopRatio);
}
@Test
@@ -230,14 +271,20 @@ public class WorkCountTrackerTest {
final int numTests = 5000;
final int totalMax = 10;
- final List<Pair<Integer, Integer>> maxLimits = List.of(Pair.create(WORK_TYPE_BG, 2));
+ final List<Pair<Integer, Integer>> maxLimits =
+ List.of(Pair.create(WORK_TYPE_BG, 2), Pair.create(WORK_TYPE_BGUSER, 1));
final List<Pair<Integer, Integer>> minLimits = List.of();
final double stopRatio = 0.5;
- final double fgJobRatio = 0.5;
+ // WorkType probabilities:
+ // WORK_TYPE_TOP -- 33%
+ // WORK_TYPE_BG -- 33%
+ // WORK_TYPE_BGUSER -- 33%
+ final double fgJobRatio = 1 / 3.0;
+ final double fgUserJobRatio = 2 / 3.0;
final double startRatio = 0.5;
checkRandom(jobs, numTests, totalMax, minLimits, maxLimits,
- startRatio, fgJobRatio, stopRatio);
+ startRatio, fgJobRatio, fgUserJobRatio, stopRatio);
}
@Test
@@ -246,14 +293,20 @@ public class WorkCountTrackerTest {
final int numTests = 5000;
final int totalMax = 6;
- final List<Pair<Integer, Integer>> maxLimits = List.of(Pair.create(WORK_TYPE_BG, 4));
+ final List<Pair<Integer, Integer>> maxLimits =
+ List.of(Pair.create(WORK_TYPE_BG, 4), Pair.create(WORK_TYPE_BGUSER, 2));
final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_BG, 2));
final double stopRatio = 0.5;
+ // WorkType probabilities:
+ // WORK_TYPE_TOP -- 10%
+ // WORK_TYPE_BG -- 80%
+ // WORK_TYPE_BGUSER -- 10%
final double fgJobRatio = 0.1;
+ final double fgUserJobRatio = 0.9;
final double startRatio = 0.5;
checkRandom(jobs, numTests, totalMax, minLimits, maxLimits,
- startRatio, fgJobRatio, stopRatio);
+ startRatio, fgJobRatio, fgUserJobRatio, stopRatio);
}
@Test
@@ -262,14 +315,20 @@ public class WorkCountTrackerTest {
final int numTests = 5000;
final int totalMax = 6;
- final List<Pair<Integer, Integer>> maxLimits = List.of(Pair.create(WORK_TYPE_BG, 4));
+ final List<Pair<Integer, Integer>> maxLimits =
+ List.of(Pair.create(WORK_TYPE_BG, 4), Pair.create(WORK_TYPE_BGUSER, 2));
final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_BG, 2));
final double stopRatio = 0.5;
+ // WorkType probabilities:
+ // WORK_TYPE_TOP -- 90%
+ // WORK_TYPE_BG -- 10%
+ // WORK_TYPE_BGUSER -- 0%
final double fgJobRatio = 0.9;
+ final double fgUserJobRatio = 1;
final double startRatio = 0.5;
checkRandom(jobs, numTests, totalMax, minLimits, maxLimits,
- startRatio, fgJobRatio, stopRatio);
+ startRatio, fgJobRatio, fgUserJobRatio, stopRatio);
}
@Test
@@ -278,14 +337,20 @@ public class WorkCountTrackerTest {
final int numTests = 5000;
final int totalMax = 6;
- final List<Pair<Integer, Integer>> maxLimits = List.of(Pair.create(WORK_TYPE_BG, 4));
+ final List<Pair<Integer, Integer>> maxLimits =
+ List.of(Pair.create(WORK_TYPE_BG, 4), Pair.create(WORK_TYPE_BGUSER, 2));
final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_BG, 2));
final double stopRatio = 0.4;
+ // WorkType probabilities:
+ // WORK_TYPE_TOP -- 10%
+ // WORK_TYPE_BG -- 10%
+ // WORK_TYPE_BGUSER -- 80%
final double fgJobRatio = 0.1;
+ final double fgUserJobRatio = 0.2;
final double startRatio = 0.5;
checkRandom(jobs, numTests, totalMax, minLimits, maxLimits,
- startRatio, fgJobRatio, stopRatio);
+ startRatio, fgJobRatio, fgUserJobRatio, stopRatio);
}
@Test
@@ -294,14 +359,90 @@ public class WorkCountTrackerTest {
final int numTests = 5000;
final int totalMax = 6;
- final List<Pair<Integer, Integer>> maxLimits = List.of(Pair.create(WORK_TYPE_BG, 4));
- final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_BG, 2));
+ final List<Pair<Integer, Integer>> maxLimits =
+ List.of(Pair.create(WORK_TYPE_BG, 4), Pair.create(WORK_TYPE_BGUSER, 2));
+ final List<Pair<Integer, Integer>> minLimits =
+ List.of(Pair.create(WORK_TYPE_BG, 2), Pair.create(WORK_TYPE_BGUSER, 1));
final double stopRatio = 0.4;
+ // WorkType probabilities:
+ // WORK_TYPE_TOP -- 90%
+ // WORK_TYPE_BG -- 5%
+ // WORK_TYPE_BGUSER -- 5%
final double fgJobRatio = 0.9;
+ final double fgUserJobRatio = 0.95;
final double startRatio = 0.5;
checkRandom(jobs, numTests, totalMax, minLimits, maxLimits,
- startRatio, fgJobRatio, stopRatio);
+ startRatio, fgJobRatio, fgUserJobRatio, stopRatio);
+ }
+
+ @Test
+ public void testRandom9() {
+ final Jobs jobs = new Jobs();
+
+ final int numTests = 5000;
+ final int totalMax = 6;
+ final List<Pair<Integer, Integer>> maxLimits =
+ List.of(Pair.create(WORK_TYPE_BG, 4), Pair.create(WORK_TYPE_BGUSER, 2));
+ final List<Pair<Integer, Integer>> minLimits =
+ List.of(Pair.create(WORK_TYPE_BG, 2), Pair.create(WORK_TYPE_BGUSER, 1));
+ final double stopRatio = 0.5;
+ // WorkType probabilities:
+ // WORK_TYPE_TOP -- 0%
+ // WORK_TYPE_BG -- 50%
+ // WORK_TYPE_BGUSER -- 50%
+ final double fgJobRatio = 0;
+ final double fgUserJobRatio = 0.5;
+ final double startRatio = 0.5;
+
+ checkRandom(jobs, numTests, totalMax, minLimits, maxLimits,
+ startRatio, fgJobRatio, fgUserJobRatio, stopRatio);
+ }
+
+ @Test
+ public void testRandom10() {
+ final Jobs jobs = new Jobs();
+
+ final int numTests = 5000;
+ final int totalMax = 6;
+ final List<Pair<Integer, Integer>> maxLimits =
+ List.of(Pair.create(WORK_TYPE_BG, 4), Pair.create(WORK_TYPE_BGUSER, 2));
+ final List<Pair<Integer, Integer>> minLimits =
+ List.of(Pair.create(WORK_TYPE_BG, 2), Pair.create(WORK_TYPE_BGUSER, 1));
+ final double stopRatio = 0.5;
+ // WorkType probabilities:
+ // WORK_TYPE_TOP -- 0%
+ // WORK_TYPE_BG -- 10%
+ // WORK_TYPE_BGUSER -- 90%
+ final double fgJobRatio = 0;
+ final double fgUserJobRatio = 0.1;
+ final double startRatio = 0.5;
+
+ checkRandom(jobs, numTests, totalMax, minLimits, maxLimits,
+ startRatio, fgJobRatio, fgUserJobRatio, stopRatio);
+ }
+
+ @Test
+ public void testRandom11() {
+ final Jobs jobs = new Jobs();
+
+ final int numTests = 5000;
+ final int totalMax = 6;
+ final List<Pair<Integer, Integer>> maxLimits =
+ List.of(Pair.create(WORK_TYPE_BG, 4), Pair.create(WORK_TYPE_BGUSER, 2));
+ final List<Pair<Integer, Integer>> minLimits =
+ List.of(Pair.create(WORK_TYPE_BG, 2), Pair.create(WORK_TYPE_BGUSER, 1));
+ final double stopRatio = 0.5;
+ // WorkType probabilities:
+ // WORK_TYPE_TOP -- 0%
+ // WORK_TYPE_BG -- 90%
+ // WORK_TYPE_BGUSER -- 10%
+ final double fgJobRatio = 0;
+ final double fgUserJobRatio = 0.9;
+ final double startRatio = 0.5;
+
+ checkRandom(jobs, numTests, totalMax, minLimits, maxLimits,
+ startRatio, fgJobRatio, fgUserJobRatio, stopRatio);
}
/** Used by the following tests */
@@ -337,7 +478,7 @@ public class WorkCountTrackerTest {
public void testBasic() {
checkSimple(6,
/* min */ List.of(Pair.create(WORK_TYPE_BG, 2)),
- /* max */ List.of(Pair.create(WORK_TYPE_BG, 4)),
+ /* max */ List.of(Pair.create(WORK_TYPE_BG, 4), Pair.create(WORK_TYPE_BGUSER, 2)),
/* run */ List.of(),
/* pen */ List.of(Pair.create(WORK_TYPE_TOP, 1)),
/* resRun */ List.of(Pair.create(WORK_TYPE_TOP, 1)),
@@ -345,7 +486,7 @@ public class WorkCountTrackerTest {
checkSimple(6,
/* min */ List.of(Pair.create(WORK_TYPE_BG, 2)),
- /* max */ List.of(Pair.create(WORK_TYPE_BG, 4)),
+ /* max */ List.of(Pair.create(WORK_TYPE_BG, 4), Pair.create(WORK_TYPE_BGUSER, 2)),
/* run */ List.of(),
/* pen */ List.of(Pair.create(WORK_TYPE_TOP, 10)),
/* resRun */ List.of(Pair.create(WORK_TYPE_TOP, 6)),
@@ -354,7 +495,7 @@ public class WorkCountTrackerTest {
// When there are BG jobs pending, 2 (min-BG) jobs should run.
checkSimple(6,
/* min */ List.of(Pair.create(WORK_TYPE_BG, 2)),
- /* max */ List.of(Pair.create(WORK_TYPE_BG, 4)),
+ /* max */ List.of(Pair.create(WORK_TYPE_BG, 4), Pair.create(WORK_TYPE_BGUSER, 2)),
/* run */ List.of(),
/* pen */ List.of(Pair.create(WORK_TYPE_TOP, 10), Pair.create(WORK_TYPE_BG, 1)),
/* resRun */ List.of(Pair.create(WORK_TYPE_TOP, 5), Pair.create(WORK_TYPE_BG, 1)),
@@ -385,6 +526,16 @@ public class WorkCountTrackerTest {
checkSimple(8,
/* min */ List.of(Pair.create(WORK_TYPE_BG, 2)),
+ /* max */ List.of(Pair.create(WORK_TYPE_BG, 6)),
+ /* run */ List.of(Pair.create(WORK_TYPE_TOP, 2), Pair.create(WORK_TYPE_BG, 4)),
+ /* pen */ List.of(Pair.create(WORK_TYPE_TOP, 49), Pair.create(WORK_TYPE_BG, 49)),
+ /* resRun */ List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_BG, 4)),
+ /* resPen */ List.of(Pair.create(WORK_TYPE_TOP, 47), Pair.create(WORK_TYPE_BG, 49))
+ );
+
+
+ checkSimple(8,
+ /* min */ List.of(Pair.create(WORK_TYPE_BG, 2)),
/* max */ List.of(Pair.create(WORK_TYPE_TOP, 6), Pair.create(WORK_TYPE_BG, 6)),
/* run */ List.of(Pair.create(WORK_TYPE_TOP, 2), Pair.create(WORK_TYPE_BG, 4)),
/* pen */ List.of(Pair.create(WORK_TYPE_TOP, 10), Pair.create(WORK_TYPE_BG, 49)),
@@ -407,6 +558,14 @@ public class WorkCountTrackerTest {
/* resRun */ List.of(Pair.create(WORK_TYPE_TOP, 2), Pair.create(WORK_TYPE_BG, 6)),
/* resPen */ List.of(Pair.create(WORK_TYPE_TOP, 8), Pair.create(WORK_TYPE_BG, 49)));
+ checkSimple(8,
+ /* min */ List.of(Pair.create(WORK_TYPE_BG, 1)),
+ /* max */ List.of(Pair.create(WORK_TYPE_TOP, 6), Pair.create(WORK_TYPE_BG, 2)),
+ /* run */ List.of(Pair.create(WORK_TYPE_BG, 6)),
+ /* pen */ List.of(Pair.create(WORK_TYPE_TOP, 10), Pair.create(WORK_TYPE_BG, 49)),
+ /* resRun */ List.of(Pair.create(WORK_TYPE_TOP, 2), Pair.create(WORK_TYPE_BG, 6)),
+ /* resPen */ List.of(Pair.create(WORK_TYPE_TOP, 8), Pair.create(WORK_TYPE_BG, 49)));
+
checkSimple(6,
/* min */ List.of(Pair.create(WORK_TYPE_BG, 2)),
/* max */ List.of(Pair.create(WORK_TYPE_BG, 4)),
@@ -425,6 +584,38 @@ public class WorkCountTrackerTest {
/* pen */ List.of(Pair.create(WORK_TYPE_TOP, 10), Pair.create(WORK_TYPE_BG, 3)),
/* resRun */ List.of(Pair.create(WORK_TYPE_BG, 6)),
/* resPen */ List.of(Pair.create(WORK_TYPE_TOP, 10), Pair.create(WORK_TYPE_BG, 3)));
+
+ checkSimple(6,
+ /* min */ List.of(Pair.create(WORK_TYPE_BG, 2)),
+ /* max */ List.of(Pair.create(WORK_TYPE_BG, 4), Pair.create(WORK_TYPE_BGUSER, 2)),
+ /* run */ List.of(Pair.create(WORK_TYPE_BG, 2)),
+ /* pen */ List.of(Pair.create(WORK_TYPE_TOP, 10),
+ Pair.create(WORK_TYPE_BG, 3),
+ Pair.create(WORK_TYPE_BGUSER, 3)),
+ /* resRun */ List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_BG, 2)),
+ /* resPen */ List.of(Pair.create(WORK_TYPE_TOP, 6),
+ Pair.create(WORK_TYPE_BG, 3),
+ Pair.create(WORK_TYPE_BGUSER, 3)));
+
+ checkSimple(6,
+ /* min */ List.of(Pair.create(WORK_TYPE_BG, 2)),
+ /* max */ List.of(Pair.create(WORK_TYPE_BG, 4), Pair.create(WORK_TYPE_BGUSER, 3)),
+ /* run */ List.of(Pair.create(WORK_TYPE_BG, 2)),
+ /* pen */ List.of(Pair.create(WORK_TYPE_BG, 3), Pair.create(WORK_TYPE_BGUSER, 3)),
+ /* resRun */ List.of(
+ Pair.create(WORK_TYPE_BG, 4), Pair.create(WORK_TYPE_BGUSER, 2)),
+ /* resPen */ List.of(
+ Pair.create(WORK_TYPE_BG, 1), Pair.create(WORK_TYPE_BGUSER, 1)));
+
+ checkSimple(6,
+ /* min */ List.of(Pair.create(WORK_TYPE_BG, 2)),
+ /* max */ List.of(Pair.create(WORK_TYPE_BG, 4), Pair.create(WORK_TYPE_BGUSER, 1)),
+ /* run */ List.of(Pair.create(WORK_TYPE_BG, 2)),
+ /* pen */ List.of(Pair.create(WORK_TYPE_BG, 3), Pair.create(WORK_TYPE_BGUSER, 3)),
+ /* resRun */ List.of(
+ Pair.create(WORK_TYPE_BG, 4), Pair.create(WORK_TYPE_BGUSER, 1)),
+ /* resPen */ List.of(
+ Pair.create(WORK_TYPE_BG, 1), Pair.create(WORK_TYPE_BGUSER, 2)));
}
/** Tests that the counter updates properly when jobs are stopped. */
diff --git a/services/tests/servicestests/src/com/android/server/job/WorkTypeConfigTest.java b/services/tests/servicestests/src/com/android/server/job/WorkTypeConfigTest.java
index c28292f03357..bbc1f1d26282 100644
--- a/services/tests/servicestests/src/com/android/server/job/WorkTypeConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/job/WorkTypeConfigTest.java
@@ -16,8 +16,10 @@
package com.android.server.job;
import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_BG;
+import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_BGUSER;
import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_TOP;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import android.annotation.NonNull;
@@ -31,7 +33,6 @@ import androidx.test.runner.AndroidJUnit4;
import com.android.server.job.JobConcurrencyManager.WorkTypeConfig;
import org.junit.After;
-import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -44,8 +45,10 @@ public class WorkTypeConfigTest {
private static final String KEY_MAX_TOTAL = "concurrency_max_total_test";
private static final String KEY_MAX_TOP = "concurrency_max_top_test";
private static final String KEY_MAX_BG = "concurrency_max_bg_test";
+ private static final String KEY_MAX_BGUSER = "concurrency_max_bguser_test";
private static final String KEY_MIN_TOP = "concurrency_min_top_test";
private static final String KEY_MIN_BG = "concurrency_min_bg_test";
+ private static final String KEY_MIN_BGUSER = "concurrency_min_bguser_test";
@After
public void tearDown() throws Exception {
@@ -57,17 +60,21 @@ public class WorkTypeConfigTest {
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MAX_TOTAL, "", false);
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MAX_TOP, "", false);
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MAX_BG, "", false);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MAX_BGUSER, "", false);
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MIN_TOP, "", false);
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MIN_BG, "", false);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MIN_BGUSER, "", false);
}
private void check(@Nullable DeviceConfig.Properties config,
int defaultTotal,
@Nullable Pair<Integer, Integer> defaultTopLimits,
@Nullable Pair<Integer, Integer> defaultBgLimits,
+ @Nullable Pair<Integer, Integer> defaultBgUserLimits,
boolean expectedValid, int expectedTotal,
@NonNull Pair<Integer, Integer> expectedTopLimits,
- @NonNull Pair<Integer, Integer> expectedBgLimits) throws Exception {
+ @NonNull Pair<Integer, Integer> expectedBgLimits,
+ @NonNull Pair<Integer, Integer> expectedBgUserLimits) throws Exception {
resetConfig();
if (config != null) {
DeviceConfig.setProperties(config);
@@ -92,6 +99,14 @@ public class WorkTypeConfigTest {
defaultMax.add(Pair.create(WORK_TYPE_BG, val));
}
}
+ if (defaultBgUserLimits != null) {
+ if ((val = defaultBgUserLimits.first) != null) {
+ defaultMin.add(Pair.create(WORK_TYPE_BGUSER, val));
+ }
+ if ((val = defaultBgUserLimits.second) != null) {
+ defaultMax.add(Pair.create(WORK_TYPE_BGUSER, val));
+ }
+ }
final WorkTypeConfig counts;
try {
@@ -112,40 +127,45 @@ public class WorkTypeConfigTest {
counts.update(DeviceConfig.getProperties(DeviceConfig.NAMESPACE_JOB_SCHEDULER));
- Assert.assertEquals(expectedTotal, counts.getMaxTotal());
- Assert.assertEquals((int) expectedTopLimits.first, counts.getMinReserved(WORK_TYPE_TOP));
- Assert.assertEquals((int) expectedTopLimits.second, counts.getMax(WORK_TYPE_TOP));
- Assert.assertEquals((int) expectedBgLimits.first, counts.getMinReserved(WORK_TYPE_BG));
- Assert.assertEquals((int) expectedBgLimits.second, counts.getMax(WORK_TYPE_BG));
+ assertEquals(expectedTotal, counts.getMaxTotal());
+ assertEquals((int) expectedTopLimits.first, counts.getMinReserved(WORK_TYPE_TOP));
+ assertEquals((int) expectedTopLimits.second, counts.getMax(WORK_TYPE_TOP));
+ assertEquals((int) expectedBgLimits.first, counts.getMinReserved(WORK_TYPE_BG));
+ assertEquals((int) expectedBgLimits.second, counts.getMax(WORK_TYPE_BG));
+ assertEquals((int) expectedBgUserLimits.first, counts.getMinReserved(WORK_TYPE_BGUSER));
+ assertEquals((int) expectedBgUserLimits.second, counts.getMax(WORK_TYPE_BGUSER));
}
@Test
public void test() throws Exception {
// Tests with various combinations.
- check(null, /*default*/ 5, Pair.create(4, null), Pair.create(0, 1),
- /*expected*/ true, 5, Pair.create(4, 5), Pair.create(0, 1));
- check(null, /*default*/ 5, Pair.create(5, null), Pair.create(0, 0),
- /*expected*/ true, 5, Pair.create(5, 5), Pair.create(0, 1));
- check(null, /*default*/ 0, Pair.create(5, null), Pair.create(0, 0),
- /*expected*/ false, 1, Pair.create(1, 1), Pair.create(0, 1));
- check(null, /*default*/ -1, null, Pair.create(-1, -1),
- /*expected*/ false, 1, Pair.create(1, 1), Pair.create(0, 1));
- check(null, /*default*/ 5, null, Pair.create(5, 5),
- /*expected*/ true, 5, Pair.create(1, 5), Pair.create(4, 5));
- check(null, /*default*/ 6, Pair.create(1, null), Pair.create(6, 5),
- /*expected*/ false, 6, Pair.create(1, 6), Pair.create(5, 5));
- check(null, /*default*/ 4, null, Pair.create(6, 5),
- /*expected*/ false, 4, Pair.create(1, 4), Pair.create(3, 4));
- check(null, /*default*/ 5, Pair.create(4, null), Pair.create(1, 1),
- /*expected*/ true, 5, Pair.create(4, 5), Pair.create(1, 1));
- check(null, /*default*/ 15, null, Pair.create(15, 15),
- /*expected*/ true, 15, Pair.create(1, 15), Pair.create(14, 15));
- check(null, /*default*/ 16, null, Pair.create(16, 16),
- /*expected*/ true, 16, Pair.create(1, 16), Pair.create(15, 16));
- check(null, /*default*/ 20, null, Pair.create(20, 20),
- /*expected*/ false, 16, Pair.create(1, 16), Pair.create(15, 16));
- check(null, /*default*/ 20, null, Pair.create(16, 16),
- /*expected*/ true, 16, Pair.create(1, 16), Pair.create(15, 16));
+ check(null, /*default*/ 5, Pair.create(4, null), Pair.create(0, 1), Pair.create(0, 1),
+ /*expected*/ true, 5, Pair.create(4, 5), Pair.create(0, 1), Pair.create(0, 1));
+ check(null, /*default*/ 5, Pair.create(5, null), Pair.create(0, 0), Pair.create(0, 0),
+ /*expected*/ true, 5, Pair.create(5, 5), Pair.create(0, 1), Pair.create(0, 1));
+ check(null, /*default*/ 0, Pair.create(5, null), Pair.create(0, 0), Pair.create(0, 0),
+ /*expected*/ false, 1, Pair.create(1, 1), Pair.create(0, 1), Pair.create(0, 1));
+ check(null, /*default*/ -1, null, Pair.create(-1, -1), Pair.create(-1, -1),
+ /*expected*/ false, 1, Pair.create(1, 1), Pair.create(0, 1), Pair.create(0, 1));
+ check(null, /*default*/ 5, null, Pair.create(5, 5), Pair.create(0, 5),
+ /*expected*/ true, 5, Pair.create(1, 5), Pair.create(4, 5), Pair.create(0, 5));
+ check(null, /*default*/ 6, Pair.create(1, null), Pair.create(6, 5), Pair.create(2, 1),
+ /*expected*/ false, 6, Pair.create(1, 6), Pair.create(5, 5), Pair.create(0, 1));
+ check(null, /*default*/ 4, null, Pair.create(6, 5), Pair.create(6, 5),
+ /*expected*/ false, 4, Pair.create(1, 4), Pair.create(3, 4), Pair.create(0, 4));
+ check(null, /*default*/ 5, Pair.create(4, null), Pair.create(1, 1), Pair.create(0, 5),
+ /*expected*/ true, 5, Pair.create(4, 5), Pair.create(1, 1), Pair.create(0, 5));
+ check(null, /*default*/ 5, Pair.create(4, null), Pair.create(0, 1), Pair.create(1, 5),
+ /*expected*/ true, 5, Pair.create(4, 5), Pair.create(0, 1), Pair.create(1, 5));
+ check(null, /*default*/ 15, null, Pair.create(15, 15), Pair.create(0, 15),
+ /*expected*/ true, 15, Pair.create(1, 15), Pair.create(14, 15), Pair.create(0, 15));
+ check(null, /*default*/ 16, null, Pair.create(16, 16), Pair.create(0, 16),
+ /*expected*/ true, 16, Pair.create(1, 16), Pair.create(15, 16), Pair.create(0, 16));
+ check(null, /*default*/ 20, null, Pair.create(20, 20), Pair.create(10, 20),
+ /*expected*/ false, 16,
+ Pair.create(1, 16), Pair.create(15, 16), Pair.create(0, 16));
+ check(null, /*default*/ 20, null, Pair.create(16, 16), Pair.create(0, 16),
+ /*expected*/ true, 16, Pair.create(1, 16), Pair.create(15, 16), Pair.create(0, 16));
// Test for overriding with a setting string.
check(new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER)
@@ -153,26 +173,26 @@ public class WorkTypeConfigTest {
.setInt(KEY_MAX_BG, 4)
.setInt(KEY_MIN_BG, 3)
.build(),
- /*default*/ 9, null, Pair.create(9, 9),
- /*expected*/ true, 5, Pair.create(1, 5), Pair.create(3, 4));
+ /*default*/ 9, null, Pair.create(9, 9), Pair.create(0, 2),
+ /*expected*/ true, 5, Pair.create(1, 5), Pair.create(3, 4), Pair.create(0, 2));
check(new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER)
.setInt(KEY_MAX_TOTAL, 5).build(),
- /*default*/ 9, null, Pair.create(9, 9),
- /*expected*/ true, 5, Pair.create(1, 5), Pair.create(4, 5));
+ /*default*/ 9, null, Pair.create(9, 9), Pair.create(0, 0),
+ /*expected*/ true, 5, Pair.create(1, 5), Pair.create(4, 5), Pair.create(0, 1));
check(new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER)
.setInt(KEY_MAX_BG, 4).build(),
- /*default*/ 9, null, Pair.create(9, 9),
- /*expected*/ true, 9, Pair.create(1, 9), Pair.create(4, 4));
+ /*default*/ 9, null, Pair.create(9, 9), Pair.create(0, 9),
+ /*expected*/ true, 9, Pair.create(1, 9), Pair.create(4, 4), Pair.create(0, 9));
check(new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER)
.setInt(KEY_MIN_BG, 3).build(),
- /*default*/ 9, null, Pair.create(9, 9),
- /*expected*/ true, 9, Pair.create(1, 9), Pair.create(3, 9));
+ /*default*/ 9, null, Pair.create(9, 9), Pair.create(0, 6),
+ /*expected*/ true, 9, Pair.create(1, 9), Pair.create(3, 9), Pair.create(0, 6));
check(new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER)
.setInt(KEY_MAX_TOTAL, 20)
.setInt(KEY_MAX_BG, 20)
.setInt(KEY_MIN_BG, 8)
.build(),
- /*default*/ 9, null, Pair.create(9, 9),
- /*expected*/ true, 16, Pair.create(1, 16), Pair.create(8, 16));
+ /*default*/ 9, null, Pair.create(9, 9), Pair.create(0, 8),
+ /*expected*/ true, 16, Pair.create(1, 16), Pair.create(8, 16), Pair.create(0, 8));
}
}