Loading services/core/java/com/android/server/job/JobSchedulerService.java +79 −0 Original line number Diff line number Diff line Loading @@ -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); Loading Loading @@ -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; Loading Loading @@ -1884,6 +1895,8 @@ public class JobSchedulerService extends com.android.server.SystemService newReadyJobs = new ArrayList<JobStatus>(); } newReadyJobs.add(job); } else { evaluateControllerStatesLocked(job); } } Loading Loading @@ -1957,6 +1970,8 @@ public class JobSchedulerService extends com.android.server.SystemService runnableJobs = new ArrayList<>(); } runnableJobs.add(job); } else { evaluateControllerStatesLocked(job); } } Loading Loading @@ -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. Loading Loading @@ -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 Loading services/core/java/com/android/server/job/controllers/ConnectivityController.java +216 −0 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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. Loading Loading @@ -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); } /** Loading Loading @@ -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) { Loading @@ -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); } }; Loading @@ -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++) { Loading services/core/java/com/android/server/job/controllers/JobStatus.java +18 −2 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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 Loading @@ -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); Loading services/core/java/com/android/server/job/controllers/StateController.java +36 −0 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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, Loading services/tests/servicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java→services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java +581 −0 File changed and moved.Preview size limit exceeded, changes collapsed. Show changes Loading
services/core/java/com/android/server/job/JobSchedulerService.java +79 −0 Original line number Diff line number Diff line Loading @@ -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); Loading Loading @@ -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; Loading Loading @@ -1884,6 +1895,8 @@ public class JobSchedulerService extends com.android.server.SystemService newReadyJobs = new ArrayList<JobStatus>(); } newReadyJobs.add(job); } else { evaluateControllerStatesLocked(job); } } Loading Loading @@ -1957,6 +1970,8 @@ public class JobSchedulerService extends com.android.server.SystemService runnableJobs = new ArrayList<>(); } runnableJobs.add(job); } else { evaluateControllerStatesLocked(job); } } Loading Loading @@ -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. Loading Loading @@ -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 Loading
services/core/java/com/android/server/job/controllers/ConnectivityController.java +216 −0 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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. Loading Loading @@ -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); } /** Loading Loading @@ -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) { Loading @@ -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); } }; Loading @@ -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++) { Loading
services/core/java/com/android/server/job/controllers/JobStatus.java +18 −2 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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 Loading @@ -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); Loading
services/core/java/com/android/server/job/controllers/StateController.java +36 −0 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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, Loading
services/tests/servicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java→services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java +581 −0 File changed and moved.Preview size limit exceeded, changes collapsed. Show changes