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