• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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