[NS06] Implement the don't-reap mechanism

This exposes a mechanism for network providers to tell
the network stack that a given network must be kept up
for some specific reason. This is meant to be easier
for them than to have to file a request, in particular
because there is no guaranteed way to make sure the
request will be best matched by any given network.

Test: new test for this
Bug: 167544279
Merged-In: I3c2563d4ae4e3715d0c6270344ba8f7ef067872f
Merged-In: I238a3ee5ee9262477a23b897e4141769dd1505d1
Change-Id: I238a3ee5ee9262477a23b897e4141769dd1505d1
  (cherry-picked from ag/13929760)
This commit is contained in:
Chalard Jean
2021-03-08 22:29:27 +09:00
committed by Junyu Lai
parent 5c3bb5b1ce
commit 947acd4275
8 changed files with 171 additions and 18 deletions

View File

@@ -341,14 +341,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);
}

View File

@@ -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,21 +61,33 @@ 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() {
return mLegacyInt;
}
/**
* Returns the keep-connected reason, or KEEP_CONNECTED_NONE.
*/
public int getKeepConnectedReason() {
return mKeepConnectedReason;
}
/**
* @return whether this score has a particular policy.
*
@@ -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.
@@ -123,13 +152,24 @@ public final class NetworkScore implements Parcelable {
return this;
}
/**
* 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);
}
}
}

View File

@@ -3974,6 +3974,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:
@@ -3987,9 +3993,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()

View File

@@ -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) {

View File

@@ -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);

View File

@@ -104,6 +104,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;
@@ -10226,6 +10227,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;

View File

@@ -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))

View File

@@ -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)