Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit bc45d452 authored by Kweku Adams's avatar Kweku Adams
Browse files

Include BAL factor in UI job scheduling.

Don't allow apps to schedule user-initiated jobs unless they're in the
TOP state or can launch activities from the background.

Also, give immediacy privilege to the jobs if the apps are in a BAL
state.

Bug: 261999509
Test: atest CtsJobSchedulerTestCases:JobSchedulingTest
Test: atest CtsJobSchedulerTestCases:JobInfoTest
Test: atest CtsJobSchedulerTestCases:JobParametersTest
Test: atest CtsJobSchedulerTestCases:UserInitiatedJobTest
Test: atest FrameworksMockingServicesTests:JobConcurrencyManagerTest
Change-Id: Id781d3fb8a9249160948c17d6127cc13996e9af5
parent db63d9a6
Loading
Loading
Loading
Loading
+54 −7
Original line number Diff line number Diff line
@@ -27,6 +27,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.BackgroundStartPrivileges;
import android.app.UserSwitchObserver;
import android.app.job.JobInfo;
import android.app.job.JobParameters;
@@ -388,6 +389,12 @@ class JobConcurrencyManager {
    private final ArrayList<ContextAssignment> mRecycledPreferredUidOnly = new ArrayList<>();
    private final ArrayList<ContextAssignment> mRecycledStoppable = new ArrayList<>();
    private final AssignmentInfo mRecycledAssignmentInfo = new AssignmentInfo();
    private final SparseIntArray mRecycledPrivilegedState = new SparseIntArray();

    private static final int PRIVILEGED_STATE_UNDEFINED = 0;
    private static final int PRIVILEGED_STATE_NONE = 1;
    private static final int PRIVILEGED_STATE_BAL = 2;
    private static final int PRIVILEGED_STATE_TOP = 3;

    private final Pools.Pool<ContextAssignment> mContextAssignmentPool =
            new Pools.SimplePool<>(MAX_RETAINED_OBJECTS);
@@ -792,7 +799,7 @@ class JobConcurrencyManager {

        cleanUpAfterAssignmentChangesLocked(
                mRecycledChanged, mRecycledIdle, mRecycledPreferredUidOnly, mRecycledStoppable,
                mRecycledAssignmentInfo);
                mRecycledAssignmentInfo, mRecycledPrivilegedState);

        noteConcurrency();
    }
@@ -915,7 +922,8 @@ class JobConcurrencyManager {
                continue;
            }

            final boolean hasImmediacyPrivilege = hasImmediacyPrivilegeLocked(nextPending);
            final boolean hasImmediacyPrivilege =
                    hasImmediacyPrivilegeLocked(nextPending, mRecycledPrivilegedState);
            if (DEBUG && isSimilarJobRunningLocked(nextPending)) {
                Slog.w(TAG, "Already running similar job to: " + nextPending);
            }
