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

Commit 15407846 authored by Makoto Onuki's avatar Makoto Onuki
Browse files

EBS: Exempt jobs scheduled for foreground apps

Jobs that are scheduled when the service app is in the foreground
(as opposed to the calling app being in the foreground) will be exempted
from EBS job throttling, *unless* they have time constraints.

Bug: 72125364
Test: Manual test with sync
Test: atest CtsBatterySavingTestCases
Test: atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
Test: atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/ForceAppStandbyTrackerTest.java
Change-Id: Ied7e24b0dbb104d5d7f95f853ab939cd9a403456
parent bab202f7
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -558,4 +558,6 @@ message JobStatusDumpProto {

    optional int64 last_successful_run_time = 22;
    optional int64 last_failed_run_time = 23;

    optional int64 internal_flags = 24;
}
+7 −3
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@ package com.android.server;

import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.AppOpsManager;
import android.app.AppOpsManager.PackageOps;
import android.app.IActivityManager;
@@ -825,9 +826,10 @@ public class ForceAppStandbyTracker {
    /**
     * @return whether jobs should be restricted for a UID package-name.
     */
    public boolean areJobsRestricted(int uid, @NonNull String packageName) {
    public boolean areJobsRestricted(int uid, @NonNull String packageName,
            boolean hasForegroundExemption) {
        return isRestricted(uid, packageName, /*useTempWhitelistToo=*/ true,
                /* exemptOnBatterySaver =*/ false);
                hasForegroundExemption);
    }

    /**
@@ -861,7 +863,9 @@ public class ForceAppStandbyTracker {
    /**
     * @return whether a UID is in the foreground or not.
     *
     * Note clients normally shouldn't need to access it. It's only for dumpsys.
     * Note this information is based on the UID proc state callback, meaning it's updated
     * asynchronously and may subtly be stale. If the fresh data is needed, use
     * {@link ActivityManagerInternal#getUidProcessState} instead.
     */
    public boolean isInForeground(int uid) {
        if (!UserHandle.isApp(uid)) {
+42 −0
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED

import android.app.Activity;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.AppGlobals;
import android.app.IUidObserver;
import android.app.job.IJobScheduler;
@@ -72,8 +73,10 @@ import com.android.internal.app.procstats.ProcessStats;
import com.android.internal.os.BackgroundThread;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.Preconditions;
import com.android.server.DeviceIdleController;
import com.android.server.FgThread;
import com.android.server.ForceAppStandbyTracker;
import com.android.server.LocalServices;
import com.android.server.job.JobSchedulerServiceDumpProto.ActiveJob;
import com.android.server.job.JobSchedulerServiceDumpProto.PendingJob;
@@ -101,6 +104,7 @@ import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.function.Predicate;

/**
 * Responsible for taking jobs representing work to be performed by a client app, and determining
@@ -174,8 +178,10 @@ public final class JobSchedulerService extends com.android.server.SystemService
    final JobSchedulerStub mJobSchedulerStub;

    PackageManagerInternal mLocalPM;
    ActivityManagerInternal mActivityManagerInternal;
    IBatteryStats mBatteryStats;
    DeviceIdleController.LocalService mLocalDeviceIdleController;
    final ForceAppStandbyTracker mForceAppStandbyTracker;

    /**
     * Set to true once we are allowed to run third party apps.
@@ -777,6 +783,22 @@ public final class JobSchedulerService extends com.android.server.SystemService
        mStartedUsers = ArrayUtils.removeInt(mStartedUsers, userHandle);
    }

    /**
     * Return whether an UID is in the foreground or not.
     */
    private boolean isUidInForeground(int uid) {
        synchronized (mLock) {
            if (mUidPriorityOverride.get(uid, 0) > 0) {
                return true;
            }
        }
        // Note UID observer may not be called in time, so we always check with the AM.
        return mActivityManagerInternal.getUidProcessState(uid)
                <= ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
    }

    private final Predicate<Integer> mIsUidInForegroundPredicate = this::isUidInForeground;

    public int scheduleAsPackage(JobInfo job, JobWorkItem work, int uId, String packageName,
            int userId, String tag) {
        try {
@@ -796,12 +818,25 @@ public final class JobSchedulerService extends com.android.server.SystemService
                // Fast path: we are adding work to an existing job, and the JobInfo is not
                // changing.  We can just directly enqueue this work in to the job.
                if (toCancel.getJob().equals(job)) {

                    toCancel.enqueueWorkLocked(ActivityManager.getService(), work);

                    // If any of work item is enqueued when the source is in the foreground,
                    // exempt the entire job.
                    toCancel.maybeAddForegroundExemption(mIsUidInForegroundPredicate);

                    return JobScheduler.RESULT_SUCCESS;
                }
            }

            JobStatus jobStatus = JobStatus.createFromJobInfo(job, uId, packageName, userId, tag);

            // Give exemption if the source is in the foreground just now.
            // Note if it's a sync job, this method is called on the handler so it's not exactly
            // the state when requestSync() was called, but that should be fine because of the
            // 1 minute foreground grace period.
            jobStatus.maybeAddForegroundExemption(mIsUidInForegroundPredicate);

            if (DEBUG) Slog.d(TAG, "SCHEDULE: " + jobStatus.toShortString());
            // Jobs on behalf of others don't apply to the per-app job cap
            if (ENFORCE_MAX_JOBS && packageName == null) {
@@ -1044,6 +1079,8 @@ public final class JobSchedulerService extends com.android.server.SystemService
        super(context);

        mLocalPM = LocalServices.getService(PackageManagerInternal.class);
        mActivityManagerInternal = Preconditions.checkNotNull(
                LocalServices.getService(ActivityManagerInternal.class));

        mHandler = new JobHandler(context.getMainLooper());
        mConstants = new Constants(mHandler);
@@ -1075,6 +1112,8 @@ public final class JobSchedulerService extends com.android.server.SystemService
        mDeviceIdleJobsController = DeviceIdleJobsController.get(this);
        mControllers.add(mDeviceIdleJobsController);

        mForceAppStandbyTracker = ForceAppStandbyTracker.getInstance(context);

        // If the job store determined that it can't yet reschedule persisted jobs,
        // we need to start watching the clock.
        if (!mJobs.jobTimesInflatedValid()) {
@@ -1134,6 +1173,9 @@ public final class JobSchedulerService extends com.android.server.SystemService
    public void onBootPhase(int phase) {
        if (PHASE_SYSTEM_SERVICES_READY == phase) {
            mConstants.start(getContext().getContentResolver());

            mForceAppStandbyTracker.start();

            // Register br for package removals and user removals.
            final IntentFilter filter = new IntentFilter();
            filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+12 −2
Original line number Diff line number Diff line
@@ -72,6 +72,9 @@ import java.util.Set;
 *      This is important b/c {@link com.android.server.job.JobStore.WriteJobsMapToDiskRunnable}
 *      and {@link com.android.server.job.JobStore.ReadJobMapFromDiskRunnable} lock on that
 *      object.
 *
 * Test:
 * atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
 */
public final class JobStore {
    private static final String TAG = "JobStore";
@@ -427,6 +430,9 @@ public final class JobStore {
            out.attribute(null, "uid", Integer.toString(jobStatus.getUid()));
            out.attribute(null, "priority", String.valueOf(jobStatus.getPriority()));
            out.attribute(null, "flags", String.valueOf(jobStatus.getFlags()));
            if (jobStatus.getInternalFlags() != 0) {
                out.attribute(null, "internalFlags", String.valueOf(jobStatus.getInternalFlags()));
            }

            out.attribute(null, "lastSuccessfulRunTime",
                    String.valueOf(jobStatus.getLastSuccessfulRunTime()));
@@ -689,6 +695,7 @@ public final class JobStore {
            int uid, sourceUserId;
            long lastSuccessfulRunTime;
            long lastFailedRunTime;
            int internalFlags = 0;

            // Read out job identifier attributes and priority.
            try {
@@ -704,6 +711,10 @@ public final class JobStore {
                if (val != null) {
                    jobBuilder.setFlags(Integer.parseInt(val));
                }
                val = parser.getAttributeValue(null, "internalFlags");
                if (val != null) {
                    internalFlags = Integer.parseInt(val);
                }
                val = parser.getAttributeValue(null, "sourceUserId");
                sourceUserId = val == null ? -1 : Integer.parseInt(val);

@@ -718,7 +729,6 @@ public final class JobStore {
            }

            String sourcePackageName = parser.getAttributeValue(null, "sourcePackageName");

            final String sourceTag = parser.getAttributeValue(null, "sourceTag");

            int eventType;
@@ -857,7 +867,7 @@ public final class JobStore {
                    appBucket, currentHeartbeat, sourceTag,
                    elapsedRuntimes.first, elapsedRuntimes.second,
                    lastSuccessfulRunTime, lastFailedRunTime,
                    (rtcIsGood) ? null : rtcRuntimes);
                    (rtcIsGood) ? null : rtcRuntimes, internalFlags);
            return js;
        }

+3 −1
Original line number Diff line number Diff line
@@ -197,7 +197,9 @@ public final class BackgroundJobsController extends StateController {
        final int uid = jobStatus.getSourceUid();
        final String packageName = jobStatus.getSourcePackageName();

        final boolean canRun = !mForceAppStandbyTracker.areJobsRestricted(uid, packageName);
        final boolean canRun = !mForceAppStandbyTracker.areJobsRestricted(uid, packageName,
                (jobStatus.getInternalFlags() & JobStatus.INTERNAL_FLAG_HAS_FOREGROUND_EXEMPTION)
                        != 0);

        return jobStatus.setBackgroundNotRestrictedConstraintSatisfied(canRun);
    }
Loading