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