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
This commit is contained in:
Quang Anh Luong 2024-07-08 16:50:00 +09:00
commit f7b6434a65
4 changed files with 324 additions and 48 deletions

View file

@ -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)

View file

@ -240,6 +240,11 @@ public class RequestTracker {
mServingRequests.entrySet().removeIf(e -> e.getValue().getTetheringType() == type);
}
@VisibleForTesting
List<TetheringRequest> 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.

View file

@ -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<TetheringRequest> getPendingTetheringRequests() {
return mRequestTracker.getPendingTetheringRequests();
}
@VisibleForTesting
List<TetheringRequest> getServingTetheringRequests() {
return mRequestTracker.getServingTetheringRequests();
}
@VisibleForTesting
boolean isTetheringActive() {
return getTetheredIfaces().length > 0;

View file

@ -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<SoftApCallback> 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<SoftApCallback> 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<IDhcpEventCallbacks> 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<InterfaceConfigurationParcel> 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(