1 /*
2  * Copyright (C) 2017 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.ConnectivityManager.TYPE_MOBILE_DUN;
20 import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI;
21 import static android.net.ConnectivityManager.TYPE_WIFI;
22 import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN;
23 import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
24 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
25 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
26 
27 import static com.android.networkstack.tethering.UpstreamNetworkMonitor.TYPE_NONE;
28 
29 import static org.junit.Assert.assertEquals;
30 import static org.junit.Assert.assertFalse;
31 import static org.junit.Assert.assertTrue;
32 import static org.junit.Assert.fail;
33 import static org.mockito.Mockito.any;
34 import static org.mockito.Mockito.anyInt;
35 import static org.mockito.Mockito.anyString;
36 import static org.mockito.Mockito.eq;
37 import static org.mockito.Mockito.reset;
38 import static org.mockito.Mockito.spy;
39 import static org.mockito.Mockito.times;
40 import static org.mockito.Mockito.verify;
41 import static org.mockito.Mockito.verifyNoMoreInteractions;
42 import static org.mockito.Mockito.when;
43 
44 import android.content.Context;
45 import android.net.ConnectivityManager;
46 import android.net.ConnectivityManager.NetworkCallback;
47 import android.net.IConnectivityManager;
48 import android.net.IpPrefix;
49 import android.net.LinkAddress;
50 import android.net.LinkProperties;
51 import android.net.Network;
52 import android.net.NetworkCapabilities;
53 import android.net.NetworkRequest;
54 import android.net.util.SharedLog;
55 import android.os.Handler;
56 import android.os.Message;
57 
58 import androidx.test.filters.SmallTest;
59 import androidx.test.runner.AndroidJUnit4;
60 
61 import com.android.internal.util.State;
62 import com.android.internal.util.StateMachine;
63 
64 import org.junit.After;
65 import org.junit.Before;
66 import org.junit.Test;
67 import org.junit.runner.RunWith;
68 import org.mockito.Mock;
69 import org.mockito.MockitoAnnotations;
70 
71 import java.util.ArrayList;
72 import java.util.Collection;
73 import java.util.Collections;
74 import java.util.HashMap;
75 import java.util.HashSet;
76 import java.util.Map;
77 import java.util.Objects;
78 import java.util.Set;
79 
80 @RunWith(AndroidJUnit4.class)
81 @SmallTest
82 public class UpstreamNetworkMonitorTest {
83     private static final int EVENT_UNM_UPDATE = 1;
84 
85     private static final boolean INCLUDES = true;
86     private static final boolean EXCLUDES = false;
87 
88     // Actual contents of the request don't matter for this test. The lack of
89     // any specific TRANSPORT_* is sufficient to identify this request.
90     private static final NetworkRequest sDefaultRequest = new NetworkRequest.Builder().build();
91 
92     @Mock private Context mContext;
93     @Mock private EntitlementManager mEntitleMgr;
94     @Mock private IConnectivityManager mCS;
95     @Mock private SharedLog mLog;
96 
97     private TestStateMachine mSM;
98     private TestConnectivityManager mCM;
99     private UpstreamNetworkMonitor mUNM;
100 
setUp()101     @Before public void setUp() throws Exception {
102         MockitoAnnotations.initMocks(this);
103         reset(mContext);
104         reset(mCS);
105         reset(mLog);
106         when(mLog.forSubComponent(anyString())).thenReturn(mLog);
107         when(mEntitleMgr.isCellularUpstreamPermitted()).thenReturn(true);
108 
109         mCM = spy(new TestConnectivityManager(mContext, mCS));
110         mSM = new TestStateMachine();
111         mUNM = new UpstreamNetworkMonitor(
112                 (ConnectivityManager) mCM, mSM, mLog, EVENT_UNM_UPDATE);
113     }
114 
tearDown()115     @After public void tearDown() throws Exception {
116         if (mSM != null) {
117             mSM.quit();
118             mSM = null;
119         }
120     }
121 
122     @Test
testStopWithoutStartIsNonFatal()123     public void testStopWithoutStartIsNonFatal() {
124         mUNM.stop();
125         mUNM.stop();
126         mUNM.stop();
127     }
128 
129     @Test
testDoesNothingBeforeTrackDefaultAndStarted()130     public void testDoesNothingBeforeTrackDefaultAndStarted() throws Exception {
131         assertTrue(mCM.hasNoCallbacks());
132         assertFalse(mUNM.mobileNetworkRequested());
133 
134         mUNM.updateMobileRequiresDun(true);
135         assertTrue(mCM.hasNoCallbacks());
136         mUNM.updateMobileRequiresDun(false);
137         assertTrue(mCM.hasNoCallbacks());
138     }
139 
140     @Test
testDefaultNetworkIsTracked()141     public void testDefaultNetworkIsTracked() throws Exception {
142         assertTrue(mCM.hasNoCallbacks());
143         mUNM.startTrackDefaultNetwork(sDefaultRequest, mEntitleMgr);
144 
145         mUNM.startObserveAllNetworks();
146         assertEquals(1, mCM.trackingDefault.size());
147 
148         mUNM.stop();
149         assertTrue(mCM.onlyHasDefaultCallbacks());
150     }
151 
152     @Test
testListensForAllNetworks()153     public void testListensForAllNetworks() throws Exception {
154         assertTrue(mCM.listening.isEmpty());
155 
156         mUNM.startTrackDefaultNetwork(sDefaultRequest, mEntitleMgr);
157         mUNM.startObserveAllNetworks();
158         assertFalse(mCM.listening.isEmpty());
159         assertTrue(mCM.isListeningForAll());
160 
161         mUNM.stop();
162         assertTrue(mCM.onlyHasDefaultCallbacks());
163     }
164 
165     @Test
testCallbacksRegistered()166     public void testCallbacksRegistered() {
167         mUNM.startTrackDefaultNetwork(sDefaultRequest, mEntitleMgr);
168         verify(mCM, times(1)).requestNetwork(
169                 eq(sDefaultRequest), any(NetworkCallback.class), any(Handler.class));
170         mUNM.startObserveAllNetworks();
171         verify(mCM, times(1)).registerNetworkCallback(
172                 any(NetworkRequest.class), any(NetworkCallback.class), any(Handler.class));
173 
174         mUNM.stop();
175         verify(mCM, times(1)).unregisterNetworkCallback(any(NetworkCallback.class));
176     }
177 
178     @Test
testRequestsMobileNetwork()179     public void testRequestsMobileNetwork() throws Exception {
180         assertFalse(mUNM.mobileNetworkRequested());
181         assertEquals(0, mCM.requested.size());
182 
183         mUNM.startObserveAllNetworks();
184         assertFalse(mUNM.mobileNetworkRequested());
185         assertEquals(0, mCM.requested.size());
186 
187         mUNM.updateMobileRequiresDun(false);
188         assertFalse(mUNM.mobileNetworkRequested());
189         assertEquals(0, mCM.requested.size());
190 
191         mUNM.registerMobileNetworkRequest();
192         assertTrue(mUNM.mobileNetworkRequested());
193         assertUpstreamTypeRequested(TYPE_MOBILE_HIPRI);
194         assertFalse(isDunRequested());
195 
196         mUNM.stop();
197         assertFalse(mUNM.mobileNetworkRequested());
198         assertTrue(mCM.hasNoCallbacks());
199     }
200 
201     @Test
testDuplicateMobileRequestsIgnored()202     public void testDuplicateMobileRequestsIgnored() throws Exception {
203         assertFalse(mUNM.mobileNetworkRequested());
204         assertEquals(0, mCM.requested.size());
205 
206         mUNM.startObserveAllNetworks();
207         verify(mCM, times(1)).registerNetworkCallback(
208                 any(NetworkRequest.class), any(NetworkCallback.class), any(Handler.class));
209         assertFalse(mUNM.mobileNetworkRequested());
210         assertEquals(0, mCM.requested.size());
211 
212         mUNM.updateMobileRequiresDun(true);
213         mUNM.registerMobileNetworkRequest();
214         verify(mCM, times(1)).requestNetwork(
215                 any(NetworkRequest.class), anyInt(), anyInt(), any(Handler.class),
216                 any(NetworkCallback.class));
217 
218         assertTrue(mUNM.mobileNetworkRequested());
219         assertUpstreamTypeRequested(TYPE_MOBILE_DUN);
220         assertTrue(isDunRequested());
221 
222         // Try a few things that must not result in any state change.
223         mUNM.registerMobileNetworkRequest();
224         mUNM.updateMobileRequiresDun(true);
225         mUNM.registerMobileNetworkRequest();
226 
227         assertTrue(mUNM.mobileNetworkRequested());
228         assertUpstreamTypeRequested(TYPE_MOBILE_DUN);
229         assertTrue(isDunRequested());
230 
231         mUNM.stop();
232         verify(mCM, times(2)).unregisterNetworkCallback(any(NetworkCallback.class));
233 
234         verifyNoMoreInteractions(mCM);
235     }
236 
237     @Test
testRequestsDunNetwork()238     public void testRequestsDunNetwork() throws Exception {
239         assertFalse(mUNM.mobileNetworkRequested());
240         assertEquals(0, mCM.requested.size());
241 
242         mUNM.startObserveAllNetworks();
243         assertFalse(mUNM.mobileNetworkRequested());
244         assertEquals(0, mCM.requested.size());
245 
246         mUNM.updateMobileRequiresDun(true);
247         assertFalse(mUNM.mobileNetworkRequested());
248         assertEquals(0, mCM.requested.size());
249 
250         mUNM.registerMobileNetworkRequest();
251         assertTrue(mUNM.mobileNetworkRequested());
252         assertUpstreamTypeRequested(TYPE_MOBILE_DUN);
253         assertTrue(isDunRequested());
254 
255         mUNM.stop();
256         assertFalse(mUNM.mobileNetworkRequested());
257         assertTrue(mCM.hasNoCallbacks());
258     }
259 
260     @Test
testUpdateMobileRequiresDun()261     public void testUpdateMobileRequiresDun() throws Exception {
262         mUNM.startObserveAllNetworks();
263 
264         // Test going from no-DUN to DUN correctly re-registers callbacks.
265         mUNM.updateMobileRequiresDun(false);
266         mUNM.registerMobileNetworkRequest();
267         assertTrue(mUNM.mobileNetworkRequested());
268         assertUpstreamTypeRequested(TYPE_MOBILE_HIPRI);
269         assertFalse(isDunRequested());
270         mUNM.updateMobileRequiresDun(true);
271         assertTrue(mUNM.mobileNetworkRequested());
272         assertUpstreamTypeRequested(TYPE_MOBILE_DUN);
273         assertTrue(isDunRequested());
274 
275         // Test going from DUN to no-DUN correctly re-registers callbacks.
276         mUNM.updateMobileRequiresDun(false);
277         assertTrue(mUNM.mobileNetworkRequested());
278         assertUpstreamTypeRequested(TYPE_MOBILE_HIPRI);
279         assertFalse(isDunRequested());
280 
281         mUNM.stop();
282         assertFalse(mUNM.mobileNetworkRequested());
283     }
284 
285     @Test
testSelectPreferredUpstreamType()286     public void testSelectPreferredUpstreamType() throws Exception {
287         final Collection<Integer> preferredTypes = new ArrayList<>();
288         preferredTypes.add(TYPE_WIFI);
289 
290         mUNM.startTrackDefaultNetwork(sDefaultRequest, mEntitleMgr);
291         mUNM.startObserveAllNetworks();
292         // There are no networks, so there is nothing to select.
293         assertSatisfiesLegacyType(TYPE_NONE, mUNM.selectPreferredUpstreamType(preferredTypes));
294 
295         final TestNetworkAgent wifiAgent = new TestNetworkAgent(mCM, TRANSPORT_WIFI);
296         wifiAgent.fakeConnect();
297         // WiFi is up, we should prefer it.
298         assertSatisfiesLegacyType(TYPE_WIFI, mUNM.selectPreferredUpstreamType(preferredTypes));
299         wifiAgent.fakeDisconnect();
300         // There are no networks, so there is nothing to select.
301         assertSatisfiesLegacyType(TYPE_NONE, mUNM.selectPreferredUpstreamType(preferredTypes));
302 
303         final TestNetworkAgent cellAgent = new TestNetworkAgent(mCM, TRANSPORT_CELLULAR);
304         cellAgent.fakeConnect();
305         assertSatisfiesLegacyType(TYPE_NONE, mUNM.selectPreferredUpstreamType(preferredTypes));
306 
307         preferredTypes.add(TYPE_MOBILE_DUN);
308         // This is coupled with preferred types in TetheringConfiguration.
309         mUNM.updateMobileRequiresDun(true);
310         // DUN is available, but only use regular cell: no upstream selected.
311         assertSatisfiesLegacyType(TYPE_NONE, mUNM.selectPreferredUpstreamType(preferredTypes));
312         preferredTypes.remove(TYPE_MOBILE_DUN);
313         // No WiFi, but our preferred flavour of cell is up.
314         preferredTypes.add(TYPE_MOBILE_HIPRI);
315         // This is coupled with preferred types in TetheringConfiguration.
316         mUNM.updateMobileRequiresDun(false);
317         assertSatisfiesLegacyType(TYPE_MOBILE_HIPRI,
318                 mUNM.selectPreferredUpstreamType(preferredTypes));
319         // Check to see we filed an explicit request.
320         assertEquals(1, mCM.requested.size());
321         NetworkRequest netReq = (NetworkRequest) mCM.requested.values().toArray()[0];
322         assertTrue(netReq.networkCapabilities.hasTransport(TRANSPORT_CELLULAR));
323         assertFalse(netReq.networkCapabilities.hasCapability(NET_CAPABILITY_DUN));
324         // mobile is not permitted, we should not use HIPRI.
325         when(mEntitleMgr.isCellularUpstreamPermitted()).thenReturn(false);
326         assertSatisfiesLegacyType(TYPE_NONE, mUNM.selectPreferredUpstreamType(preferredTypes));
327         assertEquals(0, mCM.requested.size());
328         // mobile change back to permitted, HIRPI should come back
329         when(mEntitleMgr.isCellularUpstreamPermitted()).thenReturn(true);
330         assertSatisfiesLegacyType(TYPE_MOBILE_HIPRI,
331                 mUNM.selectPreferredUpstreamType(preferredTypes));
332 
333         wifiAgent.fakeConnect();
334         // WiFi is up, and we should prefer it over cell.
335         assertSatisfiesLegacyType(TYPE_WIFI, mUNM.selectPreferredUpstreamType(preferredTypes));
336         assertEquals(0, mCM.requested.size());
337 
338         preferredTypes.remove(TYPE_MOBILE_HIPRI);
339         preferredTypes.add(TYPE_MOBILE_DUN);
340         // This is coupled with preferred types in TetheringConfiguration.
341         mUNM.updateMobileRequiresDun(true);
342         assertSatisfiesLegacyType(TYPE_WIFI, mUNM.selectPreferredUpstreamType(preferredTypes));
343 
344         final TestNetworkAgent dunAgent = new TestNetworkAgent(mCM, TRANSPORT_CELLULAR);
345         dunAgent.networkCapabilities.addCapability(NET_CAPABILITY_DUN);
346         dunAgent.fakeConnect();
347 
348         // WiFi is still preferred.
349         assertSatisfiesLegacyType(TYPE_WIFI, mUNM.selectPreferredUpstreamType(preferredTypes));
350 
351         // WiFi goes down, cell and DUN are still up but only DUN is preferred.
352         wifiAgent.fakeDisconnect();
353         assertSatisfiesLegacyType(TYPE_MOBILE_DUN,
354                 mUNM.selectPreferredUpstreamType(preferredTypes));
355         // Check to see we filed an explicit request.
356         assertEquals(1, mCM.requested.size());
357         netReq = (NetworkRequest) mCM.requested.values().toArray()[0];
358         assertTrue(netReq.networkCapabilities.hasTransport(TRANSPORT_CELLULAR));
359         assertTrue(netReq.networkCapabilities.hasCapability(NET_CAPABILITY_DUN));
360         // mobile is not permitted, we should not use DUN.
361         when(mEntitleMgr.isCellularUpstreamPermitted()).thenReturn(false);
362         assertSatisfiesLegacyType(TYPE_NONE, mUNM.selectPreferredUpstreamType(preferredTypes));
363         assertEquals(0, mCM.requested.size());
364         // mobile change back to permitted, DUN should come back
365         when(mEntitleMgr.isCellularUpstreamPermitted()).thenReturn(true);
366         assertSatisfiesLegacyType(TYPE_MOBILE_DUN,
367                 mUNM.selectPreferredUpstreamType(preferredTypes));
368     }
369 
370     @Test
testGetCurrentPreferredUpstream()371     public void testGetCurrentPreferredUpstream() throws Exception {
372         mUNM.startTrackDefaultNetwork(sDefaultRequest, mEntitleMgr);
373         mUNM.startObserveAllNetworks();
374         mUNM.updateMobileRequiresDun(false);
375 
376         // [0] Mobile connects, DUN not required -> mobile selected.
377         final TestNetworkAgent cellAgent = new TestNetworkAgent(mCM, TRANSPORT_CELLULAR);
378         cellAgent.fakeConnect();
379         mCM.makeDefaultNetwork(cellAgent);
380         assertEquals(cellAgent.networkId, mUNM.getCurrentPreferredUpstream().network);
381 
382         // [1] Mobile connects but not permitted -> null selected
383         when(mEntitleMgr.isCellularUpstreamPermitted()).thenReturn(false);
384         assertEquals(null, mUNM.getCurrentPreferredUpstream());
385         when(mEntitleMgr.isCellularUpstreamPermitted()).thenReturn(true);
386 
387         // [2] WiFi connects but not validated/promoted to default -> mobile selected.
388         final TestNetworkAgent wifiAgent = new TestNetworkAgent(mCM, TRANSPORT_WIFI);
389         wifiAgent.fakeConnect();
390         assertEquals(cellAgent.networkId, mUNM.getCurrentPreferredUpstream().network);
391 
392         // [3] WiFi validates and is promoted to the default network -> WiFi selected.
393         mCM.makeDefaultNetwork(wifiAgent);
394         assertEquals(wifiAgent.networkId, mUNM.getCurrentPreferredUpstream().network);
395 
396         // [4] DUN required, no other changes -> WiFi still selected
397         mUNM.updateMobileRequiresDun(true);
398         assertEquals(wifiAgent.networkId, mUNM.getCurrentPreferredUpstream().network);
399 
400         // [5] WiFi no longer validated, mobile becomes default, DUN required -> null selected.
401         mCM.makeDefaultNetwork(cellAgent);
402         assertEquals(null, mUNM.getCurrentPreferredUpstream());
403         // TODO: make sure that a DUN request has been filed. This is currently
404         // triggered by code over in Tethering, but once that has been moved
405         // into UNM we should test for this here.
406 
407         // [6] DUN network arrives -> DUN selected
408         final TestNetworkAgent dunAgent = new TestNetworkAgent(mCM, TRANSPORT_CELLULAR);
409         dunAgent.networkCapabilities.addCapability(NET_CAPABILITY_DUN);
410         dunAgent.networkCapabilities.removeCapability(NET_CAPABILITY_INTERNET);
411         dunAgent.fakeConnect();
412         assertEquals(dunAgent.networkId, mUNM.getCurrentPreferredUpstream().network);
413 
414         // [7] Mobile is not permitted -> null selected
415         when(mEntitleMgr.isCellularUpstreamPermitted()).thenReturn(false);
416         assertEquals(null, mUNM.getCurrentPreferredUpstream());
417     }
418 
419     @Test
testLocalPrefixes()420     public void testLocalPrefixes() throws Exception {
421         mUNM.startTrackDefaultNetwork(sDefaultRequest, mEntitleMgr);
422         mUNM.startObserveAllNetworks();
423 
424         // [0] Test minimum set of local prefixes.
425         Set<IpPrefix> local = mUNM.getLocalPrefixes();
426         assertTrue(local.isEmpty());
427 
428         final Set<String> alreadySeen = new HashSet<>();
429 
430         // [1] Pretend Wi-Fi connects.
431         final TestNetworkAgent wifiAgent = new TestNetworkAgent(mCM, TRANSPORT_WIFI);
432         final LinkProperties wifiLp = wifiAgent.linkProperties;
433         wifiLp.setInterfaceName("wlan0");
434         final String[] wifi_addrs = {
435                 "fe80::827a:bfff:fe6f:374d", "100.112.103.18",
436                 "2001:db8:4:fd00:827a:bfff:fe6f:374d",
437                 "2001:db8:4:fd00:6dea:325a:fdae:4ef4",
438                 "fd6a:a640:60bf:e985::123",  // ULA address for good measure.
439         };
440         for (String addrStr : wifi_addrs) {
441             final String cidr = addrStr.contains(":") ? "/64" : "/20";
442             wifiLp.addLinkAddress(new LinkAddress(addrStr + cidr));
443         }
444         wifiAgent.fakeConnect();
445         wifiAgent.sendLinkProperties();
446 
447         local = mUNM.getLocalPrefixes();
448         assertPrefixSet(local, INCLUDES, alreadySeen);
449         final String[] wifiLinkPrefixes = {
450                 // Link-local prefixes are excluded and dealt with elsewhere.
451                 "100.112.96.0/20", "2001:db8:4:fd00::/64", "fd6a:a640:60bf:e985::/64",
452         };
453         assertPrefixSet(local, INCLUDES, wifiLinkPrefixes);
454         Collections.addAll(alreadySeen, wifiLinkPrefixes);
455         assertEquals(alreadySeen.size(), local.size());
456 
457         // [2] Pretend mobile connects.
458         final TestNetworkAgent cellAgent = new TestNetworkAgent(mCM, TRANSPORT_CELLULAR);
459         final LinkProperties cellLp = cellAgent.linkProperties;
460         cellLp.setInterfaceName("rmnet_data0");
461         final String[] cell_addrs = {
462                 "10.102.211.48", "2001:db8:0:1:b50e:70d9:10c9:433d",
463         };
464         for (String addrStr : cell_addrs) {
465             final String cidr = addrStr.contains(":") ? "/64" : "/27";
466             cellLp.addLinkAddress(new LinkAddress(addrStr + cidr));
467         }
468         cellAgent.fakeConnect();
469         cellAgent.sendLinkProperties();
470 
471         local = mUNM.getLocalPrefixes();
472         assertPrefixSet(local, INCLUDES, alreadySeen);
473         final String[] cellLinkPrefixes = { "10.102.211.32/27", "2001:db8:0:1::/64" };
474         assertPrefixSet(local, INCLUDES, cellLinkPrefixes);
475         Collections.addAll(alreadySeen, cellLinkPrefixes);
476         assertEquals(alreadySeen.size(), local.size());
477 
478         // [3] Pretend DUN connects.
479         final TestNetworkAgent dunAgent = new TestNetworkAgent(mCM, TRANSPORT_CELLULAR);
480         dunAgent.networkCapabilities.addCapability(NET_CAPABILITY_DUN);
481         dunAgent.networkCapabilities.removeCapability(NET_CAPABILITY_INTERNET);
482         final LinkProperties dunLp = dunAgent.linkProperties;
483         dunLp.setInterfaceName("rmnet_data1");
484         final String[] dun_addrs = {
485                 "192.0.2.48", "2001:db8:1:2:b50e:70d9:10c9:433d",
486         };
487         for (String addrStr : dun_addrs) {
488             final String cidr = addrStr.contains(":") ? "/64" : "/27";
489             dunLp.addLinkAddress(new LinkAddress(addrStr + cidr));
490         }
491         dunAgent.fakeConnect();
492         dunAgent.sendLinkProperties();
493 
494         local = mUNM.getLocalPrefixes();
495         assertPrefixSet(local, INCLUDES, alreadySeen);
496         final String[] dunLinkPrefixes = { "192.0.2.32/27", "2001:db8:1:2::/64" };
497         assertPrefixSet(local, INCLUDES, dunLinkPrefixes);
498         Collections.addAll(alreadySeen, dunLinkPrefixes);
499         assertEquals(alreadySeen.size(), local.size());
500 
501         // [4] Pretend Wi-Fi disconnected.  It's addresses/prefixes should no
502         // longer be included (should be properly removed).
503         wifiAgent.fakeDisconnect();
504         local = mUNM.getLocalPrefixes();
505         assertPrefixSet(local, EXCLUDES, wifiLinkPrefixes);
506         assertPrefixSet(local, INCLUDES, cellLinkPrefixes);
507         assertPrefixSet(local, INCLUDES, dunLinkPrefixes);
508 
509         // [5] Pretend mobile disconnected.
510         cellAgent.fakeDisconnect();
511         local = mUNM.getLocalPrefixes();
512         assertPrefixSet(local, EXCLUDES, wifiLinkPrefixes);
513         assertPrefixSet(local, EXCLUDES, cellLinkPrefixes);
514         assertPrefixSet(local, INCLUDES, dunLinkPrefixes);
515 
516         // [6] Pretend DUN disconnected.
517         dunAgent.fakeDisconnect();
518         local = mUNM.getLocalPrefixes();
519         assertTrue(local.isEmpty());
520     }
521 
522     @Test
testSelectMobileWhenMobileIsNotDefault()523     public void testSelectMobileWhenMobileIsNotDefault() {
524         final Collection<Integer> preferredTypes = new ArrayList<>();
525         // Mobile has higher pirority than wifi.
526         preferredTypes.add(TYPE_MOBILE_HIPRI);
527         preferredTypes.add(TYPE_WIFI);
528         mUNM.startTrackDefaultNetwork(sDefaultRequest, mEntitleMgr);
529         mUNM.startObserveAllNetworks();
530         // Setup wifi and make wifi as default network.
531         final TestNetworkAgent wifiAgent = new TestNetworkAgent(mCM, TRANSPORT_WIFI);
532         wifiAgent.fakeConnect();
533         mCM.makeDefaultNetwork(wifiAgent);
534         // Setup mobile network.
535         final TestNetworkAgent cellAgent = new TestNetworkAgent(mCM, TRANSPORT_CELLULAR);
536         cellAgent.fakeConnect();
537 
538         assertSatisfiesLegacyType(TYPE_MOBILE_HIPRI,
539                 mUNM.selectPreferredUpstreamType(preferredTypes));
540         verify(mEntitleMgr, times(1)).maybeRunProvisioning();
541     }
542 
assertSatisfiesLegacyType(int legacyType, UpstreamNetworkState ns)543     private void assertSatisfiesLegacyType(int legacyType, UpstreamNetworkState ns) {
544         if (legacyType == TYPE_NONE) {
545             assertTrue(ns == null);
546             return;
547         }
548 
549         final NetworkCapabilities nc =
550                 UpstreamNetworkMonitor.networkCapabilitiesForType(legacyType);
551         assertTrue(nc.satisfiedByNetworkCapabilities(ns.networkCapabilities));
552     }
553 
assertUpstreamTypeRequested(int upstreamType)554     private void assertUpstreamTypeRequested(int upstreamType) throws Exception {
555         assertEquals(1, mCM.requested.size());
556         assertEquals(1, mCM.legacyTypeMap.size());
557         assertEquals(Integer.valueOf(upstreamType),
558                 mCM.legacyTypeMap.values().iterator().next());
559     }
560 
isDunRequested()561     private boolean isDunRequested() {
562         for (NetworkRequest req : mCM.requested.values()) {
563             if (req.networkCapabilities.hasCapability(NET_CAPABILITY_DUN)) {
564                 return true;
565             }
566         }
567         return false;
568     }
569 
570     public static class TestConnectivityManager extends ConnectivityManager {
571         public Map<NetworkCallback, Handler> allCallbacks = new HashMap<>();
572         public Set<NetworkCallback> trackingDefault = new HashSet<>();
573         public TestNetworkAgent defaultNetwork = null;
574         public Map<NetworkCallback, NetworkRequest> listening = new HashMap<>();
575         public Map<NetworkCallback, NetworkRequest> requested = new HashMap<>();
576         public Map<NetworkCallback, Integer> legacyTypeMap = new HashMap<>();
577 
578         private int mNetworkId = 100;
579 
TestConnectivityManager(Context ctx, IConnectivityManager svc)580         public TestConnectivityManager(Context ctx, IConnectivityManager svc) {
581             super(ctx, svc);
582         }
583 
hasNoCallbacks()584         boolean hasNoCallbacks() {
585             return allCallbacks.isEmpty()
586                     && trackingDefault.isEmpty()
587                     && listening.isEmpty()
588                     && requested.isEmpty()
589                     && legacyTypeMap.isEmpty();
590         }
591 
onlyHasDefaultCallbacks()592         boolean onlyHasDefaultCallbacks() {
593             return (allCallbacks.size() == 1)
594                     && (trackingDefault.size() == 1)
595                     && listening.isEmpty()
596                     && requested.isEmpty()
597                     && legacyTypeMap.isEmpty();
598         }
599 
isListeningForAll()600         boolean isListeningForAll() {
601             final NetworkCapabilities empty = new NetworkCapabilities();
602             empty.clearAll();
603 
604             for (NetworkRequest req : listening.values()) {
605                 if (req.networkCapabilities.equalRequestableCapabilities(empty)) {
606                     return true;
607                 }
608             }
609             return false;
610         }
611 
getNetworkId()612         int getNetworkId() {
613             return ++mNetworkId;
614         }
615 
makeDefaultNetwork(TestNetworkAgent agent)616         void makeDefaultNetwork(TestNetworkAgent agent) {
617             if (Objects.equals(defaultNetwork, agent)) return;
618 
619             final TestNetworkAgent formerDefault = defaultNetwork;
620             defaultNetwork = agent;
621 
622             for (NetworkCallback cb : trackingDefault) {
623                 if (defaultNetwork != null) {
624                     cb.onAvailable(defaultNetwork.networkId);
625                     cb.onCapabilitiesChanged(
626                             defaultNetwork.networkId, defaultNetwork.networkCapabilities);
627                     cb.onLinkPropertiesChanged(
628                             defaultNetwork.networkId, defaultNetwork.linkProperties);
629                 }
630             }
631         }
632 
633         @Override
requestNetwork(NetworkRequest req, NetworkCallback cb, Handler h)634         public void requestNetwork(NetworkRequest req, NetworkCallback cb, Handler h) {
635             assertFalse(allCallbacks.containsKey(cb));
636             allCallbacks.put(cb, h);
637             if (sDefaultRequest.equals(req)) {
638                 assertFalse(trackingDefault.contains(cb));
639                 trackingDefault.add(cb);
640             } else {
641                 assertFalse(requested.containsKey(cb));
642                 requested.put(cb, req);
643             }
644         }
645 
646         @Override
requestNetwork(NetworkRequest req, NetworkCallback cb)647         public void requestNetwork(NetworkRequest req, NetworkCallback cb) {
648             fail("Should never be called.");
649         }
650 
651         @Override
requestNetwork(NetworkRequest req, int timeoutMs, int legacyType, Handler h, NetworkCallback cb)652         public void requestNetwork(NetworkRequest req,
653                 int timeoutMs, int legacyType, Handler h, NetworkCallback cb) {
654             assertFalse(allCallbacks.containsKey(cb));
655             allCallbacks.put(cb, h);
656             assertFalse(requested.containsKey(cb));
657             requested.put(cb, req);
658             assertFalse(legacyTypeMap.containsKey(cb));
659             if (legacyType != ConnectivityManager.TYPE_NONE) {
660                 legacyTypeMap.put(cb, legacyType);
661             }
662         }
663 
664         @Override
registerNetworkCallback(NetworkRequest req, NetworkCallback cb, Handler h)665         public void registerNetworkCallback(NetworkRequest req, NetworkCallback cb, Handler h) {
666             assertFalse(allCallbacks.containsKey(cb));
667             allCallbacks.put(cb, h);
668             assertFalse(listening.containsKey(cb));
669             listening.put(cb, req);
670         }
671 
672         @Override
registerNetworkCallback(NetworkRequest req, NetworkCallback cb)673         public void registerNetworkCallback(NetworkRequest req, NetworkCallback cb) {
674             fail("Should never be called.");
675         }
676 
677         @Override
registerDefaultNetworkCallback(NetworkCallback cb, Handler h)678         public void registerDefaultNetworkCallback(NetworkCallback cb, Handler h) {
679             fail("Should never be called.");
680         }
681 
682         @Override
registerDefaultNetworkCallback(NetworkCallback cb)683         public void registerDefaultNetworkCallback(NetworkCallback cb) {
684             fail("Should never be called.");
685         }
686 
687         @Override
unregisterNetworkCallback(NetworkCallback cb)688         public void unregisterNetworkCallback(NetworkCallback cb) {
689             if (trackingDefault.contains(cb)) {
690                 trackingDefault.remove(cb);
691             } else if (listening.containsKey(cb)) {
692                 listening.remove(cb);
693             } else if (requested.containsKey(cb)) {
694                 requested.remove(cb);
695                 legacyTypeMap.remove(cb);
696             } else {
697                 fail("Unexpected callback removed");
698             }
699             allCallbacks.remove(cb);
700 
701             assertFalse(allCallbacks.containsKey(cb));
702             assertFalse(trackingDefault.contains(cb));
703             assertFalse(listening.containsKey(cb));
704             assertFalse(requested.containsKey(cb));
705         }
706     }
707 
708     public static class TestNetworkAgent {
709         public final TestConnectivityManager cm;
710         public final Network networkId;
711         public final int transportType;
712         public final NetworkCapabilities networkCapabilities;
713         public final LinkProperties linkProperties;
714 
TestNetworkAgent(TestConnectivityManager cm, int transportType)715         public TestNetworkAgent(TestConnectivityManager cm, int transportType) {
716             this.cm = cm;
717             this.networkId = new Network(cm.getNetworkId());
718             this.transportType = transportType;
719             networkCapabilities = new NetworkCapabilities();
720             networkCapabilities.addTransportType(transportType);
721             networkCapabilities.addCapability(NET_CAPABILITY_INTERNET);
722             linkProperties = new LinkProperties();
723         }
724 
fakeConnect()725         public void fakeConnect() {
726             for (NetworkCallback cb : cm.listening.keySet()) {
727                 cb.onAvailable(networkId);
728                 cb.onCapabilitiesChanged(networkId, copy(networkCapabilities));
729                 cb.onLinkPropertiesChanged(networkId, copy(linkProperties));
730             }
731         }
732 
fakeDisconnect()733         public void fakeDisconnect() {
734             for (NetworkCallback cb : cm.listening.keySet()) {
735                 cb.onLost(networkId);
736             }
737         }
738 
sendLinkProperties()739         public void sendLinkProperties() {
740             for (NetworkCallback cb : cm.listening.keySet()) {
741                 cb.onLinkPropertiesChanged(networkId, copy(linkProperties));
742             }
743         }
744 
745         @Override
toString()746         public String toString() {
747             return String.format("TestNetworkAgent: %s %s", networkId, networkCapabilities);
748         }
749     }
750 
751     public static class TestStateMachine extends StateMachine {
752         public final ArrayList<Message> messages = new ArrayList<>();
753         private final State mLoggingState = new LoggingState();
754 
755         class LoggingState extends State {
enter()756             @Override public void enter() {
757                 messages.clear();
758             }
759 
exit()760             @Override public void exit() {
761                 messages.clear();
762             }
763 
processMessage(Message msg)764             @Override public boolean processMessage(Message msg) {
765                 messages.add(msg);
766                 return true;
767             }
768         }
769 
TestStateMachine()770         public TestStateMachine() {
771             super("UpstreamNetworkMonitor.TestStateMachine");
772             addState(mLoggingState);
773             setInitialState(mLoggingState);
774             super.start();
775         }
776     }
777 
copy(NetworkCapabilities nc)778     static NetworkCapabilities copy(NetworkCapabilities nc) {
779         return new NetworkCapabilities(nc);
780     }
781 
copy(LinkProperties lp)782     static LinkProperties copy(LinkProperties lp) {
783         return new LinkProperties(lp);
784     }
785 
assertPrefixSet(Set<IpPrefix> prefixes, boolean expectation, String... expected)786     static void assertPrefixSet(Set<IpPrefix> prefixes, boolean expectation, String... expected) {
787         final Set<String> expectedSet = new HashSet<>();
788         Collections.addAll(expectedSet, expected);
789         assertPrefixSet(prefixes, expectation, expectedSet);
790     }
791 
assertPrefixSet(Set<IpPrefix> prefixes, boolean expectation, Set<String> expected)792     static void assertPrefixSet(Set<IpPrefix> prefixes, boolean expectation, Set<String> expected) {
793         for (String expectedPrefix : expected) {
794             final String errStr = expectation ? "did not find" : "found";
795             assertEquals(
796                     String.format("Failed expectation: %s prefix: %s", errStr, expectedPrefix),
797                     expectation, prefixes.contains(new IpPrefix(expectedPrefix)));
798         }
799     }
800 }
801