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