Merge "Release mobile request when default upstream is not mobile"

This commit is contained in:
Mark Chien
2021-04-23 11:48:36 +00:00
committed by Gerrit Code Review
5 changed files with 203 additions and 54 deletions

View File

@@ -64,6 +64,7 @@ import static android.telephony.CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANG
import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
import static com.android.networkstack.tethering.TetheringNotificationUpdater.DOWNSTREAM_NONE;
import static com.android.networkstack.tethering.UpstreamNetworkMonitor.isCellular;
import android.app.usage.NetworkStatsManager;
import android.bluetooth.BluetoothAdapter;
@@ -1590,6 +1591,7 @@ public class Tethering {
? mUpstreamNetworkMonitor.getCurrentPreferredUpstream()
: mUpstreamNetworkMonitor.selectPreferredUpstreamType(
config.preferredUpstreamIfaceTypes);
if (ns == null) {
if (tryCell) {
mUpstreamNetworkMonitor.setTryCell(true);
@@ -1597,7 +1599,10 @@ public class Tethering {
} else {
sendMessageDelayed(CMD_RETRY_UPSTREAM, UPSTREAM_SETTLE_TIME_MS);
}
} else if (!isCellular(ns)) {
mUpstreamNetworkMonitor.setTryCell(false);
}
setUpstreamNetwork(ns);
final Network newUpstream = (ns != null) ? ns.network : null;
if (mTetherUpstream != newUpstream) {

View File

@@ -318,18 +318,6 @@ public class UpstreamNetworkMonitor {
if (!mIsDefaultCellularUpstream) {
mEntitlementMgr.maybeRunProvisioning();
}
// If we're on DUN, put our own grab on it.
registerMobileNetworkRequest();
break;
case TYPE_NONE:
// If we found NONE and mobile upstream is permitted we don't want to do this
// as we want any previous requests to keep trying to bring up something we can use.
if (!isCellularUpstreamPermitted()) releaseMobileNetworkRequest();
break;
default:
// If we've found an active upstream connection that's not DUN/HIPRI
// we should stop any outstanding DUN/HIPRI requests.
releaseMobileNetworkRequest();
break;
}
@@ -647,7 +635,8 @@ public class UpstreamNetworkMonitor {
return prefixSet;
}
private static boolean isCellular(UpstreamNetworkState ns) {
/** Check whether upstream is cellular. */
static boolean isCellular(UpstreamNetworkState ns) {
return (ns != null) && isCellular(ns.networkCapabilities);
}

View File

@@ -203,9 +203,11 @@ public class TestConnectivityManager extends ConnectivityManager {
public void requestNetwork(NetworkRequest req,
int timeoutMs, int legacyType, Handler h, NetworkCallback cb) {
assertFalse(mAllCallbacks.containsKey(cb));
mAllCallbacks.put(cb, new NetworkRequestInfo(req, h));
NetworkRequest newReq = new NetworkRequest(req.networkCapabilities, legacyType,
-1 /** testId */, req.type);
mAllCallbacks.put(cb, new NetworkRequestInfo(newReq, h));
assertFalse(mRequested.containsKey(cb));
mRequested.put(cb, new NetworkRequestInfo(req, h));
mRequested.put(cb, new NetworkRequestInfo(newReq, h));
assertFalse(mLegacyTypeMap.containsKey(cb));
if (legacyType != ConnectivityManager.TYPE_NONE) {
mLegacyTypeMap.put(cb, legacyType);
@@ -313,14 +315,26 @@ public class TestConnectivityManager extends ConnectivityManager {
return matchesLegacyType(networkCapabilities, legacyType);
}
public void fakeConnect() {
for (NetworkRequestInfo nri : cm.mRequested.values()) {
if (matchesLegacyType(nri.request.legacyType)) {
cm.sendConnectivityAction(legacyType, true /* connected */);
private void maybeSendConnectivityBroadcast(boolean connected) {
for (Integer requestedLegacyType : cm.mLegacyTypeMap.values()) {
if (requestedLegacyType.intValue() == legacyType) {
cm.sendConnectivityAction(legacyType, connected /* connected */);
// In practice, a given network can match only one legacy type.
break;
}
}
}
public void fakeConnect() {
fakeConnect(BROADCAST_FIRST, null);
}
public void fakeConnect(boolean order, @Nullable Runnable inBetween) {
if (order == BROADCAST_FIRST) {
maybeSendConnectivityBroadcast(true /* connected */);
if (inBetween != null) inBetween.run();
}
for (NetworkCallback cb : cm.mListening.keySet()) {
final NetworkRequestInfo nri = cm.mListening.get(cb);
nri.handler.post(() -> cb.onAvailable(networkId));
@@ -328,19 +342,32 @@ public class TestConnectivityManager extends ConnectivityManager {
networkId, copy(networkCapabilities)));
nri.handler.post(() -> cb.onLinkPropertiesChanged(networkId, copy(linkProperties)));
}
if (order == CALLBACKS_FIRST) {
if (inBetween != null) inBetween.run();
maybeSendConnectivityBroadcast(true /* connected */);
}
// mTrackingDefault will be updated if/when the caller calls makeDefaultNetwork
}
public void fakeDisconnect() {
for (NetworkRequestInfo nri : cm.mRequested.values()) {
if (matchesLegacyType(nri.request.legacyType)) {
cm.sendConnectivityAction(legacyType, false /* connected */);
break;
fakeDisconnect(BROADCAST_FIRST, null);
}
public void fakeDisconnect(boolean order, @Nullable Runnable inBetween) {
if (order == BROADCAST_FIRST) {
maybeSendConnectivityBroadcast(false /* connected */);
if (inBetween != null) inBetween.run();
}
for (NetworkCallback cb : cm.mListening.keySet()) {
cb.onLost(networkId);
}
if (order == CALLBACKS_FIRST) {
if (inBetween != null) inBetween.run();
maybeSendConnectivityBroadcast(false /* connected */);
}
// mTrackingDefault will be updated if/when the caller calls makeDefaultNetwork
}

View File

@@ -25,7 +25,9 @@ import static android.hardware.usb.UsbManager.USB_FUNCTION_RNDIS;
import static android.net.ConnectivityManager.ACTION_RESTRICT_BACKGROUND_CHANGED;
import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_DISABLED;
import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_ENABLED;
import static android.net.ConnectivityManager.TYPE_MOBILE_DUN;
import static android.net.ConnectivityManager.TYPE_NONE;
import static android.net.ConnectivityManager.TYPE_WIFI;
import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN;
import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
import static android.net.NetworkCapabilities.TRANSPORT_BLUETOOTH;
@@ -204,6 +206,7 @@ public class TetheringTest {
private static final int IFINDEX_OFFSET = 100;
private static final String TEST_MOBILE_IFNAME = "test_rmnet_data0";
private static final String TEST_DUN_IFNAME = "test_dun0";
private static final String TEST_XLAT_MOBILE_IFNAME = "v4-test_rmnet_data0";
private static final String TEST_USB_IFNAME = "test_rndis0";
private static final String TEST_WIFI_IFNAME = "test_wlan0";
@@ -343,13 +346,14 @@ public class TetheringTest {
|| ifName.equals(TEST_WLAN_IFNAME)
|| ifName.equals(TEST_WIFI_IFNAME)
|| ifName.equals(TEST_MOBILE_IFNAME)
|| ifName.equals(TEST_DUN_IFNAME)
|| ifName.equals(TEST_P2P_IFNAME)
|| ifName.equals(TEST_NCM_IFNAME)
|| ifName.equals(TEST_ETH_IFNAME)
|| ifName.equals(TEST_BT_IFNAME));
final String[] ifaces = new String[] {
TEST_USB_IFNAME, TEST_WLAN_IFNAME, TEST_WIFI_IFNAME, TEST_MOBILE_IFNAME,
TEST_P2P_IFNAME, TEST_NCM_IFNAME, TEST_ETH_IFNAME};
TEST_DUN_IFNAME, TEST_P2P_IFNAME, TEST_NCM_IFNAME, TEST_ETH_IFNAME};
return new InterfaceParams(ifName, ArrayUtils.indexOf(ifaces, ifName) + IFINDEX_OFFSET,
MacAddress.ALL_ZEROS_ADDRESS);
}
@@ -547,8 +551,7 @@ public class TetheringTest {
private static NetworkCapabilities buildUpstreamCapabilities(int transport, int... otherCaps) {
// TODO: add NOT_VCN_MANAGED.
final NetworkCapabilities nc = new NetworkCapabilities()
.addTransportType(transport)
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
.addTransportType(transport);
for (int cap : otherCaps) {
nc.addCapability(cap);
}
@@ -559,7 +562,7 @@ public class TetheringTest {
boolean withIPv6, boolean with464xlat) {
return new UpstreamNetworkState(
buildUpstreamLinkProperties(TEST_MOBILE_IFNAME, withIPv4, withIPv6, with464xlat),
buildUpstreamCapabilities(TRANSPORT_CELLULAR),
buildUpstreamCapabilities(TRANSPORT_CELLULAR, NET_CAPABILITY_INTERNET),
new Network(CELLULAR_NETID));
}
@@ -583,13 +586,13 @@ public class TetheringTest {
return new UpstreamNetworkState(
buildUpstreamLinkProperties(TEST_WIFI_IFNAME, true /* IPv4 */, true /* IPv6 */,
false /* 464xlat */),
buildUpstreamCapabilities(TRANSPORT_WIFI),
buildUpstreamCapabilities(TRANSPORT_WIFI, NET_CAPABILITY_INTERNET),
new Network(WIFI_NETID));
}
private static UpstreamNetworkState buildDunUpstreamState() {
return new UpstreamNetworkState(
buildUpstreamLinkProperties(TEST_MOBILE_IFNAME, true /* IPv4 */, true /* IPv6 */,
buildUpstreamLinkProperties(TEST_DUN_IFNAME, true /* IPv4 */, true /* IPv6 */,
false /* 464xlat */),
buildUpstreamCapabilities(TRANSPORT_CELLULAR, NET_CAPABILITY_DUN),
new Network(DUN_NETID));
@@ -688,7 +691,8 @@ public class TetheringTest {
.thenReturn(new String[] { "test_pan\\d" });
when(mResources.getStringArray(R.array.config_tether_ncm_regexs))
.thenReturn(new String[] { "test_ncm\\d" });
when(mResources.getIntArray(R.array.config_tether_upstream_types)).thenReturn(new int[0]);
when(mResources.getIntArray(R.array.config_tether_upstream_types)).thenReturn(
new int[] { TYPE_WIFI, TYPE_MOBILE_DUN });
when(mResources.getBoolean(R.bool.config_tether_upstream_automatic)).thenReturn(true);
}
@@ -1101,15 +1105,13 @@ public class TetheringTest {
verify(mUpstreamNetworkMonitor, times(1)).setCurrentUpstream(upstreamState.network);
}
@Test
public void testAutomaticUpstreamSelection() throws Exception {
private void upstreamSelectionTestCommon(final boolean automatic, InOrder inOrder,
TestNetworkAgent mobile, TestNetworkAgent wifi) throws Exception {
// Enable automatic upstream selection.
when(mResources.getBoolean(R.bool.config_tether_upstream_automatic)).thenReturn(true);
when(mResources.getBoolean(R.bool.config_tether_upstream_automatic)).thenReturn(automatic);
sendConfigurationChanged();
mLooper.dispatchAll();
InOrder inOrder = inOrder(mCm, mUpstreamNetworkMonitor);
// Start USB tethering with no current upstream.
prepareUsbTethering();
sendUsbBroadcast(true, true, true, TETHERING_USB);
@@ -1117,23 +1119,31 @@ public class TetheringTest {
inOrder.verify(mUpstreamNetworkMonitor).setTryCell(true);
// Pretend cellular connected and expect the upstream to be set.
TestNetworkAgent mobile = new TestNetworkAgent(mCm, buildMobileDualStackUpstreamState());
mobile.fakeConnect();
mCm.makeDefaultNetwork(mobile, BROADCAST_FIRST);
mLooper.dispatchAll();
inOrder.verify(mUpstreamNetworkMonitor).setCurrentUpstream(mobile.networkId);
// Switch upstreams a few times.
TestNetworkAgent wifi = new TestNetworkAgent(mCm, buildWifiUpstreamState());
// Switch upstream to wifi.
wifi.fakeConnect();
mCm.makeDefaultNetwork(wifi, BROADCAST_FIRST);
mLooper.dispatchAll();
inOrder.verify(mUpstreamNetworkMonitor).setCurrentUpstream(wifi.networkId);
}
@Test
public void testAutomaticUpstreamSelection() throws Exception {
TestNetworkAgent mobile = new TestNetworkAgent(mCm, buildMobileDualStackUpstreamState());
TestNetworkAgent wifi = new TestNetworkAgent(mCm, buildWifiUpstreamState());
InOrder inOrder = inOrder(mCm, mUpstreamNetworkMonitor);
// Enable automatic upstream selection.
upstreamSelectionTestCommon(true, inOrder, mobile, wifi);
// This code has historically been racy, so test different orderings of CONNECTIVITY_ACTION
// broadcasts and callbacks, and add mLooper.dispatchAll() calls between the two.
final Runnable doDispatchAll = () -> mLooper.dispatchAll();
// Switch upstreams a few times.
mCm.makeDefaultNetwork(mobile, BROADCAST_FIRST, doDispatchAll);
mLooper.dispatchAll();
inOrder.verify(mUpstreamNetworkMonitor).setCurrentUpstream(mobile.networkId);
@@ -1200,6 +1210,138 @@ public class TetheringTest {
mLooper.dispatchAll();
}
@Test
public void testLegacyUpstreamSelection() throws Exception {
TestNetworkAgent mobile = new TestNetworkAgent(mCm, buildMobileDualStackUpstreamState());
TestNetworkAgent wifi = new TestNetworkAgent(mCm, buildWifiUpstreamState());
InOrder inOrder = inOrder(mCm, mUpstreamNetworkMonitor);
// Enable legacy upstream selection.
upstreamSelectionTestCommon(false, inOrder, mobile, wifi);
// Wifi disconnecting and the default network switch to mobile, the upstream should also
// switch to mobile.
wifi.fakeDisconnect();
mLooper.dispatchAll();
mCm.makeDefaultNetwork(mobile, CALLBACKS_FIRST, null);
mLooper.dispatchAll();
inOrder.verify(mUpstreamNetworkMonitor).setCurrentUpstream(mobile.networkId);
wifi.fakeConnect();
mLooper.dispatchAll();
mCm.makeDefaultNetwork(wifi, CALLBACKS_FIRST, null);
mLooper.dispatchAll();
}
@Test
public void testChooseDunUpstreamByAutomaticMode() throws Exception {
// Enable automatic upstream selection.
TestNetworkAgent mobile = new TestNetworkAgent(mCm, buildMobileDualStackUpstreamState());
TestNetworkAgent wifi = new TestNetworkAgent(mCm, buildWifiUpstreamState());
TestNetworkAgent dun = new TestNetworkAgent(mCm, buildDunUpstreamState());
InOrder inOrder = inOrder(mCm, mUpstreamNetworkMonitor);
chooseDunUpstreamTestCommon(true, inOrder, mobile, wifi, dun);
// When default network switch to mobile and wifi is connected (may have low signal),
// automatic mode would request dun again and choose it as upstream.
mCm.makeDefaultNetwork(mobile, CALLBACKS_FIRST);
mLooper.dispatchAll();
ArgumentCaptor<NetworkCallback> captor = ArgumentCaptor.forClass(NetworkCallback.class);
inOrder.verify(mCm).requestNetwork(any(), eq(0), eq(TYPE_MOBILE_DUN), any(), any());
inOrder.verify(mUpstreamNetworkMonitor).setCurrentUpstream(null);
final Runnable doDispatchAll = () -> mLooper.dispatchAll();
dun.fakeConnect(CALLBACKS_FIRST, doDispatchAll);
mLooper.dispatchAll();
inOrder.verify(mUpstreamNetworkMonitor).setCurrentUpstream(dun.networkId);
// Lose and regain upstream again.
dun.fakeDisconnect(CALLBACKS_FIRST, doDispatchAll);
mLooper.dispatchAll();
inOrder.verify(mUpstreamNetworkMonitor).setCurrentUpstream(null);
inOrder.verify(mCm, never()).unregisterNetworkCallback(any(NetworkCallback.class));
dun.fakeConnect(CALLBACKS_FIRST, doDispatchAll);
mLooper.dispatchAll();
inOrder.verify(mUpstreamNetworkMonitor).setCurrentUpstream(dun.networkId);
}
@Test
public void testChooseDunUpstreamByLegacyMode() throws Exception {
// Enable Legacy upstream selection.
TestNetworkAgent mobile = new TestNetworkAgent(mCm, buildMobileDualStackUpstreamState());
TestNetworkAgent wifi = new TestNetworkAgent(mCm, buildWifiUpstreamState());
TestNetworkAgent dun = new TestNetworkAgent(mCm, buildDunUpstreamState());
InOrder inOrder = inOrder(mCm, mUpstreamNetworkMonitor);
chooseDunUpstreamTestCommon(false, inOrder, mobile, wifi, dun);
// Legacy mode would keep use wifi as upstream (because it has higher priority in the
// list).
mCm.makeDefaultNetwork(mobile, CALLBACKS_FIRST);
mLooper.dispatchAll();
inOrder.verify(mUpstreamNetworkMonitor, never()).setCurrentUpstream(any());
// BUG: when wifi disconnect, the dun request would not be filed again because wifi is
// no longer be default network which do not have CONNECTIVIY_ACTION broadcast.
wifi.fakeDisconnect();
mLooper.dispatchAll();
inOrder.verify(mCm, never()).requestNetwork(any(), eq(0), eq(TYPE_MOBILE_DUN), any(),
any());
// Change the legacy priority list that dun is higher than wifi.
when(mResources.getIntArray(R.array.config_tether_upstream_types)).thenReturn(
new int[] { TYPE_MOBILE_DUN, TYPE_WIFI });
sendConfigurationChanged();
mLooper.dispatchAll();
// Make wifi as default network. Note: mobile also connected.
wifi.fakeConnect();
mLooper.dispatchAll();
mCm.makeDefaultNetwork(wifi, CALLBACKS_FIRST);
mLooper.dispatchAll();
// BUG: dun has higher priority than wifi but tethering don't file dun request because
// current upstream is wifi.
inOrder.verify(mCm, never()).requestNetwork(any(), eq(0), eq(TYPE_MOBILE_DUN), any(),
any());
}
private void chooseDunUpstreamTestCommon(final boolean automatic, InOrder inOrder,
TestNetworkAgent mobile, TestNetworkAgent wifi, TestNetworkAgent dun) throws Exception {
when(mResources.getBoolean(R.bool.config_tether_upstream_automatic)).thenReturn(automatic);
when(mTelephonyManager.isTetheringApnRequired()).thenReturn(true);
sendConfigurationChanged();
mLooper.dispatchAll();
// Start USB tethering with no current upstream.
prepareUsbTethering();
sendUsbBroadcast(true, true, true, TETHERING_USB);
inOrder.verify(mUpstreamNetworkMonitor).startObserveAllNetworks();
inOrder.verify(mUpstreamNetworkMonitor).setTryCell(true);
ArgumentCaptor<NetworkCallback> captor = ArgumentCaptor.forClass(NetworkCallback.class);
inOrder.verify(mCm).requestNetwork(any(), eq(0), eq(TYPE_MOBILE_DUN), any(),
captor.capture());
final NetworkCallback dunNetworkCallback1 = captor.getValue();
// Pretend cellular connected and expect the upstream to be set.
mobile.fakeConnect();
mCm.makeDefaultNetwork(mobile, BROADCAST_FIRST);
mLooper.dispatchAll();
inOrder.verify(mUpstreamNetworkMonitor, never()).setCurrentUpstream(mobile.networkId);
// Pretend dun connected and expect choose dun as upstream.
final Runnable doDispatchAll = () -> mLooper.dispatchAll();
dun.fakeConnect(BROADCAST_FIRST, doDispatchAll);
mLooper.dispatchAll();
inOrder.verify(mUpstreamNetworkMonitor).setCurrentUpstream(dun.networkId);
// When wifi connected, unregister dun request and choose wifi as upstream.
wifi.fakeConnect();
mCm.makeDefaultNetwork(wifi, CALLBACKS_FIRST);
mLooper.dispatchAll();
inOrder.verify(mUpstreamNetworkMonitor).setTryCell(false);
inOrder.verify(mCm).unregisterNetworkCallback(eq(dunNetworkCallback1));
inOrder.verify(mUpstreamNetworkMonitor).setCurrentUpstream(wifi.networkId);
dun.fakeDisconnect(BROADCAST_FIRST, doDispatchAll);
mLooper.dispatchAll();
inOrder.verify(mUpstreamNetworkMonitor, never()).setCurrentUpstream(any());
}
private void runNcmTethering() {
prepareNcmTethering();
sendUsbBroadcast(true, true, true, TETHERING_NCM);

View File

@@ -325,16 +325,9 @@ public class UpstreamNetworkMonitorTest {
mUNM.setUpstreamConfig(false /* autoUpstream */, false /* dunRequired */);
assertSatisfiesLegacyType(TYPE_MOBILE_HIPRI,
mUNM.selectPreferredUpstreamType(preferredTypes));
// Check to see we filed an explicit request.
assertEquals(1, mCM.mRequested.size());
NetworkRequest netReq = ((NetworkRequestInfo) mCM.mRequested.values().toArray()[0]).request;
assertTrue(netReq.networkCapabilities.hasTransport(TRANSPORT_CELLULAR));
assertFalse(netReq.networkCapabilities.hasCapability(NET_CAPABILITY_DUN));
// mobile is not permitted, we should not use HIPRI.
when(mEntitleMgr.isCellularUpstreamPermitted()).thenReturn(false);
assertSatisfiesLegacyType(TYPE_NONE, mUNM.selectPreferredUpstreamType(preferredTypes));
assertEquals(0, mCM.mRequested.size());
// mobile change back to permitted, HIRPI should come back
when(mEntitleMgr.isCellularUpstreamPermitted()).thenReturn(true);
assertSatisfiesLegacyType(TYPE_MOBILE_HIPRI,
mUNM.selectPreferredUpstreamType(preferredTypes));
@@ -343,7 +336,6 @@ public class UpstreamNetworkMonitorTest {
mLooper.dispatchAll();
// WiFi is up, and we should prefer it over cell.
assertSatisfiesLegacyType(TYPE_WIFI, mUNM.selectPreferredUpstreamType(preferredTypes));
assertEquals(0, mCM.mRequested.size());
preferredTypes.remove(TYPE_MOBILE_HIPRI);
preferredTypes.add(TYPE_MOBILE_DUN);
@@ -363,15 +355,9 @@ public class UpstreamNetworkMonitorTest {
mLooper.dispatchAll();
assertSatisfiesLegacyType(TYPE_MOBILE_DUN,
mUNM.selectPreferredUpstreamType(preferredTypes));
// Check to see we filed an explicit request.
assertEquals(1, mCM.mRequested.size());
netReq = ((NetworkRequestInfo) mCM.mRequested.values().toArray()[0]).request;
assertTrue(netReq.networkCapabilities.hasTransport(TRANSPORT_CELLULAR));
assertTrue(netReq.networkCapabilities.hasCapability(NET_CAPABILITY_DUN));
// mobile is not permitted, we should not use DUN.
when(mEntitleMgr.isCellularUpstreamPermitted()).thenReturn(false);
assertSatisfiesLegacyType(TYPE_NONE, mUNM.selectPreferredUpstreamType(preferredTypes));
assertEquals(0, mCM.mRequested.size());
// mobile change back to permitted, DUN should come back
when(mEntitleMgr.isCellularUpstreamPermitted()).thenReturn(true);
assertSatisfiesLegacyType(TYPE_MOBILE_DUN,