Fix CtsTetheringTest on devices without permanent softAp interfaces.
Normally stop wifi tethering flow would be:
Tethering#stopTethering -> WifiManager#stopSoftAp -> softAp disabled,
then have WIIF_AP_STATE_CHANGED intent -> stop IpServer and broadcast
TETHER_STATE_CHANGED intent. SoftAp is disabled before tethering stop.
Because tethering would shutdown the corresponding IpServer if it
observed the interface is removed. For those devices that softAp
interface would be removed when stop tethering, the flow may be
Tethering#stopTethering -> WifiManager#stopSoftAp -> softAp disabing,
softAp interface is removed -> tethering trigger stop IpServer and
broadcast TETHER_STATE_CHANGED intent -> -> softAp disabled, then wifi
broadcast WIIF_AP_STATE_CHANGED intent. In this case, tethering is
stopped ready before softap is disabled.
For this case, CtsTeteringTest would have race between two test cases.
If two case need to start wifi tethering for testing and stop wifi
tethering after finish testing.
The second test may suffer from startTethering fail problem due to
softAP is not disabled yet.
E WifiService: Tethering is already active.
Bug: 157806780
Test: atest CtsTetheringTest
Original-Change: https://android-review.googlesource.com/1331096
Merged-In: I0ba6bc9dcbf7829dcad5561c707d5f5c5540f10b
Change-Id: I0ba6bc9dcbf7829dcad5561c707d5f5c5540f10b
diff --git a/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java b/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java
index f7160dd..5e2f627 100644
--- a/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java
+++ b/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java
@@ -57,8 +57,11 @@
import android.net.TetheringManager.TetheringRequest;
import android.net.cts.util.CtsNetUtils;
import android.net.cts.util.CtsNetUtils.TestNetworkCallback;
+import android.net.wifi.WifiClient;
import android.net.wifi.WifiManager;
+import android.net.wifi.WifiManager.SoftApCallback;
import android.os.Bundle;
+import android.os.ConditionVariable;
import android.os.PersistableBundle;
import android.os.ResultReceiver;
import android.telephony.CarrierConfigManager;
@@ -135,6 +138,40 @@
dropShellPermissionIdentity();
}
+ private static class StopSoftApCallback implements SoftApCallback {
+ private final ConditionVariable mWaiting = new ConditionVariable();
+ @Override
+ public void onStateChanged(int state, int failureReason) {
+ if (state == WifiManager.WIFI_AP_STATE_DISABLED) mWaiting.open();
+ }
+
+ @Override
+ public void onConnectedClientsChanged(List<WifiClient> clients) { }
+
+ public void waitForSoftApStopped() {
+ if (!mWaiting.block(DEFAULT_TIMEOUT_MS)) {
+ fail("stopSoftAp Timeout");
+ }
+ }
+ }
+
+ // Wait for softAp to be disabled. This is necessary on devices where stopping softAp
+ // deletes the interface. On these devices, tethering immediately stops when the softAp
+ // interface is removed, but softAp is not yet fully disabled. Wait for softAp to be
+ // fully disabled, because otherwise the next test might fail because it attempts to
+ // start softAp before it's fully stopped.
+ private void expectSoftApDisabled() {
+ final StopSoftApCallback callback = new StopSoftApCallback();
+ try {
+ mWm.registerSoftApCallback(c -> c.run(), callback);
+ // registerSoftApCallback will immediately call the callback with the current state, so
+ // this callback will fire even if softAp is already disabled.
+ callback.waitForSoftApStopped();
+ } finally {
+ mWm.unregisterSoftApCallback(callback);
+ }
+ }
+
private class TetherChangeReceiver extends BroadcastReceiver {
private class TetherState {
final ArrayList<String> mAvailable;
@@ -294,6 +331,7 @@
mTetherChangeReceiver.expectTethering(true /* active */, wifiRegexs);
mTM.stopTethering(TETHERING_WIFI);
+ expectSoftApDisabled();
mTetherChangeReceiver.expectTethering(false /* active */, wifiRegexs);
}
@@ -544,6 +582,7 @@
private void stopWifiTethering(final TestTetheringEventCallback callback) {
mTM.stopTethering(TETHERING_WIFI);
+ expectSoftApDisabled();
callback.expectTetheredInterfacesChanged(null);
callback.expectOneOfOffloadStatusChanged(TETHER_HARDWARE_OFFLOAD_STOPPED);
}