@@ -1183,7 +1191,8 @@ class JobConcurrencyManager {
            final ArraySet<ContextAssignment> idle,
            final List<ContextAssignment> preferredUidOnly,
            final List<ContextAssignment> stoppable,
            final AssignmentInfo assignmentInfo) {
            final AssignmentInfo assignmentInfo,
            final SparseIntArray privilegedState) {
        for (int s = stoppable.size() - 1; s >= 0; --s) {
            final ContextAssignment assignment = stoppable.get(s);
            assignment.clear();
@@ -1205,20 +1214,58 @@ class JobConcurrencyManager {
        stoppable.clear();
        preferredUidOnly.clear();
        assignmentInfo.clear();
        privilegedState.clear();
        mWorkCountTracker.resetStagingCount();
        mActivePkgStats.forEach(mPackageStatsStagingCountClearer);
    }

    @VisibleForTesting
    @GuardedBy("mLock")
    boolean hasImmediacyPrivilegeLocked(@NonNull JobStatus job) {
    boolean hasImmediacyPrivilegeLocked(@NonNull JobStatus job,
            @NonNull SparseIntArray cachedPrivilegedState) {
        if (!job.shouldTreatAsExpeditedJob() && !job.shouldTreatAsUserInitiatedJob()) {
            return false;
        }
        // EJs & user-initiated jobs for the TOP app should run immediately.
        // However, even for user-initiated jobs, if the app has not recently been in TOP or BAL
        // state, we don't give the immediacy privilege so that we can try and maintain
        // reasonably concurrency behavior.
        return job.lastEvaluatedBias == JobInfo.BIAS_TOP_APP
                // TODO(): include BAL state for user-initiated jobs
                && (job.shouldTreatAsExpeditedJob() || job.shouldTreatAsUserInitiatedJob());
        if (job.lastEvaluatedBias == JobInfo.BIAS_TOP_APP) {
            return true;
        }
        final int uid = job.getSourceUid();
        final int privilegedState = cachedPrivilegedState.get(uid, PRIVILEGED_STATE_UNDEFINED);
        switch (privilegedState) {
            case PRIVILEGED_STATE_TOP:
                return true;
            case PRIVILEGED_STATE_BAL:
                return job.shouldTreatAsUserInitiatedJob();
            case PRIVILEGED_STATE_NONE:
                return false;
            case PRIVILEGED_STATE_UNDEFINED:
            default:
                final ActivityManagerInternal activityManagerInternal =
                        LocalServices.getService(ActivityManagerInternal.class);
                final int procState = activityManagerInternal.getUidProcessState(uid);
                if (procState == ActivityManager.PROCESS_STATE_TOP) {
                    cachedPrivilegedState.put(uid, PRIVILEGED_STATE_TOP);
                    return true;
                }
                if (job.shouldTreatAsExpeditedJob()) {
                    // EJs only get the TOP privilege.
                    return false;
                }

                final BackgroundStartPrivileges bsp =
                        activityManagerInternal.getBackgroundStartPrivileges(uid);
                final boolean balAllowed = bsp.allowsBackgroundActivityStarts();
                if (DEBUG) {
                    Slog.d(TAG, "Job " + job.toShortString() + " bal state: " + bsp);
                }
                cachedPrivilegedState.put(uid,
                        balAllowed ? PRIVILEGED_STATE_BAL : PRIVILEGED_STATE_NONE);
                return balAllowed;
        }
    }

    @GuardedBy("mLock")
+20 −0
Original line number Diff line number Diff line
@@ -31,6 +31,7 @@ import android.app.Activity;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.AppGlobals;
import android.app.BackgroundStartPrivileges;
import android.app.IUidObserver;
import android.app.compat.CompatChanges;
import android.app.job.IJobScheduler;
@@ -3841,6 +3842,25 @@ public class JobSchedulerService extends com.android.server.SystemService
                        return callingResult;
                    }
                }

                final int uid = sourceUid != -1 ? sourceUid : callingUid;
                final int procState = mActivityManagerInternal.getUidProcessState(uid);
                if (DEBUG) {
                    Slog.d(TAG, "Uid " + uid + " proc state="
                            + ActivityManager.procStateToString(procState));
                }
                if (procState != ActivityManager.PROCESS_STATE_TOP) {
                    final BackgroundStartPrivileges bsp =
                            mActivityManagerInternal.getBackgroundStartPrivileges(uid);
                    if (DEBUG) {
                        Slog.d(TAG, "Uid " + uid + ": " + bsp);
                    }
                    if (!bsp.allowsBackgroundActivityStarts()) {
                        Slog.e(TAG,
                                "Uid " + uid + " not in a state to schedule user-initiated jobs");
                        return JobScheduler.RESULT_FAILURE;
                    }
                }
            }
            if (jobWorkItem != null) {
                jobWorkItem.enforceValidity(rejectNegativeNetworkEstimates);
+5 −0
Original line number Diff line number Diff line
@@ -464,6 +464,11 @@ public abstract class ActivityManagerInternal {
    public abstract boolean isActivityStartsLoggingEnabled();
    /** Returns true if the background activity starts is enabled. */
    public abstract boolean isBackgroundActivityStartsEnabled();
    /**
     * Returns The current {@link BackgroundStartPrivileges} of the UID.
     */
    @NonNull
    public abstract BackgroundStartPrivileges getBackgroundStartPrivileges(int uid);
    public abstract void reportCurKeyguardUsageEvent(boolean keyguardShowing);

    /** @see com.android.server.am.ActivityManagerService#monitor */
+44 −0
Original line number Diff line number Diff line
@@ -485,6 +485,7 @@ import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Supplier;
public class ActivityManagerService extends IActivityManager.Stub
@@ -6450,6 +6451,44 @@ public class ActivityManagerService extends IActivityManager.Stub
        return entry == null ? null : entry.second;
    }
    private static class GetBackgroundStartPrivilegesFunctor implements Consumer<ProcessRecord> {
        private BackgroundStartPrivileges mBackgroundStartPrivileges =
                BackgroundStartPrivileges.NONE;
        private int mUid;
        void prepare(int uid) {
            mUid = uid;
            mBackgroundStartPrivileges = BackgroundStartPrivileges.NONE;
        }
        @NonNull
        BackgroundStartPrivileges getResult() {
            return mBackgroundStartPrivileges;
        }
        public void accept(ProcessRecord pr) {
            if (pr.uid == mUid) {
                mBackgroundStartPrivileges =
                        mBackgroundStartPrivileges.merge(pr.getBackgroundStartPrivileges());
            }
        }
    }
    private final GetBackgroundStartPrivilegesFunctor mGetBackgroundStartPrivilegesFunctor =
            new GetBackgroundStartPrivilegesFunctor();
    /**
     * Returns the current complete {@link BackgroundStartPrivileges} of the UID.
     */
    @NonNull
    private BackgroundStartPrivileges getBackgroundStartPrivileges(int uid) {
        synchronized (mProcLock) {
            mGetBackgroundStartPrivilegesFunctor.prepare(uid);
            mProcessList.forEachLruProcessesLOSP(false, mGetBackgroundStartPrivilegesFunctor);
            return mGetBackgroundStartPrivilegesFunctor.getResult();
        }
    }
    /**
     * @return allowlist tag for a uid from mPendingTempAllowlist, null if not currently on
     * the allowlist
@@ -17806,6 +17845,11 @@ public class ActivityManagerService extends IActivityManager.Stub
            return mConstants.mFlagBackgroundActivityStartsEnabled;
        }
        @Override
        public BackgroundStartPrivileges getBackgroundStartPrivileges(int uid) {
            return ActivityManagerService.this.getBackgroundStartPrivileges(uid);
        }
        public void reportCurKeyguardUsageEvent(boolean keyguardShowing) {
            ActivityManagerService.this.reportGlobalUsageEvent(keyguardShowing
                    ? UsageEvents.Event.KEYGUARD_SHOWN
+58 −0
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.am.ActivityManagerService.MY_PID;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ApplicationExitInfo;
@@ -340,6 +341,24 @@ class ProcessRecord implements WindowProcessListener {
    @GuardedBy("mService")
    private boolean mInFullBackup;

    /**
     * A set of tokens that currently contribute to this process being temporarily allowed
     * to start certain components (eg. activities or foreground services) even if it's not
     * in the foreground.
     */
    @GuardedBy("mBackgroundStartPrivileges")
    private final ArrayMap<Binder, BackgroundStartPrivileges> mBackgroundStartPrivileges =
            new ArrayMap<>();

    /**
     * The merged BackgroundStartPrivileges based on what's in {@link #mBackgroundStartPrivileges}.
     * This is lazily generated using {@link #getBackgroundStartPrivileges()}.
     */
    @Nullable
    @GuardedBy("mBackgroundStartPrivileges")
    private BackgroundStartPrivileges mBackgroundStartPrivilegesMerged =
            BackgroundStartPrivileges.NONE;

    /**
     * Controller for driving the process state on the window manager side.
     */
@@ -1325,11 +1344,50 @@ class ProcessRecord implements WindowProcessListener {
        Objects.requireNonNull(entity);
        mWindowProcessController.addOrUpdateBackgroundStartPrivileges(entity,
                backgroundStartPrivileges);
        setBackgroundStartPrivileges(entity, backgroundStartPrivileges);
    }

    void removeBackgroundStartPrivileges(Binder entity) {
        Objects.requireNonNull(entity);
        mWindowProcessController.removeBackgroundStartPrivileges(entity);
        setBackgroundStartPrivileges(entity, null);
    }

    @NonNull
    BackgroundStartPrivileges getBackgroundStartPrivileges() {
        synchronized (mBackgroundStartPrivileges) {
            if (mBackgroundStartPrivilegesMerged == null) {
                // Lazily generate the merged version when it's actually needed.
                mBackgroundStartPrivilegesMerged = BackgroundStartPrivileges.NONE;
                for (int i = mBackgroundStartPrivileges.size() - 1; i >= 0; --i) {
                    mBackgroundStartPrivilegesMerged =
                            mBackgroundStartPrivilegesMerged.merge(
                                    mBackgroundStartPrivileges.valueAt(i));
                }
            }
            return mBackgroundStartPrivilegesMerged;
        }
    }

    private void setBackgroundStartPrivileges(@NonNull Binder entity,
            @Nullable BackgroundStartPrivileges backgroundStartPrivileges) {
        synchronized (mBackgroundStartPrivileges) {
            final boolean changed;
            if (backgroundStartPrivileges == null) {
                changed = mBackgroundStartPrivileges.remove(entity) != null;
            } else {
                final BackgroundStartPrivileges oldBsp =
                        mBackgroundStartPrivileges.put(entity, backgroundStartPrivileges);
                // BackgroundStartPrivileges tries to reuse the same object and avoid creating
                // additional objects. For now, we just compare the reference to see if something
                // has changed.
                // TODO: actually compare the individual values to see if there's a change
                changed = backgroundStartPrivileges != oldBsp;
            }
            if (changed) {
                mBackgroundStartPrivilegesMerged = null;
            }
        }
    }

    @Override
Loading