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

Commit 0e2372d4 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Requesting network exception for app idle jobs."

parents d92fd48b cdbfcb90
Loading
Loading
Loading
Loading
+79 −0
Original line number Diff line number Diff line
@@ -839,6 +839,15 @@ public class JobSchedulerService extends com.android.server.SystemService
                                break;
                            }
                        }
                        if (DEBUG) {
                            Slog.d(TAG, "Something in " + pkgName
                                    + " changed. Reevaluating controller states.");
                        }
                        synchronized (mLock) {
                            for (int c = mControllers.size() - 1; c >= 0; --c) {
                                mControllers.get(c).reevaluateStateLocked(pkgUid);
                            }
                        }
                    }
                } else {
                    Slog.w(TAG, "PACKAGE_CHANGED for " + pkgName + " / uid " + pkgUid);
@@ -1042,6 +1051,8 @@ public class JobSchedulerService extends com.android.server.SystemService
                mJobPackageTracker.notePending(jobStatus);
                addOrderedItem(mPendingJobs, jobStatus, mEnqueueTimeComparator);
                maybeRunPendingJobsLocked();
            } else {
                evaluateControllerStatesLocked(jobStatus);
            }
        }
        return JobScheduler.RESULT_SUCCESS;
@@ -1884,6 +1895,8 @@ public class JobSchedulerService extends com.android.server.SystemService
                    newReadyJobs = new ArrayList<JobStatus>();
                }
                newReadyJobs.add(job);
            } else {
                evaluateControllerStatesLocked(job);
            }
        }

@@ -1957,6 +1970,8 @@ public class JobSchedulerService extends com.android.server.SystemService
                    runnableJobs = new ArrayList<>();
                }
                runnableJobs.add(job);
            } else {
                evaluateControllerStatesLocked(job);
            }
        }

