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