diff options
| author | 2023-11-10 19:39:48 +0000 | |
|---|---|---|
| committer | 2023-11-10 19:39:48 +0000 | |
| commit | 032cc572136987d025c8ffdff86956b0c7660f80 (patch) | |
| tree | 922e68f2b6c5d7d0ba9dbacbb831ff2bde7af0ba | |
| parent | aa3e3af194316ca0559251c21581fd507587247e (diff) | |
| parent | 366583d33138d8565e8fa774b3a8dbc05418de9a (diff) | |
Merge "Add transport affinities for flex scheduling." into main
6 files changed, 463 insertions, 71 deletions
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java index bff43534ce05..07475b4f2136 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java @@ -998,6 +998,7 @@ public class JobSchedulerService extends com.android.server.SystemService DEFAULT_SYSTEM_STOP_TO_FAILURE_RATIO); } + // TODO(141645789): move into ConnectivityController.CcConfig private void updateConnectivityConstantsLocked() { CONN_CONGESTION_DELAY_FRAC = DeviceConfig.getFloat(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_CONN_CONGESTION_DELAY_FRAC, diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java index ae4e99cfeef3..0cf0cc5dcd22 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java @@ -19,6 +19,9 @@ package com.android.server.job.controllers; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED; import static android.net.NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED; +import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; +import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET; +import static android.net.NetworkCapabilities.TRANSPORT_WIFI; import static com.android.server.job.JobSchedulerService.RESTRICTED_INDEX; import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock; @@ -29,6 +32,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; import android.app.job.JobInfo; +import android.content.pm.PackageManager; import android.net.ConnectivityManager; import android.net.ConnectivityManager.NetworkCallback; import android.net.INetworkPolicyListener; @@ -40,6 +44,7 @@ import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.UserHandle; +import android.provider.DeviceConfig; import android.telephony.CellSignalStrength; import android.telephony.SignalStrength; import android.telephony.TelephonyCallback; @@ -53,6 +58,7 @@ import android.util.Pools; import android.util.Slog; import android.util.SparseArray; import android.util.SparseBooleanArray; +import android.util.SparseIntArray; import android.util.TimeUtils; import android.util.proto.ProtoOutputStream; @@ -117,6 +123,26 @@ public final class ConnectivityController extends RestrictingController implemen | ConnectivityManager.BLOCKED_METERED_REASON_DATA_SAVER | ConnectivityManager.BLOCKED_METERED_REASON_USER_RESTRICTED); + @VisibleForTesting + static final int TRANSPORT_AFFINITY_UNDEFINED = 0; + @VisibleForTesting + static final int TRANSPORT_AFFINITY_PREFER = 1; + @VisibleForTesting + static final int TRANSPORT_AFFINITY_AVOID = 2; + /** + * Set of affinities to different network transports. If a given network has multiple + * transports, the avoided ones take priority --- a network with an avoided transport + * should be avoided if possible, even if the network has preferred transports as well. + */ + @VisibleForTesting + static final SparseIntArray sNetworkTransportAffinities = new SparseIntArray(); + static { + sNetworkTransportAffinities.put(TRANSPORT_CELLULAR, TRANSPORT_AFFINITY_AVOID); + sNetworkTransportAffinities.put(TRANSPORT_WIFI, TRANSPORT_AFFINITY_PREFER); + sNetworkTransportAffinities.put(TRANSPORT_ETHERNET, TRANSPORT_AFFINITY_PREFER); + } + + private final CcConfig mCcConfig; private final ConnectivityManager mConnManager; private final NetworkPolicyManager mNetPolicyManager; private final NetworkPolicyManagerInternal mNetPolicyManagerInternal; @@ -138,7 +164,7 @@ public final class ConnectivityController extends RestrictingController implemen * latest capabilities to avoid unnecessary calls into ConnectivityManager. */ @GuardedBy("mLock") - private final ArrayMap<Network, NetworkCapabilities> mAvailableNetworks = new ArrayMap<>(); + private final ArrayMap<Network, CachedNetworkMetadata> mAvailableNetworks = new ArrayMap<>(); private final SparseArray<UidDefaultNetworkCallback> mCurrentDefaultNetworkCallbacks = new SparseArray<>(); @@ -267,6 +293,7 @@ public final class ConnectivityController extends RestrictingController implemen @NonNull FlexibilityController flexibilityController) { super(service); mHandler = new CcHandler(AppSchedulingModuleThread.get().getLooper()); + mCcConfig = new CcConfig(); mConnManager = mContext.getSystemService(ConnectivityManager.class); mNetPolicyManager = mContext.getSystemService(NetworkPolicyManager.class); @@ -279,6 +306,11 @@ public final class ConnectivityController extends RestrictingController implemen mConnManager.registerNetworkCallback(request, mNetworkCallback); mNetPolicyManager.registerListener(mNetPolicyListener); + + if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)) { + // For now, we don't have network affinities on watches. + sNetworkTransportAffinities.clear(); + } } @GuardedBy("mLock") @@ -386,7 +418,9 @@ public final class ConnectivityController extends RestrictingController implemen synchronized (mLock) { for (int i = 0; i < mAvailableNetworks.size(); ++i) { final Network network = mAvailableNetworks.keyAt(i); - final NetworkCapabilities capabilities = mAvailableNetworks.valueAt(i); + final CachedNetworkMetadata metadata = mAvailableNetworks.valueAt(i); + final NetworkCapabilities capabilities = + metadata == null ? null : metadata.networkCapabilities; final boolean satisfied = isSatisfied(job, network, capabilities, mConstants); if (DEBUG) { Slog.v(TAG, "isNetworkAvailable(" + job + ") with network " + network @@ -589,6 +623,43 @@ public final class ConnectivityController extends RestrictingController implemen mHandler.sendEmptyMessage(MSG_UPDATE_ALL_TRACKED_JOBS); } + @Override + public void prepareForUpdatedConstantsLocked() { + mCcConfig.mShouldReprocessNetworkCapabilities = false; + mCcConfig.mFlexIsEnabled = mFlexibilityController.isEnabled(); + } + + @Override + public void processConstantLocked(@NonNull DeviceConfig.Properties properties, + @NonNull String key) { + mCcConfig.processConstantLocked(properties, key); + } + + @Override + public void onConstantsUpdatedLocked() { + if (mCcConfig.mShouldReprocessNetworkCapabilities + || (mFlexibilityController.isEnabled() != mCcConfig.mFlexIsEnabled)) { + AppSchedulingModuleThread.getHandler().post(() -> { + boolean shouldUpdateJobs = false; + for (int i = 0; i < mAvailableNetworks.size(); ++i) { + CachedNetworkMetadata metadata = mAvailableNetworks.valueAt(i); + if (metadata == null || metadata.networkCapabilities == null) { + continue; + } + boolean satisfies = satisfiesTransportAffinities(metadata.networkCapabilities); + if (metadata.satisfiesTransportAffinities != satisfies) { + metadata.satisfiesTransportAffinities = satisfies; + // Something changed. Update jobs. + shouldUpdateJobs = true; + } + } + if (shouldUpdateJobs) { + updateAllTrackedJobsLocked(false); + } + }); + } + } + private boolean isUsable(NetworkCapabilities capabilities) { return capabilities != null && capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED); @@ -831,7 +902,7 @@ public final class ConnectivityController extends RestrictingController implemen if (!constants.CONN_USE_CELL_SIGNAL_STRENGTH) { return true; } - if (!capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) { + if (!capabilities.hasTransport(TRANSPORT_CELLULAR)) { return true; } if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_VPN)) { @@ -988,6 +1059,52 @@ public final class ConnectivityController extends RestrictingController implemen return false; } + private boolean satisfiesTransportAffinities(@Nullable NetworkCapabilities capabilities) { + if (!mFlexibilityController.isEnabled()) { + return true; + } + if (capabilities == null) { + Slog.wtf(TAG, "Network constraint satisfied with null capabilities"); + return !mCcConfig.AVOID_UNDEFINED_TRANSPORT_AFFINITY; + } + + if (sNetworkTransportAffinities.size() == 0) { + return !mCcConfig.AVOID_UNDEFINED_TRANSPORT_AFFINITY; + } + + final int[] transports = capabilities.getTransportTypes(); + if (transports.length == 0) { + return !mCcConfig.AVOID_UNDEFINED_TRANSPORT_AFFINITY; + } + + for (int t : transports) { + int affinity = sNetworkTransportAffinities.get(t, TRANSPORT_AFFINITY_UNDEFINED); + if (DEBUG) { + Slog.d(TAG, + "satisfiesTransportAffinities transport=" + t + " aff=" + affinity); + } + switch (affinity) { + case TRANSPORT_AFFINITY_UNDEFINED: + if (mCcConfig.AVOID_UNDEFINED_TRANSPORT_AFFINITY) { + // Avoided transports take precedence. + // Return as soon as we encounter a transport to avoid. + return false; + } + break; + case TRANSPORT_AFFINITY_PREFER: + // Nothing to do here. We like this transport. + break; + case TRANSPORT_AFFINITY_AVOID: + // Avoided transports take precedence. + // Return as soon as we encounter a transport to avoid. + return false; + } + } + + // Didn't see any transport to avoid. + return true; + } + @GuardedBy("mLock") private void maybeRegisterDefaultNetworkCallbackLocked(JobStatus jobStatus) { final int sourceUid = jobStatus.getSourceUid(); @@ -1172,6 +1289,12 @@ public final class ConnectivityController extends RestrictingController implemen @Nullable private NetworkCapabilities getNetworkCapabilities(@Nullable Network network) { + final CachedNetworkMetadata metadata = getNetworkMetadata(network); + return metadata == null ? null : metadata.networkCapabilities; + } + + @Nullable + private CachedNetworkMetadata getNetworkMetadata(@Nullable Network network) { if (network == null) { return null; } @@ -1234,14 +1357,16 @@ public final class ConnectivityController extends RestrictingController implemen return updateConstraintsSatisfied(jobStatus, nowElapsed, null, null); } final Network network = getNetworkLocked(jobStatus); - final NetworkCapabilities capabilities = getNetworkCapabilities(network); - return updateConstraintsSatisfied(jobStatus, nowElapsed, network, capabilities); + final CachedNetworkMetadata networkMetadata = getNetworkMetadata(network); + return updateConstraintsSatisfied(jobStatus, nowElapsed, network, networkMetadata); } private boolean updateConstraintsSatisfied(JobStatus jobStatus, final long nowElapsed, - Network network, NetworkCapabilities capabilities) { + Network network, @Nullable CachedNetworkMetadata networkMetadata) { // TODO: consider matching against non-default networks + final NetworkCapabilities capabilities = + networkMetadata == null ? null : networkMetadata.networkCapabilities; final boolean satisfied = isSatisfied(jobStatus, network, capabilities, mConstants); if (!satisfied && jobStatus.network != null @@ -1263,10 +1388,10 @@ public final class ConnectivityController extends RestrictingController implemen final boolean changed = jobStatus.setConnectivityConstraintSatisfied(nowElapsed, satisfied); - if (jobStatus.getPreferUnmetered()) { - jobStatus.setHasAccessToUnmetered(satisfied && capabilities != null - && capabilities.hasCapability(NET_CAPABILITY_NOT_METERED)); - + jobStatus.setTransportAffinitiesSatisfied(satisfied && networkMetadata != null + && networkMetadata.satisfiesTransportAffinities); + if (jobStatus.canApplyTransportAffinities()) { + // Only modify the flex constraint if the job actually needs it. jobStatus.setFlexibilityConstraintSatisfied(nowElapsed, mFlexibilityController.isFlexibilitySatisfiedLocked(jobStatus)); } @@ -1367,7 +1492,6 @@ public final class ConnectivityController extends RestrictingController implemen final JobStatus js = jobs.valueAt(i); final Network net = getNetworkLocked(js); - final NetworkCapabilities netCap = getNetworkCapabilities(net); final boolean match = (filterNetwork == null || Objects.equals(filterNetwork, net)); @@ -1375,7 +1499,7 @@ public final class ConnectivityController extends RestrictingController implemen // job hasn't yet been evaluated against the currently // active network; typically when we just lost a network. if (match || !Objects.equals(js.network, net)) { - changed |= updateConstraintsSatisfied(js, nowElapsed, net, netCap); + changed |= updateConstraintsSatisfied(js, nowElapsed, net, getNetworkMetadata(net)); } } return changed; @@ -1417,10 +1541,18 @@ public final class ConnectivityController extends RestrictingController implemen Slog.v(TAG, "onCapabilitiesChanged: " + network); } synchronized (mLock) { - final NetworkCapabilities oldCaps = mAvailableNetworks.put(network, capabilities); - if (oldCaps != null) { - maybeUnregisterSignalStrengthCallbackLocked(oldCaps); + CachedNetworkMetadata cnm = mAvailableNetworks.get(network); + if (cnm == null) { + cnm = new CachedNetworkMetadata(); + mAvailableNetworks.put(network, cnm); + } else { + final NetworkCapabilities oldCaps = cnm.networkCapabilities; + if (oldCaps != null) { + maybeUnregisterSignalStrengthCallbackLocked(oldCaps); + } } + cnm.networkCapabilities = capabilities; + cnm.satisfiesTransportAffinities = satisfiesTransportAffinities(capabilities); maybeRegisterSignalStrengthCallbackLocked(capabilities); updateTrackedJobsLocked(-1, network); postAdjustCallbacks(); @@ -1433,9 +1565,9 @@ public final class ConnectivityController extends RestrictingController implemen Slog.v(TAG, "onLost: " + network); } synchronized (mLock) { - final NetworkCapabilities capabilities = mAvailableNetworks.remove(network); - if (capabilities != null) { - maybeUnregisterSignalStrengthCallbackLocked(capabilities); + final CachedNetworkMetadata cnm = mAvailableNetworks.remove(network); + if (cnm != null && cnm.networkCapabilities != null) { + maybeUnregisterSignalStrengthCallbackLocked(cnm.networkCapabilities); } for (int u = 0; u < mCurrentDefaultNetworkCallbacks.size(); ++u) { UidDefaultNetworkCallback callback = mCurrentDefaultNetworkCallbacks.valueAt(u); @@ -1451,7 +1583,7 @@ public final class ConnectivityController extends RestrictingController implemen @GuardedBy("mLock") private void maybeRegisterSignalStrengthCallbackLocked( @NonNull NetworkCapabilities capabilities) { - if (!capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) { + if (!capabilities.hasTransport(TRANSPORT_CELLULAR)) { return; } TelephonyManager telephonyManager = mContext.getSystemService(TelephonyManager.class); @@ -1476,14 +1608,17 @@ public final class ConnectivityController extends RestrictingController implemen @GuardedBy("mLock") private void maybeUnregisterSignalStrengthCallbackLocked( @NonNull NetworkCapabilities capabilities) { - if (!capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) { + if (!capabilities.hasTransport(TRANSPORT_CELLULAR)) { return; } ArraySet<Integer> activeIds = new ArraySet<>(); for (int i = 0, size = mAvailableNetworks.size(); i < size; ++i) { - NetworkCapabilities nc = mAvailableNetworks.valueAt(i); - if (nc.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) { - activeIds.addAll(nc.getSubscriptionIds()); + final CachedNetworkMetadata metadata = mAvailableNetworks.valueAt(i); + if (metadata == null || metadata.networkCapabilities == null) { + continue; + } + if (metadata.networkCapabilities.hasTransport(TRANSPORT_CELLULAR)) { + activeIds.addAll(metadata.networkCapabilities.getSubscriptionIds()); } } if (DEBUG) { @@ -1573,6 +1708,57 @@ public final class ConnectivityController extends RestrictingController implemen } } + @VisibleForTesting + class CcConfig { + private boolean mFlexIsEnabled = FlexibilityController.FcConfig.DEFAULT_FLEXIBILITY_ENABLED; + private boolean mShouldReprocessNetworkCapabilities = false; + + /** + * Prefix to use with all constant keys in order to "sub-namespace" the keys. + * "conn_" is used for legacy reasons. + */ + private static final String CC_CONFIG_PREFIX = "conn_"; + + @VisibleForTesting + static final String KEY_AVOID_UNDEFINED_TRANSPORT_AFFINITY = + CC_CONFIG_PREFIX + "avoid_undefined_transport_affinity"; + + private static final boolean DEFAULT_AVOID_UNDEFINED_TRANSPORT_AFFINITY = false; + + /** + * If true, will avoid network transports that don't have an explicitly defined affinity. + */ + public boolean AVOID_UNDEFINED_TRANSPORT_AFFINITY = + DEFAULT_AVOID_UNDEFINED_TRANSPORT_AFFINITY; + + @GuardedBy("mLock") + public void processConstantLocked(@NonNull DeviceConfig.Properties properties, + @NonNull String key) { + switch (key) { + case KEY_AVOID_UNDEFINED_TRANSPORT_AFFINITY: + final boolean avoid = properties.getBoolean(key, + DEFAULT_AVOID_UNDEFINED_TRANSPORT_AFFINITY); + if (AVOID_UNDEFINED_TRANSPORT_AFFINITY != avoid) { + AVOID_UNDEFINED_TRANSPORT_AFFINITY = avoid; + mShouldReprocessNetworkCapabilities = true; + } + break; + } + } + + private void dump(IndentingPrintWriter pw) { + pw.println(); + pw.print(ConnectivityController.class.getSimpleName()); + pw.println(":"); + pw.increaseIndent(); + + pw.print(KEY_AVOID_UNDEFINED_TRANSPORT_AFFINITY, + AVOID_UNDEFINED_TRANSPORT_AFFINITY).println(); + + pw.decreaseIndent(); + } + } + private class UidDefaultNetworkCallback extends NetworkCallback { private int mUid; @Nullable @@ -1676,6 +1862,18 @@ public final class ConnectivityController extends RestrictingController implemen } } + private static class CachedNetworkMetadata { + public NetworkCapabilities networkCapabilities; + public boolean satisfiesTransportAffinities; + + public String toString() { + return "CNM{" + + networkCapabilities.toString() + + ", satisfiesTransportAffinities=" + satisfiesTransportAffinities + + "}"; + } + } + private static class UidStats { public final int uid; public int baseBias; @@ -1739,6 +1937,17 @@ public final class ConnectivityController extends RestrictingController implemen } } + @VisibleForTesting + @NonNull + CcConfig getCcConfig() { + return mCcConfig; + } + + @Override + public void dumpConstants(IndentingPrintWriter pw) { + mCcConfig.dump(pw); + } + @GuardedBy("mLock") @Override public void dumpControllerStateLocked(IndentingPrintWriter pw, diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java index 0e03ea1ebe7d..70f9a52f3299 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java @@ -247,6 +247,12 @@ public final class FlexibilityController extends StateController { mPrefetchLifeCycleStart.delete(userId); } + boolean isEnabled() { + synchronized (mLock) { + return mFlexibilityEnabled; + } + } + /** Checks if the flexibility constraint is actively satisfied for a given job. */ @GuardedBy("mLock") boolean isFlexibilitySatisfiedLocked(JobStatus js) { @@ -262,7 +268,8 @@ public final class FlexibilityController extends StateController { int getNumSatisfiedRequiredConstraintsLocked(JobStatus js) { return Integer.bitCount(mSatisfiedFlexibleConstraints) // Connectivity is job-specific, so must be handled separately. - + (js.getHasAccessToUnmetered() ? 1 : 0); + + (js.canApplyTransportAffinities() + && js.areTransportAffinitiesSatisfied() ? 1 : 0); } /** @@ -495,7 +502,7 @@ public final class FlexibilityController extends StateController { final int curPercent = getCurPercentOfLifecycleLocked(js, nowElapsed); int toDrop = 0; final int jsMaxFlexibleConstraints = NUM_SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS - + (js.getPreferUnmetered() ? 1 : 0); + + (js.canApplyTransportAffinities() ? 1 : 0); for (int i = 0; i < jsMaxFlexibleConstraints; i++) { if (curPercent >= mPercentToDropConstraints[i]) { toDrop++; @@ -661,7 +668,6 @@ public final class FlexibilityController extends StateController { } } - @VisibleForTesting class FcConfig { private boolean mShouldReevaluateConstraints = false; @@ -682,7 +688,7 @@ public final class FlexibilityController extends StateController { static final String KEY_RESCHEDULED_JOB_DEADLINE_MS = FC_CONFIG_PREFIX + "rescheduled_job_deadline_ms"; - private static final boolean DEFAULT_FLEXIBILITY_ENABLED = false; + static final boolean DEFAULT_FLEXIBILITY_ENABLED = false; @VisibleForTesting static final long DEFAULT_DEADLINE_PROXIMITY_LIMIT_MS = 15 * MINUTE_IN_MILLIS; @VisibleForTesting diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java index cb6cc2bd58aa..d6ada4cd7fdc 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java @@ -16,7 +16,6 @@ package com.android.server.job.controllers; -import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED; import static android.text.format.DateUtils.HOUR_IN_MILLIS; import static com.android.server.job.JobSchedulerService.ACTIVE_INDEX; @@ -163,9 +162,6 @@ public final class JobStatus { */ private int mNumDroppedFlexibleConstraints; - /** If the job is going to be passed an unmetered network. */ - private boolean mHasAccessToUnmetered; - /** If the effective bucket has been downgraded once due to being buggy. */ private boolean mIsDowngradedDueToBuggyApp; @@ -562,11 +558,10 @@ public final class JobStatus { /** The job's dynamic requirements have been satisfied. */ private boolean mReadyDynamicSatisfied; - /** - * The job prefers an unmetered network if it has the connectivity constraint but is - * okay with any meteredness. - */ - private final boolean mPreferUnmetered; + /** Whether to apply the optimization transport preference logic to this job. */ + private final boolean mCanApplyTransportAffinities; + /** True if the optimization transport preference is satisfied for this job. */ + private boolean mTransportAffinitiesSatisfied; /** The reason a job most recently went from ready to not ready. */ private int mReasonReadyToUnready = JobParameters.STOP_REASON_UNDEFINED; @@ -671,12 +666,12 @@ public final class JobStatus { } mHasExemptedMediaUrisOnly = exemptedMediaUrisOnly; - mPreferUnmetered = job.getRequiredNetwork() != null - && !job.getRequiredNetwork().hasCapability(NET_CAPABILITY_NOT_METERED); + mCanApplyTransportAffinities = job.getRequiredNetwork() != null + && job.getRequiredNetwork().getTransportTypes().length == 0; final boolean lacksSomeFlexibleConstraints = ((~requiredConstraints) & SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS) != 0 - || mPreferUnmetered; + || mCanApplyTransportAffinities; final boolean satisfiesMinWindowException = (latestRunTimeElapsedMillis - earliestRunTimeElapsedMillis) >= MIN_WINDOW_FOR_FLEXIBILITY_MS; @@ -688,7 +683,7 @@ public final class JobStatus { && (numFailures + numSystemStops) != 1 && lacksSomeFlexibleConstraints) { mNumRequiredFlexibleConstraints = - NUM_SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS + (mPreferUnmetered ? 1 : 0); + NUM_SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS + (mCanApplyTransportAffinities ? 1 : 0); requiredConstraints |= CONSTRAINT_FLEXIBLE; } else { mNumRequiredFlexibleConstraints = 0; @@ -1585,17 +1580,16 @@ public final class JobStatus { mOriginalLatestRunTimeElapsedMillis = latestRunTimeElapsed; } - /** Sets the jobs access to an unmetered network. */ - void setHasAccessToUnmetered(boolean access) { - mHasAccessToUnmetered = access; + boolean areTransportAffinitiesSatisfied() { + return mTransportAffinitiesSatisfied; } - boolean getHasAccessToUnmetered() { - return mHasAccessToUnmetered; + void setTransportAffinitiesSatisfied(boolean isSatisfied) { + mTransportAffinitiesSatisfied = isSatisfied; } - boolean getPreferUnmetered() { - return mPreferUnmetered; + boolean canApplyTransportAffinities() { + return mCanApplyTransportAffinities; } @JobParameters.StopReason diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java index 64e86f9ab1fd..10f8510c7c70 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java @@ -24,7 +24,9 @@ import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED; import static android.net.NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED; import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; +import static android.net.NetworkCapabilities.TRANSPORT_TEST; import static android.net.NetworkCapabilities.TRANSPORT_VPN; +import static android.net.NetworkCapabilities.TRANSPORT_WIFI; import static android.text.format.DateUtils.SECOND_IN_MILLIS; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer; @@ -38,6 +40,10 @@ import static com.android.server.job.Flags.FLAG_RELAX_PREFETCH_CONNECTIVITY_CONS import static com.android.server.job.JobSchedulerService.FREQUENT_INDEX; import static com.android.server.job.JobSchedulerService.RARE_INDEX; import static com.android.server.job.JobSchedulerService.RESTRICTED_INDEX; +import static com.android.server.job.controllers.ConnectivityController.CcConfig.KEY_AVOID_UNDEFINED_TRANSPORT_AFFINITY; +import static com.android.server.job.controllers.ConnectivityController.TRANSPORT_AFFINITY_AVOID; +import static com.android.server.job.controllers.ConnectivityController.TRANSPORT_AFFINITY_PREFER; +import static com.android.server.job.controllers.ConnectivityController.TRANSPORT_AFFINITY_UNDEFINED; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -64,16 +70,19 @@ import android.net.ConnectivityManager.NetworkCallback; import android.net.Network; import android.net.NetworkCapabilities; import android.net.NetworkPolicyManager; +import android.net.NetworkRequest; import android.os.Build; import android.os.Looper; import android.os.SystemClock; import android.platform.test.flag.junit.SetFlagsRule; +import android.provider.DeviceConfig; import android.telephony.CellSignalStrength; import android.telephony.SignalStrength; import android.telephony.TelephonyCallback; import android.telephony.TelephonyManager; import android.util.DataUnit; +import com.android.server.AppSchedulingModuleThread; import com.android.server.LocalServices; import com.android.server.job.JobSchedulerInternal; import com.android.server.job.JobSchedulerService; @@ -114,6 +123,7 @@ public class ConnectivityControllerTest { public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); private Constants mConstants; + private DeviceConfig.Properties.Builder mDeviceConfigPropertiesBuilder; private FlexibilityController mFlexibilityController; private static final int UID_RED = 10001; @@ -135,6 +145,9 @@ public class ConnectivityControllerTest { LocalServices.removeServiceForTest(JobSchedulerInternal.class); LocalServices.addService(JobSchedulerInternal.class, mock(JobSchedulerInternal.class)); + mDeviceConfigPropertiesBuilder = + new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER); + // Freeze the clocks at this moment in time JobSchedulerService.sSystemClock = Clock.fixed(Clock.systemUTC().instant(), ZoneOffset.UTC); @@ -164,7 +177,7 @@ public class ConnectivityControllerTest { when(mPackageManager.hasSystemFeature( PackageManager.FEATURE_AUTOMOTIVE)).thenReturn(false); mFlexibilityController = - new FlexibilityController(mService, mock(PrefetchController.class)); + spy(new FlexibilityController(mService, mock(PrefetchController.class))); } @Test @@ -954,6 +967,12 @@ public class ConnectivityControllerTest { @Test public void testUpdates() throws Exception { + ConnectivityController.sNetworkTransportAffinities.put( + NetworkCapabilities.TRANSPORT_CELLULAR, TRANSPORT_AFFINITY_AVOID); + ConnectivityController.sNetworkTransportAffinities.put( + NetworkCapabilities.TRANSPORT_WIFI, TRANSPORT_AFFINITY_PREFER); + ConnectivityController.sNetworkTransportAffinities.put( + NetworkCapabilities.TRANSPORT_TEST, TRANSPORT_AFFINITY_UNDEFINED); final ArgumentCaptor<NetworkCallback> callbackCaptor = ArgumentCaptor.forClass(NetworkCallback.class); doNothing().when(mConnManager).registerNetworkCallback(any(), callbackCaptor.capture()); @@ -966,16 +985,24 @@ public class ConnectivityControllerTest { doNothing().when(mConnManager).registerDefaultNetworkCallbackForUid( eq(UID_BLUE), blueCallbackCaptor.capture(), any()); + doReturn(true).when(mFlexibilityController).isEnabled(); + final ConnectivityController controller = new ConnectivityController(mService, mFlexibilityController); - final Network meteredNet = mock(Network.class); final NetworkCapabilities meteredCaps = createCapabilitiesBuilder().build(); final Network unmeteredNet = mock(Network.class); final NetworkCapabilities unmeteredCaps = createCapabilitiesBuilder() .addCapability(NET_CAPABILITY_NOT_METERED) .build(); + final NetworkCapabilities meteredWifiCaps = createCapabilitiesBuilder() + .addTransportType(TRANSPORT_WIFI) + .build(); + final NetworkCapabilities unmeteredCelullarCaps = createCapabilitiesBuilder() + .addCapability(NET_CAPABILITY_NOT_METERED) + .addTransportType(TRANSPORT_CELLULAR) + .build(); final JobStatus red = createJobStatus(createJob() .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1), 0) @@ -983,11 +1010,29 @@ public class ConnectivityControllerTest { final JobStatus blue = createJobStatus(createJob() .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1), 0) .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY), UID_BLUE); - assertFalse(red.getPreferUnmetered()); - assertTrue(blue.getPreferUnmetered()); + final JobStatus red2 = createJobStatus(createJob() + .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1), 0) + .setRequiredNetwork( + new NetworkRequest.Builder() + .addTransportType(TRANSPORT_CELLULAR) + .build()), + UID_RED); + final JobStatus blue2 = createJobStatus(createJob() + .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1), 0) + .setRequiredNetwork( + new NetworkRequest.Builder() + .addTransportType(TRANSPORT_WIFI) + .build()), + UID_BLUE); + assertTrue(red.canApplyTransportAffinities()); + assertTrue(blue.canApplyTransportAffinities()); + assertFalse(red2.canApplyTransportAffinities()); + assertFalse(blue2.canApplyTransportAffinities()); controller.maybeStartTrackingJobLocked(red, null); controller.maybeStartTrackingJobLocked(blue, null); + controller.maybeStartTrackingJobLocked(red2, null); + controller.maybeStartTrackingJobLocked(blue2, null); final NetworkCallback generalCallback = callbackCaptor.getValue(); final NetworkCallback redCallback = redCallbackCaptor.getValue(); final NetworkCallback blueCallback = blueCallbackCaptor.getValue(); @@ -998,9 +1043,13 @@ public class ConnectivityControllerTest { answerNetwork(generalCallback, blueCallback, null, null, null); assertFalse(red.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); - assertFalse(red.getHasAccessToUnmetered()); assertFalse(blue.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); - assertFalse(blue.getHasAccessToUnmetered()); + assertFalse(red2.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); + assertFalse(blue2.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); + assertFalse(red.areTransportAffinitiesSatisfied()); + assertFalse(blue.areTransportAffinitiesSatisfied()); + assertFalse(red2.areTransportAffinitiesSatisfied()); + assertFalse(blue2.areTransportAffinitiesSatisfied()); } // Metered network @@ -1011,12 +1060,26 @@ public class ConnectivityControllerTest { generalCallback.onCapabilitiesChanged(meteredNet, meteredCaps); assertFalse(red.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); - assertFalse(red.getHasAccessToUnmetered()); assertTrue(blue.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); - assertFalse(blue.getHasAccessToUnmetered()); + assertFalse(red2.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); + assertFalse(blue2.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); + // No transport is specified. Accept the network for transport affinity. + setDeviceConfigBoolean(controller, KEY_AVOID_UNDEFINED_TRANSPORT_AFFINITY, false); + controller.onConstantsUpdatedLocked(); + assertFalse(red.areTransportAffinitiesSatisfied()); + assertTrue(blue.areTransportAffinitiesSatisfied()); + assertFalse(red2.areTransportAffinitiesSatisfied()); + assertFalse(blue2.areTransportAffinitiesSatisfied()); + // No transport is specified. Avoid the network for transport affinity. + setDeviceConfigBoolean(controller, KEY_AVOID_UNDEFINED_TRANSPORT_AFFINITY, true); + controller.onConstantsUpdatedLocked(); + assertFalse(red.areTransportAffinitiesSatisfied()); + assertFalse(blue.areTransportAffinitiesSatisfied()); + assertFalse(red2.areTransportAffinitiesSatisfied()); + assertFalse(blue2.areTransportAffinitiesSatisfied()); } - // Unmetered network background + // Unmetered network background for general; metered network for apps { answerNetwork(generalCallback, redCallback, meteredNet, meteredNet, meteredCaps); answerNetwork(generalCallback, blueCallback, meteredNet, meteredNet, meteredCaps); @@ -1024,10 +1087,22 @@ public class ConnectivityControllerTest { generalCallback.onCapabilitiesChanged(unmeteredNet, unmeteredCaps); assertFalse(red.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); - assertFalse(red.getHasAccessToUnmetered()); - assertTrue(blue.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); - assertFalse(blue.getHasAccessToUnmetered()); + + // No transport is specified. Accept the network for transport affinity. + setDeviceConfigBoolean(controller, KEY_AVOID_UNDEFINED_TRANSPORT_AFFINITY, false); + controller.onConstantsUpdatedLocked(); + assertFalse(red.areTransportAffinitiesSatisfied()); + assertTrue(blue.areTransportAffinitiesSatisfied()); + assertFalse(red2.areTransportAffinitiesSatisfied()); + assertFalse(blue2.areTransportAffinitiesSatisfied()); + // No transport is specified. Avoid the network for transport affinity. + setDeviceConfigBoolean(controller, KEY_AVOID_UNDEFINED_TRANSPORT_AFFINITY, true); + controller.onConstantsUpdatedLocked(); + assertFalse(red.areTransportAffinitiesSatisfied()); + assertFalse(blue.areTransportAffinitiesSatisfied()); + assertFalse(red2.areTransportAffinitiesSatisfied()); + assertFalse(blue2.areTransportAffinitiesSatisfied()); } // Lost metered network @@ -1038,10 +1113,7 @@ public class ConnectivityControllerTest { generalCallback.onLost(meteredNet); assertTrue(red.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); - assertFalse(red.getHasAccessToUnmetered()); - assertTrue(blue.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); - assertTrue(blue.getHasAccessToUnmetered()); } // Specific UID was blocked @@ -1052,9 +1124,99 @@ public class ConnectivityControllerTest { generalCallback.onCapabilitiesChanged(unmeteredNet, unmeteredCaps); assertFalse(red.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); - assertFalse(red.getHasAccessToUnmetered()); assertTrue(blue.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); - assertTrue(blue.getHasAccessToUnmetered()); + } + + // Metered wifi + { + answerNetwork(generalCallback, redCallback, null, meteredNet, meteredWifiCaps); + answerNetwork(generalCallback, blueCallback, unmeteredNet, meteredNet, meteredWifiCaps); + + generalCallback.onCapabilitiesChanged(meteredNet, meteredWifiCaps); + + assertFalse(red.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); + assertTrue(blue.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); + assertFalse(red2.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); + assertTrue(blue2.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); + + // Wifi is preferred. + setDeviceConfigBoolean(controller, KEY_AVOID_UNDEFINED_TRANSPORT_AFFINITY, false); + controller.onConstantsUpdatedLocked(); + assertFalse(red.areTransportAffinitiesSatisfied()); + assertTrue(blue.areTransportAffinitiesSatisfied()); + assertFalse(red2.areTransportAffinitiesSatisfied()); + assertTrue(blue2.areTransportAffinitiesSatisfied()); + // Wifi is preferred. + setDeviceConfigBoolean(controller, KEY_AVOID_UNDEFINED_TRANSPORT_AFFINITY, true); + controller.onConstantsUpdatedLocked(); + assertFalse(red.areTransportAffinitiesSatisfied()); + assertTrue(blue.areTransportAffinitiesSatisfied()); + assertFalse(red2.areTransportAffinitiesSatisfied()); + assertTrue(blue2.areTransportAffinitiesSatisfied()); + } + + // Unmetered cellular + { + answerNetwork(generalCallback, redCallback, meteredNet, + unmeteredNet, unmeteredCelullarCaps); + answerNetwork(generalCallback, blueCallback, meteredNet, + unmeteredNet, unmeteredCelullarCaps); + + generalCallback.onCapabilitiesChanged(unmeteredNet, unmeteredCelullarCaps); + + assertTrue(red.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); + assertTrue(blue.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); + assertTrue(red2.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); + assertFalse(blue2.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); + + // Cellular is avoided. + setDeviceConfigBoolean(controller, KEY_AVOID_UNDEFINED_TRANSPORT_AFFINITY, false); + controller.onConstantsUpdatedLocked(); + assertFalse(red.areTransportAffinitiesSatisfied()); + assertFalse(blue.areTransportAffinitiesSatisfied()); + assertFalse(red2.areTransportAffinitiesSatisfied()); + assertFalse(blue2.areTransportAffinitiesSatisfied()); + // Cellular is avoided. + setDeviceConfigBoolean(controller, KEY_AVOID_UNDEFINED_TRANSPORT_AFFINITY, true); + controller.onConstantsUpdatedLocked(); + assertFalse(red.areTransportAffinitiesSatisfied()); + assertFalse(blue.areTransportAffinitiesSatisfied()); + assertFalse(red2.areTransportAffinitiesSatisfied()); + assertFalse(blue2.areTransportAffinitiesSatisfied()); + } + + // Undefined affinity + final NetworkCapabilities unmeteredTestCaps = createCapabilitiesBuilder() + .addCapability(NET_CAPABILITY_NOT_METERED) + .addTransportType(TRANSPORT_TEST) + .build(); + { + answerNetwork(generalCallback, redCallback, unmeteredNet, + unmeteredNet, unmeteredTestCaps); + answerNetwork(generalCallback, blueCallback, unmeteredNet, + unmeteredNet, unmeteredTestCaps); + + generalCallback.onCapabilitiesChanged(unmeteredNet, unmeteredTestCaps); + + assertTrue(red.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); + assertTrue(blue.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); + assertFalse(red2.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); + assertFalse(blue2.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); + + // Undefined is preferred. + setDeviceConfigBoolean(controller, KEY_AVOID_UNDEFINED_TRANSPORT_AFFINITY, false); + controller.onConstantsUpdatedLocked(); + assertTrue(red.areTransportAffinitiesSatisfied()); + assertTrue(blue.areTransportAffinitiesSatisfied()); + assertFalse(red2.areTransportAffinitiesSatisfied()); + assertFalse(blue2.areTransportAffinitiesSatisfied()); + // Undefined is avoided. + setDeviceConfigBoolean(controller, KEY_AVOID_UNDEFINED_TRANSPORT_AFFINITY, true); + controller.onConstantsUpdatedLocked(); + assertFalse(red.areTransportAffinitiesSatisfied()); + assertFalse(blue.areTransportAffinitiesSatisfied()); + assertFalse(red2.areTransportAffinitiesSatisfied()); + assertFalse(blue2.areTransportAffinitiesSatisfied()); } } @@ -1462,4 +1624,24 @@ public class ConnectivityControllerTest { return new JobStatus(job.build(), uid, null, -1, 0, null, null, earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis, 0, 0, 0, null, 0, 0); } + + private void setDeviceConfigBoolean(ConnectivityController connectivityController, + String key, boolean val) { + mDeviceConfigPropertiesBuilder.setBoolean(key, val); + synchronized (connectivityController.mLock) { + connectivityController.prepareForUpdatedConstantsLocked(); + mFlexibilityController.prepareForUpdatedConstantsLocked(); + connectivityController.getCcConfig() + .processConstantLocked(mDeviceConfigPropertiesBuilder.build(), key); + mFlexibilityController.getFcConfig() + .processConstantLocked(mDeviceConfigPropertiesBuilder.build(), key); + connectivityController.onConstantsUpdatedLocked(); + mFlexibilityController.onConstantsUpdatedLocked(); + } + waitForNonDelayedMessagesProcessed(); + } + + private void waitForNonDelayedMessagesProcessed() { + AppSchedulingModuleThread.getHandler().runWithScissors(() -> {}, 15_000); + } } diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java index bb9dcf1c85cc..ee68b6d0e546 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java @@ -692,15 +692,15 @@ public class FlexibilityControllerTest { } @Test - public void testConnectionToUnMeteredNetwork() { + public void testTransportAffinity() { JobInfo.Builder jb = createJob(0).setRequiredNetworkType(NETWORK_TYPE_ANY); JobStatus js = createJobStatus("testTopAppBypass", jb); synchronized (mFlexibilityController.mLock) { - js.setHasAccessToUnmetered(false); + js.setTransportAffinitiesSatisfied(false); assertEquals(0, mFlexibilityController.getNumSatisfiedRequiredConstraintsLocked(js)); - js.setHasAccessToUnmetered(true); + js.setTransportAffinitiesSatisfied(true); assertEquals(1, mFlexibilityController.getNumSatisfiedRequiredConstraintsLocked(js)); - js.setHasAccessToUnmetered(false); + js.setTransportAffinitiesSatisfied(false); assertEquals(0, mFlexibilityController.getNumSatisfiedRequiredConstraintsLocked(js)); } } @@ -937,10 +937,10 @@ public class FlexibilityControllerTest { ArraySet<JobStatus> jobs = trackedJobs.get(i); for (int j = 0; j < jobs.size(); j++) { JobStatus js = jobs.valueAt(j); - final int isUnMetered = js.getPreferUnmetered() - && js.getHasAccessToUnmetered() ? 1 : 0; + final int transportAffinitySatisfied = js.canApplyTransportAffinities() + && js.areTransportAffinitiesSatisfied() ? 1 : 0; assertEquals(js.getNumRequiredFlexibleConstraints() - <= numSatisfiedConstraints + isUnMetered, + <= numSatisfiedConstraints + transportAffinitySatisfied, js.isConstraintSatisfied(CONSTRAINT_FLEXIBLE)); } } |