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