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