diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java index 4858dfa7bb9c8..08df271234271 100644 --- a/services/core/java/com/android/server/connectivity/Tethering.java +++ b/services/core/java/com/android/server/connectivity/Tethering.java @@ -1232,8 +1232,8 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering protected void chooseUpstreamType(boolean tryCell) { updateConfiguration(); // TODO - remove? - final int upstreamType = findPreferredUpstreamType( - getConnectivityManager(), mConfig); + final int upstreamType = mUpstreamNetworkMonitor.selectPreferredUpstreamType( + mConfig.preferredUpstreamIfaceTypes); if (upstreamType == ConnectivityManager.TYPE_NONE) { if (tryCell) { mUpstreamNetworkMonitor.registerMobileNetworkRequest(); @@ -1245,58 +1245,6 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering setUpstreamByType(upstreamType); } - // TODO: Move this function into UpstreamNetworkMonitor. - protected int findPreferredUpstreamType(ConnectivityManager cm, - TetheringConfiguration cfg) { - int upType = ConnectivityManager.TYPE_NONE; - - if (VDBG) { - Log.d(TAG, "chooseUpstreamType has upstream iface types:"); - for (Integer netType : cfg.preferredUpstreamIfaceTypes) { - Log.d(TAG, " " + netType); - } - } - - for (Integer netType : cfg.preferredUpstreamIfaceTypes) { - NetworkInfo info = cm.getNetworkInfo(netType.intValue()); - // TODO: if the network is suspended we should consider - // that to be the same as connected here. - if ((info != null) && info.isConnected()) { - upType = netType.intValue(); - break; - } - } - - final int preferredUpstreamMobileApn = cfg.isDunRequired - ? ConnectivityManager.TYPE_MOBILE_DUN - : ConnectivityManager.TYPE_MOBILE_HIPRI; - mLog.log(String.format( - "findPreferredUpstreamType(), preferredApn=%s, got type=%s", - getNetworkTypeName(preferredUpstreamMobileApn), - getNetworkTypeName(upType))); - - switch (upType) { - case ConnectivityManager.TYPE_MOBILE_DUN: - case ConnectivityManager.TYPE_MOBILE_HIPRI: - // If we're on DUN, put our own grab on it. - mUpstreamNetworkMonitor.registerMobileNetworkRequest(); - break; - case ConnectivityManager.TYPE_NONE: - break; - default: - /* If we've found an active upstream connection that's not DUN/HIPRI - * we should stop any outstanding DUN/HIPRI start requests. - * - * If we found NONE we don't want to do this as we want any previous - * requests to keep trying to bring up something we can use. - */ - mUpstreamNetworkMonitor.releaseMobileNetworkRequest(); - break; - } - - return upType; - } - protected void setUpstreamByType(int upType) { final ConnectivityManager cm = getConnectivityManager(); Network network = null; diff --git a/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java b/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java index cd6038ffa0ddf..b2d50515c39d8 100644 --- a/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java +++ b/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java @@ -16,6 +16,8 @@ package com.android.server.connectivity.tethering; +import static android.net.ConnectivityManager.getNetworkTypeName; +import static android.net.ConnectivityManager.TYPE_NONE; import static android.net.ConnectivityManager.TYPE_MOBILE_DUN; import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI; @@ -176,6 +178,41 @@ public class UpstreamNetworkMonitor { return (network != null) ? mNetworkMap.get(network) : null; } + // So many TODOs here, but chief among them is: make this functionality an + // integral part of this class such that whenever a higher priority network + // becomes available and useful we (a) file a request to keep it up as + // necessary and (b) change all upstream tracking state accordingly (by + // passing LinkProperties up to Tethering). + // + // Next TODO: return NetworkState instead of just the type. + public int selectPreferredUpstreamType(Iterable preferredTypes) { + final TypeStatePair typeStatePair = findFirstAvailableUpstreamByType( + mNetworkMap.values(), preferredTypes); + + mLog.log("preferred upstream type: " + getNetworkTypeName(typeStatePair.type)); + + switch (typeStatePair.type) { + case TYPE_MOBILE_DUN: + case TYPE_MOBILE_HIPRI: + // If we're on DUN, put our own grab on it. + registerMobileNetworkRequest(); + break; + case TYPE_NONE: + break; + default: + /* If we've found an active upstream connection that's not DUN/HIPRI + * we should stop any outstanding DUN/HIPRI start requests. + * + * If we found NONE we don't want to do this as we want any previous + * requests to keep trying to bring up something we can use. + */ + releaseMobileNetworkRequest(); + break; + } + + return typeStatePair.type; + } + private void handleAvailable(int callbackType, Network network) { if (VDBG) Log.d(TAG, "EVENT_ON_AVAILABLE for " + network); @@ -365,4 +402,37 @@ public class UpstreamNetworkMonitor { private void notifyTarget(int which, NetworkState netstate) { mTarget.sendMessage(mWhat, which, 0, netstate); } + + static private class TypeStatePair { + public int type = TYPE_NONE; + public NetworkState ns = null; + } + + static private TypeStatePair findFirstAvailableUpstreamByType( + Iterable netStates, Iterable preferredTypes) { + final TypeStatePair result = new TypeStatePair(); + + for (int type : preferredTypes) { + NetworkCapabilities nc; + try { + nc = ConnectivityManager.networkCapabilitiesForType(type); + } catch (IllegalArgumentException iae) { + Log.e(TAG, "No NetworkCapabilities mapping for legacy type: " + + ConnectivityManager.getNetworkTypeName(type)); + continue; + } + + for (NetworkState value : netStates) { + if (!nc.satisfiedByNetworkCapabilities(value.networkCapabilities)) { + continue; + } + + result.type = type; + result.ns = value; + return result; + } + } + + return result; + } } diff --git a/tests/net/java/com/android/server/connectivity/TetheringTest.java b/tests/net/java/com/android/server/connectivity/TetheringTest.java index 69dc27a8c6326..be0924aa4f93b 100644 --- a/tests/net/java/com/android/server/connectivity/TetheringTest.java +++ b/tests/net/java/com/android/server/connectivity/TetheringTest.java @@ -368,7 +368,6 @@ public class TetheringTest { any(NetworkCallback.class), any(Handler.class)); // In tethering mode, in the default configuration, an explicit request // for a mobile network is also made. - verify(mConnectivityManager, atLeastOnce()).getNetworkInfo(anyInt()); verify(mConnectivityManager, times(1)).requestNetwork( any(NetworkRequest.class), any(NetworkCallback.class), eq(0), anyInt(), any(Handler.class)); diff --git a/tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java b/tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java index 9bb392a23d560..fb6066e46e66f 100644 --- a/tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java +++ b/tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java @@ -18,7 +18,12 @@ package com.android.server.connectivity.tethering; import static android.net.ConnectivityManager.TYPE_MOBILE_DUN; import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI; +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_CELLULAR; +import static android.net.NetworkCapabilities.TRANSPORT_WIFI; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -59,6 +64,7 @@ import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import java.util.ArrayList; +import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Map; @@ -240,6 +246,72 @@ public class UpstreamNetworkMonitorTest { assertFalse(mUNM.mobileNetworkRequested()); } + @Test + public void testSelectPreferredUpstreamType() throws Exception { + final Collection preferredTypes = new ArrayList<>(); + preferredTypes.add(TYPE_WIFI); + + mUNM.start(); + // There are no networks, so there is nothing to select. + assertEquals(TYPE_NONE, mUNM.selectPreferredUpstreamType(preferredTypes)); + + final TestNetworkAgent wifiAgent = new TestNetworkAgent(mCM, TRANSPORT_WIFI); + wifiAgent.fakeConnect(); + // WiFi is up, we should prefer it. + assertEquals(TYPE_WIFI, mUNM.selectPreferredUpstreamType(preferredTypes)); + wifiAgent.fakeDisconnect(); + // There are no networks, so there is nothing to select. + assertEquals(TYPE_NONE, mUNM.selectPreferredUpstreamType(preferredTypes)); + + final TestNetworkAgent cellAgent = new TestNetworkAgent(mCM, TRANSPORT_CELLULAR); + cellAgent.fakeConnect(); + assertEquals(TYPE_NONE, mUNM.selectPreferredUpstreamType(preferredTypes)); + + preferredTypes.add(TYPE_MOBILE_DUN); + // This is coupled with preferred types in TetheringConfiguration. + mUNM.updateMobileRequiresDun(true); + // DUN is available, but only use regular cell: no upstream selected. + assertEquals(TYPE_NONE, mUNM.selectPreferredUpstreamType(preferredTypes)); + preferredTypes.remove(TYPE_MOBILE_DUN); + // No WiFi, but our preferred flavour of cell is up. + preferredTypes.add(TYPE_MOBILE_HIPRI); + // This is coupled with preferred types in TetheringConfiguration. + mUNM.updateMobileRequiresDun(false); + assertEquals(TYPE_MOBILE_HIPRI, mUNM.selectPreferredUpstreamType(preferredTypes)); + // Check to see we filed an explicit request. + assertEquals(1, mCM.requested.size()); + NetworkRequest netReq = (NetworkRequest) mCM.requested.values().toArray()[0]; + assertTrue(netReq.networkCapabilities.hasTransport(TRANSPORT_CELLULAR)); + assertFalse(netReq.networkCapabilities.hasCapability(NET_CAPABILITY_DUN)); + + wifiAgent.fakeConnect(); + // WiFi is up, and we should prefer it over cell. + assertEquals(TYPE_WIFI, mUNM.selectPreferredUpstreamType(preferredTypes)); + assertEquals(0, mCM.requested.size()); + + preferredTypes.remove(TYPE_MOBILE_HIPRI); + preferredTypes.add(TYPE_MOBILE_DUN); + // This is coupled with preferred types in TetheringConfiguration. + mUNM.updateMobileRequiresDun(true); + assertEquals(TYPE_WIFI, mUNM.selectPreferredUpstreamType(preferredTypes)); + + final TestNetworkAgent dunAgent = new TestNetworkAgent(mCM, TRANSPORT_CELLULAR); + dunAgent.networkCapabilities.addCapability(NET_CAPABILITY_DUN); + dunAgent.fakeConnect(); + + // WiFi is still preferred. + assertEquals(TYPE_WIFI, mUNM.selectPreferredUpstreamType(preferredTypes)); + + // WiFi goes down, cell and DUN are still up but only DUN is preferred. + wifiAgent.fakeDisconnect(); + assertEquals(TYPE_MOBILE_DUN, mUNM.selectPreferredUpstreamType(preferredTypes)); + // Check to see we filed an explicit request. + assertEquals(1, mCM.requested.size()); + netReq = (NetworkRequest) mCM.requested.values().toArray()[0]; + assertTrue(netReq.networkCapabilities.hasTransport(TRANSPORT_CELLULAR)); + assertTrue(netReq.networkCapabilities.hasCapability(NET_CAPABILITY_DUN)); + } + private void assertUpstreamTypeRequested(int upstreamType) throws Exception { assertEquals(1, mCM.requested.size()); assertEquals(1, mCM.legacyTypeMap.size()); @@ -254,6 +326,8 @@ public class UpstreamNetworkMonitorTest { public Map requested = new HashMap<>(); public Map legacyTypeMap = new HashMap<>(); + private int mNetworkId = 100; + public TestConnectivityManager(Context ctx, IConnectivityManager svc) { super(ctx, svc); } @@ -287,6 +361,8 @@ public class UpstreamNetworkMonitorTest { return false; } + int getNetworkId() { return ++mNetworkId; } + @Override public void requestNetwork(NetworkRequest req, NetworkCallback cb, Handler h) { assertFalse(allCallbacks.containsKey(cb)); @@ -360,6 +436,35 @@ public class UpstreamNetworkMonitorTest { } } + public static class TestNetworkAgent { + public final TestConnectivityManager cm; + public final Network networkId; + public final int transportType; + public final NetworkCapabilities networkCapabilities; + + public TestNetworkAgent(TestConnectivityManager cm, int transportType) { + this.cm = cm; + this.networkId = new Network(cm.getNetworkId()); + this.transportType = transportType; + networkCapabilities = new NetworkCapabilities(); + networkCapabilities.addTransportType(transportType); + networkCapabilities.addCapability(NET_CAPABILITY_INTERNET); + } + + public void fakeConnect() { + for (NetworkCallback cb : cm.listening.keySet()) { + cb.onAvailable(networkId); + cb.onCapabilitiesChanged(networkId, copy(networkCapabilities)); + } + } + + public void fakeDisconnect() { + for (NetworkCallback cb : cm.listening.keySet()) { + cb.onLost(networkId); + } + } + } + public static class TestStateMachine extends StateMachine { public final ArrayList messages = new ArrayList<>(); private final State mLoggingState = new LoggingState(); @@ -382,4 +487,8 @@ public class UpstreamNetworkMonitorTest { super.start(); } } + + static NetworkCapabilities copy(NetworkCapabilities nc) { + return new NetworkCapabilities(nc); + } }