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
17package com.android.server.connectivity.tethering;
18
19import static android.net.ConnectivityManager.TYPE_MOBILE_DUN;
20import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI;
21import static android.net.ConnectivityManager.TYPE_NONE;
22import static android.net.ConnectivityManager.TYPE_WIFI;
23import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN;
24import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
25import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
26import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
27import static org.junit.Assert.assertEquals;
28import static org.junit.Assert.assertFalse;
29import static org.junit.Assert.assertTrue;
30import static org.junit.Assert.fail;
31import static org.mockito.Mockito.any;
32import static org.mockito.Mockito.anyInt;
33import static org.mockito.Mockito.anyString;
34import static org.mockito.Mockito.reset;
35import static org.mockito.Mockito.spy;
36import static org.mockito.Mockito.times;
37import static org.mockito.Mockito.verify;
38import static org.mockito.Mockito.verifyNoMoreInteractions;
39import static org.mockito.Mockito.when;
40
41import android.content.Context;
42import android.os.Handler;
43import android.os.Message;
44import android.net.ConnectivityManager;
45import android.net.ConnectivityManager.NetworkCallback;
46import android.net.IConnectivityManager;
47import android.net.IpPrefix;
48import android.net.LinkAddress;
49import android.net.LinkProperties;
50import android.net.Network;
51import android.net.NetworkCapabilities;
52import android.net.NetworkRequest;
53import android.net.NetworkState;
54import android.net.util.SharedLog;
55
56import android.support.test.filters.SmallTest;
57import android.support.test.runner.AndroidJUnit4;
58
59import com.android.internal.util.State;
60import com.android.internal.util.StateMachine;
61
62import org.junit.After;
63import org.junit.Before;
64import org.junit.runner.RunWith;
65import org.junit.Test;
66import org.mockito.Mock;
67import org.mockito.Mockito;
68import org.mockito.MockitoAnnotations;
69
70import java.util.ArrayList;
71import java.util.Collection;
72import java.util.Collections;
73import java.util.HashMap;
74import java.util.HashSet;
75import java.util.Map;
76import java.util.Set;
77
78
79@RunWith(AndroidJUnit4.class)
80@SmallTest
81public class UpstreamNetworkMonitorTest {
82    private static final int EVENT_UNM_UPDATE = 1;
83
84    private static final boolean INCLUDES = true;
85    private static final boolean EXCLUDES = false;
86
87    @Mock private Context mContext;
88    @Mock private IConnectivityManager mCS;
89    @Mock private SharedLog mLog;
90
91    private TestStateMachine mSM;
92    private TestConnectivityManager mCM;
93    private UpstreamNetworkMonitor mUNM;
94
95    @Before public void setUp() throws Exception {
96        MockitoAnnotations.initMocks(this);
97        reset(mContext);
98        reset(mCS);
99        reset(mLog);
100        when(mLog.forSubComponent(anyString())).thenReturn(mLog);
101
102        mCM = spy(new TestConnectivityManager(mContext, mCS));
103        mSM = new TestStateMachine();
104        mUNM = new UpstreamNetworkMonitor(
105                (ConnectivityManager) mCM, mSM, mLog, EVENT_UNM_UPDATE);
106    }
107
108    @After public void tearDown() throws Exception {
109        if (mSM != null) {
110            mSM.quit();
111            mSM = null;
112        }
113    }
114
115    @Test
116    public void testDoesNothingBeforeStarted() {
117        assertTrue(mCM.hasNoCallbacks());
118        assertFalse(mUNM.mobileNetworkRequested());
119
120        mUNM.updateMobileRequiresDun(true);
121        assertTrue(mCM.hasNoCallbacks());
122        mUNM.updateMobileRequiresDun(false);
123        assertTrue(mCM.hasNoCallbacks());
124    }
125
126    @Test
127    public void testDefaultNetworkIsTracked() throws Exception {
128        assertEquals(0, mCM.trackingDefault.size());
129
130        mUNM.start();
131        assertEquals(1, mCM.trackingDefault.size());
132
133        mUNM.stop();
134        assertTrue(mCM.hasNoCallbacks());
135    }
136
137    @Test
138    public void testListensForAllNetworks() throws Exception {
139        assertTrue(mCM.listening.isEmpty());
140
141        mUNM.start();
142        assertFalse(mCM.listening.isEmpty());
143        assertTrue(mCM.isListeningForAll());
144
145        mUNM.stop();
146        assertTrue(mCM.hasNoCallbacks());
147    }
148
149    @Test
150    public void testCallbacksRegistered() {
151        mUNM.start();
152        verify(mCM, times(1)).registerNetworkCallback(any(), any(), any());
153        verify(mCM, times(1)).registerDefaultNetworkCallback(any(), any());
154
155        mUNM.stop();
156        verify(mCM, times(2)).unregisterNetworkCallback(any(NetworkCallback.class));
157    }
158
159    @Test
160    public void testRequestsMobileNetwork() throws Exception {
161        assertFalse(mUNM.mobileNetworkRequested());
162        assertEquals(0, mCM.requested.size());
163
164        mUNM.start();
165        assertFalse(mUNM.mobileNetworkRequested());
166        assertEquals(0, mCM.requested.size());
167
168        mUNM.updateMobileRequiresDun(false);
169        assertFalse(mUNM.mobileNetworkRequested());
170        assertEquals(0, mCM.requested.size());
171
172        mUNM.registerMobileNetworkRequest();
173        assertTrue(mUNM.mobileNetworkRequested());
174        assertUpstreamTypeRequested(TYPE_MOBILE_HIPRI);
175        assertFalse(mCM.isDunRequested());
176
177        mUNM.stop();
178        assertFalse(mUNM.mobileNetworkRequested());
179        assertTrue(mCM.hasNoCallbacks());
180    }
181
182    @Test
183    public void testDuplicateMobileRequestsIgnored() throws Exception {
184        assertFalse(mUNM.mobileNetworkRequested());
185        assertEquals(0, mCM.requested.size());
186
187        mUNM.start();
188        verify(mCM, Mockito.times(1)).registerNetworkCallback(
189                any(NetworkRequest.class), any(NetworkCallback.class), any(Handler.class));
190        verify(mCM, Mockito.times(1)).registerDefaultNetworkCallback(
191                any(NetworkCallback.class), any(Handler.class));
192        assertFalse(mUNM.mobileNetworkRequested());
193        assertEquals(0, mCM.requested.size());
194
195        mUNM.updateMobileRequiresDun(true);
196        mUNM.registerMobileNetworkRequest();
197        verify(mCM, Mockito.times(1)).requestNetwork(
198                any(NetworkRequest.class), any(NetworkCallback.class), anyInt(), anyInt(),
199                any(Handler.class));
200
201        assertTrue(mUNM.mobileNetworkRequested());
202        assertUpstreamTypeRequested(TYPE_MOBILE_DUN);
203        assertTrue(mCM.isDunRequested());
204
205        // Try a few things that must not result in any state change.
206        mUNM.registerMobileNetworkRequest();
207        mUNM.updateMobileRequiresDun(true);
208        mUNM.registerMobileNetworkRequest();
209
210        assertTrue(mUNM.mobileNetworkRequested());
211        assertUpstreamTypeRequested(TYPE_MOBILE_DUN);
212        assertTrue(mCM.isDunRequested());
213
214        mUNM.stop();
215        verify(mCM, times(3)).unregisterNetworkCallback(any(NetworkCallback.class));
216
217        verifyNoMoreInteractions(mCM);
218    }
219
220    @Test
221    public void testRequestsDunNetwork() throws Exception {
222        assertFalse(mUNM.mobileNetworkRequested());
223        assertEquals(0, mCM.requested.size());
224
225        mUNM.start();
226        assertFalse(mUNM.mobileNetworkRequested());
227        assertEquals(0, mCM.requested.size());
228
229        mUNM.updateMobileRequiresDun(true);
230        assertFalse(mUNM.mobileNetworkRequested());
231        assertEquals(0, mCM.requested.size());
232
233        mUNM.registerMobileNetworkRequest();
234        assertTrue(mUNM.mobileNetworkRequested());
235        assertUpstreamTypeRequested(TYPE_MOBILE_DUN);
236        assertTrue(mCM.isDunRequested());
237
238        mUNM.stop();
239        assertFalse(mUNM.mobileNetworkRequested());
240        assertTrue(mCM.hasNoCallbacks());
241    }
242
243    @Test
244    public void testUpdateMobileRequiresDun() throws Exception {
245        mUNM.start();
246
247        // Test going from no-DUN to DUN correctly re-registers callbacks.
248        mUNM.updateMobileRequiresDun(false);
249        mUNM.registerMobileNetworkRequest();
250        assertTrue(mUNM.mobileNetworkRequested());
251        assertUpstreamTypeRequested(TYPE_MOBILE_HIPRI);
252        assertFalse(mCM.isDunRequested());
253        mUNM.updateMobileRequiresDun(true);
254        assertTrue(mUNM.mobileNetworkRequested());
255        assertUpstreamTypeRequested(TYPE_MOBILE_DUN);
256        assertTrue(mCM.isDunRequested());
257
258        // Test going from DUN to no-DUN correctly re-registers callbacks.
259        mUNM.updateMobileRequiresDun(false);
260        assertTrue(mUNM.mobileNetworkRequested());
261        assertUpstreamTypeRequested(TYPE_MOBILE_HIPRI);
262        assertFalse(mCM.isDunRequested());
263
264        mUNM.stop();
265        assertFalse(mUNM.mobileNetworkRequested());
266    }
267
268    @Test
269    public void testSelectPreferredUpstreamType() throws Exception {
270        final Collection<Integer> preferredTypes = new ArrayList<>();
271        preferredTypes.add(TYPE_WIFI);
272
273        mUNM.start();
274        // There are no networks, so there is nothing to select.
275        assertSatisfiesLegacyType(TYPE_NONE, mUNM.selectPreferredUpstreamType(preferredTypes));
276
277        final TestNetworkAgent wifiAgent = new TestNetworkAgent(mCM, TRANSPORT_WIFI);
278        wifiAgent.fakeConnect();
279        // WiFi is up, we should prefer it.
280        assertSatisfiesLegacyType(TYPE_WIFI, mUNM.selectPreferredUpstreamType(preferredTypes));
281        wifiAgent.fakeDisconnect();
282        // There are no networks, so there is nothing to select.
283        assertSatisfiesLegacyType(TYPE_NONE, mUNM.selectPreferredUpstreamType(preferredTypes));
284
285        final TestNetworkAgent cellAgent = new TestNetworkAgent(mCM, TRANSPORT_CELLULAR);
286        cellAgent.fakeConnect();
287        assertSatisfiesLegacyType(TYPE_NONE, mUNM.selectPreferredUpstreamType(preferredTypes));
288
289        preferredTypes.add(TYPE_MOBILE_DUN);
290        // This is coupled with preferred types in TetheringConfiguration.
291        mUNM.updateMobileRequiresDun(true);
292        // DUN is available, but only use regular cell: no upstream selected.
293        assertSatisfiesLegacyType(TYPE_NONE, mUNM.selectPreferredUpstreamType(preferredTypes));
294        preferredTypes.remove(TYPE_MOBILE_DUN);
295        // No WiFi, but our preferred flavour of cell is up.
296        preferredTypes.add(TYPE_MOBILE_HIPRI);
297        // This is coupled with preferred types in TetheringConfiguration.
298        mUNM.updateMobileRequiresDun(false);
299        assertSatisfiesLegacyType(TYPE_MOBILE_HIPRI,
300                mUNM.selectPreferredUpstreamType(preferredTypes));
301        // Check to see we filed an explicit request.
302        assertEquals(1, mCM.requested.size());
303        NetworkRequest netReq = (NetworkRequest) mCM.requested.values().toArray()[0];
304        assertTrue(netReq.networkCapabilities.hasTransport(TRANSPORT_CELLULAR));
305        assertFalse(netReq.networkCapabilities.hasCapability(NET_CAPABILITY_DUN));
306
307        wifiAgent.fakeConnect();
308        // WiFi is up, and we should prefer it over cell.
309        assertSatisfiesLegacyType(TYPE_WIFI, mUNM.selectPreferredUpstreamType(preferredTypes));
310        assertEquals(0, mCM.requested.size());
311
312        preferredTypes.remove(TYPE_MOBILE_HIPRI);
313        preferredTypes.add(TYPE_MOBILE_DUN);
314        // This is coupled with preferred types in TetheringConfiguration.
315        mUNM.updateMobileRequiresDun(true);
316        assertSatisfiesLegacyType(TYPE_WIFI, mUNM.selectPreferredUpstreamType(preferredTypes));
317
318        final TestNetworkAgent dunAgent = new TestNetworkAgent(mCM, TRANSPORT_CELLULAR);
319        dunAgent.networkCapabilities.addCapability(NET_CAPABILITY_DUN);
320        dunAgent.fakeConnect();
321
322        // WiFi is still preferred.
323        assertSatisfiesLegacyType(TYPE_WIFI, mUNM.selectPreferredUpstreamType(preferredTypes));
324
325        // WiFi goes down, cell and DUN are still up but only DUN is preferred.
326        wifiAgent.fakeDisconnect();
327        assertSatisfiesLegacyType(TYPE_MOBILE_DUN,
328                mUNM.selectPreferredUpstreamType(preferredTypes));
329        // Check to see we filed an explicit request.
330        assertEquals(1, mCM.requested.size());
331        netReq = (NetworkRequest) mCM.requested.values().toArray()[0];
332        assertTrue(netReq.networkCapabilities.hasTransport(TRANSPORT_CELLULAR));
333        assertTrue(netReq.networkCapabilities.hasCapability(NET_CAPABILITY_DUN));
334    }
335
336    @Test
337    public void testLocalPrefixes() throws Exception {
338        mUNM.start();
339
340        // [0] Test minimum set of local prefixes.
341        Set<IpPrefix> local = mUNM.getLocalPrefixes();
342        assertTrue(local.isEmpty());
343
344        final Set<String> alreadySeen = new HashSet<>();
345
346        // [1] Pretend Wi-Fi connects.
347        final TestNetworkAgent wifiAgent = new TestNetworkAgent(mCM, TRANSPORT_WIFI);
348        final LinkProperties wifiLp = new LinkProperties();
349        wifiLp.setInterfaceName("wlan0");
350        final String[] WIFI_ADDRS = {
351                "fe80::827a:bfff:fe6f:374d", "100.112.103.18",
352                "2001:db8:4:fd00:827a:bfff:fe6f:374d",
353                "2001:db8:4:fd00:6dea:325a:fdae:4ef4",
354                "fd6a:a640:60bf:e985::123",  // ULA address for good measure.
355        };
356        for (String addrStr : WIFI_ADDRS) {
357            final String cidr = addrStr.contains(":") ? "/64" : "/20";
358            wifiLp.addLinkAddress(new LinkAddress(addrStr + cidr));
359        }
360        wifiAgent.fakeConnect();
361        wifiAgent.sendLinkProperties(wifiLp);
362
363        local = mUNM.getLocalPrefixes();
364        assertPrefixSet(local, INCLUDES, alreadySeen);
365        final String[] wifiLinkPrefixes = {
366                // Link-local prefixes are excluded and dealt with elsewhere.
367                "100.112.96.0/20", "2001:db8:4:fd00::/64", "fd6a:a640:60bf:e985::/64",
368        };
369        assertPrefixSet(local, INCLUDES, wifiLinkPrefixes);
370        Collections.addAll(alreadySeen, wifiLinkPrefixes);
371        assertEquals(alreadySeen.size(), local.size());
372
373        // [2] Pretend mobile connects.
374        final TestNetworkAgent cellAgent = new TestNetworkAgent(mCM, TRANSPORT_CELLULAR);
375        final LinkProperties cellLp = new LinkProperties();
376        cellLp.setInterfaceName("rmnet_data0");
377        final String[] CELL_ADDRS = {
378                "10.102.211.48", "2001:db8:0:1:b50e:70d9:10c9:433d",
379        };
380        for (String addrStr : CELL_ADDRS) {
381            final String cidr = addrStr.contains(":") ? "/64" : "/27";
382            cellLp.addLinkAddress(new LinkAddress(addrStr + cidr));
383        }
384        cellAgent.fakeConnect();
385        cellAgent.sendLinkProperties(cellLp);
386
387        local = mUNM.getLocalPrefixes();
388        assertPrefixSet(local, INCLUDES, alreadySeen);
389        final String[] cellLinkPrefixes = { "10.102.211.32/27", "2001:db8:0:1::/64" };
390        assertPrefixSet(local, INCLUDES, cellLinkPrefixes);
391        Collections.addAll(alreadySeen, cellLinkPrefixes);
392        assertEquals(alreadySeen.size(), local.size());
393
394        // [3] Pretend DUN connects.
395        final TestNetworkAgent dunAgent = new TestNetworkAgent(mCM, TRANSPORT_CELLULAR);
396        dunAgent.networkCapabilities.addCapability(NET_CAPABILITY_DUN);
397        final LinkProperties dunLp = new LinkProperties();
398        dunLp.setInterfaceName("rmnet_data1");
399        final String[] DUN_ADDRS = {
400                "192.0.2.48", "2001:db8:1:2:b50e:70d9:10c9:433d",
401        };
402        for (String addrStr : DUN_ADDRS) {
403            final String cidr = addrStr.contains(":") ? "/64" : "/27";
404            cellLp.addLinkAddress(new LinkAddress(addrStr + cidr));
405        }
406        dunAgent.fakeConnect();
407        dunAgent.sendLinkProperties(dunLp);
408
409        local = mUNM.getLocalPrefixes();
410        assertPrefixSet(local, INCLUDES, alreadySeen);
411        final String[] dunLinkPrefixes = { "192.0.2.32/27", "2001:db8:1:2::/64" };
412        assertPrefixSet(local, INCLUDES, dunLinkPrefixes);
413        Collections.addAll(alreadySeen, dunLinkPrefixes);
414        assertEquals(alreadySeen.size(), local.size());
415
416        // [4] Pretend Wi-Fi disconnected.  It's addresses/prefixes should no
417        // longer be included (should be properly removed).
418        wifiAgent.fakeDisconnect();
419        local = mUNM.getLocalPrefixes();
420        assertPrefixSet(local, EXCLUDES, wifiLinkPrefixes);
421        assertPrefixSet(local, INCLUDES, cellLinkPrefixes);
422        assertPrefixSet(local, INCLUDES, dunLinkPrefixes);
423    }
424
425    private void assertSatisfiesLegacyType(int legacyType, NetworkState ns) {
426        if (legacyType == TYPE_NONE) {
427            assertTrue(ns == null);
428            return;
429        }
430
431        final NetworkCapabilities nc = ConnectivityManager.networkCapabilitiesForType(legacyType);
432        assertTrue(nc.satisfiedByNetworkCapabilities(ns.networkCapabilities));
433    }
434
435    private void assertUpstreamTypeRequested(int upstreamType) throws Exception {
436        assertEquals(1, mCM.requested.size());
437        assertEquals(1, mCM.legacyTypeMap.size());
438        assertEquals(Integer.valueOf(upstreamType),
439                mCM.legacyTypeMap.values().iterator().next());
440    }
441
442    public static class TestConnectivityManager extends ConnectivityManager {
443        public Map<NetworkCallback, Handler> allCallbacks = new HashMap<>();
444        public Set<NetworkCallback> trackingDefault = new HashSet<>();
445        public Map<NetworkCallback, NetworkRequest> listening = new HashMap<>();
446        public Map<NetworkCallback, NetworkRequest> requested = new HashMap<>();
447        public Map<NetworkCallback, Integer> legacyTypeMap = new HashMap<>();
448
449        private int mNetworkId = 100;
450
451        public TestConnectivityManager(Context ctx, IConnectivityManager svc) {
452            super(ctx, svc);
453        }
454
455        boolean hasNoCallbacks() {
456            return allCallbacks.isEmpty() &&
457                   trackingDefault.isEmpty() &&
458                   listening.isEmpty() &&
459                   requested.isEmpty() &&
460                   legacyTypeMap.isEmpty();
461        }
462
463        boolean isListeningForAll() {
464            final NetworkCapabilities empty = new NetworkCapabilities();
465            empty.clearAll();
466
467            for (NetworkRequest req : listening.values()) {
468                if (req.networkCapabilities.equalRequestableCapabilities(empty)) {
469                    return true;
470                }
471            }
472            return false;
473        }
474
475        boolean isDunRequested() {
476            for (NetworkRequest req : requested.values()) {
477                if (req.networkCapabilities.hasCapability(NET_CAPABILITY_DUN)) {
478                    return true;
479                }
480            }
481            return false;
482        }
483
484        int getNetworkId() { return ++mNetworkId; }
485
486        @Override
487        public void requestNetwork(NetworkRequest req, NetworkCallback cb, Handler h) {
488            assertFalse(allCallbacks.containsKey(cb));
489            allCallbacks.put(cb, h);
490            assertFalse(requested.containsKey(cb));
491            requested.put(cb, req);
492        }
493
494        @Override
495        public void requestNetwork(NetworkRequest req, NetworkCallback cb) {
496            fail("Should never be called.");
497        }
498
499        @Override
500        public void requestNetwork(NetworkRequest req, NetworkCallback cb,
501                int timeoutMs, int legacyType, Handler h) {
502            assertFalse(allCallbacks.containsKey(cb));
503            allCallbacks.put(cb, h);
504            assertFalse(requested.containsKey(cb));
505            requested.put(cb, req);
506            assertFalse(legacyTypeMap.containsKey(cb));
507            if (legacyType != ConnectivityManager.TYPE_NONE) {
508                legacyTypeMap.put(cb, legacyType);
509            }
510        }
511
512        @Override
513        public void registerNetworkCallback(NetworkRequest req, NetworkCallback cb, Handler h) {
514            assertFalse(allCallbacks.containsKey(cb));
515            allCallbacks.put(cb, h);
516            assertFalse(listening.containsKey(cb));
517            listening.put(cb, req);
518        }
519
520        @Override
521        public void registerNetworkCallback(NetworkRequest req, NetworkCallback cb) {
522            fail("Should never be called.");
523        }
524
525        @Override
526        public void registerDefaultNetworkCallback(NetworkCallback cb, Handler h) {
527            assertFalse(allCallbacks.containsKey(cb));
528            allCallbacks.put(cb, h);
529            assertFalse(trackingDefault.contains(cb));
530            trackingDefault.add(cb);
531        }
532
533        @Override
534        public void registerDefaultNetworkCallback(NetworkCallback cb) {
535            fail("Should never be called.");
536        }
537
538        @Override
539        public void unregisterNetworkCallback(NetworkCallback cb) {
540            if (trackingDefault.contains(cb)) {
541                trackingDefault.remove(cb);
542            } else if (listening.containsKey(cb)) {
543                listening.remove(cb);
544            } else if (requested.containsKey(cb)) {
545                requested.remove(cb);
546                legacyTypeMap.remove(cb);
547            } else {
548                fail("Unexpected callback removed");
549            }
550            allCallbacks.remove(cb);
551
552            assertFalse(allCallbacks.containsKey(cb));
553            assertFalse(trackingDefault.contains(cb));
554            assertFalse(listening.containsKey(cb));
555            assertFalse(requested.containsKey(cb));
556        }
557    }
558
559    public static class TestNetworkAgent {
560        public final TestConnectivityManager cm;
561        public final Network networkId;
562        public final int transportType;
563        public final NetworkCapabilities networkCapabilities;
564
565        public TestNetworkAgent(TestConnectivityManager cm, int transportType) {
566            this.cm = cm;
567            this.networkId = new Network(cm.getNetworkId());
568            this.transportType = transportType;
569            networkCapabilities = new NetworkCapabilities();
570            networkCapabilities.addTransportType(transportType);
571            networkCapabilities.addCapability(NET_CAPABILITY_INTERNET);
572        }
573
574        public void fakeConnect() {
575            for (NetworkCallback cb : cm.listening.keySet()) {
576                cb.onAvailable(networkId);
577                cb.onCapabilitiesChanged(networkId, copy(networkCapabilities));
578            }
579        }
580
581        public void fakeDisconnect() {
582            for (NetworkCallback cb : cm.listening.keySet()) {
583                cb.onLost(networkId);
584            }
585        }
586
587        public void sendLinkProperties(LinkProperties lp) {
588            for (NetworkCallback cb : cm.listening.keySet()) {
589                cb.onLinkPropertiesChanged(networkId, lp);
590            }
591        }
592    }
593
594    public static class TestStateMachine extends StateMachine {
595        public final ArrayList<Message> messages = new ArrayList<>();
596        private final State mLoggingState = new LoggingState();
597
598        class LoggingState extends State {
599            @Override public void enter() { messages.clear(); }
600
601            @Override public void exit() { messages.clear(); }
602
603            @Override public boolean processMessage(Message msg) {
604                messages.add(msg);
605                return true;
606            }
607        }
608
609        public TestStateMachine() {
610            super("UpstreamNetworkMonitor.TestStateMachine");
611            addState(mLoggingState);
612            setInitialState(mLoggingState);
613            super.start();
614        }
615    }
616
617    static NetworkCapabilities copy(NetworkCapabilities nc) {
618        return new NetworkCapabilities(nc);
619    }
620
621    static void assertPrefixSet(Set<IpPrefix> prefixes, boolean expectation, String... expected) {
622        final Set<String> expectedSet = new HashSet<>();
623        Collections.addAll(expectedSet, expected);
624        assertPrefixSet(prefixes, expectation, expectedSet);
625    }
626
627    static void assertPrefixSet(Set<IpPrefix> prefixes, boolean expectation, Set<String> expected) {
628        for (String expectedPrefix : expected) {
629            final String errStr = expectation ? "did not find" : "found";
630            assertEquals(
631                    String.format("Failed expectation: %s prefix: %s", errStr, expectedPrefix),
632                    expectation, prefixes.contains(new IpPrefix(expectedPrefix)));
633        }
634    }
635}
636