diff options
8 files changed, 171 insertions, 18 deletions
diff --git a/packages/Connectivity/framework/api/system-current.txt b/packages/Connectivity/framework/api/system-current.txt index daece8e05438..4f906526d2ea 100644 --- a/packages/Connectivity/framework/api/system-current.txt +++ b/packages/Connectivity/framework/api/system-current.txt @@ -336,14 +336,18 @@ package android.net { public final class NetworkScore implements android.os.Parcelable { method public int describeContents(); + method public int getKeepConnectedReason(); method public int getLegacyInt(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.net.NetworkScore> CREATOR; + field public static final int KEEP_CONNECTED_FOR_HANDOVER = 1; // 0x1 + field public static final int KEEP_CONNECTED_NONE = 0; // 0x0 } public static final class NetworkScore.Builder { ctor public NetworkScore.Builder(); method @NonNull public android.net.NetworkScore build(); + method @NonNull public android.net.NetworkScore.Builder setKeepConnectedReason(int); method @NonNull public android.net.NetworkScore.Builder setLegacyInt(int); } diff --git a/packages/Connectivity/framework/src/android/net/NetworkScore.java b/packages/Connectivity/framework/src/android/net/NetworkScore.java index 65849930fa4a..9786b09e3508 100644 --- a/packages/Connectivity/framework/src/android/net/NetworkScore.java +++ b/packages/Connectivity/framework/src/android/net/NetworkScore.java @@ -16,6 +16,7 @@ package android.net; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.SystemApi; import android.os.Parcel; @@ -23,6 +24,9 @@ import android.os.Parcelable; import com.android.internal.annotations.VisibleForTesting; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * Object representing the quality of a network as perceived by the user. * @@ -36,6 +40,17 @@ public final class NetworkScore implements Parcelable { // a migration. private final int mLegacyInt; + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(value = { + KEEP_CONNECTED_NONE, + KEEP_CONNECTED_FOR_HANDOVER + }) + public @interface KeepConnectedReason { } + + public static final int KEEP_CONNECTED_NONE = 0; + public static final int KEEP_CONNECTED_FOR_HANDOVER = 1; + // Agent-managed policies // TODO : add them here, starting from 1 /** @hide */ @@ -46,15 +61,20 @@ public final class NetworkScore implements Parcelable { // Bitmask of all the policies applied to this score. private final long mPolicies; + private final int mKeepConnectedReason; + /** @hide */ - NetworkScore(final int legacyInt, final long policies) { + NetworkScore(final int legacyInt, final long policies, + @KeepConnectedReason final int keepConnectedReason) { mLegacyInt = legacyInt; mPolicies = policies; + mKeepConnectedReason = keepConnectedReason; } private NetworkScore(@NonNull final Parcel in) { mLegacyInt = in.readInt(); mPolicies = in.readLong(); + mKeepConnectedReason = in.readInt(); } public int getLegacyInt() { @@ -62,6 +82,13 @@ public final class NetworkScore implements Parcelable { } /** + * Returns the keep-connected reason, or KEEP_CONNECTED_NONE. + */ + public int getKeepConnectedReason() { + return mKeepConnectedReason; + } + + /** * @return whether this score has a particular policy. * * @hide @@ -80,6 +107,7 @@ public final class NetworkScore implements Parcelable { public void writeToParcel(@NonNull final Parcel dest, final int flags) { dest.writeInt(mLegacyInt); dest.writeLong(mPolicies); + dest.writeInt(mKeepConnectedReason); } @Override @@ -108,6 +136,7 @@ public final class NetworkScore implements Parcelable { private static final long POLICY_NONE = 0L; private static final int INVALID_LEGACY_INT = Integer.MIN_VALUE; private int mLegacyInt = INVALID_LEGACY_INT; + private int mKeepConnectedReason = KEEP_CONNECTED_NONE; /** * Sets the legacy int for this score. @@ -124,12 +153,23 @@ public final class NetworkScore implements Parcelable { } /** + * Set the keep-connected reason. + * + * This can be reset by calling it again with {@link KEEP_CONNECTED_NONE}. + */ + @NonNull + public Builder setKeepConnectedReason(@KeepConnectedReason final int reason) { + mKeepConnectedReason = reason; + return this; + } + + /** * Builds this NetworkScore. * @return The built NetworkScore object. */ @NonNull public NetworkScore build() { - return new NetworkScore(mLegacyInt, POLICY_NONE); + return new NetworkScore(mLegacyInt, POLICY_NONE, mKeepConnectedReason); } } } diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 449a29b82cdd..9be5f18c34a3 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -3846,6 +3846,12 @@ public class ConnectivityService extends IConnectivityManager.Stub // then it should be lingered. private boolean unneeded(NetworkAgentInfo nai, UnneededFor reason) { ensureRunningOnConnectivityServiceThread(); + + if (!nai.everConnected || nai.isVPN() || nai.isInactive() + || nai.getScore().getKeepConnectedReason() != NetworkScore.KEEP_CONNECTED_NONE) { + return false; + } + final int numRequests; switch (reason) { case TEARDOWN: @@ -3859,9 +3865,8 @@ public class ConnectivityService extends IConnectivityManager.Stub return true; } - if (!nai.everConnected || nai.isVPN() || nai.isInactive() || numRequests > 0) { - return false; - } + if (numRequests > 0) return false; + for (NetworkRequestInfo nri : mNetworkRequests.values()) { if (reason == UnneededFor.LINGER && !nri.isMultilayerRequest() diff --git a/services/core/java/com/android/server/connectivity/FullScore.java b/services/core/java/com/android/server/connectivity/FullScore.java index 9326d692f6e4..375d005a0bcd 100644 --- a/services/core/java/com/android/server/connectivity/FullScore.java +++ b/services/core/java/com/android/server/connectivity/FullScore.java @@ -19,12 +19,14 @@ package com.android.server.connectivity; import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED; import static android.net.NetworkCapabilities.TRANSPORT_VPN; +import static android.net.NetworkScore.KEEP_CONNECTED_NONE; import android.annotation.IntDef; import android.annotation.NonNull; import android.net.NetworkAgentConfig; import android.net.NetworkCapabilities; import android.net.NetworkScore; +import android.net.NetworkScore.KeepConnectedReason; import com.android.internal.annotations.VisibleForTesting; @@ -95,9 +97,13 @@ public class FullScore { // Bitmask of all the policies applied to this score. private final long mPolicies; - FullScore(final int legacyInt, final long policies) { + private final int mKeepConnectedReason; + + FullScore(final int legacyInt, final long policies, + @KeepConnectedReason final int keepConnectedReason) { mLegacyInt = legacyInt; mPolicies = policies; + mKeepConnectedReason = keepConnectedReason; } /** @@ -110,14 +116,15 @@ public class FullScore { */ public static FullScore fromNetworkScore(@NonNull final NetworkScore score, @NonNull final NetworkCapabilities caps, @NonNull final NetworkAgentConfig config) { - return withPolicies(score.getLegacyInt(), caps.hasCapability(NET_CAPABILITY_VALIDATED), + return withPolicies(score.getLegacyInt(), score.getKeepConnectedReason(), + caps.hasCapability(NET_CAPABILITY_VALIDATED), caps.hasTransport(TRANSPORT_VPN), config.explicitlySelected, config.acceptUnvalidated); } /** - * Given a score supplied by the NetworkAgent, produce a prospective score for an offer. + * Given a score supplied by a NetworkProvider, produce a prospective score for an offer. * * NetworkOffers have score filters that are compared to the scores of actual networks * to see if they could possibly beat the current satisfier. Some things the agent can't @@ -139,8 +146,8 @@ public class FullScore { final boolean everUserSelected = false; // Don't assume the user will accept unvalidated connectivity. final boolean acceptUnvalidated = false; - return withPolicies(score.getLegacyInt(), mayValidate, vpn, everUserSelected, - acceptUnvalidated); + return withPolicies(score.getLegacyInt(), KEEP_CONNECTED_NONE, + mayValidate, vpn, everUserSelected, acceptUnvalidated); } /** @@ -152,13 +159,15 @@ public class FullScore { */ public FullScore mixInScore(@NonNull final NetworkCapabilities caps, @NonNull final NetworkAgentConfig config) { - return withPolicies(mLegacyInt, caps.hasCapability(NET_CAPABILITY_VALIDATED), + return withPolicies(mLegacyInt, mKeepConnectedReason, + caps.hasCapability(NET_CAPABILITY_VALIDATED), caps.hasTransport(TRANSPORT_VPN), config.explicitlySelected, config.acceptUnvalidated); } private static FullScore withPolicies(@NonNull final int legacyInt, + @KeepConnectedReason final int keepConnectedReason, final boolean isValidated, final boolean isVpn, final boolean everUserSelected, @@ -167,7 +176,8 @@ public class FullScore { (isValidated ? 1L << POLICY_IS_VALIDATED : 0) | (isVpn ? 1L << POLICY_IS_VPN : 0) | (everUserSelected ? 1L << POLICY_EVER_USER_SELECTED : 0) - | (acceptUnvalidated ? 1L << POLICY_ACCEPT_UNVALIDATED : 0)); + | (acceptUnvalidated ? 1L << POLICY_ACCEPT_UNVALIDATED : 0), + keepConnectedReason); } /** @@ -219,13 +229,21 @@ public class FullScore { return 0 != (mPolicies & (1L << policy)); } + /** + * Returns the keep-connected reason, or KEEP_CONNECTED_NONE. + */ + public int getKeepConnectedReason() { + return mKeepConnectedReason; + } + // Example output : // Score(50 ; Policies : EVER_USER_SELECTED&IS_VALIDATED) @Override public String toString() { final StringJoiner sj = new StringJoiner( "&", // delimiter - "Score(" + mLegacyInt + " ; Policies : ", // prefix + "Score(" + mLegacyInt + " ; KeepConnected : " + mKeepConnectedReason + + " ; Policies : ", // prefix ")"); // suffix for (int i = NetworkScore.MIN_AGENT_MANAGED_POLICY; i <= NetworkScore.MAX_AGENT_MANAGED_POLICY; ++i) { diff --git a/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java b/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java index e2d43cbb8efd..6245e8542b04 100644 --- a/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java +++ b/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java @@ -41,6 +41,7 @@ import android.net.NetworkAgent; import android.net.NetworkAgentConfig; import android.net.NetworkCapabilities; import android.net.NetworkProvider; +import android.net.NetworkScore; import android.net.NetworkSpecifier; import android.net.QosFilter; import android.net.SocketKeepalive; @@ -199,6 +200,11 @@ public class NetworkAgentWrapper implements TestableNetworkCallback.HasNetwork { } } + public void setScore(@NonNull final NetworkScore score) { + mScore = score.getLegacyInt(); + mNetworkAgent.sendNetworkScore(score); + } + public void adjustScore(int change) { mScore += change; mNetworkAgent.sendNetworkScore(mScore); diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java index 0f51b29c9a2f..c7e6a056628d 100644 --- a/tests/net/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java @@ -94,6 +94,7 @@ import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET; import static android.net.NetworkCapabilities.TRANSPORT_VPN; import static android.net.NetworkCapabilities.TRANSPORT_WIFI; import static android.net.NetworkCapabilities.TRANSPORT_WIFI_AWARE; +import static android.net.NetworkScore.KEEP_CONNECTED_FOR_HANDOVER; import static android.net.OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID; import static android.net.OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK; import static android.net.OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY; @@ -9939,6 +9940,83 @@ public class ConnectivityServiceTest { } } + @Test + public void testKeepConnected() throws Exception { + setAlwaysOnNetworks(false); + registerDefaultNetworkCallbacks(); + final TestNetworkCallback allNetworksCb = new TestNetworkCallback(); + final NetworkRequest allNetworksRequest = new NetworkRequest.Builder().clearCapabilities() + .build(); + mCm.registerNetworkCallback(allNetworksRequest, allNetworksCb); + + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + mCellNetworkAgent.connect(true /* validated */); + + mDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + allNetworksCb.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + mWiFiNetworkAgent.connect(true /* validated */); + + mDefaultNetworkCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent); + // While the default callback doesn't see the network before it's validated, the listen + // sees the network come up and validate later + allNetworksCb.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); + allNetworksCb.expectCallback(CallbackEntry.LOSING, mCellNetworkAgent); + allNetworksCb.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent); + allNetworksCb.expectCallback(CallbackEntry.LOST, mCellNetworkAgent, + TEST_LINGER_DELAY_MS * 2); + + // The cell network has disconnected (see LOST above) because it was outscored and + // had no requests (see setAlwaysOnNetworks(false) above) + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + final NetworkScore score = new NetworkScore.Builder().setLegacyInt(30).build(); + mCellNetworkAgent.setScore(score); + mCellNetworkAgent.connect(false /* validated */); + + // The cell network gets torn down right away. + allNetworksCb.expectAvailableCallbacksUnvalidated(mCellNetworkAgent); + allNetworksCb.expectCallback(CallbackEntry.LOST, mCellNetworkAgent, + TEST_NASCENT_DELAY_MS * 2); + allNetworksCb.assertNoCallback(); + + // Now create a cell network with KEEP_CONNECTED_FOR_HANDOVER and make sure it's + // not disconnected immediately when outscored. + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + final NetworkScore scoreKeepup = new NetworkScore.Builder().setLegacyInt(30) + .setKeepConnectedReason(KEEP_CONNECTED_FOR_HANDOVER).build(); + mCellNetworkAgent.setScore(scoreKeepup); + mCellNetworkAgent.connect(true /* validated */); + + allNetworksCb.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + mDefaultNetworkCallback.assertNoCallback(); + + mWiFiNetworkAgent.disconnect(); + + allNetworksCb.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); + mDefaultNetworkCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); + mDefaultNetworkCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); + + // Reconnect a WiFi network and make sure the cell network is still not torn down. + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + mWiFiNetworkAgent.connect(true /* validated */); + + allNetworksCb.expectAvailableThenValidatedCallbacks(mWiFiNetworkAgent); + mDefaultNetworkCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent); + + // Now remove the reason to keep connected and make sure the network lingers and is + // torn down. + mCellNetworkAgent.setScore(new NetworkScore.Builder().setLegacyInt(30).build()); + allNetworksCb.expectCallback(CallbackEntry.LOSING, mCellNetworkAgent, + TEST_NASCENT_DELAY_MS * 2); + allNetworksCb.expectCallback(CallbackEntry.LOST, mCellNetworkAgent, + TEST_LINGER_DELAY_MS * 2); + mDefaultNetworkCallback.assertNoCallback(); + + mCm.unregisterNetworkCallback(allNetworksCb); + // mDefaultNetworkCallback will be unregistered by tearDown() + } + private class QosCallbackMockHelper { @NonNull public final QosFilter mFilter; @NonNull public final IQosCallback mCallback; diff --git a/tests/net/java/com/android/server/connectivity/FullScoreTest.kt b/tests/net/java/com/android/server/connectivity/FullScoreTest.kt index eb3b4df1a282..2864bc7e794a 100644 --- a/tests/net/java/com/android/server/connectivity/FullScoreTest.kt +++ b/tests/net/java/com/android/server/connectivity/FullScoreTest.kt @@ -18,6 +18,7 @@ package com.android.server.connectivity import android.net.NetworkAgentConfig import android.net.NetworkCapabilities +import android.net.NetworkScore.KEEP_CONNECTED_NONE import android.text.TextUtils import android.util.ArraySet import androidx.test.filters.SmallTest @@ -60,11 +61,11 @@ class FullScoreTest { @Test fun testGetLegacyInt() { - val ns = FullScore(50, 0L /* policy */) + val ns = FullScore(50, 0L /* policy */, KEEP_CONNECTED_NONE) assertEquals(10, ns.legacyInt) // -40 penalty for not being validated assertEquals(50, ns.legacyIntAsValidated) - val vpnNs = FullScore(101, 0L /* policy */).withPolicies(vpn = true) + val vpnNs = FullScore(101, 0L /* policy */, KEEP_CONNECTED_NONE).withPolicies(vpn = true) assertEquals(101, vpnNs.legacyInt) // VPNs are not subject to unvalidation penalty assertEquals(101, vpnNs.legacyIntAsValidated) assertEquals(101, vpnNs.withPolicies(validated = true).legacyInt) @@ -83,7 +84,7 @@ class FullScoreTest { @Test fun testToString() { - val string = FullScore(10, 0L /* policy */) + val string = FullScore(10, 0L /* policy */, KEEP_CONNECTED_NONE) .withPolicies(vpn = true, acceptUnvalidated = true).toString() assertTrue(string.contains("Score(10"), string) assertTrue(string.contains("ACCEPT_UNVALIDATED"), string) @@ -107,7 +108,7 @@ class FullScoreTest { @Test fun testHasPolicy() { - val ns = FullScore(50, 0L /* policy */) + val ns = FullScore(50, 0L /* policy */, KEEP_CONNECTED_NONE) assertFalse(ns.hasPolicy(POLICY_IS_VALIDATED)) assertFalse(ns.hasPolicy(POLICY_IS_VPN)) assertFalse(ns.hasPolicy(POLICY_EVER_USER_SELECTED)) diff --git a/tests/net/java/com/android/server/connectivity/NetworkOfferTest.kt b/tests/net/java/com/android/server/connectivity/NetworkOfferTest.kt index 119934105cc9..409f8c309935 100644 --- a/tests/net/java/com/android/server/connectivity/NetworkOfferTest.kt +++ b/tests/net/java/com/android/server/connectivity/NetworkOfferTest.kt @@ -19,6 +19,7 @@ package com.android.server.connectivity import android.net.INetworkOfferCallback import android.net.NetworkCapabilities import android.net.NetworkRequest +import android.net.NetworkScore.KEEP_CONNECTED_NONE import androidx.test.filters.SmallTest import androidx.test.runner.AndroidJUnit4 import org.junit.Test @@ -38,7 +39,7 @@ class NetworkOfferTest { @Test fun testOfferNeededUnneeded() { - val score = FullScore(50, POLICY_NONE) + val score = FullScore(50, POLICY_NONE, KEEP_CONNECTED_NONE) val offer = NetworkOffer(score, NetworkCapabilities.Builder().build(), mockCallback, 1 /* providerId */) val request1 = mock(NetworkRequest::class.java) |