1 /* 2 * Copyright (C) 2021 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 com.android.networkstack.tethering; 18 19 import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN; 20 import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; 21 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; 22 23 import static org.junit.Assert.assertFalse; 24 import static org.junit.Assert.fail; 25 26 import android.content.Context; 27 import android.content.Intent; 28 import android.net.ConnectivityManager; 29 import android.net.IConnectivityManager; 30 import android.net.LinkProperties; 31 import android.net.Network; 32 import android.net.NetworkCapabilities; 33 import android.net.NetworkInfo; 34 import android.net.NetworkRequest; 35 import android.os.Handler; 36 import android.os.UserHandle; 37 import android.util.ArrayMap; 38 39 import androidx.annotation.NonNull; 40 import androidx.annotation.Nullable; 41 42 import java.util.Map; 43 import java.util.Objects; 44 45 /** 46 * Simulates upstream switching and sending NetworkCallbacks and CONNECTIVITY_ACTION broadcasts. 47 * 48 * Unlike any real networking code, this class is single-threaded and entirely synchronous. 49 * The effects of all method calls (including sending fake broadcasts, sending callbacks, etc.) are 50 * performed immediately on the caller's thread before returning. 51 * 52 * TODO: this duplicates a fair amount of code from ConnectivityManager and ConnectivityService. 53 * Consider using a ConnectivityService object instead, as used in ConnectivityServiceTest. 54 * 55 * Things to consider: 56 * - ConnectivityService uses a real handler for realism, and these test use TestLooper (or even 57 * invoke callbacks directly inline) for determinism. Using a real ConnectivityService would 58 * require adding dispatchAll() calls and migrating to handlers. 59 * - ConnectivityService does not provide a way to order CONNECTIVITY_ACTION before or after the 60 * NetworkCallbacks for the same network change. That ability is useful because the upstream 61 * selection code in Tethering is vulnerable to race conditions, due to its reliance on multiple 62 * separate NetworkCallbacks and BroadcastReceivers, each of which trigger different types of 63 * updates. If/when the upstream selection code is refactored to a more level-triggered model 64 * (e.g., with an idempotent function that takes into account all state every time any part of 65 * that state changes), this may become less important or unnecessary. 66 */ 67 public class TestConnectivityManager extends ConnectivityManager { 68 public static final boolean BROADCAST_FIRST = false; 69 public static final boolean CALLBACKS_FIRST = true; 70 71 final Map<NetworkCallback, NetworkCallbackInfo> mAllCallbacks = new ArrayMap<>(); 72 // This contains the callbacks tracking the system default network, whether it's registered 73 // with registerSystemDefaultNetworkCallback (S+) or with a custom request (R-). 74 final Map<NetworkCallback, NetworkCallbackInfo> mTrackingDefault = new ArrayMap<>(); 75 final Map<NetworkCallback, NetworkRequestInfo> mListening = new ArrayMap<>(); 76 final Map<NetworkCallback, NetworkRequestInfo> mRequested = new ArrayMap<>(); 77 final Map<NetworkCallback, Integer> mLegacyTypeMap = new ArrayMap<>(); 78 79 private final Context mContext; 80 81 private int mNetworkId = 100; 82 private TestNetworkAgent mDefaultNetwork = null; 83 84 /** 85 * Constructs a TestConnectivityManager. 86 * @param ctx the context to use. Must be a fake or a mock because otherwise the test will 87 * attempt to send real broadcasts and resulting in permission denials. 88 * @param svc an IConnectivityManager. Should be a fake or a mock. 89 */ TestConnectivityManager(Context ctx, IConnectivityManager svc)90 public TestConnectivityManager(Context ctx, IConnectivityManager svc) { 91 super(ctx, svc); 92 mContext = ctx; 93 } 94 95 static class NetworkCallbackInfo { 96 public final Handler handler; NetworkCallbackInfo(Handler h)97 NetworkCallbackInfo(Handler h) { 98 handler = h; 99 } 100 } 101 102 static class NetworkRequestInfo extends NetworkCallbackInfo { 103 public final NetworkRequest request; NetworkRequestInfo(NetworkRequest r, Handler h)104 NetworkRequestInfo(NetworkRequest r, Handler h) { 105 super(h); 106 request = r; 107 } 108 } 109 hasNoCallbacks()110 boolean hasNoCallbacks() { 111 return mAllCallbacks.isEmpty() 112 && mTrackingDefault.isEmpty() 113 && mListening.isEmpty() 114 && mRequested.isEmpty() 115 && mLegacyTypeMap.isEmpty(); 116 } 117 onlyHasDefaultCallbacks()118 boolean onlyHasDefaultCallbacks() { 119 return (mAllCallbacks.size() == 1) 120 && (mTrackingDefault.size() == 1) 121 && mListening.isEmpty() 122 && mRequested.isEmpty() 123 && mLegacyTypeMap.isEmpty(); 124 } 125 isListeningForAll()126 boolean isListeningForAll() { 127 final NetworkCapabilities empty = new NetworkCapabilities(); 128 empty.clearAll(); 129 130 for (NetworkRequestInfo nri : mListening.values()) { 131 if (nri.request.networkCapabilities.equalRequestableCapabilities(empty)) { 132 return true; 133 } 134 } 135 return false; 136 } 137 getNetworkId()138 int getNetworkId() { 139 return ++mNetworkId; 140 } 141 sendDefaultNetworkBroadcasts(TestNetworkAgent formerDefault, TestNetworkAgent defaultNetwork)142 private void sendDefaultNetworkBroadcasts(TestNetworkAgent formerDefault, 143 TestNetworkAgent defaultNetwork) { 144 if (formerDefault != null) { 145 sendConnectivityAction(formerDefault.legacyType, false /* connected */); 146 } 147 if (defaultNetwork != null) { 148 sendConnectivityAction(defaultNetwork.legacyType, true /* connected */); 149 } 150 } 151 sendDefaultNetworkCallbacks(TestNetworkAgent formerDefault, TestNetworkAgent defaultNetwork)152 private void sendDefaultNetworkCallbacks(TestNetworkAgent formerDefault, 153 TestNetworkAgent defaultNetwork) { 154 for (NetworkCallback cb : mTrackingDefault.keySet()) { 155 final NetworkCallbackInfo nri = mTrackingDefault.get(cb); 156 if (defaultNetwork != null) { 157 nri.handler.post(() -> cb.onAvailable(defaultNetwork.networkId)); 158 nri.handler.post(() -> cb.onCapabilitiesChanged( 159 defaultNetwork.networkId, defaultNetwork.networkCapabilities)); 160 nri.handler.post(() -> cb.onLinkPropertiesChanged( 161 defaultNetwork.networkId, defaultNetwork.linkProperties)); 162 } else if (formerDefault != null) { 163 nri.handler.post(() -> cb.onLost(formerDefault.networkId)); 164 } 165 } 166 } 167 makeDefaultNetwork(TestNetworkAgent agent, boolean order, @Nullable Runnable inBetween)168 void makeDefaultNetwork(TestNetworkAgent agent, boolean order, @Nullable Runnable inBetween) { 169 if (Objects.equals(mDefaultNetwork, agent)) return; 170 171 final TestNetworkAgent formerDefault = mDefaultNetwork; 172 mDefaultNetwork = agent; 173 174 if (order == CALLBACKS_FIRST) { 175 sendDefaultNetworkCallbacks(formerDefault, mDefaultNetwork); 176 if (inBetween != null) inBetween.run(); 177 sendDefaultNetworkBroadcasts(formerDefault, mDefaultNetwork); 178 } else { 179 sendDefaultNetworkBroadcasts(formerDefault, mDefaultNetwork); 180 if (inBetween != null) inBetween.run(); 181 sendDefaultNetworkCallbacks(formerDefault, mDefaultNetwork); 182 } 183 } 184 makeDefaultNetwork(TestNetworkAgent agent, boolean order)185 void makeDefaultNetwork(TestNetworkAgent agent, boolean order) { 186 makeDefaultNetwork(agent, order, null /* inBetween */); 187 } 188 makeDefaultNetwork(TestNetworkAgent agent)189 void makeDefaultNetwork(TestNetworkAgent agent) { 190 makeDefaultNetwork(agent, BROADCAST_FIRST, null /* inBetween */); 191 } 192 looksLikeDefaultRequest(NetworkRequest req)193 static boolean looksLikeDefaultRequest(NetworkRequest req) { 194 return req.hasCapability(NET_CAPABILITY_INTERNET) 195 && !req.hasCapability(NET_CAPABILITY_DUN) 196 && !req.hasTransport(TRANSPORT_CELLULAR); 197 } 198 199 @Override requestNetwork(NetworkRequest req, NetworkCallback cb, Handler h)200 public void requestNetwork(NetworkRequest req, NetworkCallback cb, Handler h) { 201 // For R- devices, Tethering will invoke this function in 2 cases, one is to request mobile 202 // network, the other is to track system default network. 203 if (looksLikeDefaultRequest(req)) { 204 registerSystemDefaultNetworkCallback(cb, h); 205 } else { 206 assertFalse(mAllCallbacks.containsKey(cb)); 207 mAllCallbacks.put(cb, new NetworkRequestInfo(req, h)); 208 assertFalse(mRequested.containsKey(cb)); 209 mRequested.put(cb, new NetworkRequestInfo(req, h)); 210 } 211 } 212 213 @Override registerSystemDefaultNetworkCallback( @onNull NetworkCallback cb, @NonNull Handler h)214 public void registerSystemDefaultNetworkCallback( 215 @NonNull NetworkCallback cb, @NonNull Handler h) { 216 assertFalse(mAllCallbacks.containsKey(cb)); 217 mAllCallbacks.put(cb, new NetworkCallbackInfo(h)); 218 assertFalse(mTrackingDefault.containsKey(cb)); 219 mTrackingDefault.put(cb, new NetworkCallbackInfo(h)); 220 } 221 222 @Override requestNetwork(NetworkRequest req, NetworkCallback cb)223 public void requestNetwork(NetworkRequest req, NetworkCallback cb) { 224 fail("Should never be called."); 225 } 226 227 @Override requestNetwork(NetworkRequest req, int timeoutMs, int legacyType, Handler h, NetworkCallback cb)228 public void requestNetwork(NetworkRequest req, 229 int timeoutMs, int legacyType, Handler h, NetworkCallback cb) { 230 assertFalse(mAllCallbacks.containsKey(cb)); 231 NetworkRequest newReq = new NetworkRequest(req.networkCapabilities, legacyType, 232 -1 /** testId */, req.type); 233 mAllCallbacks.put(cb, new NetworkRequestInfo(newReq, h)); 234 assertFalse(mRequested.containsKey(cb)); 235 mRequested.put(cb, new NetworkRequestInfo(newReq, h)); 236 assertFalse(mLegacyTypeMap.containsKey(cb)); 237 if (legacyType != ConnectivityManager.TYPE_NONE) { 238 mLegacyTypeMap.put(cb, legacyType); 239 } 240 } 241 242 @Override registerNetworkCallback(NetworkRequest req, NetworkCallback cb, Handler h)243 public void registerNetworkCallback(NetworkRequest req, NetworkCallback cb, Handler h) { 244 assertFalse(mAllCallbacks.containsKey(cb)); 245 mAllCallbacks.put(cb, new NetworkRequestInfo(req, h)); 246 assertFalse(mListening.containsKey(cb)); 247 mListening.put(cb, new NetworkRequestInfo(req, h)); 248 } 249 250 @Override registerNetworkCallback(NetworkRequest req, NetworkCallback cb)251 public void registerNetworkCallback(NetworkRequest req, NetworkCallback cb) { 252 fail("Should never be called."); 253 } 254 255 @Override registerDefaultNetworkCallback(NetworkCallback cb, Handler h)256 public void registerDefaultNetworkCallback(NetworkCallback cb, Handler h) { 257 fail("Should never be called."); 258 } 259 260 @Override registerDefaultNetworkCallback(NetworkCallback cb)261 public void registerDefaultNetworkCallback(NetworkCallback cb) { 262 fail("Should never be called."); 263 } 264 265 @Override unregisterNetworkCallback(NetworkCallback cb)266 public void unregisterNetworkCallback(NetworkCallback cb) { 267 if (mTrackingDefault.containsKey(cb)) { 268 mTrackingDefault.remove(cb); 269 } else if (mListening.containsKey(cb)) { 270 mListening.remove(cb); 271 } else if (mRequested.containsKey(cb)) { 272 mRequested.remove(cb); 273 mLegacyTypeMap.remove(cb); 274 } else { 275 fail("Unexpected callback removed"); 276 } 277 mAllCallbacks.remove(cb); 278 279 assertFalse(mAllCallbacks.containsKey(cb)); 280 assertFalse(mTrackingDefault.containsKey(cb)); 281 assertFalse(mListening.containsKey(cb)); 282 assertFalse(mRequested.containsKey(cb)); 283 } 284 sendConnectivityAction(int type, boolean connected)285 private void sendConnectivityAction(int type, boolean connected) { 286 NetworkInfo ni = new NetworkInfo(type, 0 /* subtype */, getNetworkTypeName(type), 287 "" /* subtypeName */); 288 NetworkInfo.DetailedState state = connected 289 ? NetworkInfo.DetailedState.CONNECTED 290 : NetworkInfo.DetailedState.DISCONNECTED; 291 ni.setDetailedState(state, "" /* reason */, "" /* extraInfo */); 292 Intent intent = new Intent(CONNECTIVITY_ACTION); 293 intent.putExtra(EXTRA_NETWORK_INFO, ni); 294 mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL); 295 } 296 297 public static class TestNetworkAgent { 298 public final TestConnectivityManager cm; 299 public final Network networkId; 300 public final NetworkCapabilities networkCapabilities; 301 public final LinkProperties linkProperties; 302 // TODO: delete when tethering no longer uses CONNECTIVITY_ACTION. 303 public final int legacyType; 304 TestNetworkAgent(TestConnectivityManager cm, NetworkCapabilities nc)305 public TestNetworkAgent(TestConnectivityManager cm, NetworkCapabilities nc) { 306 this.cm = cm; 307 this.networkId = new Network(cm.getNetworkId()); 308 networkCapabilities = copy(nc); 309 linkProperties = new LinkProperties(); 310 legacyType = toLegacyType(nc); 311 } 312 TestNetworkAgent(TestConnectivityManager cm, UpstreamNetworkState state)313 public TestNetworkAgent(TestConnectivityManager cm, UpstreamNetworkState state) { 314 this.cm = cm; 315 networkId = state.network; 316 networkCapabilities = state.networkCapabilities; 317 linkProperties = state.linkProperties; 318 this.legacyType = toLegacyType(networkCapabilities); 319 } 320 toLegacyType(NetworkCapabilities nc)321 private static int toLegacyType(NetworkCapabilities nc) { 322 for (int type = 0; type < ConnectivityManager.TYPE_TEST; type++) { 323 if (matchesLegacyType(nc, type)) return type; 324 } 325 throw new IllegalArgumentException(("Can't determine legacy type for: ") + nc); 326 } 327 matchesLegacyType(NetworkCapabilities nc, int legacyType)328 private static boolean matchesLegacyType(NetworkCapabilities nc, int legacyType) { 329 final NetworkCapabilities typeNc; 330 try { 331 typeNc = ConnectivityManager.networkCapabilitiesForType(legacyType); 332 } catch (IllegalArgumentException e) { 333 // networkCapabilitiesForType does not support all legacy types. 334 return false; 335 } 336 return typeNc.satisfiedByNetworkCapabilities(nc); 337 } 338 matchesLegacyType(int legacyType)339 private boolean matchesLegacyType(int legacyType) { 340 return matchesLegacyType(networkCapabilities, legacyType); 341 } 342 maybeSendConnectivityBroadcast(boolean connected)343 private void maybeSendConnectivityBroadcast(boolean connected) { 344 for (Integer requestedLegacyType : cm.mLegacyTypeMap.values()) { 345 if (requestedLegacyType.intValue() == legacyType) { 346 cm.sendConnectivityAction(legacyType, connected /* connected */); 347 // In practice, a given network can match only one legacy type. 348 break; 349 } 350 } 351 } 352 fakeConnect()353 public void fakeConnect() { 354 fakeConnect(BROADCAST_FIRST, null); 355 } 356 fakeConnect(boolean order, @Nullable Runnable inBetween)357 public void fakeConnect(boolean order, @Nullable Runnable inBetween) { 358 if (order == BROADCAST_FIRST) { 359 maybeSendConnectivityBroadcast(true /* connected */); 360 if (inBetween != null) inBetween.run(); 361 } 362 363 for (NetworkCallback cb : cm.mListening.keySet()) { 364 final NetworkRequestInfo nri = cm.mListening.get(cb); 365 nri.handler.post(() -> cb.onAvailable(networkId)); 366 nri.handler.post(() -> cb.onCapabilitiesChanged( 367 networkId, copy(networkCapabilities))); 368 nri.handler.post(() -> cb.onLinkPropertiesChanged(networkId, copy(linkProperties))); 369 } 370 371 if (order == CALLBACKS_FIRST) { 372 if (inBetween != null) inBetween.run(); 373 maybeSendConnectivityBroadcast(true /* connected */); 374 } 375 // mTrackingDefault will be updated if/when the caller calls makeDefaultNetwork 376 } 377 fakeDisconnect()378 public void fakeDisconnect() { 379 fakeDisconnect(BROADCAST_FIRST, null); 380 } 381 fakeDisconnect(boolean order, @Nullable Runnable inBetween)382 public void fakeDisconnect(boolean order, @Nullable Runnable inBetween) { 383 if (order == BROADCAST_FIRST) { 384 maybeSendConnectivityBroadcast(false /* connected */); 385 if (inBetween != null) inBetween.run(); 386 } 387 388 for (NetworkCallback cb : cm.mListening.keySet()) { 389 cb.onLost(networkId); 390 } 391 392 if (order == CALLBACKS_FIRST) { 393 if (inBetween != null) inBetween.run(); 394 maybeSendConnectivityBroadcast(false /* connected */); 395 } 396 // mTrackingDefault will be updated if/when the caller calls makeDefaultNetwork 397 } 398 sendLinkProperties()399 public void sendLinkProperties() { 400 for (NetworkCallback cb : cm.mListening.keySet()) { 401 cb.onLinkPropertiesChanged(networkId, copy(linkProperties)); 402 } 403 } 404 405 @Override toString()406 public String toString() { 407 return String.format("TestNetworkAgent: %s %s", networkId, networkCapabilities); 408 } 409 } 410 copy(NetworkCapabilities nc)411 static NetworkCapabilities copy(NetworkCapabilities nc) { 412 return new NetworkCapabilities(nc); 413 } 414 copy(LinkProperties lp)415 static LinkProperties copy(LinkProperties lp) { 416 return new LinkProperties(lp); 417 } 418 } 419