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