diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index 5c65238b3ef1b..cfa3934b0caec 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -472,6 +472,14 @@ public class ConnectivityManager { @SystemApi public static final int TETHERING_BLUETOOTH = 2; + /** + * Wifi P2p tethering type. + * Wifi P2p tethering is set through events automatically, and don't + * need to start from #startTethering(int, boolean, OnStartTetheringCallback). + * @hide + */ + public static final int TETHERING_WIFI_P2P = 3; + /** * Extra used for communicating with the TetherService. Includes the type of tethering to * enable if any. diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 9839884894807..a40e836698064 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -386,6 +386,12 @@ + + + + diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 5cd7c1b2b283e..a1a78bdb68f1f 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -1915,6 +1915,7 @@ + diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java index 5fd5c4bebb2a7..b3804c4d7ec5d 100644 --- a/services/core/java/com/android/server/connectivity/Tethering.java +++ b/services/core/java/com/android/server/connectivity/Tethering.java @@ -30,6 +30,7 @@ import static android.net.ConnectivityManager.TETHERING_BLUETOOTH; import static android.net.ConnectivityManager.TETHERING_INVALID; import static android.net.ConnectivityManager.TETHERING_USB; import static android.net.ConnectivityManager.TETHERING_WIFI; +import static android.net.ConnectivityManager.TETHERING_WIFI_P2P; import static android.net.ConnectivityManager.TETHER_ERROR_MASTER_ERROR; import static android.net.ConnectivityManager.TETHER_ERROR_NO_ERROR; import static android.net.ConnectivityManager.TETHER_ERROR_SERVICE_UNAVAIL; @@ -77,6 +78,9 @@ import android.net.util.PrefixUtils; import android.net.util.SharedLog; import android.net.util.VersionedBroadcastListener; import android.net.wifi.WifiManager; +import android.net.wifi.p2p.WifiP2pGroup; +import android.net.wifi.p2p.WifiP2pInfo; +import android.net.wifi.p2p.WifiP2pManager; import android.os.Binder; import android.os.Bundle; import android.os.Handler; @@ -290,6 +294,7 @@ public class Tethering extends BaseNetworkObserver { filter.addAction(CONNECTIVITY_ACTION); filter.addAction(WifiManager.WIFI_AP_STATE_CHANGED_ACTION); filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED); + filter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION); mContext.registerReceiver(mStateReceiver, filter, null, handler); filter = new IntentFilter(); @@ -354,6 +359,8 @@ public class Tethering extends BaseNetworkObserver { if (cfg.isWifi(iface)) { return TETHERING_WIFI; + } else if (cfg.isWifiP2p(iface)) { + return TETHERING_WIFI_P2P; } else if (cfg.isUsb(iface)) { return TETHERING_USB; } else if (cfg.isBluetooth(iface)) { @@ -527,6 +534,7 @@ public class Tethering extends BaseNetworkObserver { public void untetherAll() { stopTethering(TETHERING_WIFI); + stopTethering(TETHERING_WIFI_P2P); stopTethering(TETHERING_USB); stopTethering(TETHERING_BLUETOOTH); } @@ -713,6 +721,8 @@ public class Tethering extends BaseNetworkObserver { handleConnectivityAction(intent); } else if (action.equals(WifiManager.WIFI_AP_STATE_CHANGED_ACTION)) { handleWifiApAction(intent); + } else if (action.equals(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION)) { + handleWifiP2pAction(intent); } else if (action.equals(Intent.ACTION_CONFIGURATION_CHANGED)) { mLog.log("OBSERVED configuration changed"); updateConfiguration(); @@ -789,6 +799,39 @@ public class Tethering extends BaseNetworkObserver { } } } + + private void handleWifiP2pAction(Intent intent) { + if (mConfig.isWifiP2pLegacyTetheringMode()) return; + + final WifiP2pInfo p2pInfo = + (WifiP2pInfo) intent.getParcelableExtra(WifiP2pManager.EXTRA_WIFI_P2P_INFO); + final WifiP2pGroup group = + (WifiP2pGroup) intent.getParcelableExtra(WifiP2pManager.EXTRA_WIFI_P2P_GROUP); + + if (VDBG) { + Log.d(TAG, "WifiP2pAction: P2pInfo: " + p2pInfo + " Group: " + group); + } + + if (p2pInfo == null) return; + // When a p2p group is disconnected, p2pInfo would be cleared. + // group is still valid for detecting whether this device is group owner. + if (group == null || !group.isGroupOwner() + || TextUtils.isEmpty(group.getInterface())) return; + + synchronized (Tethering.this.mPublicSync) { + // Enter below only if this device is Group Owner with a valid interface. + if (p2pInfo.groupFormed) { + TetherState tetherState = mTetherStates.get(group.getInterface()); + if (tetherState == null + || (tetherState.lastState != IpServer.STATE_TETHERED + && tetherState.lastState != IpServer.STATE_LOCAL_ONLY)) { + enableWifiIpServingLocked(group.getInterface(), IFACE_IP_MODE_LOCAL_ONLY); + } + } else { + disableWifiP2pIpServingLocked(group.getInterface()); + } + } + } } @VisibleForTesting @@ -823,14 +866,11 @@ public class Tethering extends BaseNetworkObserver { } } - private void disableWifiIpServingLocked(String ifname, int apState) { - mLog.log("Canceling WiFi tethering request - AP_STATE=" + apState); - - // Regardless of whether we requested this transition, the AP has gone - // down. Don't try to tether again unless we're requested to do so. - // TODO: Remove this altogether, once Wi-Fi reliably gives us an - // interface name with every broadcast. - mWifiTetherRequested = false; + private void disableWifiIpServingLockedCommon(int tetheringType, String ifname, int apState) { + mLog.log("Canceling WiFi tethering request -" + + " type=" + tetheringType + + " interface=" + ifname + + " state=" + apState); if (!TextUtils.isEmpty(ifname)) { final TetherState ts = mTetherStates.get(ifname); @@ -842,7 +882,7 @@ public class Tethering extends BaseNetworkObserver { for (int i = 0; i < mTetherStates.size(); i++) { final IpServer ipServer = mTetherStates.valueAt(i).ipServer; - if (ipServer.interfaceType() == TETHERING_WIFI) { + if (ipServer.interfaceType() == tetheringType) { ipServer.unwanted(); return; } @@ -853,6 +893,20 @@ public class Tethering extends BaseNetworkObserver { : "specified interface: " + ifname)); } + private void disableWifiIpServingLocked(String ifname, int apState) { + // Regardless of whether we requested this transition, the AP has gone + // down. Don't try to tether again unless we're requested to do so. + // TODO: Remove this altogether, once Wi-Fi reliably gives us an + // interface name with every broadcast. + mWifiTetherRequested = false; + + disableWifiIpServingLockedCommon(TETHERING_WIFI, ifname, apState); + } + + private void disableWifiP2pIpServingLocked(String ifname) { + disableWifiIpServingLockedCommon(TETHERING_WIFI_P2P, ifname, /* dummy */ 0); + } + private void enableWifiIpServingLocked(String ifname, int wifiIpMode) { // Map wifiIpMode values to IpServer.Callback serving states, inferring // from mWifiTetherRequested as a final "best guess". @@ -870,7 +924,7 @@ public class Tethering extends BaseNetworkObserver { } if (!TextUtils.isEmpty(ifname)) { - maybeTrackNewInterfaceLocked(ifname, TETHERING_WIFI); + maybeTrackNewInterfaceLocked(ifname); changeInterfaceState(ifname, ipServingMode); } else { mLog.e(String.format( diff --git a/services/core/java/com/android/server/connectivity/tethering/TetheringConfiguration.java b/services/core/java/com/android/server/connectivity/tethering/TetheringConfiguration.java index 1907892c4d87f..a1b94ca33944b 100644 --- a/services/core/java/com/android/server/connectivity/tethering/TetheringConfiguration.java +++ b/services/core/java/com/android/server/connectivity/tethering/TetheringConfiguration.java @@ -28,6 +28,7 @@ import static com.android.internal.R.array.config_tether_bluetooth_regexs; import static com.android.internal.R.array.config_tether_dhcp_range; import static com.android.internal.R.array.config_tether_upstream_types; import static com.android.internal.R.array.config_tether_usb_regexs; +import static com.android.internal.R.array.config_tether_wifi_p2p_regexs; import static com.android.internal.R.array.config_tether_wifi_regexs; import static com.android.internal.R.bool.config_tether_upstream_automatic; import static com.android.internal.R.integer.config_mobile_hotspot_provision_check_period; @@ -85,6 +86,7 @@ public class TetheringConfiguration { public final String[] tetherableUsbRegexs; public final String[] tetherableWifiRegexs; + public final String[] tetherableWifiP2pRegexs; public final String[] tetherableBluetoothRegexs; public final boolean isDunRequired; public final boolean chooseUpstreamAutomatically; @@ -110,6 +112,7 @@ public class TetheringConfiguration { // us an interface name. Careful consideration needs to be given to // implications for Settings and for provisioning checks. tetherableWifiRegexs = getResourceStringArray(res, config_tether_wifi_regexs); + tetherableWifiP2pRegexs = getResourceStringArray(res, config_tether_wifi_p2p_regexs); tetherableBluetoothRegexs = getResourceStringArray(res, config_tether_bluetooth_regexs); isDunRequired = checkDunRequired(ctx, subId); @@ -138,6 +141,15 @@ public class TetheringConfiguration { return matchesDownstreamRegexs(iface, tetherableWifiRegexs); } + /** Check whether this interface is Wifi P2P interface. */ + public boolean isWifiP2p(String iface) { + return matchesDownstreamRegexs(iface, tetherableWifiP2pRegexs); + } + + public boolean isWifiP2pLegacyTetheringMode() { + return (tetherableWifiP2pRegexs == null || tetherableWifiP2pRegexs.length == 0); + } + public boolean isBluetooth(String iface) { return matchesDownstreamRegexs(iface, tetherableBluetoothRegexs); } @@ -152,6 +164,7 @@ public class TetheringConfiguration { dumpStringArray(pw, "tetherableUsbRegexs", tetherableUsbRegexs); dumpStringArray(pw, "tetherableWifiRegexs", tetherableWifiRegexs); + dumpStringArray(pw, "tetherableWifiP2pRegexs", tetherableWifiP2pRegexs); dumpStringArray(pw, "tetherableBluetoothRegexs", tetherableBluetoothRegexs); pw.print("isDunRequired: "); @@ -178,6 +191,7 @@ public class TetheringConfiguration { sj.add(String.format("subId:%d", subId)); sj.add(String.format("tetherableUsbRegexs:%s", makeString(tetherableUsbRegexs))); sj.add(String.format("tetherableWifiRegexs:%s", makeString(tetherableWifiRegexs))); + sj.add(String.format("tetherableWifiP2pRegexs:%s", makeString(tetherableWifiP2pRegexs))); sj.add(String.format("tetherableBluetoothRegexs:%s", makeString(tetherableBluetoothRegexs))); sj.add(String.format("isDunRequired:%s", isDunRequired)); diff --git a/services/net/java/android/net/ip/IpServer.java b/services/net/java/android/net/ip/IpServer.java index 6a6a1307723ea..3d79bba7bbe33 100644 --- a/services/net/java/android/net/ip/IpServer.java +++ b/services/net/java/android/net/ip/IpServer.java @@ -93,6 +93,8 @@ public class IpServer extends StateMachine { private static final int USB_PREFIX_LENGTH = 24; private static final String WIFI_HOST_IFACE_ADDR = "192.168.43.1"; private static final int WIFI_HOST_IFACE_PREFIX_LENGTH = 24; + private static final String WIFI_P2P_IFACE_ADDR = "192.168.49.1"; + private static final int WIFI_P2P_IFACE_PREFIX_LENGTH = 24; // TODO: have PanService use some visible version of this constant private static final String BLUETOOTH_IFACE_ADDR = "192.168.44.1"; @@ -403,6 +405,9 @@ public class IpServer extends StateMachine { } else if (mInterfaceType == ConnectivityManager.TETHERING_WIFI) { ipAsString = getRandomWifiIPv4Address(); prefixLen = WIFI_HOST_IFACE_PREFIX_LENGTH; + } else if (mInterfaceType == ConnectivityManager.TETHERING_WIFI_P2P) { + ipAsString = WIFI_P2P_IFACE_ADDR; + prefixLen = WIFI_P2P_IFACE_PREFIX_LENGTH; } else { // BT configures the interface elsewhere: only start DHCP. final Inet4Address srvAddr = (Inet4Address) numericToInetAddress(BLUETOOTH_IFACE_ADDR); diff --git a/tests/net/java/android/net/ip/IpServerTest.java b/tests/net/java/android/net/ip/IpServerTest.java index 05912e85426aa..b6ccebb3dcd30 100644 --- a/tests/net/java/android/net/ip/IpServerTest.java +++ b/tests/net/java/android/net/ip/IpServerTest.java @@ -19,11 +19,13 @@ package android.net.ip; import static android.net.ConnectivityManager.TETHERING_BLUETOOTH; import static android.net.ConnectivityManager.TETHERING_USB; import static android.net.ConnectivityManager.TETHERING_WIFI; +import static android.net.ConnectivityManager.TETHERING_WIFI_P2P; import static android.net.ConnectivityManager.TETHER_ERROR_ENABLE_NAT_ERROR; import static android.net.ConnectivityManager.TETHER_ERROR_NO_ERROR; import static android.net.ConnectivityManager.TETHER_ERROR_TETHER_IFACE_ERROR; import static android.net.dhcp.IDhcpServer.STATUS_SUCCESS; import static android.net.ip.IpServer.STATE_AVAILABLE; +import static android.net.ip.IpServer.STATE_LOCAL_ONLY; import static android.net.ip.IpServer.STATE_TETHERED; import static android.net.ip.IpServer.STATE_UNAVAILABLE; import static android.net.shared.Inet4AddressUtils.intToInet4AddressHTH; @@ -255,6 +257,23 @@ public class IpServerTest { verifyNoMoreInteractions(mNMService, mStatsService, mCallback); } + @Test + public void canBeTetheredAsWifiP2p() throws Exception { + initStateMachine(TETHERING_WIFI_P2P); + + dispatchCommand(IpServer.CMD_TETHER_REQUESTED, STATE_LOCAL_ONLY); + InOrder inOrder = inOrder(mCallback, mNMService); + inOrder.verify(mNMService).getInterfaceConfig(IFACE_NAME); + inOrder.verify(mNMService).setInterfaceConfig(IFACE_NAME, mInterfaceConfiguration); + inOrder.verify(mNMService).tetherInterface(IFACE_NAME); + inOrder.verify(mCallback).updateInterfaceState( + mIpServer, STATE_LOCAL_ONLY, TETHER_ERROR_NO_ERROR); + inOrder.verify(mCallback).updateLinkProperties( + eq(mIpServer), mLinkPropertiesCaptor.capture()); + assertIPv4AddressAndDirectlyConnectedRoute(mLinkPropertiesCaptor.getValue()); + verifyNoMoreInteractions(mNMService, mStatsService, mCallback); + } + @Test public void handlesFirstUpstreamChange() throws Exception { initTetheredStateMachine(TETHERING_BLUETOOTH, null); @@ -418,6 +437,14 @@ public class IpServerTest { assertDhcpStarted(new IpPrefix("192.168.44.0/24")); } + @Test + public void startsDhcpServerOnWifiP2p() throws Exception { + initTetheredStateMachine(TETHERING_WIFI_P2P, UPSTREAM_IFACE); + dispatchTetherConnectionChanged(UPSTREAM_IFACE); + + assertDhcpStarted(new IpPrefix("192.168.49.0/24")); + } + @Test public void doesNotStartDhcpServerIfDisabled() throws Exception { initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE, true /* usingLegacyDhcp */); diff --git a/tests/net/java/com/android/server/connectivity/TetheringTest.java b/tests/net/java/com/android/server/connectivity/TetheringTest.java index c030c3e9ac865..5f62c08f55f3d 100644 --- a/tests/net/java/com/android/server/connectivity/TetheringTest.java +++ b/tests/net/java/com/android/server/connectivity/TetheringTest.java @@ -25,8 +25,10 @@ import static android.net.ConnectivityManager.EXTRA_ACTIVE_TETHER; import static android.net.ConnectivityManager.EXTRA_AVAILABLE_TETHER; import static android.net.ConnectivityManager.TETHERING_USB; import static android.net.ConnectivityManager.TETHERING_WIFI; +import static android.net.ConnectivityManager.TETHER_ERROR_NO_ERROR; import static android.net.ConnectivityManager.TETHER_ERROR_UNKNOWN_IFACE; import static android.net.ConnectivityManager.TYPE_MOBILE; +import static android.net.ConnectivityManager.TYPE_WIFI_P2P; import static android.net.dhcp.IDhcpServer.STATUS_SUCCESS; import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_INTERFACE_NAME; import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_MODE; @@ -90,6 +92,9 @@ import android.net.util.NetworkConstants; import android.net.util.SharedLog; import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiManager; +import android.net.wifi.p2p.WifiP2pGroup; +import android.net.wifi.p2p.WifiP2pInfo; +import android.net.wifi.p2p.WifiP2pManager; import android.os.Bundle; import android.os.Handler; import android.os.INetworkManagementService; @@ -140,6 +145,7 @@ public class TetheringTest { 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_WLAN_IFNAME = "test_wlan0"; + private static final String TEST_P2P_IFNAME = "test_p2p-p2p0-0"; private static final int DHCPSERVER_START_TIMEOUT_MS = 1000; @@ -216,9 +222,10 @@ public class TetheringTest { assertTrue("Non-mocked interface " + ifName, ifName.equals(TEST_USB_IFNAME) || ifName.equals(TEST_WLAN_IFNAME) - || ifName.equals(TEST_MOBILE_IFNAME)); + || ifName.equals(TEST_MOBILE_IFNAME) + || ifName.equals(TEST_P2P_IFNAME)); final String[] ifaces = new String[] { - TEST_USB_IFNAME, TEST_WLAN_IFNAME, TEST_MOBILE_IFNAME }; + TEST_USB_IFNAME, TEST_WLAN_IFNAME, TEST_MOBILE_IFNAME, TEST_P2P_IFNAME}; return new InterfaceParams(ifName, ArrayUtils.indexOf(ifaces, ifName) + IFINDEX_OFFSET, MacAddress.ALL_ZEROS_ADDRESS); } @@ -361,6 +368,8 @@ public class TetheringTest { .thenReturn(new String[] { "test_rndis\\d" }); when(mResources.getStringArray(com.android.internal.R.array.config_tether_wifi_regexs)) .thenReturn(new String[]{ "test_wlan\\d" }); + when(mResources.getStringArray(com.android.internal.R.array.config_tether_wifi_p2p_regexs)) + .thenReturn(new String[]{ "test_p2p-p2p\\d-.*" }); when(mResources.getStringArray(com.android.internal.R.array.config_tether_bluetooth_regexs)) .thenReturn(new String[0]); when(mResources.getIntArray(com.android.internal.R.array.config_tether_upstream_types)) @@ -369,7 +378,7 @@ public class TetheringTest { .thenReturn(false); when(mNMService.listInterfaces()) .thenReturn(new String[] { - TEST_MOBILE_IFNAME, TEST_WLAN_IFNAME, TEST_USB_IFNAME}); + TEST_MOBILE_IFNAME, TEST_WLAN_IFNAME, TEST_USB_IFNAME, TEST_P2P_IFNAME}); when(mNMService.getInterfaceConfig(anyString())) .thenReturn(new InterfaceConfiguration()); when(mRouterAdvertisementDaemon.start()) @@ -423,6 +432,31 @@ public class TetheringTest { mServiceContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL); } + private static final String[] P2P_RECEIVER_PERMISSIONS_FOR_BROADCAST = { + android.Manifest.permission.ACCESS_FINE_LOCATION, + android.Manifest.permission.ACCESS_WIFI_STATE + }; + + private void sendWifiP2pConnectionChanged( + boolean isGroupFormed, boolean isGroupOwner, String ifname) { + WifiP2pInfo p2pInfo = new WifiP2pInfo(); + p2pInfo.groupFormed = isGroupFormed; + p2pInfo.isGroupOwner = isGroupOwner; + + NetworkInfo networkInfo = new NetworkInfo(TYPE_WIFI_P2P, 0, null, null); + + WifiP2pGroup group = new WifiP2pGroup(); + group.setIsGroupOwner(isGroupOwner); + group.setInterface(ifname); + + final Intent intent = new Intent(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION); + intent.putExtra(WifiP2pManager.EXTRA_WIFI_P2P_INFO, p2pInfo); + intent.putExtra(WifiP2pManager.EXTRA_NETWORK_INFO, networkInfo); + intent.putExtra(WifiP2pManager.EXTRA_WIFI_P2P_GROUP, group); + mServiceContext.sendBroadcastAsUserMultiplePermissions(intent, UserHandle.ALL, + P2P_RECEIVER_PERMISSIONS_FOR_BROADCAST); + } + private void sendUsbBroadcast(boolean connected, boolean configured, boolean rndisFunction) { final Intent intent = new Intent(UsbManager.ACTION_USB_STATE); intent.putExtra(USB_CONNECTED, connected); @@ -436,11 +470,11 @@ public class TetheringTest { mServiceContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL); } - private void verifyInterfaceServingModeStarted() throws Exception { - verify(mNMService, times(1)).getInterfaceConfig(TEST_WLAN_IFNAME); + private void verifyInterfaceServingModeStarted(String ifname) throws Exception { + verify(mNMService, times(1)).getInterfaceConfig(ifname); verify(mNMService, times(1)) - .setInterfaceConfig(eq(TEST_WLAN_IFNAME), any(InterfaceConfiguration.class)); - verify(mNMService, times(1)).tetherInterface(TEST_WLAN_IFNAME); + .setInterfaceConfig(eq(ifname), any(InterfaceConfiguration.class)); + verify(mNMService, times(1)).tetherInterface(ifname); } private void verifyTetheringBroadcast(String ifname, String whichExtra) { @@ -530,7 +564,7 @@ public class TetheringTest { sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_LOCAL_ONLY); mLooper.dispatchAll(); - verifyInterfaceServingModeStarted(); + verifyInterfaceServingModeStarted(TEST_WLAN_IFNAME); verifyTetheringBroadcast(TEST_WLAN_IFNAME, EXTRA_AVAILABLE_TETHER); verify(mNMService, times(1)).setIpForwardingEnabled(true); verify(mNMService, times(1)).startTethering(any(String[].class)); @@ -542,8 +576,9 @@ public class TetheringTest { verifyNoMoreInteractions(mWifiManager); verifyTetheringBroadcast(TEST_WLAN_IFNAME, EXTRA_ACTIVE_LOCAL_ONLY); verify(mUpstreamNetworkMonitor, times(1)).startObserveAllNetworks(); - // TODO: Figure out why this isn't exactly once, for sendTetherStateChangedBroadcast(). - assertTrue(1 <= mTetheringDependencies.isTetheringSupportedCalls); + // This will be called twice, one is on entering IpServer.STATE_AVAILABLE, + // and another one is on IpServer.STATE_TETHERED/IpServer.STATE_LOCAL_ONLY. + assertEquals(2, mTetheringDependencies.isTetheringSupportedCalls); // Emulate externally-visible WifiManager effects, when hotspot mode // is being torn down. @@ -552,9 +587,9 @@ public class TetheringTest { mLooper.dispatchAll(); verify(mNMService, times(1)).untetherInterface(TEST_WLAN_IFNAME); - // TODO: Why is {g,s}etInterfaceConfig() called more than once? - verify(mNMService, atLeastOnce()).getInterfaceConfig(TEST_WLAN_IFNAME); - verify(mNMService, atLeastOnce()) + // {g,s}etInterfaceConfig() called twice for enabling and disabling IPv4. + verify(mNMService, times(2)).getInterfaceConfig(TEST_WLAN_IFNAME); + verify(mNMService, times(2)) .setInterfaceConfig(eq(TEST_WLAN_IFNAME), any(InterfaceConfiguration.class)); verify(mNMService, times(1)).stopTethering(); verify(mNMService, times(1)).setIpForwardingEnabled(false); @@ -770,7 +805,7 @@ public class TetheringTest { sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED); mLooper.dispatchAll(); - verifyInterfaceServingModeStarted(); + verifyInterfaceServingModeStarted(TEST_WLAN_IFNAME); verifyTetheringBroadcast(TEST_WLAN_IFNAME, EXTRA_AVAILABLE_TETHER); verify(mNMService, times(1)).setIpForwardingEnabled(true); verify(mNMService, times(1)).startTethering(any(String[].class)); @@ -785,8 +820,9 @@ public class TetheringTest { // In tethering mode, in the default configuration, an explicit request // for a mobile network is also made. verify(mUpstreamNetworkMonitor, times(1)).registerMobileNetworkRequest(); - // TODO: Figure out why this isn't exactly once, for sendTetherStateChangedBroadcast(). - assertTrue(1 <= mTetheringDependencies.isTetheringSupportedCalls); + // This will be called twice, one is on entering IpServer.STATE_AVAILABLE, + // and another one is on IpServer.STATE_TETHERED/IpServer.STATE_LOCAL_ONLY. + assertEquals(2, mTetheringDependencies.isTetheringSupportedCalls); ///// // We do not currently emulate any upstream being found. @@ -809,7 +845,7 @@ public class TetheringTest { mLooper.dispatchAll(); verify(mNMService, times(1)).untetherInterface(TEST_WLAN_IFNAME); - // TODO: Why is {g,s}etInterfaceConfig() called more than once? + // {g,s}etInterfaceConfig() called twice for enabling and disabling IPv4. verify(mNMService, atLeastOnce()).getInterfaceConfig(TEST_WLAN_IFNAME); verify(mNMService, atLeastOnce()) .setInterfaceConfig(eq(TEST_WLAN_IFNAME), any(InterfaceConfiguration.class)); @@ -857,8 +893,9 @@ public class TetheringTest { TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_UNSPECIFIED); verify(mWifiManager).updateInterfaceIpState( TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_TETHERED); - // TODO: Figure out why this isn't exactly once, for sendTetherStateChangedBroadcast(). - assertTrue(1 <= mTetheringDependencies.isTetheringSupportedCalls); + // There are 3 state change event: + // AVAILABLE -> STATE_TETHERED -> STATE_AVAILABLE. + assertEquals(3, mTetheringDependencies.isTetheringSupportedCalls); verifyTetheringBroadcast(TEST_WLAN_IFNAME, EXTRA_AVAILABLE_TETHER); // This is called, but will throw. verify(mNMService, times(1)).setIpForwardingEnabled(true); @@ -1031,6 +1068,133 @@ public class TetheringTest { assertEquals(fakeSubId, newConfig.subId); } + private void workingWifiP2pGroupOwner( + boolean emulateInterfaceStatusChanged) throws Exception { + if (emulateInterfaceStatusChanged) { + mTethering.interfaceStatusChanged(TEST_P2P_IFNAME, true); + } + sendWifiP2pConnectionChanged(true, true, TEST_P2P_IFNAME); + mLooper.dispatchAll(); + + verifyInterfaceServingModeStarted(TEST_P2P_IFNAME); + verifyTetheringBroadcast(TEST_P2P_IFNAME, EXTRA_AVAILABLE_TETHER); + verify(mNMService, times(1)).setIpForwardingEnabled(true); + verify(mNMService, times(1)).startTethering(any(String[].class)); + verifyNoMoreInteractions(mNMService); + verifyTetheringBroadcast(TEST_P2P_IFNAME, EXTRA_ACTIVE_LOCAL_ONLY); + verify(mUpstreamNetworkMonitor, times(1)).startObserveAllNetworks(); + // This will be called twice, one is on entering IpServer.STATE_AVAILABLE, + // and another one is on IpServer.STATE_TETHERED/IpServer.STATE_LOCAL_ONLY. + assertEquals(2, mTetheringDependencies.isTetheringSupportedCalls); + + assertEquals(TETHER_ERROR_NO_ERROR, mTethering.getLastTetherError(TEST_P2P_IFNAME)); + + // Emulate externally-visible WifiP2pManager effects, when wifi p2p group + // is being removed. + sendWifiP2pConnectionChanged(false, true, TEST_P2P_IFNAME); + mTethering.interfaceRemoved(TEST_P2P_IFNAME); + mLooper.dispatchAll(); + + verify(mNMService, times(1)).untetherInterface(TEST_P2P_IFNAME); + // {g,s}etInterfaceConfig() called twice for enabling and disabling IPv4. + verify(mNMService, times(2)).getInterfaceConfig(TEST_P2P_IFNAME); + verify(mNMService, times(2)) + .setInterfaceConfig(eq(TEST_P2P_IFNAME), any(InterfaceConfiguration.class)); + verify(mNMService, times(1)).stopTethering(); + verify(mNMService, times(1)).setIpForwardingEnabled(false); + verify(mUpstreamNetworkMonitor, never()).getCurrentPreferredUpstream(); + verify(mUpstreamNetworkMonitor, never()).selectPreferredUpstreamType(any()); + verifyNoMoreInteractions(mNMService); + // Asking for the last error after the per-interface state machine + // has been reaped yields an unknown interface error. + assertEquals(TETHER_ERROR_UNKNOWN_IFACE, mTethering.getLastTetherError(TEST_P2P_IFNAME)); + } + + private void workingWifiP2pGroupClient( + boolean emulateInterfaceStatusChanged) throws Exception { + if (emulateInterfaceStatusChanged) { + mTethering.interfaceStatusChanged(TEST_P2P_IFNAME, true); + } + sendWifiP2pConnectionChanged(true, false, TEST_P2P_IFNAME); + mLooper.dispatchAll(); + + verify(mNMService, never()).getInterfaceConfig(TEST_P2P_IFNAME); + verify(mNMService, never()) + .setInterfaceConfig(eq(TEST_P2P_IFNAME), any(InterfaceConfiguration.class)); + verify(mNMService, never()).tetherInterface(TEST_P2P_IFNAME); + verify(mNMService, never()).setIpForwardingEnabled(true); + verify(mNMService, never()).startTethering(any(String[].class)); + + // Emulate externally-visible WifiP2pManager effects, when wifi p2p group + // is being removed. + sendWifiP2pConnectionChanged(false, false, TEST_P2P_IFNAME); + mTethering.interfaceRemoved(TEST_P2P_IFNAME); + mLooper.dispatchAll(); + + verify(mNMService, never()).untetherInterface(TEST_P2P_IFNAME); + verify(mNMService, never()).getInterfaceConfig(TEST_P2P_IFNAME); + verify(mNMService, never()) + .setInterfaceConfig(eq(TEST_P2P_IFNAME), any(InterfaceConfiguration.class)); + verify(mNMService, never()).stopTethering(); + verify(mNMService, never()).setIpForwardingEnabled(false); + verifyNoMoreInteractions(mNMService); + // Asking for the last error after the per-interface state machine + // has been reaped yields an unknown interface error. + assertEquals(TETHER_ERROR_UNKNOWN_IFACE, mTethering.getLastTetherError(TEST_P2P_IFNAME)); + } + + @Test + public void workingWifiP2pGroupOwnerWithIfaceChanged() throws Exception { + workingWifiP2pGroupOwner(true); + } + + @Test + public void workingWifiP2pGroupOwnerSansIfaceChanged() throws Exception { + workingWifiP2pGroupOwner(false); + } + + private void workingWifiP2pGroupOwnerLegacyMode( + boolean emulateInterfaceStatusChanged) throws Exception { + // change to legacy mode and update tethering information by chaning SIM + when(mResources.getStringArray(com.android.internal.R.array.config_tether_wifi_p2p_regexs)) + .thenReturn(new String[]{}); + final int fakeSubId = 1234; + mPhoneStateListener.onActiveDataSubscriptionIdChanged(fakeSubId); + + if (emulateInterfaceStatusChanged) { + mTethering.interfaceStatusChanged(TEST_P2P_IFNAME, true); + } + sendWifiP2pConnectionChanged(true, true, TEST_P2P_IFNAME); + mLooper.dispatchAll(); + + verify(mNMService, never()).getInterfaceConfig(TEST_P2P_IFNAME); + verify(mNMService, never()) + .setInterfaceConfig(eq(TEST_P2P_IFNAME), any(InterfaceConfiguration.class)); + verify(mNMService, never()).tetherInterface(TEST_P2P_IFNAME); + verify(mNMService, never()).setIpForwardingEnabled(true); + verify(mNMService, never()).startTethering(any(String[].class)); + assertEquals(TETHER_ERROR_UNKNOWN_IFACE, mTethering.getLastTetherError(TEST_P2P_IFNAME)); + } + @Test + public void workingWifiP2pGroupOwnerLegacyModeWithIfaceChanged() throws Exception { + workingWifiP2pGroupOwnerLegacyMode(true); + } + + @Test + public void workingWifiP2pGroupOwnerLegacyModeSansIfaceChanged() throws Exception { + workingWifiP2pGroupOwnerLegacyMode(false); + } + + @Test + public void workingWifiP2pGroupClientWithIfaceChanged() throws Exception { + workingWifiP2pGroupClient(true); + } + + @Test + public void workingWifiP2pGroupClientSansIfaceChanged() throws Exception { + workingWifiP2pGroupClient(false); + } + // TODO: Test that a request for hotspot mode doesn't interfere with an // already operating tethering mode interface. }