Loading apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java +190 −14 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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 { Loading @@ -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( Loading @@ -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)) ) ); /** Loading Loading @@ -171,6 +191,10 @@ class JobConcurrencyManager { "assignJobsToContexts", "refreshSystemState", }); @VisibleForTesting GracePeriodObserver mGracePeriodObserver; @VisibleForTesting boolean mShouldRestrictBgUser; interface Stats { int ASSIGN_JOBS_TO_CONTEXTS = 0; Loading @@ -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() { Loading @@ -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) { Loading Loading @@ -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; Loading Loading @@ -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; } Loading Loading @@ -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(); Loading @@ -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; } Loading @@ -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; Loading Loading @@ -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(); Loading @@ -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() { Loading @@ -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(); } } Loading @@ -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. Loading Loading @@ -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), Loading Loading @@ -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) { Loading Loading @@ -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. Loading @@ -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, Loading @@ -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) { Loading @@ -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; } Loading apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +1 −0 Original line number Diff line number Diff line Loading @@ -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? Loading core/res/res/values/config.xml +5 −0 Original line number Diff line number Diff line Loading @@ -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> Loading core/res/res/values/symbols.xml +2 −0 Original line number Diff line number Diff line Loading @@ -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" /> Loading Loading
apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java +190 −14 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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 { Loading @@ -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( Loading @@ -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)) ) ); /** Loading Loading @@ -171,6 +191,10 @@ class JobConcurrencyManager { "assignJobsToContexts", "refreshSystemState", }); @VisibleForTesting GracePeriodObserver mGracePeriodObserver; @VisibleForTesting boolean mShouldRestrictBgUser; interface Stats { int ASSIGN_JOBS_TO_CONTEXTS = 0; Loading @@ -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() { Loading @@ -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) { Loading Loading @@ -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; Loading Loading @@ -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; } Loading Loading @@ -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(); Loading @@ -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; } Loading @@ -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; Loading Loading @@ -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(); Loading @@ -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() { Loading @@ -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(); } } Loading @@ -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. Loading Loading @@ -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), Loading Loading @@ -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) { Loading Loading @@ -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. Loading @@ -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, Loading @@ -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) { Loading @@ -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; } Loading
apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +1 −0 Original line number Diff line number Diff line Loading @@ -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? Loading
core/res/res/values/config.xml +5 −0 Original line number Diff line number Diff line Loading @@ -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> Loading
core/res/res/values/symbols.xml +2 −0 Original line number Diff line number Diff line Loading @@ -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" /> Loading