1 /* 2 * Copyright (C) 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.net.cts.util; 18 19 import static android.Manifest.permission.ACCESS_NETWORK_STATE; 20 import static android.Manifest.permission.ACCESS_WIFI_STATE; 21 import static android.Manifest.permission.NETWORK_SETTINGS; 22 import static android.Manifest.permission.TETHER_PRIVILEGED; 23 import static android.net.TetheringManager.TETHERING_WIFI; 24 import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR; 25 import static android.net.TetheringManager.TETHER_HARDWARE_OFFLOAD_FAILED; 26 import static android.net.TetheringManager.TETHER_HARDWARE_OFFLOAD_STARTED; 27 import static android.net.TetheringManager.TETHER_HARDWARE_OFFLOAD_STOPPED; 28 29 import static com.android.testutils.TestPermissionUtil.runAsShell; 30 31 import static org.junit.Assert.assertEquals; 32 import static org.junit.Assert.assertNotNull; 33 import static org.junit.Assert.assertTrue; 34 import static org.junit.Assert.fail; 35 import static org.junit.Assume.assumeTrue; 36 37 import android.content.BroadcastReceiver; 38 import android.content.Context; 39 import android.content.Intent; 40 import android.content.IntentFilter; 41 import android.content.pm.PackageManager; 42 import android.net.Network; 43 import android.net.TetheredClient; 44 import android.net.TetheringInterface; 45 import android.net.TetheringManager; 46 import android.net.TetheringManager.TetheringEventCallback; 47 import android.net.TetheringManager.TetheringInterfaceRegexps; 48 import android.net.TetheringManager.TetheringRequest; 49 import android.net.wifi.SoftApConfiguration; 50 import android.net.wifi.WifiClient; 51 import android.net.wifi.WifiManager; 52 import android.net.wifi.WifiManager.SoftApCallback; 53 import android.os.ConditionVariable; 54 55 import androidx.annotation.NonNull; 56 import androidx.annotation.Nullable; 57 58 import com.android.compatibility.common.util.SystemUtil; 59 import com.android.net.module.util.ArrayTrackRecord; 60 61 import java.util.Collection; 62 import java.util.List; 63 import java.util.Set; 64 65 public final class CtsTetheringUtils { 66 private TetheringManager mTm; 67 private WifiManager mWm; 68 private Context mContext; 69 70 private static final int DEFAULT_TIMEOUT_MS = 60_000; 71 CtsTetheringUtils(Context ctx)72 public CtsTetheringUtils(Context ctx) { 73 mContext = ctx; 74 mTm = mContext.getSystemService(TetheringManager.class); 75 mWm = mContext.getSystemService(WifiManager.class); 76 } 77 78 public static class StartTetheringCallback implements TetheringManager.StartTetheringCallback { 79 private static int TIMEOUT_MS = 30_000; 80 public static class CallbackValue { 81 public final int error; 82 CallbackValue(final int e)83 private CallbackValue(final int e) { 84 error = e; 85 } 86 87 public static class OnTetheringStarted extends CallbackValue { OnTetheringStarted()88 OnTetheringStarted() { super(TETHER_ERROR_NO_ERROR); } 89 } 90 91 public static class OnTetheringFailed extends CallbackValue { OnTetheringFailed(final int error)92 OnTetheringFailed(final int error) { super(error); } 93 } 94 95 @Override toString()96 public String toString() { 97 return String.format("%s(%d)", getClass().getSimpleName(), error); 98 } 99 } 100 101 private final ArrayTrackRecord<CallbackValue>.ReadHead mHistory = 102 new ArrayTrackRecord<CallbackValue>().newReadHead(); 103 104 @Override onTetheringStarted()105 public void onTetheringStarted() { 106 mHistory.add(new CallbackValue.OnTetheringStarted()); 107 } 108 109 @Override onTetheringFailed(final int error)110 public void onTetheringFailed(final int error) { 111 mHistory.add(new CallbackValue.OnTetheringFailed(error)); 112 } 113 verifyTetheringStarted()114 public void verifyTetheringStarted() { 115 final CallbackValue cv = mHistory.poll(TIMEOUT_MS, c -> true); 116 assertNotNull("No onTetheringStarted after " + TIMEOUT_MS + " ms", cv); 117 assertTrue("Fail start tethering:" + cv, 118 cv instanceof CallbackValue.OnTetheringStarted); 119 } 120 expectTetheringFailed(final int expected)121 public void expectTetheringFailed(final int expected) throws InterruptedException { 122 final CallbackValue cv = mHistory.poll(TIMEOUT_MS, c -> true); 123 assertNotNull("No onTetheringFailed after " + TIMEOUT_MS + " ms", cv); 124 assertTrue("Expect fail with error code " + expected + ", but received: " + cv, 125 (cv instanceof CallbackValue.OnTetheringFailed) && (cv.error == expected)); 126 } 127 } 128 isRegexMatch(final String[] ifaceRegexs, String iface)129 private static boolean isRegexMatch(final String[] ifaceRegexs, String iface) { 130 if (ifaceRegexs == null) fail("ifaceRegexs should not be null"); 131 132 for (String regex : ifaceRegexs) { 133 if (iface.matches(regex)) return true; 134 } 135 136 return false; 137 } 138 isAnyIfaceMatch(final String[] ifaceRegexs, final List<String> ifaces)139 public static boolean isAnyIfaceMatch(final String[] ifaceRegexs, final List<String> ifaces) { 140 if (ifaces == null) return false; 141 142 for (String s : ifaces) { 143 if (isRegexMatch(ifaceRegexs, s)) return true; 144 } 145 146 return false; 147 } 148 getFirstMatchingTetheringInterface(final List<String> regexs, final int type, final Set<TetheringInterface> ifaces)149 private static TetheringInterface getFirstMatchingTetheringInterface(final List<String> regexs, 150 final int type, final Set<TetheringInterface> ifaces) { 151 if (ifaces == null || regexs == null) return null; 152 153 final String[] regexArray = regexs.toArray(new String[0]); 154 for (TetheringInterface iface : ifaces) { 155 if (isRegexMatch(regexArray, iface.getInterface()) && type == iface.getType()) { 156 return iface; 157 } 158 } 159 160 return null; 161 } 162 163 // Must poll the callback before looking at the member. 164 public static class TestTetheringEventCallback implements TetheringEventCallback { 165 private static final int TIMEOUT_MS = 30_000; 166 167 public enum CallbackType { 168 ON_SUPPORTED, 169 ON_UPSTREAM, 170 ON_TETHERABLE_REGEX, 171 ON_TETHERABLE_IFACES, 172 ON_TETHERED_IFACES, 173 ON_ERROR, 174 ON_CLIENTS, 175 ON_OFFLOAD_STATUS, 176 }; 177 178 public static class CallbackValue { 179 public final CallbackType callbackType; 180 public final Object callbackParam; 181 public final int callbackParam2; 182 CallbackValue(final CallbackType type, final Object param, final int param2)183 private CallbackValue(final CallbackType type, final Object param, final int param2) { 184 this.callbackType = type; 185 this.callbackParam = param; 186 this.callbackParam2 = param2; 187 } 188 } 189 190 private final ArrayTrackRecord<CallbackValue> mHistory = 191 new ArrayTrackRecord<CallbackValue>(); 192 193 private final ArrayTrackRecord<CallbackValue>.ReadHead mCurrent = 194 mHistory.newReadHead(); 195 196 private TetheringInterfaceRegexps mTetherableRegex; 197 private List<String> mTetherableIfaces; 198 private List<String> mTetheredIfaces; 199 private String mErrorIface; 200 private int mErrorCode; 201 202 @Override onTetheringSupported(boolean supported)203 public void onTetheringSupported(boolean supported) { 204 mHistory.add(new CallbackValue(CallbackType.ON_SUPPORTED, null, (supported ? 1 : 0))); 205 } 206 207 @Override onUpstreamChanged(Network network)208 public void onUpstreamChanged(Network network) { 209 mHistory.add(new CallbackValue(CallbackType.ON_UPSTREAM, network, 0)); 210 } 211 212 @Override onTetherableInterfaceRegexpsChanged(TetheringInterfaceRegexps reg)213 public void onTetherableInterfaceRegexpsChanged(TetheringInterfaceRegexps reg) { 214 mTetherableRegex = reg; 215 mHistory.add(new CallbackValue(CallbackType.ON_TETHERABLE_REGEX, reg, 0)); 216 } 217 218 @Override onTetherableInterfacesChanged(List<String> interfaces)219 public void onTetherableInterfacesChanged(List<String> interfaces) { 220 mTetherableIfaces = interfaces; 221 } 222 // Call the interface default implementation, which will call 223 // onTetherableInterfacesChanged(List<String>). This ensures that the default implementation 224 // of the new callback method calls the old callback method and avoids the need to convert 225 // Set<TetheringInterface> to List<String> in this code. 226 @Override onTetherableInterfacesChanged(Set<TetheringInterface> interfaces)227 public void onTetherableInterfacesChanged(Set<TetheringInterface> interfaces) { 228 TetheringEventCallback.super.onTetherableInterfacesChanged(interfaces); 229 assertHasAllTetheringInterfaces(interfaces, mTetherableIfaces); 230 mHistory.add(new CallbackValue(CallbackType.ON_TETHERABLE_IFACES, interfaces, 0)); 231 } 232 233 @Override onTetheredInterfacesChanged(List<String> interfaces)234 public void onTetheredInterfacesChanged(List<String> interfaces) { 235 mTetheredIfaces = interfaces; 236 } 237 238 @Override onTetheredInterfacesChanged(Set<TetheringInterface> interfaces)239 public void onTetheredInterfacesChanged(Set<TetheringInterface> interfaces) { 240 TetheringEventCallback.super.onTetheredInterfacesChanged(interfaces); 241 assertHasAllTetheringInterfaces(interfaces, mTetheredIfaces); 242 mHistory.add(new CallbackValue(CallbackType.ON_TETHERED_IFACES, interfaces, 0)); 243 } 244 245 @Override onError(String ifName, int error)246 public void onError(String ifName, int error) { 247 mErrorIface = ifName; 248 mErrorCode = error; 249 } 250 251 @Override onError(TetheringInterface ifName, int error)252 public void onError(TetheringInterface ifName, int error) { 253 TetheringEventCallback.super.onError(ifName, error); 254 assertEquals(ifName.getInterface(), mErrorIface); 255 assertEquals(error, mErrorCode); 256 mHistory.add(new CallbackValue(CallbackType.ON_ERROR, ifName, error)); 257 } 258 259 @Override onClientsChanged(Collection<TetheredClient> clients)260 public void onClientsChanged(Collection<TetheredClient> clients) { 261 mHistory.add(new CallbackValue(CallbackType.ON_CLIENTS, clients, 0)); 262 } 263 264 @Override onOffloadStatusChanged(int status)265 public void onOffloadStatusChanged(int status) { 266 mHistory.add(new CallbackValue(CallbackType.ON_OFFLOAD_STATUS, status, 0)); 267 } 268 assertHasAllTetheringInterfaces(Set<TetheringInterface> tetheringIfaces, List<String> ifaces)269 private void assertHasAllTetheringInterfaces(Set<TetheringInterface> tetheringIfaces, 270 List<String> ifaces) { 271 // This does not check that the interfaces are the same. This checks that the 272 // List<String> has all the interface names contained by the Set<TetheringInterface>. 273 assertEquals(tetheringIfaces.size(), ifaces.size()); 274 for (TetheringInterface tether : tetheringIfaces) { 275 assertTrue("iface " + tether.getInterface() 276 + " seen by new callback but not old callback", 277 ifaces.contains(tether.getInterface())); 278 } 279 } 280 expectTetherableInterfacesChanged(@onNull final List<String> regexs, final int type)281 public void expectTetherableInterfacesChanged(@NonNull final List<String> regexs, 282 final int type) { 283 assertNotNull("No expected tetherable ifaces callback", mCurrent.poll(TIMEOUT_MS, 284 (cv) -> { 285 if (cv.callbackType != CallbackType.ON_TETHERABLE_IFACES) return false; 286 final Set<TetheringInterface> interfaces = 287 (Set<TetheringInterface>) cv.callbackParam; 288 return getFirstMatchingTetheringInterface(regexs, type, interfaces) != null; 289 })); 290 } 291 expectNoTetheringActive()292 public void expectNoTetheringActive() { 293 assertNotNull("At least one tethering type unexpectedly active", 294 mCurrent.poll(TIMEOUT_MS, (cv) -> { 295 if (cv.callbackType != CallbackType.ON_TETHERED_IFACES) return false; 296 297 return ((Set<TetheringInterface>) cv.callbackParam).isEmpty(); 298 })); 299 } 300 301 @Nullable pollTetheredInterfacesChanged( @onNull final List<String> regexs, final int type, long timeOutMs)302 public TetheringInterface pollTetheredInterfacesChanged( 303 @NonNull final List<String> regexs, final int type, long timeOutMs) { 304 while (true) { 305 final CallbackValue cv = mCurrent.poll(timeOutMs, c -> true); 306 if (cv == null) return null; 307 308 if (cv.callbackType != CallbackType.ON_TETHERED_IFACES) continue; 309 310 final Set<TetheringInterface> interfaces = 311 (Set<TetheringInterface>) cv.callbackParam; 312 313 final TetheringInterface iface = 314 getFirstMatchingTetheringInterface(regexs, type, interfaces); 315 316 if (iface != null) return iface; 317 } 318 } 319 320 @NonNull expectTetheredInterfacesChanged( @onNull final List<String> regexs, final int type)321 public TetheringInterface expectTetheredInterfacesChanged( 322 @NonNull final List<String> regexs, final int type) { 323 final TetheringInterface iface = pollTetheredInterfacesChanged(regexs, type, 324 TIMEOUT_MS); 325 326 if (iface == null) { 327 fail("No expected tethered ifaces callback, expected type: " + type); 328 } 329 330 return iface; 331 } 332 expectCallbackStarted()333 public void expectCallbackStarted() { 334 // This method uses its own readhead because it just check whether last tethering status 335 // is updated after TetheringEventCallback get registered but do not check content 336 // of received callbacks. Using shared readhead (mCurrent) only when the callbacks the 337 // method polled is also not necessary for other methods which using shared readhead. 338 // All of methods using mCurrent is order mattered. 339 final ArrayTrackRecord<CallbackValue>.ReadHead history = 340 mHistory.newReadHead(); 341 int receivedBitMap = 0; 342 // The each bit represent a type from CallbackType.ON_*. 343 // Expect all of callbacks except for ON_ERROR. 344 final int expectedBitMap = 0xff ^ (1 << CallbackType.ON_ERROR.ordinal()); 345 // Receive ON_ERROR on started callback is not matter. It just means tethering is 346 // failed last time, should able to continue the test this time. 347 while ((receivedBitMap & expectedBitMap) != expectedBitMap) { 348 final CallbackValue cv = history.poll(TIMEOUT_MS, c -> true); 349 if (cv == null) { 350 fail("No expected callbacks, " + "expected bitmap: " 351 + expectedBitMap + ", actual: " + receivedBitMap); 352 } 353 354 receivedBitMap |= (1 << cv.callbackType.ordinal()); 355 } 356 } 357 expectOneOfOffloadStatusChanged(int... offloadStatuses)358 public void expectOneOfOffloadStatusChanged(int... offloadStatuses) { 359 assertNotNull("No offload status changed", mCurrent.poll(TIMEOUT_MS, (cv) -> { 360 if (cv.callbackType != CallbackType.ON_OFFLOAD_STATUS) return false; 361 362 final int status = (int) cv.callbackParam; 363 for (int offloadStatus : offloadStatuses) { 364 if (offloadStatus == status) return true; 365 } 366 367 return false; 368 })); 369 } 370 expectErrorOrTethered(final TetheringInterface iface)371 public void expectErrorOrTethered(final TetheringInterface iface) { 372 assertNotNull("No expected callback", mCurrent.poll(TIMEOUT_MS, (cv) -> { 373 if (cv.callbackType == CallbackType.ON_ERROR 374 && iface.equals((TetheringInterface) cv.callbackParam)) { 375 return true; 376 } 377 if (cv.callbackType == CallbackType.ON_TETHERED_IFACES 378 && ((Set<TetheringInterface>) cv.callbackParam).contains(iface)) { 379 return true; 380 } 381 382 return false; 383 })); 384 } 385 getCurrentValidUpstream()386 public Network getCurrentValidUpstream() { 387 final CallbackValue result = mCurrent.poll(TIMEOUT_MS, (cv) -> { 388 return (cv.callbackType == CallbackType.ON_UPSTREAM) 389 && cv.callbackParam != null; 390 }); 391 392 assertNotNull("No valid upstream", result); 393 return (Network) result.callbackParam; 394 } 395 assumeTetheringSupported()396 public void assumeTetheringSupported() { 397 assumeTrue(isTetheringSupported()); 398 } 399 isTetheringSupported()400 private boolean isTetheringSupported() { 401 final ArrayTrackRecord<CallbackValue>.ReadHead history = 402 mHistory.newReadHead(); 403 final CallbackValue result = history.poll(TIMEOUT_MS, (cv) -> { 404 return cv.callbackType == CallbackType.ON_SUPPORTED; 405 }); 406 407 assertNotNull("No onSupported callback", result); 408 return result.callbackParam2 == 1 /* supported */; 409 } 410 assumeWifiTetheringSupported(final Context ctx)411 public void assumeWifiTetheringSupported(final Context ctx) throws Exception { 412 assumeTrue(isWifiTetheringSupported(ctx)); 413 } 414 isWifiTetheringSupported(final Context ctx)415 public boolean isWifiTetheringSupported(final Context ctx) throws Exception { 416 return isTetheringSupported() 417 && !getTetheringInterfaceRegexps().getTetherableWifiRegexs().isEmpty() 418 && isPortableHotspotSupported(ctx); 419 } 420 getTetheringInterfaceRegexps()421 public TetheringInterfaceRegexps getTetheringInterfaceRegexps() { 422 return mTetherableRegex; 423 } 424 } 425 isWifiEnabled(final WifiManager wm)426 private static boolean isWifiEnabled(final WifiManager wm) { 427 return runAsShell(ACCESS_WIFI_STATE, () -> wm.isWifiEnabled()); 428 429 } 430 waitForWifiEnabled(final Context ctx)431 private static void waitForWifiEnabled(final Context ctx) throws Exception { 432 WifiManager wm = ctx.getSystemService(WifiManager.class); 433 if (isWifiEnabled(wm)) return; 434 435 final ConditionVariable mWaiting = new ConditionVariable(); 436 final BroadcastReceiver receiver = new BroadcastReceiver() { 437 @Override 438 public void onReceive(Context context, Intent intent) { 439 String action = intent.getAction(); 440 if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(action)) { 441 if (isWifiEnabled(wm)) mWaiting.open(); 442 } 443 } 444 }; 445 try { 446 ctx.registerReceiver(receiver, new IntentFilter(WifiManager.WIFI_STATE_CHANGED_ACTION)); 447 if (!mWaiting.block(DEFAULT_TIMEOUT_MS)) { 448 assertTrue("Wifi did not become enabled after " + DEFAULT_TIMEOUT_MS + "ms", 449 isWifiEnabled(wm)); 450 } 451 } finally { 452 ctx.unregisterReceiver(receiver); 453 } 454 } 455 registerTetheringEventCallback()456 public TestTetheringEventCallback registerTetheringEventCallback() { 457 final TestTetheringEventCallback tetherEventCallback = 458 new TestTetheringEventCallback(); 459 460 runAsShell(ACCESS_NETWORK_STATE, NETWORK_SETTINGS, () -> { 461 mTm.registerTetheringEventCallback(c -> c.run() /* executor */, tetherEventCallback); 462 tetherEventCallback.expectCallbackStarted(); 463 }); 464 465 return tetherEventCallback; 466 } 467 unregisterTetheringEventCallback(final TestTetheringEventCallback callback)468 public void unregisterTetheringEventCallback(final TestTetheringEventCallback callback) { 469 runAsShell(ACCESS_NETWORK_STATE, () -> mTm.unregisterTetheringEventCallback(callback)); 470 } 471 getWifiTetherableInterfaceRegexps( final TestTetheringEventCallback callback)472 private static List<String> getWifiTetherableInterfaceRegexps( 473 final TestTetheringEventCallback callback) { 474 return callback.getTetheringInterfaceRegexps().getTetherableWifiRegexs(); 475 } 476 477 /* Returns if wifi supports hotspot. */ isPortableHotspotSupported(final Context ctx)478 private static boolean isPortableHotspotSupported(final Context ctx) throws Exception { 479 final PackageManager pm = ctx.getPackageManager(); 480 if (!pm.hasSystemFeature(PackageManager.FEATURE_WIFI)) return false; 481 final WifiManager wm = ctx.getSystemService(WifiManager.class); 482 // Wifi feature flags only work when wifi is on. 483 final boolean previousWifiEnabledState = isWifiEnabled(wm); 484 try { 485 if (!previousWifiEnabledState) SystemUtil.runShellCommand("svc wifi enable"); 486 waitForWifiEnabled(ctx); 487 return runAsShell(ACCESS_WIFI_STATE, () -> wm.isPortableHotspotSupported()); 488 } finally { 489 if (!previousWifiEnabledState) { 490 new CtsNetUtils(ctx).disableWifi(); 491 } 492 } 493 } 494 495 /** 496 * Starts Wi-Fi tethering. 497 */ startWifiTethering(final TestTetheringEventCallback callback)498 public TetheringInterface startWifiTethering(final TestTetheringEventCallback callback) 499 throws InterruptedException { 500 return startWifiTethering(callback, null); 501 } 502 503 /** 504 * Starts Wi-Fi tethering with the specified SoftApConfiguration. 505 */ startWifiTethering(final TestTetheringEventCallback callback, final SoftApConfiguration softApConfiguration)506 public TetheringInterface startWifiTethering(final TestTetheringEventCallback callback, 507 final SoftApConfiguration softApConfiguration) 508 throws InterruptedException { 509 final List<String> wifiRegexs = getWifiTetherableInterfaceRegexps(callback); 510 511 final StartTetheringCallback startTetheringCallback = new StartTetheringCallback(); 512 TetheringRequest.Builder builder = new TetheringRequest.Builder(TETHERING_WIFI) 513 .setShouldShowEntitlementUi(false); 514 if (softApConfiguration != null) { 515 builder.setSoftApConfiguration(softApConfiguration); 516 } 517 final TetheringRequest request = builder.build(); 518 519 return runAsShell(TETHER_PRIVILEGED, () -> { 520 mTm.startTethering(request, c -> c.run() /* executor */, startTetheringCallback); 521 startTetheringCallback.verifyTetheringStarted(); 522 523 final TetheringInterface iface = 524 callback.expectTetheredInterfacesChanged(wifiRegexs, TETHERING_WIFI); 525 526 callback.expectOneOfOffloadStatusChanged( 527 TETHER_HARDWARE_OFFLOAD_STARTED, 528 TETHER_HARDWARE_OFFLOAD_FAILED); 529 530 return iface; 531 }); 532 } 533 534 private static class StopSoftApCallback implements SoftApCallback { 535 private final ConditionVariable mWaiting = new ConditionVariable(); 536 @Override onStateChanged(int state, int failureReason)537 public void onStateChanged(int state, int failureReason) { 538 if (state == WifiManager.WIFI_AP_STATE_DISABLED) mWaiting.open(); 539 } 540 541 @Override onConnectedClientsChanged(List<WifiClient> clients)542 public void onConnectedClientsChanged(List<WifiClient> clients) { } 543 waitForSoftApStopped()544 public void waitForSoftApStopped() { 545 if (!mWaiting.block(DEFAULT_TIMEOUT_MS)) { 546 fail("stopSoftAp Timeout"); 547 } 548 } 549 } 550 551 // Wait for softAp to be disabled. This is necessary on devices where stopping softAp 552 // deletes the interface. On these devices, tethering immediately stops when the softAp 553 // interface is removed, but softAp is not yet fully disabled. Wait for softAp to be 554 // fully disabled, because otherwise the next test might fail because it attempts to 555 // start softAp before it's fully stopped. expectSoftApDisabled()556 public void expectSoftApDisabled() { 557 final StopSoftApCallback callback = new StopSoftApCallback(); 558 try { 559 runAsShell(NETWORK_SETTINGS, () -> mWm.registerSoftApCallback(c -> c.run(), callback)); 560 // registerSoftApCallback will immediately call the callback with the current state, so 561 // this callback will fire even if softAp is already disabled. 562 callback.waitForSoftApStopped(); 563 } finally { 564 runAsShell(NETWORK_SETTINGS, () -> mWm.unregisterSoftApCallback(callback)); 565 } 566 } 567 stopWifiTethering(final TestTetheringEventCallback callback)568 public void stopWifiTethering(final TestTetheringEventCallback callback) { 569 runAsShell(TETHER_PRIVILEGED, () -> { 570 mTm.stopTethering(TETHERING_WIFI); 571 callback.expectNoTetheringActive(); 572 callback.expectOneOfOffloadStatusChanged(TETHER_HARDWARE_OFFLOAD_STOPPED); 573 }); 574 expectSoftApDisabled(); 575 } 576 stopAllTethering()577 public void stopAllTethering() { 578 final TestTetheringEventCallback callback = registerTetheringEventCallback(); 579 try { 580 runAsShell(TETHER_PRIVILEGED, () -> { 581 mTm.stopAllTethering(); 582 callback.expectNoTetheringActive(); 583 }); 584 } finally { 585 unregisterTetheringEventCallback(callback); 586 } 587 } 588 } 589