From f7b6434a65a7ef1f300210649e9bdf8cc155aad7 Mon Sep 17 00:00:00 2001 From: Quang Anh Luong Date: Mon, 8 Jul 2024 16:50:00 +0900 Subject: [PATCH] Start tethered hotspot with TetheringRequest Use WifiManager.startTetheredHotspot(TetheringRequest, Executor, SoftApCallback) to start softap. This allows for passing in the requested SoftApConfiguration via TetheringRequest. Bug: 216524590 Test: atest TetheringTest TetheringManagerTest Change-Id: Ie4fb851e8b2243d233f9a8b3e18e2fa335cec307 --- .../src/android/net/TetheringManager.java | 5 + .../tethering/RequestTracker.java | 5 + .../networkstack/tethering/Tethering.java | 95 ++++++- .../networkstack/tethering/TetheringTest.java | 267 +++++++++++++++--- 4 files changed, 324 insertions(+), 48 deletions(-) diff --git a/Tethering/common/TetheringLib/src/android/net/TetheringManager.java b/Tethering/common/TetheringLib/src/android/net/TetheringManager.java index 2f9c3bce50..a8a471dbc1 100644 --- a/Tethering/common/TetheringLib/src/android/net/TetheringManager.java +++ b/Tethering/common/TetheringLib/src/android/net/TetheringManager.java @@ -340,6 +340,11 @@ public class TetheringManager { * @hide */ public static final int TETHER_ERROR_BLUETOOTH_SERVICE_PENDING = 19; + /** + * Never used outside Tethering.java. + * @hide + */ + public static final int TETHER_ERROR_SOFT_AP_CALLBACK_PENDING = 20; /** @hide */ @Retention(RetentionPolicy.SOURCE) diff --git a/Tethering/src/com/android/networkstack/tethering/RequestTracker.java b/Tethering/src/com/android/networkstack/tethering/RequestTracker.java index 9c61716206..c91ff586e4 100644 --- a/Tethering/src/com/android/networkstack/tethering/RequestTracker.java +++ b/Tethering/src/com/android/networkstack/tethering/RequestTracker.java @@ -240,6 +240,11 @@ public class RequestTracker { mServingRequests.entrySet().removeIf(e -> e.getValue().getTetheringType() == type); } + @VisibleForTesting + List getServingTetheringRequests() { + return new ArrayList<>(mServingRequests.values()); + } + /** * Returns an existing (pending or serving) request that fuzzy matches the given request. * Optionally specify matchUid to only return requests with the same uid. diff --git a/Tethering/src/com/android/networkstack/tethering/Tethering.java b/Tethering/src/com/android/networkstack/tethering/Tethering.java index c7ae353958..0cf008bec5 100644 --- a/Tethering/src/com/android/networkstack/tethering/Tethering.java +++ b/Tethering/src/com/android/networkstack/tethering/Tethering.java @@ -47,6 +47,7 @@ import static android.net.TetheringManager.TETHER_ERROR_DUPLICATE_REQUEST; import static android.net.TetheringManager.TETHER_ERROR_INTERNAL_ERROR; import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR; import static android.net.TetheringManager.TETHER_ERROR_SERVICE_UNAVAIL; +import static android.net.TetheringManager.TETHER_ERROR_SOFT_AP_CALLBACK_PENDING; import static android.net.TetheringManager.TETHER_ERROR_UNAVAIL_IFACE; import static android.net.TetheringManager.TETHER_ERROR_UNKNOWN_IFACE; import static android.net.TetheringManager.TETHER_ERROR_UNKNOWN_REQUEST; @@ -114,6 +115,7 @@ import android.net.TetheringInterface; import android.net.TetheringManager.TetheringRequest; import android.net.Uri; import android.net.ip.IpServer; +import android.net.wifi.SoftApState; import android.net.wifi.WifiClient; import android.net.wifi.WifiManager; import android.net.wifi.p2p.WifiP2pGroup; @@ -775,7 +777,7 @@ public class Tethering { final int result; switch (type) { case TETHERING_WIFI: - result = setWifiTethering(enable); + result = setWifiTethering(enable, request, listener); break; case TETHERING_USB: result = setUsbTethering(enable); @@ -800,6 +802,9 @@ public class Tethering { // The result of Bluetooth tethering will be sent after the pan service connects. if (result == TETHER_ERROR_BLUETOOTH_SERVICE_PENDING) return; + // The result of Wifi tethering will be sent after the SoftApCallback result. + if (result == TETHER_ERROR_SOFT_AP_CALLBACK_PENDING) return; + sendTetherResultAndRemoveOnError(request, listener, result); } @@ -824,7 +829,8 @@ public class Tethering { } } - private int setWifiTethering(final boolean enable) { + private int setWifiTethering(final boolean enable, TetheringRequest request, + IIntResultListener listener) { final long ident = Binder.clearCallingIdentity(); try { final WifiManager mgr = getWifiManager(); @@ -832,8 +838,34 @@ public class Tethering { mLog.e("setWifiTethering: failed to get WifiManager!"); return TETHER_ERROR_SERVICE_UNAVAIL; } - if ((enable && mgr.startTetheredHotspot(null /* use existing softap config */)) - || (!enable && mgr.stopSoftAp())) { + final boolean success; + if (enable) { + if (isTetheringWithSoftApConfigEnabled()) { + // Notes: + // - A call to startTetheredHotspot can only succeed if the SoftAp is idle. If + // the SoftAp is running or is being disabled, the call will fail. + // - If a call to startTetheredHotspot fails, the callback is immediately called + // with WIFI_AP_STATE_FAILED and a null interface. + // - If a call to startTetheredHotspot succeeds, the passed-in callback is the + // only callback that will receive future WIFI_AP_STATE_ENABLED and + // WIFI_AP_STATE_DISABLED events in the future, until another call to + // startTetheredHotspot succeeds, at which point the old callback will stop + // receiving any events. + // - Wifi may decide to restart the hotspot at any time (such as for a CC + // change), and if it does so, it will send WIFI_AP_STATE_DISABLED and then + // either WIFI_AP_STATE_ENABLED or (if restarting fails) WIFI_AP_STATE_FAILED. + mgr.startTetheredHotspot(request, mExecutor, + new StartTetheringSoftApCallback(listener)); + // Result isn't used since we get the real result via + // StartTetheringSoftApCallback. + return TETHER_ERROR_SOFT_AP_CALLBACK_PENDING; + } + success = mgr.startTetheredHotspot(null); + } else { + success = mgr.stopSoftAp(); + } + + if (success) { return TETHER_ERROR_NO_ERROR; } } finally { @@ -1458,6 +1490,9 @@ public class Tethering { final String ifname = intent.getStringExtra(EXTRA_WIFI_AP_INTERFACE_NAME); final int ipmode = intent.getIntExtra(EXTRA_WIFI_AP_MODE, IFACE_IP_MODE_UNSPECIFIED); + // In B+, Tethered AP is handled by StartTetheringSoftApCallback. + if (isTetheringWithSoftApConfigEnabled() && ipmode == IFACE_IP_MODE_TETHERED) return; + switch (curState) { case WifiManager.WIFI_AP_STATE_ENABLING: // We can see this state on the way to both enabled and failure states. @@ -1538,11 +1573,63 @@ public class Tethering { } } + class StartTetheringSoftApCallback implements SoftApCallback { + + @Nullable + IIntResultListener mPendingListener; + + StartTetheringSoftApCallback(IIntResultListener pendingListener) { + mPendingListener = pendingListener; + } + + @Override + public void onStateChanged(SoftApState softApState) { + final int state = softApState.getState(); + final String iface = softApState.getIface(); + final TetheringRequest request = softApState.getTetheringRequest(); + switch (softApState.getState()) { + case WifiManager.WIFI_AP_STATE_ENABLED: + enableIpServing(request, iface); + // If stopTethering has already been called, IP serving will still be started, + // but as soon as the wifi code processes the stop, WIFI_AP_STATE_DISABLED will + // be sent and tethering will be stopped again. + sendTetherResultAndRemoveOnError(request, mPendingListener, + TETHER_ERROR_NO_ERROR); + mPendingListener = null; + break; + case WifiManager.WIFI_AP_STATE_FAILED: + // TODO: if a call to startTethering happens just after a call to stopTethering, + // the start will fail because hotspot is still being disabled. This likely + // cannot be fixed in tethering code but must be fixed in WiFi. + sendTetherResultAndRemoveOnError(request, mPendingListener, + TETHER_ERROR_INTERNAL_ERROR); + mPendingListener = null; + break; + case WifiManager.WIFI_AP_STATE_DISABLED: + // TODO(b/403164072): SoftAP may restart due to CC change, in which we'll get + // DISABLED -> ENABLED (or FAILED). Before the transition back to ENABLED is + // complete, it is possible that a new Wifi request is accepted since there's no + // active request to fuzzy-match it, which will unexpectedly cause Wifi to + // overwrite this SoftApCallback. This should be fixed in Wifi to disallow any + // new calls to startTetheredHotspot while SoftAP is restarting. + disableWifiIpServing(iface, state); + break; + default: + break; + } + } + } + @VisibleForTesting List getPendingTetheringRequests() { return mRequestTracker.getPendingTetheringRequests(); } + @VisibleForTesting + List getServingTetheringRequests() { + return mRequestTracker.getServingTetheringRequests(); + } + @VisibleForTesting boolean isTetheringActive() { return getTetheredIfaces().length > 0; diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java index 1083ef98e5..dc3cbd2614 100644 --- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java +++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java @@ -69,6 +69,7 @@ import static android.net.wifi.WifiManager.IFACE_IP_MODE_LOCAL_ONLY; import static android.net.wifi.WifiManager.IFACE_IP_MODE_TETHERED; import static android.net.wifi.WifiManager.WIFI_AP_STATE_DISABLED; import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLED; +import static android.net.wifi.WifiManager.WIFI_AP_STATE_FAILED; import static android.system.OsConstants.RT_SCOPE_UNIVERSE; import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID; @@ -169,6 +170,7 @@ import android.net.ip.DadProxy; import android.net.ip.IpServer; import android.net.ip.RouterAdvertisementDaemon; import android.net.wifi.SoftApConfiguration; +import android.net.wifi.SoftApState; import android.net.wifi.WifiClient; import android.net.wifi.WifiManager; import android.net.wifi.WifiManager.SoftApCallback; @@ -241,6 +243,7 @@ import java.util.Collections; import java.util.List; import java.util.Set; import java.util.Vector; +import java.util.concurrent.Executor; @RunWith(AndroidJUnit4.class) @SmallTest @@ -842,6 +845,38 @@ public class TetheringTest { mLooper.dispatchAll(); } + private void sendStartTetheringSoftApCallback(int state, TetheringRequest request, + String ifname) { + ArgumentCaptor callbackCaptor = + ArgumentCaptor.forClass(SoftApCallback.class); + verify(mWifiManager, atLeastOnce()).startTetheredHotspot(any(TetheringRequest.class), + any(Executor.class), callbackCaptor.capture()); + SoftApState softApState = mock(SoftApState.class); + when(softApState.getState()).thenReturn(state); + when(softApState.getTetheringRequest()).thenReturn(request); + when(softApState.getIface()).thenReturn(ifname); + callbackCaptor.getValue().onStateChanged(softApState); + mLooper.dispatchAll(); + } + + private void verifyWifiTetheringRequested() { + if (mTetheringDependencies.isTetheringWithSoftApConfigEnabled()) { + verify(mWifiManager).startTetheredHotspot(any(), any(), any()); + } else { + verify(mWifiManager).startTetheredHotspot(null); + } + verify(mWifiManager, never()).stopSoftAp(); + verifyNoMoreInteractions(mWifiManager); + } + + private void sendSoftApEvent(int state, TetheringRequest request, String ifname) { + if (mTetheringDependencies.isTetheringWithSoftApConfigEnabled()) { + sendStartTetheringSoftApCallback(state, request, ifname); + } else { + sendWifiApStateChanged(state, ifname, IFACE_IP_MODE_TETHERED); + } + } + private static final String[] P2P_RECEIVER_PERMISSIONS_FOR_BROADCAST = { android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.ACCESS_WIFI_STATE @@ -1964,11 +1999,9 @@ public class TetheringTest { initTetheringOnTestThread(); // Emulate pressing the WiFi tethering button. - mTethering.startTethering(createTetheringRequest(TETHERING_WIFI), TEST_CALLER_PKG, - null); + mTethering.startTethering(createTetheringRequest(TETHERING_WIFI), TEST_CALLER_PKG, null); mLooper.dispatchAll(); - verify(mWifiManager, times(1)).startTetheredHotspot(null); - verifyNoMoreInteractions(mWifiManager); + verifyWifiTetheringRequested(); verifyNoMoreInteractions(mNetd); // Emulate externally-visible WifiManager effects, causing the @@ -1997,14 +2030,15 @@ public class TetheringTest { // TODO: Test with and without interfaceStatusChanged(). @Test public void workingWifiTetheringEnrichedApBroadcast() throws Exception { + // B+ uses SoftApCallback instead of WIFI_AP_STATE_CHANGED for tethered hotspot. + mTetheringWithSoftApConfigEnabled = false; initTetheringOnTestThread(); // Emulate pressing the WiFi tethering button. mTethering.startTethering(createTetheringRequest(TETHERING_WIFI), TEST_CALLER_PKG, null); mLooper.dispatchAll(); - verify(mWifiManager, times(1)).startTetheredHotspot(null); - verifyNoMoreInteractions(mWifiManager); + verifyWifiTetheringRequested(); verifyNoMoreInteractions(mNetd); // Emulate externally-visible WifiManager effects, causing the @@ -2042,6 +2076,122 @@ public class TetheringTest { verifyStopHotpot(); } + @Test + public void startWifiTetheringWithSoftApConfigurationSuccess() throws Exception { + assumeTrue(mTetheringDependencies.isTetheringWithSoftApConfigEnabled()); + initTetheringOnTestThread(); + + // Emulate pressing the WiFi tethering button. + TetheringRequest request = new TetheringRequest.Builder(TETHERING_WIFI) + .setSoftApConfiguration(new SoftApConfiguration.Builder() + .setWifiSsid(WifiSsid.fromBytes("SSID".getBytes(StandardCharsets.UTF_8))) + .build()) + .build(); + IIntResultListener startResultListener = mock(IIntResultListener.class); + mTethering.startTethering(request, TEST_CALLER_PKG, startResultListener); + mLooper.dispatchAll(); + verifyNoMoreInteractions(mNetd); + verify(startResultListener, never()).onResult(anyInt()); + // Emulate Wifi iface enabled + sendStartTetheringSoftApCallback(WIFI_AP_STATE_ENABLED, request, TEST_WLAN_IFNAME); + + verifyStartHotspot(); + verifyTetheringBroadcast(TEST_WLAN_IFNAME, EXTRA_ACTIVE_TETHER); + verify(startResultListener).onResult(TETHER_ERROR_NO_ERROR); + } + + @Test + public void startWifiTetheringWithSoftApConfigurationFailure() throws Exception { + assumeTrue(mTetheringDependencies.isTetheringWithSoftApConfigEnabled()); + initTetheringOnTestThread(); + + // Emulate pressing the WiFi tethering button. + TetheringRequest request = new TetheringRequest.Builder(TETHERING_WIFI) + .setSoftApConfiguration(new SoftApConfiguration.Builder() + .setWifiSsid(WifiSsid.fromBytes("SSID".getBytes(StandardCharsets.UTF_8))) + .build()) + .build(); + IIntResultListener startResultListener = mock(IIntResultListener.class); + mTethering.startTethering(request, TEST_CALLER_PKG, startResultListener); + mLooper.dispatchAll(); + verify(startResultListener, never()).onResult(anyInt()); + // Emulate Wifi iface failure + sendStartTetheringSoftApCallback(WIFI_AP_STATE_FAILED, request, TEST_WLAN_IFNAME); + + verify(startResultListener).onResult(TETHER_ERROR_INTERNAL_ERROR); + } + + @Test + public void startWifiTetheringWithSoftApConfigurationRestartAfterStarting() throws Exception { + assumeTrue(mTetheringDependencies.isTetheringWithSoftApConfigEnabled()); + initTetheringOnTestThread(); + TestTetheringEventCallback callback = new TestTetheringEventCallback(); + SoftApConfiguration softApConfig = new SoftApConfiguration.Builder() + .setWifiSsid(WifiSsid.fromBytes("SSID".getBytes(StandardCharsets.UTF_8))) + .build(); + final TetheringInterface wifiIface = new TetheringInterface( + TETHERING_WIFI, TEST_WLAN_IFNAME); + final TetheringInterface wifiIfaceWithConfig = new TetheringInterface( + TETHERING_WIFI, TEST_WLAN_IFNAME, softApConfig); + + // 1. Register one callback before running any tethering. + mTethering.registerTetheringEventCallback(callback); + mLooper.dispatchAll(); + assertTetherStatesNotNullButEmpty(callback.pollTetherStatesChanged()); + // Emulate pressing the WiFi tethering button. + TetheringRequest request = new TetheringRequest.Builder(TETHERING_WIFI) + .setSoftApConfiguration(softApConfig) + .build(); + IIntResultListener startResultListener = mock(IIntResultListener.class); + mTethering.startTethering(request, TEST_CALLER_PKG, startResultListener); + mLooper.dispatchAll(); + + // Wifi success + sendStartTetheringSoftApCallback(WIFI_AP_STATE_ENABLED, request, TEST_WLAN_IFNAME); + verifyStartHotspot(); + TetherStatesParcel tetherState = callback.pollTetherStatesChanged(); + assertArrayEquals(tetherState.availableList, new TetheringInterface[] {wifiIface}); + tetherState = callback.pollTetherStatesChanged(); + assertArrayEquals(tetherState.tetheredList, new TetheringInterface[] {wifiIfaceWithConfig}); + verify(startResultListener).onResult(TETHER_ERROR_NO_ERROR); + + // Restart Wifi + sendStartTetheringSoftApCallback(WIFI_AP_STATE_DISABLED, request, TEST_WLAN_IFNAME); + sendStartTetheringSoftApCallback(WIFI_AP_STATE_ENABLED, request, TEST_WLAN_IFNAME); + + // Verify we go from TETHERED -> AVAILABLE -> TETHERED with the same config. + tetherState = callback.pollTetherStatesChanged(); + assertArrayEquals(tetherState.availableList, new TetheringInterface[] {wifiIface}); + tetherState = callback.pollTetherStatesChanged(); + assertArrayEquals(tetherState.tetheredList, new TetheringInterface[] {wifiIfaceWithConfig}); + } + + @Test + public void startWifiApBroadcastDoesNotStartIpServing() throws Exception { + assumeTrue(mTetheringDependencies.isTetheringWithSoftApConfigEnabled()); + initTetheringOnTestThread(); + + // Call startTethering for wifi + TetheringRequest request = new TetheringRequest.Builder(TETHERING_WIFI) + .setSoftApConfiguration(new SoftApConfiguration.Builder() + .setWifiSsid(WifiSsid.fromBytes("SSID".getBytes(StandardCharsets.UTF_8))) + .build()) + .build(); + IIntResultListener startResultListener = mock(IIntResultListener.class); + mTethering.startTethering(request, TEST_CALLER_PKG, startResultListener); + mLooper.dispatchAll(); + + // WIFI_AP_STATE_CHANGED broadcast should be ignored since we should only be using + // SoftApCallback for tethered AP. + sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED); + sendWifiApStateChanged(WIFI_AP_STATE_DISABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED); + verify(mNetd, never()).tetherStartWithConfiguration(any()); + verify(mNotificationUpdater, never()).onDownstreamChanged(DOWNSTREAM_NONE); + verify(mWifiManager, never()).updateInterfaceIpState( + TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_UNSPECIFIED); + assertTrue(mTethering.getServingTetheringRequests().isEmpty()); + } + // TODO: Test with and without interfaceStatusChanged(). @Test public void failureEnablingIpForwarding() throws Exception { @@ -2049,11 +2199,10 @@ public class TetheringTest { doThrow(new RemoteException()).when(mNetd).ipfwdEnableForwarding(TETHERING_NAME); // Emulate pressing the WiFi tethering button. - mTethering.startTethering(createTetheringRequest(TETHERING_WIFI), TEST_CALLER_PKG, - null); + TetheringRequest request = createTetheringRequest(TETHERING_WIFI); + mTethering.startTethering(request, TEST_CALLER_PKG, null); mLooper.dispatchAll(); - verify(mWifiManager, times(1)).startTetheredHotspot(null); - verifyNoMoreInteractions(mWifiManager); + verifyWifiTetheringRequested(); verifyNoMoreInteractions(mNetd); verify(mTetheringMetrics).createBuilder(eq(TETHERING_WIFI), anyString()); @@ -2061,7 +2210,7 @@ public class TetheringTest { // per-interface state machine to start up, and telling us that // tethering mode is to be started. mTethering.interfaceStatusChanged(TEST_WLAN_IFNAME, true); - sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED); + sendSoftApEvent(WIFI_AP_STATE_ENABLED, request, TEST_WLAN_IFNAME); // We verify get/set called three times here: twice for setup and once during // teardown because all events happen over the course of the single @@ -2390,7 +2539,8 @@ public class TetheringTest { UpstreamNetworkState upstreamState = buildMobileDualStackUpstreamState(); initTetheringUpstream(upstreamState); - mTethering.startTethering(createTetheringRequest(TETHERING_WIFI), TEST_CALLER_PKG, + TetheringRequest request = createTetheringRequest(TETHERING_WIFI); + mTethering.startTethering(request, TEST_CALLER_PKG, null); mTethering.interfaceStatusChanged(TEST_WLAN_IFNAME, true); mLooper.dispatchAll(); @@ -2398,7 +2548,7 @@ public class TetheringTest { // Starting in B, ignore the interfaceStatusChanged callback.assertNoStateChangeCallback(); } - sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED); + sendSoftApEvent(WIFI_AP_STATE_ENABLED, request, TEST_WLAN_IFNAME); mLooper.dispatchAll(); tetherState = callback.pollTetherStatesChanged(); assertArrayEquals(tetherState.availableList, new TetheringInterface[] {wifiIface}); @@ -2426,8 +2576,7 @@ public class TetheringTest { if (isAtLeastT()) { // After T, tethering doesn't support WIFI_AP_STATE_DISABLED with null interface name. callback2.assertNoStateChangeCallback(); - sendWifiApStateChanged(WIFI_AP_STATE_DISABLED, TEST_WLAN_IFNAME, - IFACE_IP_MODE_TETHERED); + sendSoftApEvent(WIFI_AP_STATE_DISABLED, request, TEST_WLAN_IFNAME); } tetherState = callback2.pollTetherStatesChanged(); assertArrayEquals(tetherState.availableList, new TetheringInterface[] {wifiIface}); @@ -2500,7 +2649,8 @@ public class TetheringTest { mLooper.dispatchAll(); // Netd "up" event should not trigger a state change callback in B+. callback.assertNoStateChangeCallback(); - sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED); + sendStartTetheringSoftApCallback(WIFI_AP_STATE_ENABLED, tetheringRequest, + TEST_WLAN_IFNAME); // Verify we see Available -> Tethered states assertArrayEquals(new TetheringInterface[] {wifiIfaceWithoutConfig}, callback.pollTetherStatesChanged().availableList); @@ -2520,8 +2670,8 @@ public class TetheringTest { // Disable wifi tethering mTethering.stopTethering(TETHERING_WIFI); mLooper.dispatchAll(); - sendWifiApStateChanged(WIFI_AP_STATE_DISABLED, TEST_WLAN_IFNAME, - IFACE_IP_MODE_TETHERED); + sendStartTetheringSoftApCallback(WIFI_AP_STATE_DISABLED, tetheringRequest, + TEST_WLAN_IFNAME); assertArrayEquals(new TetheringInterface[] {wifiIfaceWithoutConfig}, callback.pollTetherStatesChanged().availableList); assertArrayEquals(new TetheringInterface[] {wifiIfaceWithoutConfig}, @@ -2566,7 +2716,7 @@ public class TetheringTest { ResultListener successListener = new ResultListener(TETHER_ERROR_NO_ERROR); mTethering.startTethering(tetheringRequest, TEST_CALLER_PKG, successListener); mLooper.dispatchAll(); - successListener.assertHasResult(); + successListener.assertDoesNotHaveResult(); // Try starting wifi tethering with various fuzzy-matching requests and verify we get // TETHER_ERROR_DUPLICATE_REQUEST. @@ -2581,8 +2731,7 @@ public class TetheringTest { ResultListener differentIpAddrListener = new ResultListener(TETHER_ERROR_DUPLICATE_REQUEST); mTethering.startTethering(differentIpAddr, TEST_CALLER_PKG, differentIpAddrListener); mLooper.dispatchAll(); - verify(mWifiManager, times(1)).startTetheredHotspot(any()); - verify(mWifiManager, never()).stopSoftAp(); + verifyWifiTetheringRequested(); differentIpAddrListener.assertHasResult(); // Different UID @@ -2593,13 +2742,13 @@ public class TetheringTest { mTethering.startTethering(differentUid, TEST_CALLER_PKG, differentUidListener); mLooper.dispatchAll(); differentUidListener.assertHasResult(); - verify(mWifiManager, times(1)).startTetheredHotspot(any()); - verify(mWifiManager, never()).stopSoftAp(); + verifyWifiTetheringRequested(); - // Mock the link layer event to start IP serving and verify we still get - // TETHER_ERROR_DUPLICATE_REQUEST even though the request is no longer pending and is - // already serving. - sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED); + // Mock the link layer event to start IP serving and verify + // 1) The original request's result listener is called. + // 2) We still get TETHER_ERROR_DUPLICATE_REQUEST for new requests. + sendStartTetheringSoftApCallback(WIFI_AP_STATE_ENABLED, tetheringRequest, TEST_WLAN_IFNAME); + successListener.assertHasResult(); assertArrayEquals(new TetheringInterface[] {wifiIfaceWithoutConfig}, callback.pollTetherStatesChanged().availableList); assertArrayEquals(new TetheringInterface[] {wifiIfaceWithConfig}, @@ -2613,7 +2762,7 @@ public class TetheringTest { mLooper.dispatchAll(); differentIpAddrListener.assertHasResult(); differentUidListener.assertHasResult(); - verify(mWifiManager, times(1)).startTetheredHotspot(any()); + verify(mWifiManager, times(1)).startTetheredHotspot(any(), any(), any()); verify(mWifiManager, never()).stopSoftAp(); } @@ -2630,8 +2779,8 @@ public class TetheringTest { ResultListener successListener = new ResultListener(TETHER_ERROR_NO_ERROR); mTethering.startTethering(tetheringRequest, TEST_CALLER_PKG, successListener); mLooper.dispatchAll(); + sendStartTetheringSoftApCallback(WIFI_AP_STATE_ENABLED, tetheringRequest, TEST_WLAN_IFNAME); successListener.assertHasResult(); - sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED); // Starting wifi again will cause TETHER_ERROR_DUPLICATE_REQUEST ResultListener failureListener = new ResultListener(TETHER_ERROR_DUPLICATE_REQUEST); @@ -2662,9 +2811,8 @@ public class TetheringTest { ResultListener successListener = new ResultListener(TETHER_ERROR_NO_ERROR); mTethering.startTethering(tetheringRequest, TEST_CALLER_PKG, successListener); mLooper.dispatchAll(); + sendStartTetheringSoftApCallback(WIFI_AP_STATE_ENABLED, tetheringRequest, TEST_WLAN_IFNAME); successListener.assertHasResult(); - sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED); - // Starting wifi again will cause TETHER_ERROR_DUPLICATE_REQUEST ResultListener failureListener = new ResultListener(TETHER_ERROR_DUPLICATE_REQUEST); mTethering.startTethering(tetheringRequest, TEST_CALLER_PKG, failureListener); @@ -2672,12 +2820,15 @@ public class TetheringTest { failureListener.assertHasResult(); // Trigger wifi ap state change to tell IpServer it's unwanted. - sendWifiApStateChanged(WIFI_AP_STATE_DISABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED); + sendStartTetheringSoftApCallback(WIFI_AP_STATE_DISABLED, tetheringRequest, + TEST_WLAN_IFNAME); // We should be able to request the same Wifi again ResultListener successListener2 = new ResultListener(TETHER_ERROR_NO_ERROR); mTethering.startTethering(tetheringRequest, TEST_CALLER_PKG, successListener2); mLooper.dispatchAll(); + sendStartTetheringSoftApCallback(WIFI_AP_STATE_ENABLED, tetheringRequest, + TEST_WLAN_IFNAME); successListener2.assertHasResult(); } @@ -2697,14 +2848,15 @@ public class TetheringTest { ResultListener successListener = new ResultListener(TETHER_ERROR_NO_ERROR); mTethering.startTethering(tetheringRequest, TEST_CALLER_PKG, successListener); mLooper.dispatchAll(); + sendStartTetheringSoftApCallback(WIFI_AP_STATE_ENABLED, tetheringRequest, TEST_WLAN_IFNAME); successListener.assertHasResult(); - sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED); // We should be able to request the same Wifi again since the DHCP server transitioned the // IpServer back to InitialState. ResultListener successListener2 = new ResultListener(TETHER_ERROR_NO_ERROR); mTethering.startTethering(tetheringRequest, TEST_CALLER_PKG, successListener2); mLooper.dispatchAll(); + sendStartTetheringSoftApCallback(WIFI_AP_STATE_ENABLED, tetheringRequest, TEST_WLAN_IFNAME); successListener2.assertHasResult(); } @@ -2721,7 +2873,10 @@ public class TetheringTest { ResultListener successListener = new ResultListener(TETHER_ERROR_NO_ERROR); mTethering.startTethering(tetheringRequest, TEST_CALLER_PKG, successListener); mLooper.dispatchAll(); - successListener.assertHasResult(); + ArgumentCaptor callbackCaptor = + ArgumentCaptor.forClass(SoftApCallback.class); + verify(mWifiManager, atLeastOnce()).startTetheredHotspot(any(TetheringRequest.class), + any(Executor.class), callbackCaptor.capture()); // Starting wifi again will cause TETHER_ERROR_DUPLICATE_REQUEST ResultListener failureListener = new ResultListener(TETHER_ERROR_DUPLICATE_REQUEST); @@ -2736,6 +2891,20 @@ public class TetheringTest { ResultListener successListener2 = new ResultListener(TETHER_ERROR_NO_ERROR); mTethering.startTethering(tetheringRequest, TEST_CALLER_PKG, successListener2); mLooper.dispatchAll(); + sendStartTetheringSoftApCallback(WIFI_AP_STATE_ENABLED, tetheringRequest, TEST_WLAN_IFNAME); + successListener2.assertHasResult(); + + // Mock the first request going up and then down from the stop request. + SoftApState softApState = mock(SoftApState.class); + when(softApState.getState()).thenReturn(WIFI_AP_STATE_ENABLED); + when(softApState.getTetheringRequest()).thenReturn(tetheringRequest); + when(softApState.getIface()).thenReturn(TEST_WLAN_IFNAME); + callbackCaptor.getValue().onStateChanged(softApState); + mLooper.dispatchAll(); + successListener.assertHasResult(); + + // Mock the second request going up + sendStartTetheringSoftApCallback(WIFI_AP_STATE_ENABLED, tetheringRequest, TEST_WLAN_IFNAME); successListener2.assertHasResult(); } @@ -2752,8 +2921,8 @@ public class TetheringTest { ResultListener successListener = new ResultListener(TETHER_ERROR_NO_ERROR); mTethering.startTethering(tetheringRequest, TEST_CALLER_PKG, successListener); mLooper.dispatchAll(); + sendStartTetheringSoftApCallback(WIFI_AP_STATE_ENABLED, tetheringRequest, TEST_WLAN_IFNAME); successListener.assertHasResult(); - sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED); // Starting wifi again will cause TETHER_ERROR_DUPLICATE_REQUEST ResultListener failureListener = new ResultListener(TETHER_ERROR_DUPLICATE_REQUEST); @@ -2768,6 +2937,7 @@ public class TetheringTest { ResultListener successListener2 = new ResultListener(TETHER_ERROR_NO_ERROR); mTethering.startTethering(tetheringRequest, TEST_CALLER_PKG, successListener2); mLooper.dispatchAll(); + sendStartTetheringSoftApCallback(WIFI_AP_STATE_ENABLED, tetheringRequest, TEST_WLAN_IFNAME); successListener2.assertHasResult(); } @@ -3216,7 +3386,7 @@ public class TetheringTest { serverLinkAddr, clientLinkAddr, false, CONNECTIVITY_SCOPE_GLOBAL, null), TEST_CALLER_PKG, null); mLooper.dispatchAll(); - verify(mWifiManager, times(1)).startTetheredHotspot(any()); + verifyWifiTetheringRequested(); mTethering.interfaceStatusChanged(TEST_WLAN_IFNAME, true); // Call legacyTether on the interface before the link layer event comes back. @@ -3588,8 +3758,11 @@ public class TetheringTest { reset(mDhcpServer); // Run wifi tethering. + TetheringRequest request = createTetheringRequest(TETHERING_WIFI); + mTethering.startTethering(request, TEST_CALLER_PKG, null); + mLooper.dispatchAll(); mTethering.interfaceStatusChanged(TEST_WLAN_IFNAME, true); - sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED); + sendSoftApEvent(WIFI_AP_STATE_ENABLED, request, TEST_WLAN_IFNAME); verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS)).startWithCallbacks( any(), dhcpEventCbsCaptor.capture()); eventCallbacks = dhcpEventCbsCaptor.getValue(); @@ -3652,8 +3825,12 @@ public class TetheringTest { }); callback.expectTetheredClientChanged(Collections.emptyList()); + TetheringRequest request = createTetheringRequest(TETHERING_WIFI); + mTethering.startTethering(request, TEST_CALLER_PKG, null); + mLooper.dispatchAll(); + verifyWifiTetheringRequested(); mTethering.interfaceStatusChanged(TEST_WLAN_IFNAME, true); - sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED); + sendSoftApEvent(WIFI_AP_STATE_ENABLED, request, TEST_WLAN_IFNAME); final ArgumentCaptor dhcpEventCbsCaptor = ArgumentCaptor.forClass(IDhcpEventCallbacks.class); verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS)).startWithCallbacks( @@ -4184,8 +4361,11 @@ public class TetheringTest { @Test public void testIpv4AddressForSapAndLohsConcurrency() throws Exception { initTetheringOnTestThread(); + TetheringRequest request = createTetheringRequest(TETHERING_WIFI); + mTethering.startTethering(request, TEST_CALLER_PKG, null); + mLooper.dispatchAll(); mTethering.interfaceStatusChanged(TEST_WLAN_IFNAME, true); - sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED); + sendSoftApEvent(WIFI_AP_STATE_ENABLED, request, TEST_WLAN_IFNAME); ArgumentCaptor ifaceConfigCaptor = ArgumentCaptor.forClass(InterfaceConfigurationParcel.class); @@ -4234,14 +4414,13 @@ public class TetheringTest { assertEquals(TETHER_ERROR_NO_ERROR, mTethering.getLastErrorForTest(TEST_P2P_IFNAME)); // Emulate pressing the WiFi tethering button. - mTethering.startTethering(createTetheringRequest(TETHERING_WIFI), TEST_CALLER_PKG, - null); + TetheringRequest request = createTetheringRequest(TETHERING_WIFI); + mTethering.startTethering(request, TEST_CALLER_PKG, null); mLooper.dispatchAll(); - verify(mWifiManager).startTetheredHotspot(null); - verifyNoMoreInteractions(mWifiManager); + verifyWifiTetheringRequested(); mTethering.interfaceStatusChanged(TEST_WLAN_IFNAME, true); - sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED); + sendSoftApEvent(WIFI_AP_STATE_ENABLED, request, TEST_WLAN_IFNAME); verifyTetheringBroadcast(TEST_WLAN_IFNAME, EXTRA_AVAILABLE_TETHER); verify(mWifiManager).updateInterfaceIpState(