@@ -2087,6 +2102,15 @@ public class JobSchedulerService extends com.android.server.SystemService
                HEARTBEAT_TAG, mHeartbeatAlarm, mHandler);
    }

    /** Returns true if both the calling and source users for the job are started. */
    private boolean areUsersStartedLocked(final JobStatus job) {
        boolean sourceStarted = ArrayUtils.contains(mStartedUsers, job.getSourceUserId());
        if (job.getUserId() == job.getSourceUserId()) {
            return sourceStarted;
        }
        return sourceStarted && ArrayUtils.contains(mStartedUsers, job.getUserId());
    }

    /**
     * Criteria for moving a job into the pending queue:
     *      - It's ready.
@@ -2219,6 +2243,61 @@ public class JobSchedulerService extends com.android.server.SystemService
        return componentPresent;
    }

    private void evaluateControllerStatesLocked(final JobStatus job) {
        for (int c = mControllers.size() - 1; c >= 0; --c) {
            final StateController sc = mControllers.get(c);
            sc.evaluateStateLocked(job);
        }
    }

    /**
     * Returns true if non-job constraint components are in place -- if job.isReady() returns true
     * and this method returns true, then the job is ready to be executed.
     */
    public boolean areComponentsInPlaceLocked(JobStatus job) {
        // This code is very similar to the code in isReadyToBeExecutedLocked --- it uses the same
        // conditions.

        final boolean jobExists = mJobs.containsJob(job);
        final boolean userStarted = areUsersStartedLocked(job);

        if (DEBUG) {
            Slog.v(TAG, "areComponentsInPlaceLocked: " + job.toShortString()
                    + " exists=" + jobExists + " userStarted=" + userStarted);
        }

        // These are also fairly cheap to check, though they typically will not
        // be conditions we fail.
        if (!jobExists || !userStarted) {
            return false;
        }

        // Job pending/active doesn't affect the readiness of a job.

        // Skipping the hearbeat check as this will only come into play when using the rolling
        // window quota management system.

        // The expensive check last: validate that the defined package+service is
        // still present & viable.
        final boolean componentPresent;
        try {
            // TODO: cache result until we're notified that something in the package changed.
            componentPresent = (AppGlobals.getPackageManager().getServiceInfo(
                    job.getServiceComponent(), PackageManager.MATCH_DEBUG_TRIAGED_MISSING,
                    job.getUserId()) != null);
        } catch (RemoteException e) {
            throw e.rethrowAsRuntimeException();
        }

        if (DEBUG) {
            Slog.v(TAG, "areComponentsInPlaceLocked: " + job.toShortString()
                    + " componentPresent=" + componentPresent);
        }

        // Everything else checked out so far, so this is the final yes/no check
        return componentPresent;
    }

    /**
     * Reconcile jobs in the pending queue against available execution contexts.
     * A controller can force a job into the pending queue even if it's already running, but
+216 −0
Original line number Diff line number Diff line
@@ -41,10 +41,12 @@ import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.LocalServices;
import com.android.server.job.JobSchedulerService;
import com.android.server.job.JobSchedulerService.Constants;
import com.android.server.job.JobServiceContext;
import com.android.server.job.StateControllerProto;
import com.android.server.net.NetworkPolicyManagerInternal;

import java.util.Objects;
import java.util.function.Predicate;
@@ -66,16 +68,29 @@ public final class ConnectivityController extends StateController implements

    private final ConnectivityManager mConnManager;
    private final NetworkPolicyManager mNetPolicyManager;
    private final NetworkPolicyManagerInternal mNetPolicyManagerInternal;

    /** List of tracked jobs keyed by source UID. */
    @GuardedBy("mLock")
    private final SparseArray<ArraySet<JobStatus>> mTrackedJobs = new SparseArray<>();

    /**
     * Keep track of all the UID's jobs that the controller has requested that NetworkPolicyManager
     * grant an exception to in the app standby chain.
     */
    @GuardedBy("mLock")
    private final SparseArray<ArraySet<JobStatus>> mRequestedWhitelistJobs = new SparseArray<>();

    /** List of currently available networks. */
    @GuardedBy("mLock")
    private final ArraySet<Network> mAvailableNetworks = new ArraySet<>();

    public ConnectivityController(JobSchedulerService service) {
        super(service);

        mConnManager = mContext.getSystemService(ConnectivityManager.class);
        mNetPolicyManager = mContext.getSystemService(NetworkPolicyManager.class);
        mNetPolicyManagerInternal = LocalServices.getService(NetworkPolicyManagerInternal.class);

        // We're interested in all network changes; internally we match these
        // network changes against the active network for each UID with jobs.
@@ -109,7 +124,176 @@ public final class ConnectivityController extends StateController implements
            if (jobs != null) {
                jobs.remove(jobStatus);
            }
            maybeRevokeStandbyExceptionLocked(jobStatus);
        }
    }

    @GuardedBy("mLock")
    @Override
    public void onConstantsUpdatedLocked() {
        if (mConstants.USE_HEARTBEATS) {
            // App idle exceptions are only requested for the rolling quota system.
            if (DEBUG) Slog.i(TAG, "Revoking all standby exceptions");
            for (int i = 0; i < mRequestedWhitelistJobs.size(); ++i) {
                int uid = mRequestedWhitelistJobs.keyAt(i);
                mNetPolicyManagerInternal.setAppIdleWhitelist(uid, false);
            }
            mRequestedWhitelistJobs.clear();
        }
    }

    /**
     * Returns true if the job's requested network is available. This DOES NOT necesarilly mean
     * that the UID has been granted access to the network.
     */
    public boolean isNetworkAvailable(JobStatus job) {
        synchronized (mLock) {
            for (int i = 0; i < mAvailableNetworks.size(); ++i) {
                final Network network = mAvailableNetworks.valueAt(i);
                final NetworkCapabilities capabilities = mConnManager.getNetworkCapabilities(
                        network);
                final boolean satisfied = isSatisfied(job, network, capabilities, mConstants);
                if (DEBUG) {
                    Slog.v(TAG, "isNetworkAvailable(" + job + ") with network " + network
                            + " and capabilities " + capabilities + ". Satisfied=" + satisfied);
                }
                if (satisfied) {
                    return true;
                }
            }
            return false;
        }
    }

    /**
     * Request that NetworkPolicyManager grant an exception to the uid from its standby policy
     * chain.
     */
    @VisibleForTesting
    @GuardedBy("mLock")
    void requestStandbyExceptionLocked(JobStatus job) {
        final int uid = job.getSourceUid();
        // Need to call this before adding the job.
        final boolean isExceptionRequested = isStandbyExceptionRequestedLocked(uid);
        ArraySet<JobStatus> jobs = mRequestedWhitelistJobs.get(uid);
        if (jobs == null) {
            jobs = new ArraySet<JobStatus>();
            mRequestedWhitelistJobs.put(uid, jobs);
        }
        if (!jobs.add(job) || isExceptionRequested) {
            if (DEBUG) {
                Slog.i(TAG, "requestStandbyExceptionLocked found exception already requested.");
            }
            return;
        }
        if (DEBUG) Slog.i(TAG, "Requesting standby exception for UID: " + uid);
        mNetPolicyManagerInternal.setAppIdleWhitelist(uid, true);
    }

    /** Returns whether a standby exception has been requested for the UID. */
    @VisibleForTesting
    @GuardedBy("mLock")
    boolean isStandbyExceptionRequestedLocked(final int uid) {
        ArraySet jobs = mRequestedWhitelistJobs.get(uid);
        return jobs != null && jobs.size() > 0;
    }

    @VisibleForTesting
    @GuardedBy("mLock")
    boolean wouldBeReadyWithConnectivityLocked(JobStatus jobStatus) {
        final boolean networkAvailable = isNetworkAvailable(jobStatus);
        if (DEBUG) {
            Slog.v(TAG, "wouldBeReadyWithConnectivityLocked: " + jobStatus.toShortString()
                    + " networkAvailable=" + networkAvailable);
        }
        // If the network isn't available, then requesting an exception won't help.

        return networkAvailable && wouldBeReadyWithConstraintLocked(jobStatus,
                JobStatus.CONSTRAINT_CONNECTIVITY);
    }

    /**
     * Tell NetworkPolicyManager not to block a UID's network connection if that's the only
     * thing stopping a job from running.
     */
    @GuardedBy("mLock")
    @Override
    public void evaluateStateLocked(JobStatus jobStatus) {
        if (mConstants.USE_HEARTBEATS) {
            // This should only be used for the rolling quota system.
            return;
        }

        if (!jobStatus.hasConnectivityConstraint()) {
            return;
        }

        // Always check the full job readiness stat in case the component has been disabled.
        if (wouldBeReadyWithConnectivityLocked(jobStatus)) {
            if (DEBUG) {
                Slog.i(TAG, "evaluateStateLocked finds job " + jobStatus + " would be ready.");
            }
            requestStandbyExceptionLocked(jobStatus);
        } else {
            if (DEBUG) {
                Slog.i(TAG, "evaluateStateLocked finds job " + jobStatus + " would not be ready.");
            }
            maybeRevokeStandbyExceptionLocked(jobStatus);
        }
    }

    @GuardedBy("mLock")
    @Override
    public void reevaluateStateLocked(final int uid) {
        if (mConstants.USE_HEARTBEATS) {
            return;
        }
        // Check if we still need a connectivity exception in case the JobService was disabled.
        ArraySet<JobStatus> jobs = mTrackedJobs.get(uid);
        if (jobs == null) {
            return;
        }
        for (int i = jobs.size() - 1; i >= 0; i--) {
            evaluateStateLocked(jobs.valueAt(i));
        }
    }

    /** Cancel the requested standby exception if none of the jobs would be ready to run anyway. */
    @VisibleForTesting
    @GuardedBy("mLock")
    void maybeRevokeStandbyExceptionLocked(final JobStatus job) {
        final int uid = job.getSourceUid();
        if (!isStandbyExceptionRequestedLocked(uid)) {
            return;
        }
        ArraySet<JobStatus> jobs = mRequestedWhitelistJobs.get(uid);
        if (jobs == null) {
            Slog.wtf(TAG,
                    "maybeRevokeStandbyExceptionLocked found null jobs array even though a "
                            + "standby exception has been requested.");
            return;
        }
        if (!jobs.remove(job) || jobs.size() > 0) {
            if (DEBUG) {
                Slog.i(TAG,
                        "maybeRevokeStandbyExceptionLocked not revoking because there are still "
                                + jobs.size() + " jobs left.");
            }
            return;
        }
        // No more jobs that need an exception.
        revokeStandbyExceptionLocked(uid);
    }

    /**
     * Tell NetworkPolicyManager to revoke any exception it granted from its standby policy chain
     * for the uid.
     */
    @GuardedBy("mLock")
    private void revokeStandbyExceptionLocked(final int uid) {
        if (DEBUG) Slog.i(TAG, "Revoking standby exception for UID: " + uid);
        mNetPolicyManagerInternal.setAppIdleWhitelist(uid, false);
        mRequestedWhitelistJobs.remove(uid);
    }

    /**
@@ -325,6 +509,14 @@ public final class ConnectivityController extends StateController implements
    }

    private final NetworkCallback mNetworkCallback = new NetworkCallback() {
        @Override
        public void onAvailable(Network network) {
            if (DEBUG) Slog.v(TAG, "onAvailable: " + network);
            synchronized (mLock) {
                mAvailableNetworks.add(network);
            }
        }

        @Override
        public void onCapabilitiesChanged(Network network, NetworkCapabilities capabilities) {
            if (DEBUG) {
@@ -338,6 +530,9 @@ public final class ConnectivityController extends StateController implements
            if (DEBUG) {
                Slog.v(TAG, "onLost: " + network);
            }
            synchronized (mLock) {
                mAvailableNetworks.remove(network);
            }
            updateTrackedJobs(-1, network);
        }
    };
@@ -356,6 +551,27 @@ public final class ConnectivityController extends StateController implements
    @Override
    public void dumpControllerStateLocked(IndentingPrintWriter pw,
            Predicate<JobStatus> predicate) {
        if (mRequestedWhitelistJobs.size() > 0) {
            pw.print("Requested standby exceptions:");
            for (int i = 0; i < mRequestedWhitelistJobs.size(); i++) {
                pw.print(" ");
                pw.print(mRequestedWhitelistJobs.keyAt(i));
                pw.print(" (");
                pw.print(mRequestedWhitelistJobs.valueAt(i).size());
                pw.print(" jobs)");
            }
            pw.println();
        }
        if (mAvailableNetworks.size() > 0) {
            pw.println("Available networks:");
            pw.increaseIndent();
            for (int i = 0; i < mAvailableNetworks.size(); i++) {
                pw.println(mAvailableNetworks.valueAt(i));
            }
            pw.decreaseIndent();
        } else {
            pw.println("No available networks");
        }
        for (int i = 0; i < mTrackedJobs.size(); i++) {
            final ArraySet<JobStatus> jobs = mTrackedJobs.valueAt(i);
            for (int j = 0; j < jobs.size(); j++) {
+18 −2
Original line number Diff line number Diff line
@@ -1007,6 +1007,18 @@ public final class JobStatus {
     * @return Whether or not this job is ready to run, based on its requirements.
     */
    public boolean isReady() {
        return isReady(mSatisfiedConstraintsOfInterest);
    }

    /**
     * @return Whether or not this job would be ready to run if it had the specified constraint
     * granted, based on its requirements.
     */
    public boolean wouldBeReadyWithConstraint(int constraint) {
        return isReady(mSatisfiedConstraintsOfInterest | constraint);
    }

    private boolean isReady(int satisfiedConstraints) {
        // Quota constraints trumps all other constraints.
        if (!mReadyWithinQuota) {
            return false;
@@ -1017,7 +1029,7 @@ public final class JobStatus {
        // DeviceNotDozing implicit constraint must be satisfied
        // NotRestrictedInBackground implicit constraint must be satisfied
        return mReadyNotDozing && mReadyNotRestrictedInBg && (mReadyDeadlineSatisfied
                || isConstraintsSatisfied());
                || isConstraintsSatisfied(satisfiedConstraints));
    }

    static final int CONSTRAINTS_OF_INTEREST = CONSTRAINT_CHARGING | CONSTRAINT_BATTERY_NOT_LOW
@@ -1033,12 +1045,16 @@ public final class JobStatus {
     * @return Whether the constraints set on this job are satisfied.
     */
    public boolean isConstraintsSatisfied() {
        return isConstraintsSatisfied(mSatisfiedConstraintsOfInterest);
    }

    private boolean isConstraintsSatisfied(int satisfiedConstraints) {
        if (overrideState == OVERRIDE_FULL) {
            // force override: the job is always runnable
            return true;
        }

        int sat = mSatisfiedConstraintsOfInterest;
        int sat = satisfiedConstraints;
        if (overrideState == OVERRIDE_SOFT) {
            // override: pretend all 'soft' requirements are satisfied
            sat |= (requiredConstraints & SOFT_OVERRIDE_CONSTRAINTS);
+36 −0
Original line number Diff line number Diff line
@@ -16,7 +16,10 @@

package com.android.server.job.controllers;

import static com.android.server.job.JobSchedulerService.DEBUG;

import android.content.Context;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;

import com.android.internal.util.IndentingPrintWriter;
@@ -32,6 +35,8 @@ import java.util.function.Predicate;
 * are ready to run, or whether they must be stopped.
 */
public abstract class StateController {
    private static final String TAG = "JobScheduler.SC";

    protected final JobSchedulerService mService;
    protected final StateChangedListener mStateChangedListener;
    protected final Context mContext;
@@ -78,6 +83,37 @@ public abstract class StateController {
    public void onConstantsUpdatedLocked() {
    }

    protected boolean wouldBeReadyWithConstraintLocked(JobStatus jobStatus, int constraint) {
        // This is very cheap to check (just a few conditions on data in JobStatus).
        final boolean jobWouldBeReady = jobStatus.wouldBeReadyWithConstraint(constraint);
        if (DEBUG) {
            Slog.v(TAG, "wouldBeReadyWithConstraintLocked: " + jobStatus.toShortString()
                    + " readyWithConstraint=" + jobWouldBeReady);
        }
        if (!jobWouldBeReady) {
            // If the job wouldn't be ready, nothing to do here.
            return false;
        }

        // This is potentially more expensive since JSS may have to query component
        // presence.
        return mService.areComponentsInPlaceLocked(jobStatus);
    }

    /**
     * Called when JobSchedulerService has determined that the job is not ready to be run. The
     * Controller can evaluate if it can or should do something to promote this job's readiness.
     */
    public void evaluateStateLocked(JobStatus jobStatus) {
    }

    /**
     * Called when something with the UID has changed. The controller should re-evaluate any
     * internal state tracking dependent on this UID.
     */
    public void reevaluateStateLocked(int uid) {
    }

    public abstract void dumpControllerStateLocked(IndentingPrintWriter pw,
            Predicate<JobStatus> predicate);
    public abstract void dumpControllerStateLocked(ProtoOutputStream proto, long fieldId,
+581 −0

File changed and moved.

Preview size limit exceeded, changes collapsed.

Loading