Merge "Add a mode for cell radios unable to time share"

This commit is contained in:
Chalard Jean
2021-09-18 12:39:24 +00:00
committed by Gerrit Code Review
6 changed files with 280 additions and 12 deletions

View File

@@ -1055,6 +1055,14 @@ public final class NetworkCapabilities implements Parcelable {
return isValidTransport(transportType) && ((mTransportTypes & (1 << transportType)) != 0);
}
/**
* Returns true iff this NetworkCapabilities has the specified transport and no other.
* @hide
*/
public boolean hasSingleTransport(@Transport int transportType) {
return mTransportTypes == (1 << transportType);
}
private void combineTransportTypes(NetworkCapabilities nc) {
this.mTransportTypes |= nc.mTransportTypes;
}

View File

@@ -125,4 +125,15 @@
details on what is happening. -->
<bool name="config_partialConnectivityNotifiedAsNoInternet">false</bool>
<!-- Whether the cell radio of the device is capable of timesharing.
Whether the cell radio is capable of timesharing between two different networks
even for a few seconds. When this is false, the networking stack will ask telephony
networks to disconnect immediately, instead of lingering, when outscored by some
other telephony network (typically on another subscription). This deprives apps
of a chance to gracefully migrate to the new network and degrades the experience
for apps, so it should only be set to false when timesharing on the cell radio has
extreme adverse effects on performance of the new network.
-->
<bool translatable="false" name="config_cellular_radio_timesharing_capable">true</bool>
</resources>

View File

@@ -36,6 +36,7 @@
<item type="bool" name="config_partialConnectivityNotifiedAsNoInternet"/>
<item type="drawable" name="stat_notify_wifi_in_range"/>
<item type="drawable" name="stat_notify_rssi_in_range"/>
<item type="bool" name="config_cellular_radio_timesharing_capable" />
</policy>
</overlayable>
</resources>

View File

