Merge "Add concurrency restriction at the USER level" into sc-dev

This commit is contained in:
Kweku Adams
2021-02-08 23:53:04 +00:00
committed by Android (Google) Code Review
8 changed files with 774 additions and 85 deletions

View File

@@ -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();
}
}
@@ -872,6 +960,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;
}

View File

@@ -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?

View File

@@ -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>

View File

@@ -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" />

View File

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

View File

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

View File

@@ -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)),
@@ -383,6 +524,16 @@ 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_BG, 47)));
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)),
@@ -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. */

View File

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