diff options
| author | 2021-02-08 23:53:04 +0000 | |
|---|---|---|
| committer | 2021-02-08 23:53:04 +0000 | |
| commit | 076669f905517c3637066dfa3caf6821e400358c (patch) | |
| tree | eb1127c5eb1fdeab3f9cc64ba8b35bf75200f2d1 | |
| parent | 5ac0513b61ceb76f6b67f67a6dee7be4e0d2d09b (diff) | |
| parent | 31b53cfe9395bdbe7a1b7a0f0b69b48d89058f98 (diff) | |
Merge "Add concurrency restriction at the USER level" into sc-dev
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)); } } |