@@ -80,6 +80,7 @@ import static android.net.NetworkCapabilities.TRANSPORT_TEST;
import static android.net.NetworkCapabilities.TRANSPORT_VPN;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
import static android.net.NetworkRequest.Type.LISTEN_FOR_BEST;
import static android.net.NetworkScore.POLICY_TRANSPORT_PRIMARY;
import static android.net.OemNetworkPreferences.OEM_NETWORK_PREFERENCE_TEST;
import static android.net.OemNetworkPreferences.OEM_NETWORK_PREFERENCE_TEST_ONLY;
import static android.net.shared.NetworkMonitorUtils.isPrivateDnsValidationRequired;
@@ -335,6 +336,9 @@ public class ConnectivityService extends IConnectivityManager.Stub
protected int mLingerDelayMs; // Can't be final, or test subclass constructors can't change it.
@VisibleForTesting
protected int mNascentDelayMs;
// True if the cell radio of the device is capable of time-sharing.
@VisibleForTesting
protected boolean mCellularRadioTimesharingCapable = true;
// How long to delay to removal of a pending intent based request.
// See ConnectivitySettingsManager.CONNECTIVITY_RELEASE_PENDING_INTENT_DELAY_MS
@@ -1375,6 +1379,12 @@ public class ConnectivityService extends IConnectivityManager.Stub
NetworkCapabilities.NET_CAPABILITY_VEHICLE_INTERNAL,
NetworkRequest.Type.BACKGROUND_REQUEST);
mLingerDelayMs = mSystemProperties.getInt(LINGER_DELAY_PROPERTY, DEFAULT_LINGER_DELAY_MS);
// TODO: Consider making the timer customizable.
mNascentDelayMs = DEFAULT_NASCENT_DELAY_MS;
mCellularRadioTimesharingCapable =
mResources.get().getBoolean(R.bool.config_cellular_radio_timesharing_capable);
mHandlerThread = mDeps.makeHandlerThread();
mHandlerThread.start();
mHandler = new InternalHandler(mHandlerThread.getLooper());
@@ -1385,10 +1395,6 @@ public class ConnectivityService extends IConnectivityManager.Stub
mReleasePendingIntentDelayMs = Settings.Secure.getInt(context.getContentResolver(),
ConnectivitySettingsManager.CONNECTIVITY_RELEASE_PENDING_INTENT_DELAY_MS, 5_000);
mLingerDelayMs = mSystemProperties.getInt(LINGER_DELAY_PROPERTY, DEFAULT_LINGER_DELAY_MS);
// TODO: Consider making the timer customizable.
mNascentDelayMs = DEFAULT_NASCENT_DELAY_MS;
mStatsManager = mContext.getSystemService(NetworkStatsManager.class);
mPolicyManager = mContext.getSystemService(NetworkPolicyManager.class);
mDnsResolver = Objects.requireNonNull(dnsresolver, "missing IDnsResolver");
@@ -7824,6 +7830,30 @@ public class ConnectivityService extends IConnectivityManager.Stub
bundle.putParcelable(t.getClass().getSimpleName(), t);
}
/**
* Returns whether reassigning a request from an NAI to another can be done gracefully.
*
* When a request should be assigned to a new network, it is normally lingered to give
* time for apps to gracefully migrate their connections. When both networks are on the same
* radio, but that radio can't do time-sharing efficiently, this may end up being
* counter-productive because any traffic on the old network may drastically reduce the
* performance of the new network.
* The stack supports a configuration to let modem vendors state that their radio can't
* do time-sharing efficiently. If this configuration is set, the stack assumes moving
* from one cell network to another can't be done gracefully.
*
* @param oldNai the old network serving the request
* @param newNai the new network serving the request
* @return whether the switch can be graceful
*/
private boolean canSupportGracefulNetworkSwitch(@NonNull final NetworkAgentInfo oldSatisfier,
@NonNull final NetworkAgentInfo newSatisfier) {
if (mCellularRadioTimesharingCapable) return true;
return !oldSatisfier.networkCapabilities.hasSingleTransport(TRANSPORT_CELLULAR)
|| !newSatisfier.networkCapabilities.hasSingleTransport(TRANSPORT_CELLULAR)
|| !newSatisfier.getScore().hasPolicy(POLICY_TRANSPORT_PRIMARY);
}
private void teardownUnneededNetwork(NetworkAgentInfo nai) {
if (nai.numRequestNetworkRequests() != 0) {
for (int i = 0; i < nai.numNetworkRequests(); i++) {
@@ -8084,7 +8114,13 @@ public class ConnectivityService extends IConnectivityManager.Stub
log(" accepting network in place of " + previousSatisfier.toShortString());
}
previousSatisfier.removeRequest(previousRequest.requestId);
if (canSupportGracefulNetworkSwitch(previousSatisfier, newSatisfier)) {
// If this network switch can't be supported gracefully, the request is not
// lingered. This allows letting go of the network sooner to reclaim some
// performance on the new network, since the radio can't do both at the same
// time while preserving good performance.
previousSatisfier.lingerRequest(previousRequest.requestId, now);
}
} else {
if (VDBG || DDBG) log(" accepting network in place of null");
}

View File

@@ -27,6 +27,7 @@ import static android.net.NetworkCapabilities.TRANSPORT_WIFI_AWARE;
import static com.android.server.ConnectivityServiceTestUtils.transportToLegacyType;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
import static org.junit.Assert.assertEquals;
@@ -310,6 +311,10 @@ public class NetworkAgentWrapper implements TestableNetworkCallback.HasNetwork {
assertTrue(mDisconnected.block(timeoutMs));
}
public void assertNotDisconnected(long timeoutMs) {
assertFalse(mDisconnected.block(timeoutMs));
}
public void sendLinkProperties(LinkProperties lp) {
mNetworkAgent.sendLinkProperties(lp);
}

View File

@@ -1792,6 +1792,8 @@ public class ConnectivityServiceTest {
doReturn(R.integer.config_networkAvoidBadWifi).when(mResources)
.getIdentifier(eq("config_networkAvoidBadWifi"), eq("integer"), any());
doReturn(1).when(mResources).getInteger(R.integer.config_networkAvoidBadWifi);
doReturn(true).when(mResources)
.getBoolean(R.bool.config_cellular_radio_timesharing_capable);
final ConnectivityResources connRes = mock(ConnectivityResources.class);
doReturn(mResources).when(connRes).get();
@@ -2273,8 +2275,22 @@ public class ConnectivityServiceTest {
// After waitForIdle(), the message was processed and the service didn't crash.
}
// TODO : migrate to @Parameterized
@Test
public void testValidatedCellularOutscoresUnvalidatedWiFi() throws Exception {
public void testValidatedCellularOutscoresUnvalidatedWiFi_CanTimeShare() throws Exception {
// The behavior of this test should be the same whether the radio can time share or not.
doTestValidatedCellularOutscoresUnvalidatedWiFi(true);
}
// TODO : migrate to @Parameterized
@Test
public void testValidatedCellularOutscoresUnvalidatedWiFi_CannotTimeShare() throws Exception {
doTestValidatedCellularOutscoresUnvalidatedWiFi(false);
}
public void doTestValidatedCellularOutscoresUnvalidatedWiFi(
final boolean cellRadioTimesharingCapable) throws Exception {
mService.mCellularRadioTimesharingCapable = cellRadioTimesharingCapable;
// Test bringing up unvalidated WiFi
mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
ExpectedBroadcast b = registerConnectivityBroadcast(1);
@@ -2308,8 +2324,21 @@ public class ConnectivityServiceTest {
verifyNoNetwork();
}
// TODO : migrate to @Parameterized
@Test
public void testUnvalidatedWifiOutscoresUnvalidatedCellular() throws Exception {
public void testUnvalidatedWifiOutscoresUnvalidatedCellular_CanTimeShare() throws Exception {
doTestUnvalidatedWifiOutscoresUnvalidatedCellular(true);
}
// TODO : migrate to @Parameterized
@Test
public void testUnvalidatedWifiOutscoresUnvalidatedCellular_CannotTimeShare() throws Exception {
doTestUnvalidatedWifiOutscoresUnvalidatedCellular(false);
}
public void doTestUnvalidatedWifiOutscoresUnvalidatedCellular(
final boolean cellRadioTimesharingCapable) throws Exception {
mService.mCellularRadioTimesharingCapable = cellRadioTimesharingCapable;
// Test bringing up unvalidated cellular.
mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
ExpectedBroadcast b = registerConnectivityBroadcast(1);
@@ -2334,8 +2363,21 @@ public class ConnectivityServiceTest {
verifyNoNetwork();
}
// TODO : migrate to @Parameterized
@Test
public void testUnlingeringDoesNotValidate() throws Exception {
public void testUnlingeringDoesNotValidate_CanTimeShare() throws Exception {
doTestUnlingeringDoesNotValidate(true);
}
// TODO : migrate to @Parameterized
@Test
public void testUnlingeringDoesNotValidate_CannotTimeShare() throws Exception {
doTestUnlingeringDoesNotValidate(false);
}
public void doTestUnlingeringDoesNotValidate(
final boolean cellRadioTimesharingCapable) throws Exception {
mService.mCellularRadioTimesharingCapable = cellRadioTimesharingCapable;
// Test bringing up unvalidated WiFi.
mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
ExpectedBroadcast b = registerConnectivityBroadcast(1);
@@ -2362,8 +2404,134 @@ public class ConnectivityServiceTest {
NET_CAPABILITY_VALIDATED));
}
// TODO : migrate to @Parameterized
@Test
public void testCellularOutscoresWeakWifi() throws Exception {
public void testRequestMigrationToSameTransport_CanTimeShare() throws Exception {
// Simulate a device where the cell radio is capable of time sharing
mService.mCellularRadioTimesharingCapable = true;
doTestRequestMigrationToSameTransport(TRANSPORT_CELLULAR, true);
doTestRequestMigrationToSameTransport(TRANSPORT_WIFI, true);
doTestRequestMigrationToSameTransport(TRANSPORT_ETHERNET, true);
}
// TODO : migrate to @Parameterized
@Test
public void testRequestMigrationToSameTransport_CannotTimeShare() throws Exception {
// Simulate a device where the cell radio is not capable of time sharing
mService.mCellularRadioTimesharingCapable = false;
doTestRequestMigrationToSameTransport(TRANSPORT_CELLULAR, false);
doTestRequestMigrationToSameTransport(TRANSPORT_WIFI, true);
doTestRequestMigrationToSameTransport(TRANSPORT_ETHERNET, true);
}
public void doTestRequestMigrationToSameTransport(final int transport,
final boolean expectLingering) throws Exception {
// To speed up tests the linger delay is very short by default in tests but this
// test needs to make sure the delay is not incurred so a longer value is safer (it
// reduces the risk that a bug exists but goes undetected). The alarm manager in the test
// throws and crashes CS if this is set to anything more than the below constant though.
mService.mLingerDelayMs = UNREASONABLY_LONG_ALARM_WAIT_MS;
final TestNetworkCallback generalCb = new TestNetworkCallback();
final TestNetworkCallback defaultCb = new TestNetworkCallback();
mCm.registerNetworkCallback(
new NetworkRequest.Builder().addTransportType(transport | transport).build(),
generalCb);
mCm.registerDefaultNetworkCallback(defaultCb);
// Bring up net agent 1
final TestNetworkAgentWrapper net1 = new TestNetworkAgentWrapper(transport);
net1.connect(true);
// Make sure the default request is on net 1
generalCb.expectAvailableThenValidatedCallbacks(net1);
defaultCb.expectAvailableThenValidatedCallbacks(net1);
// Bring up net 2 with primary and mms
final TestNetworkAgentWrapper net2 = new TestNetworkAgentWrapper(transport);
net2.addCapability(NET_CAPABILITY_MMS);
net2.setScore(new NetworkScore.Builder().setTransportPrimary(true).build());
net2.connect(true);
// Make sure the default request goes to net 2
generalCb.expectAvailableCallbacksUnvalidated(net2);
if (expectLingering) {
generalCb.expectCallback(CallbackEntry.LOSING, net1);
}
generalCb.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, net2);
defaultCb.expectAvailableDoubleValidatedCallbacks(net2);
// Make sure cell 1 is unwanted immediately if the radio can't time share, but only
// after some delay if it can.
if (expectLingering) {
net1.assertNotDisconnected(TEST_CALLBACK_TIMEOUT_MS); // always incurs the timeout
generalCb.assertNoCallback();
// assertNotDisconnected waited for TEST_CALLBACK_TIMEOUT_MS, so waiting for the
// linger period gives TEST_CALLBACK_TIMEOUT_MS time for the event to process.
net1.expectDisconnected(UNREASONABLY_LONG_ALARM_WAIT_MS);
} else {
net1.expectDisconnected(TEST_CALLBACK_TIMEOUT_MS);
}
net1.disconnect();
generalCb.expectCallback(CallbackEntry.LOST, net1);
// Remove primary from net 2
net2.setScore(new NetworkScore.Builder().build());
// Request MMS
final TestNetworkCallback mmsCallback = new TestNetworkCallback();
mCm.requestNetwork(new NetworkRequest.Builder().addCapability(NET_CAPABILITY_MMS).build(),
mmsCallback);
mmsCallback.expectAvailableCallbacksValidated(net2);
// Bring up net 3 with primary but without MMS
final TestNetworkAgentWrapper net3 = new TestNetworkAgentWrapper(transport);
net3.setScore(new NetworkScore.Builder().setTransportPrimary(true).build());
net3.connect(true);
// Make sure default goes to net 3, but the MMS request doesn't
generalCb.expectAvailableThenValidatedCallbacks(net3);
defaultCb.expectAvailableDoubleValidatedCallbacks(net3);
mmsCallback.assertNoCallback();
net2.assertNotDisconnected(TEST_CALLBACK_TIMEOUT_MS); // Always incurs the timeout
// Revoke MMS request and make sure net 2 is torn down with the appropriate delay
mCm.unregisterNetworkCallback(mmsCallback);
if (expectLingering) {
// If the radio can time share, the linger delay hasn't elapsed yet, so apps will
// get LOSING. If the radio can't time share, this is a hard loss, since the last
// request keeping up this network has been removed and the network isn't lingering
// for any other request.
generalCb.expectCallback(CallbackEntry.LOSING, net2);
net2.assertNotDisconnected(TEST_CALLBACK_TIMEOUT_MS);
generalCb.assertNoCallback();
net2.expectDisconnected(UNREASONABLY_LONG_ALARM_WAIT_MS);
} else {
net2.expectDisconnected(TEST_CALLBACK_TIMEOUT_MS);
}
net2.disconnect();
generalCb.expectCallback(CallbackEntry.LOST, net2);
defaultCb.assertNoCallback();
net3.disconnect();
mCm.unregisterNetworkCallback(defaultCb);
mCm.unregisterNetworkCallback(generalCb);
}
// TODO : migrate to @Parameterized
@Test
public void testCellularOutscoresWeakWifi_CanTimeShare() throws Exception {
// The behavior of this test should be the same whether the radio can time share or not.
doTestCellularOutscoresWeakWifi(true);
}
// TODO : migrate to @Parameterized
@Test
public void testCellularOutscoresWeakWifi_CannotTimeShare() throws Exception {
doTestCellularOutscoresWeakWifi(false);
}
public void doTestCellularOutscoresWeakWifi(
final boolean cellRadioTimesharingCapable) throws Exception {
mService.mCellularRadioTimesharingCapable = cellRadioTimesharingCapable;
// Test bringing up validated cellular.
mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
ExpectedBroadcast b = registerConnectivityBroadcast(1);
@@ -2388,8 +2556,21 @@ public class ConnectivityServiceTest {
verifyActiveNetwork(TRANSPORT_WIFI);
}
// TODO : migrate to @Parameterized
@Test
public void testReapingNetwork() throws Exception {
public void testReapingNetwork_CanTimeShare() throws Exception {
doTestReapingNetwork(true);
}
// TODO : migrate to @Parameterized
@Test
public void testReapingNetwork_CannotTimeShare() throws Exception {
doTestReapingNetwork(false);
}
public void doTestReapingNetwork(
final boolean cellRadioTimesharingCapable) throws Exception {
mService.mCellularRadioTimesharingCapable = cellRadioTimesharingCapable;
// Test bringing up WiFi without NET_CAPABILITY_INTERNET.
// Expect it to be torn down immediately because it satisfies no requests.
mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
@@ -2417,8 +2598,21 @@ public class ConnectivityServiceTest {
mWiFiNetworkAgent.expectDisconnected();
}
// TODO : migrate to @Parameterized
@Test
public void testCellularFallback() throws Exception {
public void testCellularFallback_CanTimeShare() throws Exception {
doTestCellularFallback(true);
}
// TODO : migrate to @Parameterized
@Test
public void testCellularFallback_CannotTimeShare() throws Exception {
doTestCellularFallback(false);
}
public void doTestCellularFallback(
final boolean cellRadioTimesharingCapable) throws Exception {
mService.mCellularRadioTimesharingCapable = cellRadioTimesharingCapable;
// Test bringing up validated cellular.
mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
ExpectedBroadcast b = registerConnectivityBroadcast(1);
@@ -2455,8 +2649,21 @@ public class ConnectivityServiceTest {
verifyActiveNetwork(TRANSPORT_WIFI);
}
// TODO : migrate to @Parameterized
@Test
public void testWiFiFallback() throws Exception {
public void testWiFiFallback_CanTimeShare() throws Exception {
doTestWiFiFallback(true);
}
// TODO : migrate to @Parameterized
@Test
public void testWiFiFallback_CannotTimeShare() throws Exception {
doTestWiFiFallback(false);
}
public void doTestWiFiFallback(
final boolean cellRadioTimesharingCapable) throws Exception {
mService.mCellularRadioTimesharingCapable = cellRadioTimesharingCapable;
// Test bringing up unvalidated WiFi.
mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
ExpectedBroadcast b = registerConnectivityBroadcast(1);