• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.server.connectivity;
18 
19 import static android.net.CaptivePortal.APP_RETURN_DISMISSED;
20 import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_DNS;
21 import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_FALLBACK;
22 import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_HTTP;
23 import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_HTTPS;
24 import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_PRIVDNS;
25 import static android.net.INetworkMonitor.NETWORK_VALIDATION_RESULT_PARTIAL;
26 import static android.net.INetworkMonitor.NETWORK_VALIDATION_RESULT_VALID;
27 import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
28 import static android.net.util.DataStallUtils.CONFIG_DATA_STALL_CONSECUTIVE_DNS_TIMEOUT_THRESHOLD;
29 import static android.net.util.DataStallUtils.CONFIG_DATA_STALL_EVALUATION_TYPE;
30 import static android.net.util.DataStallUtils.CONFIG_DATA_STALL_MIN_EVALUATE_INTERVAL;
31 import static android.net.util.DataStallUtils.CONFIG_DATA_STALL_VALID_DNS_TIME_THRESHOLD;
32 import static android.net.util.DataStallUtils.DATA_STALL_EVALUATION_TYPE_DNS;
33 import static android.net.util.NetworkStackUtils.CAPTIVE_PORTAL_FALLBACK_PROBE_SPECS;
34 import static android.net.util.NetworkStackUtils.CAPTIVE_PORTAL_OTHER_FALLBACK_URLS;
35 import static android.net.util.NetworkStackUtils.CAPTIVE_PORTAL_USE_HTTPS;
36 
37 import static junit.framework.Assert.assertEquals;
38 import static junit.framework.Assert.assertFalse;
39 
40 import static org.junit.Assert.assertArrayEquals;
41 import static org.junit.Assert.assertNotNull;
42 import static org.junit.Assert.assertNull;
43 import static org.junit.Assert.assertTrue;
44 import static org.junit.Assert.fail;
45 import static org.mockito.ArgumentMatchers.eq;
46 import static org.mockito.Mockito.any;
47 import static org.mockito.Mockito.anyInt;
48 import static org.mockito.Mockito.doAnswer;
49 import static org.mockito.Mockito.doReturn;
50 import static org.mockito.Mockito.doThrow;
51 import static org.mockito.Mockito.never;
52 import static org.mockito.Mockito.reset;
53 import static org.mockito.Mockito.timeout;
54 import static org.mockito.Mockito.times;
55 import static org.mockito.Mockito.verify;
56 import static org.mockito.Mockito.when;
57 
58 import android.annotation.NonNull;
59 import android.content.BroadcastReceiver;
60 import android.content.Context;
61 import android.content.Intent;
62 import android.content.res.Resources;
63 import android.net.ConnectivityManager;
64 import android.net.DnsResolver;
65 import android.net.INetworkMonitorCallbacks;
66 import android.net.InetAddresses;
67 import android.net.LinkProperties;
68 import android.net.Network;
69 import android.net.NetworkCapabilities;
70 import android.net.NetworkInfo;
71 import android.net.captiveportal.CaptivePortalProbeResult;
72 import android.net.metrics.IpConnectivityLog;
73 import android.net.shared.PrivateDnsConfig;
74 import android.net.util.SharedLog;
75 import android.net.wifi.WifiInfo;
76 import android.net.wifi.WifiManager;
77 import android.os.Bundle;
78 import android.os.ConditionVariable;
79 import android.os.Handler;
80 import android.os.Looper;
81 import android.os.Process;
82 import android.os.RemoteException;
83 import android.os.SystemClock;
84 import android.provider.Settings;
85 import android.telephony.CellSignalStrength;
86 import android.telephony.TelephonyManager;
87 import android.util.ArrayMap;
88 
89 import androidx.test.filters.SmallTest;
90 import androidx.test.runner.AndroidJUnit4;
91 
92 import com.android.networkstack.R;
93 import com.android.networkstack.metrics.DataStallDetectionStats;
94 import com.android.networkstack.metrics.DataStallStatsUtils;
95 
96 import org.junit.After;
97 import org.junit.Before;
98 import org.junit.Test;
99 import org.junit.runner.RunWith;
100 import org.mockito.ArgumentCaptor;
101 import org.mockito.Captor;
102 import org.mockito.Mock;
103 import org.mockito.MockitoAnnotations;
104 import org.mockito.Spy;
105 import org.mockito.verification.VerificationWithTimeout;
106 
107 import java.io.IOException;
108 import java.net.HttpURLConnection;
109 import java.net.InetAddress;
110 import java.net.URL;
111 import java.net.UnknownHostException;
112 import java.util.ArrayList;
113 import java.util.HashSet;
114 import java.util.List;
115 import java.util.Random;
116 import java.util.concurrent.Executor;
117 
118 import javax.net.ssl.SSLHandshakeException;
119 
120 @RunWith(AndroidJUnit4.class)
121 @SmallTest
122 public class NetworkMonitorTest {
123     private static final String LOCATION_HEADER = "location";
124 
125     private @Mock Context mContext;
126     private @Mock Resources mResources;
127     private @Mock IpConnectivityLog mLogger;
128     private @Mock SharedLog mValidationLogger;
129     private @Mock NetworkInfo mNetworkInfo;
130     private @Mock DnsResolver mDnsResolver;
131     private @Mock ConnectivityManager mCm;
132     private @Mock TelephonyManager mTelephony;
133     private @Mock WifiManager mWifi;
134     private @Mock HttpURLConnection mHttpConnection;
135     private @Mock HttpURLConnection mHttpsConnection;
136     private @Mock HttpURLConnection mFallbackConnection;
137     private @Mock HttpURLConnection mOtherFallbackConnection;
138     private @Mock Random mRandom;
139     private @Mock NetworkMonitor.Dependencies mDependencies;
140     private @Mock INetworkMonitorCallbacks mCallbacks;
141     private @Spy Network mCleartextDnsNetwork = new Network(TEST_NETID);
142     private @Mock Network mNetwork;
143     private @Mock DataStallStatsUtils mDataStallStatsUtils;
144     private @Mock WifiInfo mWifiInfo;
145     private @Captor ArgumentCaptor<String> mNetworkTestedRedirectUrlCaptor;
146 
147     private HashSet<WrappedNetworkMonitor> mCreatedNetworkMonitors;
148     private HashSet<BroadcastReceiver> mRegisteredReceivers;
149 
150     private static final int TEST_NETID = 4242;
151     private static final String TEST_HTTP_URL = "http://www.google.com/gen_204";
152     private static final String TEST_HTTPS_URL = "https://www.google.com/gen_204";
153     private static final String TEST_FALLBACK_URL = "http://fallback.google.com/gen_204";
154     private static final String TEST_OTHER_FALLBACK_URL = "http://otherfallback.google.com/gen_204";
155     private static final String TEST_MCCMNC = "123456";
156 
157     private static final int VALIDATION_RESULT_INVALID = 0;
158     private static final int VALIDATION_RESULT_PORTAL = 0;
159     private static final String TEST_REDIRECT_URL = "android.com";
160     private static final int VALIDATION_RESULT_PARTIAL = NETWORK_VALIDATION_PROBE_DNS
161             | NETWORK_VALIDATION_PROBE_HTTP
162             | NETWORK_VALIDATION_RESULT_PARTIAL;
163     private static final int VALIDATION_RESULT_FALLBACK_PARTIAL = NETWORK_VALIDATION_PROBE_DNS
164             | NETWORK_VALIDATION_PROBE_FALLBACK
165             | NETWORK_VALIDATION_RESULT_PARTIAL;
166     private static final int VALIDATION_RESULT_VALID = NETWORK_VALIDATION_PROBE_DNS
167             | NETWORK_VALIDATION_PROBE_HTTPS
168             | NETWORK_VALIDATION_RESULT_VALID;
169 
170     private static final int RETURN_CODE_DNS_SUCCESS = 0;
171     private static final int RETURN_CODE_DNS_TIMEOUT = 255;
172     private static final int DEFAULT_DNS_TIMEOUT_THRESHOLD = 5;
173 
174     private static final int HANDLER_TIMEOUT_MS = 1000;
175 
176     private static final LinkProperties TEST_LINK_PROPERTIES = new LinkProperties();
177 
178     private static final NetworkCapabilities METERED_CAPABILITIES = new NetworkCapabilities()
179             .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
180             .addCapability(NET_CAPABILITY_INTERNET);
181 
182     private static final NetworkCapabilities NOT_METERED_CAPABILITIES = new NetworkCapabilities()
183             .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
184             .addCapability(NET_CAPABILITY_INTERNET)
185             .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
186 
187     private static final NetworkCapabilities NO_INTERNET_CAPABILITIES = new NetworkCapabilities()
188             .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
189 
190     /**
191      * Fakes DNS responses.
192      *
193      * Allows test methods to configure the IP addresses that will be resolved by
194      * Network#getAllByName and by DnsResolver#query.
195      */
196     class FakeDns {
197         private final ArrayMap<String, List<InetAddress>> mAnswers = new ArrayMap<>();
198         private boolean mNonBypassPrivateDnsWorking = true;
199 
200         /** Whether DNS queries on mNonBypassPrivateDnsWorking should succeed. */
setNonBypassPrivateDnsWorking(boolean working)201         private void setNonBypassPrivateDnsWorking(boolean working) {
202             mNonBypassPrivateDnsWorking = working;
203         }
204 
205         /** Clears all DNS entries. */
clearAll()206         private synchronized void clearAll() {
207             mAnswers.clear();
208         }
209 
210         /** Returns the answer for a given name on the given mock network. */
getAnswer(Object mock, String hostname)211         private synchronized List<InetAddress> getAnswer(Object mock, String hostname) {
212             if (mock == mNetwork && !mNonBypassPrivateDnsWorking) {
213                 return null;
214             }
215             if (mAnswers.containsKey(hostname)) {
216                 return mAnswers.get(hostname);
217             }
218             return mAnswers.get("*");
219         }
220 
221         /** Sets the answer for a given name. */
setAnswer(String hostname, String[] answer)222         private synchronized void setAnswer(String hostname, String[] answer)
223                 throws UnknownHostException {
224             if (answer == null) {
225                 mAnswers.remove(hostname);
226             } else {
227                 List<InetAddress> answerList = new ArrayList<>();
228                 for (String addr : answer) {
229                     answerList.add(InetAddresses.parseNumericAddress(addr));
230                 }
231                 mAnswers.put(hostname, answerList);
232             }
233         }
234 
235         /** Simulates a getAllByName call for the specified name on the specified mock network. */
getAllByName(Object mock, String hostname)236         private InetAddress[] getAllByName(Object mock, String hostname)
237                 throws UnknownHostException {
238             List<InetAddress> answer = getAnswer(mock, hostname);
239             if (answer == null || answer.size() == 0) {
240                 throw new UnknownHostException(hostname);
241             }
242             return answer.toArray(new InetAddress[0]);
243         }
244 
245         /** Starts mocking DNS queries. */
startMocking()246         private void startMocking() throws UnknownHostException {
247             // Queries on mNetwork using getAllByName.
248             doAnswer(invocation -> {
249                 return getAllByName(invocation.getMock(), invocation.getArgument(0));
250             }).when(mNetwork).getAllByName(any());
251 
252             // Queries on mCleartextDnsNetwork using DnsResolver#query.
253             doAnswer(invocation -> {
254                 String hostname = (String) invocation.getArgument(1);
255                 Executor executor = (Executor) invocation.getArgument(3);
256                 DnsResolver.Callback<List<InetAddress>> callback = invocation.getArgument(5);
257 
258                 List<InetAddress> answer = getAnswer(invocation.getMock(), hostname);
259                 if (answer != null && answer.size() > 0) {
260                     new Handler(Looper.getMainLooper()).post(() -> {
261                         executor.execute(() -> callback.onAnswer(answer, 0));
262                     });
263                 }
264                 // If no answers, do nothing. sendDnsProbeWithTimeout will time out and throw UHE.
265                 return null;
266             }).when(mDnsResolver).query(any(), any(), anyInt(), any(), any(), any());
267 
268             // Queries on mCleartextDnsNetwork using using DnsResolver#query with QueryType.
269             doAnswer(invocation -> {
270                 String hostname = (String) invocation.getArgument(1);
271                 Executor executor = (Executor) invocation.getArgument(4);
272                 DnsResolver.Callback<List<InetAddress>> callback = invocation.getArgument(6);
273 
274                 List<InetAddress> answer = getAnswer(invocation.getMock(), hostname);
275                 if (answer != null && answer.size() > 0) {
276                     new Handler(Looper.getMainLooper()).post(() -> {
277                         executor.execute(() -> callback.onAnswer(answer, 0));
278                     });
279                 }
280                 // If no answers, do nothing. sendDnsProbeWithTimeout will time out and throw UHE.
281                 return null;
282             }).when(mDnsResolver).query(any(), any(), anyInt(), anyInt(), any(), any(), any());
283         }
284     }
285 
286     private FakeDns mFakeDns;
287 
288     @Before
setUp()289     public void setUp() throws Exception {
290         MockitoAnnotations.initMocks(this);
291         when(mDependencies.getPrivateDnsBypassNetwork(any())).thenReturn(mCleartextDnsNetwork);
292         when(mDependencies.getDnsResolver()).thenReturn(mDnsResolver);
293         when(mDependencies.getRandom()).thenReturn(mRandom);
294         when(mDependencies.getSetting(any(), eq(Settings.Global.CAPTIVE_PORTAL_MODE), anyInt()))
295                 .thenReturn(Settings.Global.CAPTIVE_PORTAL_MODE_PROMPT);
296         when(mDependencies.getDeviceConfigPropertyInt(any(), eq(CAPTIVE_PORTAL_USE_HTTPS),
297                 anyInt())).thenReturn(1);
298         when(mDependencies.getSetting(any(), eq(Settings.Global.CAPTIVE_PORTAL_HTTP_URL), any()))
299                 .thenReturn(TEST_HTTP_URL);
300         when(mDependencies.getSetting(any(), eq(Settings.Global.CAPTIVE_PORTAL_HTTPS_URL), any()))
301                 .thenReturn(TEST_HTTPS_URL);
302 
303         doReturn(mCleartextDnsNetwork).when(mNetwork).getPrivateDnsBypassingCopy();
304 
305         when(mContext.getSystemService(Context.CONNECTIVITY_SERVICE)).thenReturn(mCm);
306         when(mContext.getSystemService(Context.TELEPHONY_SERVICE)).thenReturn(mTelephony);
307         when(mContext.getSystemService(Context.WIFI_SERVICE)).thenReturn(mWifi);
308         when(mContext.getResources()).thenReturn(mResources);
309 
310         when(mResources.getString(anyInt())).thenReturn("");
311         when(mResources.getStringArray(anyInt())).thenReturn(new String[0]);
312 
313         when(mNetworkInfo.getType()).thenReturn(ConnectivityManager.TYPE_WIFI);
314         setFallbackUrl(TEST_FALLBACK_URL);
315         setOtherFallbackUrls(TEST_OTHER_FALLBACK_URL);
316         setFallbackSpecs(null); // Test with no fallback spec by default
317         when(mRandom.nextInt()).thenReturn(0);
318 
319         // DNS probe timeout should not be defined more than half of HANDLER_TIMEOUT_MS. Otherwise,
320         // it will fail the test because of timeout expired for querying AAAA and A sequentially.
321         when(mResources.getInteger(eq(R.integer.config_captive_portal_dns_probe_timeout)))
322                 .thenReturn(200);
323 
324         doAnswer((invocation) -> {
325             URL url = invocation.getArgument(0);
326             switch(url.toString()) {
327                 case TEST_HTTP_URL:
328                     return mHttpConnection;
329                 case TEST_HTTPS_URL:
330                     return mHttpsConnection;
331                 case TEST_FALLBACK_URL:
332                     return mFallbackConnection;
333                 case TEST_OTHER_FALLBACK_URL:
334                     return mOtherFallbackConnection;
335                 default:
336                     fail("URL not mocked: " + url.toString());
337                     return null;
338             }
339         }).when(mCleartextDnsNetwork).openConnection(any());
340         when(mHttpConnection.getRequestProperties()).thenReturn(new ArrayMap<>());
341         when(mHttpsConnection.getRequestProperties()).thenReturn(new ArrayMap<>());
342 
343         mFakeDns = new FakeDns();
344         mFakeDns.startMocking();
345         mFakeDns.setAnswer("*", new String[]{"2001:db8::1", "192.0.2.2"});
346 
347         when(mContext.registerReceiver(any(BroadcastReceiver.class), any())).then((invocation) -> {
348             mRegisteredReceivers.add(invocation.getArgument(0));
349             return new Intent();
350         });
351 
352         doAnswer((invocation) -> {
353             mRegisteredReceivers.remove(invocation.getArgument(0));
354             return null;
355         }).when(mContext).unregisterReceiver(any());
356 
357         resetCallbacks();
358 
359         setMinDataStallEvaluateInterval(500);
360         setDataStallEvaluationType(DATA_STALL_EVALUATION_TYPE_DNS);
361         setValidDataStallDnsTimeThreshold(500);
362         setConsecutiveDnsTimeoutThreshold(5);
363 
364         mCreatedNetworkMonitors = new HashSet<>();
365         mRegisteredReceivers = new HashSet<>();
366     }
367 
368     @After
tearDown()369     public void tearDown() {
370         mFakeDns.clearAll();
371         assertTrue(mCreatedNetworkMonitors.size() > 0);
372         // Make a local copy of mCreatedNetworkMonitors because during the iteration below,
373         // WrappedNetworkMonitor#onQuitting will delete elements from it on the handler threads.
374         WrappedNetworkMonitor[] networkMonitors = mCreatedNetworkMonitors.toArray(
375                 new WrappedNetworkMonitor[0]);
376         for (WrappedNetworkMonitor nm : networkMonitors) {
377             nm.notifyNetworkDisconnected();
378             nm.awaitQuit();
379         }
380         assertEquals("NetworkMonitor still running after disconnect",
381                 0, mCreatedNetworkMonitors.size());
382         assertEquals("BroadcastReceiver still registered after disconnect",
383                 0, mRegisteredReceivers.size());
384     }
385 
resetCallbacks()386     private void resetCallbacks() {
387         reset(mCallbacks);
388         // TODO: make this a parameterized test.
389         try {
390             when(mCallbacks.getInterfaceVersion()).thenReturn(3);
391         } catch (RemoteException e) {
392             // Can't happen as mCallbacks is a mock
393             fail("Error mocking getInterfaceVersion" + e);
394         }
395     }
396 
397     private class WrappedNetworkMonitor extends NetworkMonitor {
398         private long mProbeTime = 0;
399         private final ConditionVariable mQuitCv = new ConditionVariable(false);
400 
WrappedNetworkMonitor()401         WrappedNetworkMonitor() {
402             super(mContext, mCallbacks, mNetwork, mLogger, mValidationLogger,
403                     mDependencies, mDataStallStatsUtils);
404         }
405 
406         @Override
getLastProbeTime()407         protected long getLastProbeTime() {
408             return mProbeTime;
409         }
410 
setLastProbeTime(long time)411         protected void setLastProbeTime(long time) {
412             mProbeTime = time;
413         }
414 
415         @Override
addDnsEvents(@onNull final DataStallDetectionStats.Builder stats)416         protected void addDnsEvents(@NonNull final DataStallDetectionStats.Builder stats) {
417             generateTimeoutDnsEvent(stats, DEFAULT_DNS_TIMEOUT_THRESHOLD);
418         }
419 
420         @Override
onQuitting()421         protected void onQuitting() {
422             assertTrue(mCreatedNetworkMonitors.remove(this));
423             mQuitCv.open();
424         }
425 
awaitQuit()426         protected void awaitQuit() {
427             assertTrue("NetworkMonitor did not quit after " + HANDLER_TIMEOUT_MS + "ms",
428                     mQuitCv.block(HANDLER_TIMEOUT_MS));
429         }
430     }
431 
makeMonitor(NetworkCapabilities nc)432     private WrappedNetworkMonitor makeMonitor(NetworkCapabilities nc) {
433         final WrappedNetworkMonitor nm = new WrappedNetworkMonitor();
434         nm.start();
435         setNetworkCapabilities(nm, nc);
436         waitForIdle(nm.getHandler());
437         mCreatedNetworkMonitors.add(nm);
438         return nm;
439     }
440 
makeMeteredNetworkMonitor()441     private WrappedNetworkMonitor makeMeteredNetworkMonitor() {
442         final WrappedNetworkMonitor nm = makeMonitor(METERED_CAPABILITIES);
443         return nm;
444     }
445 
makeNotMeteredNetworkMonitor()446     private WrappedNetworkMonitor makeNotMeteredNetworkMonitor() {
447         final WrappedNetworkMonitor nm = makeMonitor(NOT_METERED_CAPABILITIES);
448         return nm;
449     }
450 
setNetworkCapabilities(NetworkMonitor nm, NetworkCapabilities nc)451     private void setNetworkCapabilities(NetworkMonitor nm, NetworkCapabilities nc) {
452         nm.notifyNetworkCapabilitiesChanged(nc);
453         waitForIdle(nm.getHandler());
454     }
455 
waitForIdle(Handler handler)456     private void waitForIdle(Handler handler) {
457         final ConditionVariable cv = new ConditionVariable(false);
458         handler.post(cv::open);
459         if (!cv.block(HANDLER_TIMEOUT_MS)) {
460             fail("Timed out waiting for handler");
461         }
462     }
463 
464     @Test
testGetIntSetting()465     public void testGetIntSetting() throws Exception {
466         WrappedNetworkMonitor wnm = makeNotMeteredNetworkMonitor();
467 
468         // No config resource, no device config. Expect to get default resource.
469         doThrow(new Resources.NotFoundException())
470                 .when(mResources).getInteger(eq(R.integer.config_captive_portal_dns_probe_timeout));
471         doAnswer(invocation -> {
472             int defaultValue = invocation.getArgument(2);
473             return defaultValue;
474         }).when(mDependencies).getDeviceConfigPropertyInt(any(),
475                 eq(NetworkMonitor.CONFIG_CAPTIVE_PORTAL_DNS_PROBE_TIMEOUT),
476                 anyInt());
477         when(mResources.getInteger(eq(R.integer.default_captive_portal_dns_probe_timeout)))
478                 .thenReturn(42);
479         assertEquals(42, wnm.getIntSetting(mContext,
480                 R.integer.config_captive_portal_dns_probe_timeout,
481                 NetworkMonitor.CONFIG_CAPTIVE_PORTAL_DNS_PROBE_TIMEOUT,
482                 R.integer.default_captive_portal_dns_probe_timeout));
483 
484         // Set device config. Expect to get device config.
485         when(mDependencies.getDeviceConfigPropertyInt(any(),
486                 eq(NetworkMonitor.CONFIG_CAPTIVE_PORTAL_DNS_PROBE_TIMEOUT), anyInt()))
487                         .thenReturn(1234);
488         assertEquals(1234, wnm.getIntSetting(mContext,
489                 R.integer.config_captive_portal_dns_probe_timeout,
490                 NetworkMonitor.CONFIG_CAPTIVE_PORTAL_DNS_PROBE_TIMEOUT,
491                 R.integer.default_captive_portal_dns_probe_timeout));
492 
493         // Set config resource. Expect to get config resource.
494         when(mResources.getInteger(eq(R.integer.config_captive_portal_dns_probe_timeout)))
495                 .thenReturn(5678);
496         assertEquals(5678, wnm.getIntSetting(mContext,
497                 R.integer.config_captive_portal_dns_probe_timeout,
498                 NetworkMonitor.CONFIG_CAPTIVE_PORTAL_DNS_PROBE_TIMEOUT,
499                 R.integer.default_captive_portal_dns_probe_timeout));
500     }
501 
502     @Test
testIsCaptivePortal_HttpProbeIsPortal()503     public void testIsCaptivePortal_HttpProbeIsPortal() throws IOException {
504         setSslException(mHttpsConnection);
505         setPortal302(mHttpConnection);
506         runPortalNetworkTest(VALIDATION_RESULT_PORTAL);
507     }
508 
509     @Test
testIsCaptivePortal_HttpsProbeIsNotPortal()510     public void testIsCaptivePortal_HttpsProbeIsNotPortal() throws IOException {
511         setStatus(mHttpsConnection, 204);
512         setStatus(mHttpConnection, 500);
513 
514         runNotPortalNetworkTest();
515     }
516 
517     @Test
testIsCaptivePortal_FallbackProbeIsPortal()518     public void testIsCaptivePortal_FallbackProbeIsPortal() throws IOException {
519         setSslException(mHttpsConnection);
520         setStatus(mHttpConnection, 500);
521         setPortal302(mFallbackConnection);
522         runPortalNetworkTest(VALIDATION_RESULT_INVALID);
523     }
524 
525     @Test
testIsCaptivePortal_FallbackProbeIsNotPortal()526     public void testIsCaptivePortal_FallbackProbeIsNotPortal() throws IOException {
527         setSslException(mHttpsConnection);
528         setStatus(mHttpConnection, 500);
529         setStatus(mFallbackConnection, 500);
530 
531         // Fallback probe did not see portal, HTTPS failed -> inconclusive
532         runFailedNetworkTest();
533     }
534 
535     @Test
testIsCaptivePortal_OtherFallbackProbeIsPortal()536     public void testIsCaptivePortal_OtherFallbackProbeIsPortal() throws IOException {
537         // Set all fallback probes but one to invalid URLs to verify they are being skipped
538         setFallbackUrl(TEST_FALLBACK_URL);
539         setOtherFallbackUrls(TEST_FALLBACK_URL + "," + TEST_OTHER_FALLBACK_URL);
540 
541         setSslException(mHttpsConnection);
542         setStatus(mHttpConnection, 500);
543         setStatus(mFallbackConnection, 500);
544         setPortal302(mOtherFallbackConnection);
545 
546         // TEST_OTHER_FALLBACK_URL is third
547         when(mRandom.nextInt()).thenReturn(2);
548 
549         // First check always uses the first fallback URL: inconclusive
550         final NetworkMonitor monitor = runNetworkTest(VALIDATION_RESULT_INVALID);
551         assertNull(mNetworkTestedRedirectUrlCaptor.getValue());
552         verify(mFallbackConnection, times(1)).getResponseCode();
553         verify(mOtherFallbackConnection, never()).getResponseCode();
554 
555         // Second check uses the URL chosen by Random
556         final CaptivePortalProbeResult result = monitor.isCaptivePortal();
557         assertTrue(result.isPortal());
558         verify(mOtherFallbackConnection, times(1)).getResponseCode();
559     }
560 
561     @Test
testIsCaptivePortal_AllProbesFailed()562     public void testIsCaptivePortal_AllProbesFailed() throws IOException {
563         setSslException(mHttpsConnection);
564         setStatus(mHttpConnection, 500);
565         setStatus(mFallbackConnection, 404);
566 
567         runFailedNetworkTest();
568         verify(mFallbackConnection, times(1)).getResponseCode();
569         verify(mOtherFallbackConnection, never()).getResponseCode();
570     }
571 
572     @Test
testIsCaptivePortal_InvalidUrlSkipped()573     public void testIsCaptivePortal_InvalidUrlSkipped() throws IOException {
574         setFallbackUrl("invalid");
575         setOtherFallbackUrls("otherinvalid," + TEST_OTHER_FALLBACK_URL + ",yetanotherinvalid");
576 
577         setSslException(mHttpsConnection);
578         setStatus(mHttpConnection, 500);
579         setPortal302(mOtherFallbackConnection);
580         runPortalNetworkTest(VALIDATION_RESULT_INVALID);
581         verify(mOtherFallbackConnection, times(1)).getResponseCode();
582         verify(mFallbackConnection, never()).getResponseCode();
583     }
584 
setupFallbackSpec()585     private void setupFallbackSpec() throws IOException {
586         setFallbackSpecs("http://example.com@@/@@204@@/@@"
587                 + "@@,@@"
588                 + TEST_OTHER_FALLBACK_URL + "@@/@@30[12]@@/@@https://(www\\.)?google.com/?.*");
589 
590         setSslException(mHttpsConnection);
591         setStatus(mHttpConnection, 500);
592 
593         // Use the 2nd fallback spec
594         when(mRandom.nextInt()).thenReturn(1);
595     }
596 
597     @Test
testIsCaptivePortal_FallbackSpecIsPartial()598     public void testIsCaptivePortal_FallbackSpecIsPartial() throws IOException {
599         setupFallbackSpec();
600         set302(mOtherFallbackConnection, "https://www.google.com/test?q=3");
601 
602         // HTTPS failed, fallback spec went through -> partial connectivity
603         runPartialConnectivityNetworkTest(VALIDATION_RESULT_FALLBACK_PARTIAL);
604         verify(mOtherFallbackConnection, times(1)).getResponseCode();
605         verify(mFallbackConnection, never()).getResponseCode();
606     }
607 
608     @Test
testIsCaptivePortal_FallbackSpecIsPortal()609     public void testIsCaptivePortal_FallbackSpecIsPortal() throws IOException {
610         setupFallbackSpec();
611         set302(mOtherFallbackConnection, "http://login.portal.example.com");
612         runPortalNetworkTest(VALIDATION_RESULT_INVALID);
613     }
614 
615     @Test
testIsCaptivePortal_IgnorePortals()616     public void testIsCaptivePortal_IgnorePortals() throws IOException {
617         setCaptivePortalMode(Settings.Global.CAPTIVE_PORTAL_MODE_IGNORE);
618         setSslException(mHttpsConnection);
619         setPortal302(mHttpConnection);
620 
621         runNoValidationNetworkTest();
622     }
623 
624     @Test
testIsDataStall_EvaluationDisabled()625     public void testIsDataStall_EvaluationDisabled() {
626         setDataStallEvaluationType(0);
627         WrappedNetworkMonitor wrappedMonitor = makeMeteredNetworkMonitor();
628         wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 100);
629         assertFalse(wrappedMonitor.isDataStall());
630     }
631 
632     @Test
testIsDataStall_EvaluationDnsOnNotMeteredNetwork()633     public void testIsDataStall_EvaluationDnsOnNotMeteredNetwork() {
634         WrappedNetworkMonitor wrappedMonitor = makeNotMeteredNetworkMonitor();
635         wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 100);
636         makeDnsTimeoutEvent(wrappedMonitor, DEFAULT_DNS_TIMEOUT_THRESHOLD);
637         assertTrue(wrappedMonitor.isDataStall());
638     }
639 
640     @Test
testIsDataStall_EvaluationDnsOnMeteredNetwork()641     public void testIsDataStall_EvaluationDnsOnMeteredNetwork() {
642         WrappedNetworkMonitor wrappedMonitor = makeMeteredNetworkMonitor();
643         wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 100);
644         assertFalse(wrappedMonitor.isDataStall());
645 
646         wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 1000);
647         makeDnsTimeoutEvent(wrappedMonitor, DEFAULT_DNS_TIMEOUT_THRESHOLD);
648         assertTrue(wrappedMonitor.isDataStall());
649     }
650 
651     @Test
testIsDataStall_EvaluationDnsWithDnsTimeoutCount()652     public void testIsDataStall_EvaluationDnsWithDnsTimeoutCount() {
653         WrappedNetworkMonitor wrappedMonitor = makeMeteredNetworkMonitor();
654         wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 1000);
655         makeDnsTimeoutEvent(wrappedMonitor, 3);
656         assertFalse(wrappedMonitor.isDataStall());
657         // Reset consecutive timeout counts.
658         makeDnsSuccessEvent(wrappedMonitor, 1);
659         makeDnsTimeoutEvent(wrappedMonitor, 2);
660         assertFalse(wrappedMonitor.isDataStall());
661 
662         makeDnsTimeoutEvent(wrappedMonitor, 3);
663         assertTrue(wrappedMonitor.isDataStall());
664 
665         // Set the value to larger than the default dns log size.
666         setConsecutiveDnsTimeoutThreshold(51);
667         wrappedMonitor = makeMeteredNetworkMonitor();
668         wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 1000);
669         makeDnsTimeoutEvent(wrappedMonitor, 50);
670         assertFalse(wrappedMonitor.isDataStall());
671 
672         makeDnsTimeoutEvent(wrappedMonitor, 1);
673         assertTrue(wrappedMonitor.isDataStall());
674     }
675 
676     @Test
testIsDataStall_EvaluationDnsWithDnsTimeThreshold()677     public void testIsDataStall_EvaluationDnsWithDnsTimeThreshold() {
678         // Test dns events happened in valid dns time threshold.
679         WrappedNetworkMonitor wrappedMonitor = makeMeteredNetworkMonitor();
680         wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 100);
681         makeDnsTimeoutEvent(wrappedMonitor, DEFAULT_DNS_TIMEOUT_THRESHOLD);
682         assertFalse(wrappedMonitor.isDataStall());
683         wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 1000);
684         assertTrue(wrappedMonitor.isDataStall());
685 
686         // Test dns events happened before valid dns time threshold.
687         setValidDataStallDnsTimeThreshold(0);
688         wrappedMonitor = makeMeteredNetworkMonitor();
689         wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 100);
690         makeDnsTimeoutEvent(wrappedMonitor, DEFAULT_DNS_TIMEOUT_THRESHOLD);
691         assertFalse(wrappedMonitor.isDataStall());
692         wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 1000);
693         assertFalse(wrappedMonitor.isDataStall());
694     }
695 
696     @Test
testBrokenNetworkNotValidated()697     public void testBrokenNetworkNotValidated() throws Exception {
698         setSslException(mHttpsConnection);
699         setStatus(mHttpConnection, 500);
700         setStatus(mFallbackConnection, 404);
701 
702         runFailedNetworkTest();
703     }
704 
705     @Test
testNoInternetCapabilityValidated()706     public void testNoInternetCapabilityValidated() throws Exception {
707         runNetworkTest(NO_INTERNET_CAPABILITIES, NETWORK_VALIDATION_RESULT_VALID);
708         verify(mCleartextDnsNetwork, never()).openConnection(any());
709     }
710 
711     @Test
testLaunchCaptivePortalApp()712     public void testLaunchCaptivePortalApp() throws Exception {
713         setSslException(mHttpsConnection);
714         setPortal302(mHttpConnection);
715 
716         final NetworkMonitor nm = makeMonitor(METERED_CAPABILITIES);
717         nm.notifyNetworkConnected(TEST_LINK_PROPERTIES, METERED_CAPABILITIES);
718 
719         verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
720                 .showProvisioningNotification(any(), any());
721 
722         assertEquals(1, mRegisteredReceivers.size());
723 
724         // Check that startCaptivePortalApp sends the expected intent.
725         nm.launchCaptivePortalApp();
726 
727         final ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
728         final ArgumentCaptor<Network> networkCaptor = ArgumentCaptor.forClass(Network.class);
729         verify(mCm, timeout(HANDLER_TIMEOUT_MS).times(1))
730                 .startCaptivePortalApp(networkCaptor.capture(), bundleCaptor.capture());
731         final Bundle bundle = bundleCaptor.getValue();
732         final Network bundleNetwork = bundle.getParcelable(ConnectivityManager.EXTRA_NETWORK);
733         assertEquals(TEST_NETID, bundleNetwork.netId);
734         // network is passed both in bundle and as parameter, as the bundle is opaque to the
735         // framework and only intended for the captive portal app, but the framework needs
736         // the network to identify the right NetworkMonitor.
737         assertEquals(TEST_NETID, networkCaptor.getValue().netId);
738 
739         // Have the app report that the captive portal is dismissed, and check that we revalidate.
740         setStatus(mHttpsConnection, 204);
741         setStatus(mHttpConnection, 204);
742 
743         resetCallbacks();
744         nm.notifyCaptivePortalAppFinished(APP_RETURN_DISMISSED);
745         verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).atLeastOnce())
746                 .notifyNetworkTested(eq(NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTP
747                         | NETWORK_VALIDATION_RESULT_VALID), any());
748         assertEquals(0, mRegisteredReceivers.size());
749     }
750 
751     @Test
testPrivateDnsSuccess()752     public void testPrivateDnsSuccess() throws Exception {
753         setStatus(mHttpsConnection, 204);
754         setStatus(mHttpConnection, 204);
755         mFakeDns.setAnswer("dns.google", new String[]{"2001:db8::53"});
756 
757         WrappedNetworkMonitor wnm = makeNotMeteredNetworkMonitor();
758         wnm.notifyPrivateDnsSettingsChanged(new PrivateDnsConfig("dns.google", new InetAddress[0]));
759         wnm.notifyNetworkConnected(TEST_LINK_PROPERTIES, NOT_METERED_CAPABILITIES);
760         verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
761                 .notifyNetworkTested(eq(VALIDATION_RESULT_VALID | NETWORK_VALIDATION_PROBE_PRIVDNS),
762                         eq(null));
763     }
764 
765     @Test
testPrivateDnsResolutionRetryUpdate()766     public void testPrivateDnsResolutionRetryUpdate() throws Exception {
767         // Set a private DNS hostname that doesn't resolve and expect validation to fail.
768         mFakeDns.setAnswer("dns.google", new String[0]);
769         setStatus(mHttpsConnection, 204);
770         setStatus(mHttpConnection, 204);
771 
772         WrappedNetworkMonitor wnm = makeNotMeteredNetworkMonitor();
773         wnm.notifyPrivateDnsSettingsChanged(new PrivateDnsConfig("dns.google", new InetAddress[0]));
774         wnm.notifyNetworkConnected(TEST_LINK_PROPERTIES, NOT_METERED_CAPABILITIES);
775         verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).atLeastOnce())
776                 .notifyNetworkTested(
777                         eq(NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTPS),
778                         eq(null));
779 
780         // Fix DNS and retry, expect validation to succeed.
781         resetCallbacks();
782         mFakeDns.setAnswer("dns.google", new String[]{"2001:db8::1"});
783 
784         wnm.forceReevaluation(Process.myUid());
785         verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).atLeastOnce())
786                 .notifyNetworkTested(eq(VALIDATION_RESULT_VALID | NETWORK_VALIDATION_PROBE_PRIVDNS),
787                         eq(null));
788 
789         // Change configuration to an invalid DNS name, expect validation to fail.
790         resetCallbacks();
791         mFakeDns.setAnswer("dns.bad", new String[0]);
792         wnm.notifyPrivateDnsSettingsChanged(new PrivateDnsConfig("dns.bad", new InetAddress[0]));
793         // Strict mode hostname resolve fail. Expect only notification for evaluation fail. No probe
794         // notification.
795         verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS))
796                 .notifyNetworkTested(eq(NETWORK_VALIDATION_PROBE_DNS
797                         | NETWORK_VALIDATION_PROBE_HTTPS), eq(null));
798 
799         // Change configuration back to working again, but make private DNS not work.
800         // Expect validation to fail.
801         resetCallbacks();
802         mFakeDns.setNonBypassPrivateDnsWorking(false);
803         wnm.notifyPrivateDnsSettingsChanged(new PrivateDnsConfig("dns.google",
804                 new InetAddress[0]));
805         verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).atLeastOnce())
806                 .notifyNetworkTested(
807                         eq(NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTPS),
808                         eq(null));
809 
810         // Make private DNS work again. Expect validation to succeed.
811         resetCallbacks();
812         mFakeDns.setNonBypassPrivateDnsWorking(true);
813         wnm.forceReevaluation(Process.myUid());
814         verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).atLeastOnce())
815                 .notifyNetworkTested(
816                         eq(VALIDATION_RESULT_VALID | NETWORK_VALIDATION_PROBE_PRIVDNS), eq(null));
817     }
818 
819     @Test
testDataStall_StallSuspectedAndSendMetrics()820     public void testDataStall_StallSuspectedAndSendMetrics() throws IOException {
821         WrappedNetworkMonitor wrappedMonitor = makeNotMeteredNetworkMonitor();
822         wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 1000);
823         makeDnsTimeoutEvent(wrappedMonitor, 5);
824         assertTrue(wrappedMonitor.isDataStall());
825         verify(mDataStallStatsUtils, times(1)).write(makeEmptyDataStallDetectionStats(), any());
826     }
827 
828     @Test
testDataStall_NoStallSuspectedAndSendMetrics()829     public void testDataStall_NoStallSuspectedAndSendMetrics() throws IOException {
830         WrappedNetworkMonitor wrappedMonitor = makeNotMeteredNetworkMonitor();
831         wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 1000);
832         makeDnsTimeoutEvent(wrappedMonitor, 3);
833         assertFalse(wrappedMonitor.isDataStall());
834         verify(mDataStallStatsUtils, never()).write(makeEmptyDataStallDetectionStats(), any());
835     }
836 
837     @Test
testCollectDataStallMetrics()838     public void testCollectDataStallMetrics() {
839         WrappedNetworkMonitor wrappedMonitor = makeNotMeteredNetworkMonitor();
840 
841         when(mTelephony.getDataNetworkType()).thenReturn(TelephonyManager.NETWORK_TYPE_LTE);
842         when(mTelephony.getNetworkOperator()).thenReturn(TEST_MCCMNC);
843         when(mTelephony.getSimOperator()).thenReturn(TEST_MCCMNC);
844 
845         DataStallDetectionStats.Builder stats =
846                 new DataStallDetectionStats.Builder()
847                 .setEvaluationType(DATA_STALL_EVALUATION_TYPE_DNS)
848                 .setNetworkType(NetworkCapabilities.TRANSPORT_CELLULAR)
849                 .setCellData(TelephonyManager.NETWORK_TYPE_LTE /* radioType */,
850                         true /* roaming */,
851                         TEST_MCCMNC /* networkMccmnc */,
852                         TEST_MCCMNC /* simMccmnc */,
853                         CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN /* signalStrength */);
854         generateTimeoutDnsEvent(stats, DEFAULT_DNS_TIMEOUT_THRESHOLD);
855 
856         assertEquals(wrappedMonitor.buildDataStallDetectionStats(
857                  NetworkCapabilities.TRANSPORT_CELLULAR), stats.build());
858 
859         when(mWifi.getConnectionInfo()).thenReturn(mWifiInfo);
860 
861         stats = new DataStallDetectionStats.Builder()
862                 .setEvaluationType(DATA_STALL_EVALUATION_TYPE_DNS)
863                 .setNetworkType(NetworkCapabilities.TRANSPORT_WIFI)
864                 .setWiFiData(mWifiInfo);
865         generateTimeoutDnsEvent(stats, DEFAULT_DNS_TIMEOUT_THRESHOLD);
866 
867         assertEquals(
868                 wrappedMonitor.buildDataStallDetectionStats(NetworkCapabilities.TRANSPORT_WIFI),
869                 stats.build());
870     }
871 
872     @Test
testIgnoreHttpsProbe()873     public void testIgnoreHttpsProbe() throws Exception {
874         setSslException(mHttpsConnection);
875         setStatus(mHttpConnection, 204);
876         // Expect to send HTTP, HTTPS, FALLBACK probe and evaluation result notifications to CS.
877         final NetworkMonitor nm = runNetworkTest(VALIDATION_RESULT_PARTIAL);
878 
879         resetCallbacks();
880         nm.setAcceptPartialConnectivity();
881         // Expect to update evaluation result notifications to CS.
882         verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1)).notifyNetworkTested(
883                 eq(VALIDATION_RESULT_PARTIAL | NETWORK_VALIDATION_RESULT_VALID), eq(null));
884     }
885 
886     @Test
testIsPartialConnectivity()887     public void testIsPartialConnectivity() throws IOException {
888         setStatus(mHttpsConnection, 500);
889         setStatus(mHttpConnection, 204);
890         setStatus(mFallbackConnection, 500);
891         runPartialConnectivityNetworkTest(VALIDATION_RESULT_PARTIAL);
892 
893         resetCallbacks();
894         setStatus(mHttpsConnection, 500);
895         setStatus(mHttpConnection, 500);
896         setStatus(mFallbackConnection, 204);
897         runPartialConnectivityNetworkTest(VALIDATION_RESULT_FALLBACK_PARTIAL);
898     }
899 
assertIpAddressArrayEquals(String[] expected, InetAddress[] actual)900     private void assertIpAddressArrayEquals(String[] expected, InetAddress[] actual) {
901         String[] actualStrings = new String[actual.length];
902         for (int i = 0; i < actual.length; i++) {
903             actualStrings[i] = actual[i].getHostAddress();
904         }
905         assertArrayEquals("Array of IP addresses differs", expected, actualStrings);
906     }
907 
908     @Test
testSendDnsProbeWithTimeout()909     public void testSendDnsProbeWithTimeout() throws Exception {
910         WrappedNetworkMonitor wnm = makeNotMeteredNetworkMonitor();
911         final int shortTimeoutMs = 200;
912 
913         // Clear the wildcard DNS response created in setUp.
914         mFakeDns.setAnswer("*", null);
915 
916         String[] expected = new String[]{"2001:db8::"};
917         mFakeDns.setAnswer("www.google.com", expected);
918         InetAddress[] actual = wnm.sendDnsProbeWithTimeout("www.google.com", shortTimeoutMs);
919         assertIpAddressArrayEquals(expected, actual);
920 
921         expected = new String[]{"2001:db8::", "192.0.2.1"};
922         mFakeDns.setAnswer("www.googleapis.com", expected);
923         actual = wnm.sendDnsProbeWithTimeout("www.googleapis.com", shortTimeoutMs);
924         assertIpAddressArrayEquals(expected, actual);
925 
926         mFakeDns.setAnswer("www.google.com", new String[0]);
927         try {
928             wnm.sendDnsProbeWithTimeout("www.google.com", shortTimeoutMs);
929             fail("No DNS results, expected UnknownHostException");
930         } catch (UnknownHostException e) {
931         }
932 
933         mFakeDns.setAnswer("www.google.com", null);
934         try {
935             wnm.sendDnsProbeWithTimeout("www.google.com", shortTimeoutMs);
936             fail("DNS query timed out, expected UnknownHostException");
937         } catch (UnknownHostException e) {
938         }
939     }
940 
941     @Test
testNotifyNetwork_WithforceReevaluation()942     public void testNotifyNetwork_WithforceReevaluation() throws Exception {
943         final NetworkMonitor nm = runValidatedNetworkTest();
944         // Verify forceReevalution will not reset the validation result but only probe result until
945         // getting the validation result.
946         resetCallbacks();
947         setSslException(mHttpsConnection);
948         setStatus(mHttpConnection, 500);
949         setStatus(mFallbackConnection, 204);
950         nm.forceReevaluation(Process.myUid());
951         final ArgumentCaptor<Integer> intCaptor = ArgumentCaptor.forClass(Integer.class);
952         // Expect to send HTTP, HTTPs, FALLBACK and evaluation results.
953         verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS))
954             .notifyNetworkTested(eq(NETWORK_VALIDATION_PROBE_DNS |
955                     NETWORK_VALIDATION_PROBE_FALLBACK | NETWORK_VALIDATION_RESULT_PARTIAL), any());
956     }
957 
958     @Test
testEvaluationState_clearProbeResults()959     public void testEvaluationState_clearProbeResults() throws Exception {
960         final NetworkMonitor nm = runValidatedNetworkTest();
961         nm.getEvaluationState().clearProbeResults();
962         // Verify probe results are all reset and only evaluation result left.
963         assertEquals(NETWORK_VALIDATION_RESULT_VALID,
964                 nm.getEvaluationState().getNetworkTestResult());
965     }
966 
967     @Test
testEvaluationState_reportProbeResult()968     public void testEvaluationState_reportProbeResult() throws Exception {
969         final NetworkMonitor nm = runValidatedNetworkTest();
970 
971         resetCallbacks();
972 
973         nm.reportHttpProbeResult(NETWORK_VALIDATION_PROBE_HTTP, CaptivePortalProbeResult.SUCCESS);
974         // Verify result should be appended and notifyNetworkTested callback is triggered once.
975         assertEquals(nm.getEvaluationState().getNetworkTestResult(),
976                 VALIDATION_RESULT_VALID | NETWORK_VALIDATION_PROBE_HTTP);
977 
978         nm.reportHttpProbeResult(NETWORK_VALIDATION_PROBE_HTTP, CaptivePortalProbeResult.FAILED);
979         // Verify DNS probe result should not be cleared.
980         assertTrue((nm.getEvaluationState().getNetworkTestResult() & NETWORK_VALIDATION_PROBE_DNS)
981                 == NETWORK_VALIDATION_PROBE_DNS);
982     }
983 
984     @Test
testEvaluationState_reportEvaluationResult()985     public void testEvaluationState_reportEvaluationResult() throws Exception {
986         final NetworkMonitor nm = runValidatedNetworkTest();
987 
988         nm.getEvaluationState().reportEvaluationResult(NETWORK_VALIDATION_RESULT_PARTIAL,
989                 null /* redirectUrl */);
990         verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1)).notifyNetworkTested(
991                 eq(NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTPS
992                 | NETWORK_VALIDATION_RESULT_PARTIAL), eq(null));
993 
994         nm.getEvaluationState().reportEvaluationResult(
995                 NETWORK_VALIDATION_RESULT_VALID | NETWORK_VALIDATION_RESULT_PARTIAL,
996                 null /* redirectUrl */);
997         verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1)).notifyNetworkTested(
998                 eq(VALIDATION_RESULT_VALID | NETWORK_VALIDATION_RESULT_PARTIAL), eq(null));
999 
1000         nm.getEvaluationState().reportEvaluationResult(VALIDATION_RESULT_INVALID,
1001                 TEST_REDIRECT_URL);
1002         verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1)).notifyNetworkTested(
1003                 eq(NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTPS),
1004                 eq(TEST_REDIRECT_URL));
1005     }
1006 
makeDnsTimeoutEvent(WrappedNetworkMonitor wrappedMonitor, int count)1007     private void makeDnsTimeoutEvent(WrappedNetworkMonitor wrappedMonitor, int count) {
1008         for (int i = 0; i < count; i++) {
1009             wrappedMonitor.getDnsStallDetector().accumulateConsecutiveDnsTimeoutCount(
1010                     RETURN_CODE_DNS_TIMEOUT);
1011         }
1012     }
1013 
makeDnsSuccessEvent(WrappedNetworkMonitor wrappedMonitor, int count)1014     private void makeDnsSuccessEvent(WrappedNetworkMonitor wrappedMonitor, int count) {
1015         for (int i = 0; i < count; i++) {
1016             wrappedMonitor.getDnsStallDetector().accumulateConsecutiveDnsTimeoutCount(
1017                     RETURN_CODE_DNS_SUCCESS);
1018         }
1019     }
1020 
makeEmptyDataStallDetectionStats()1021     private DataStallDetectionStats makeEmptyDataStallDetectionStats() {
1022         return new DataStallDetectionStats.Builder().build();
1023     }
1024 
setDataStallEvaluationType(int type)1025     private void setDataStallEvaluationType(int type) {
1026         when(mDependencies.getDeviceConfigPropertyInt(any(),
1027             eq(CONFIG_DATA_STALL_EVALUATION_TYPE), anyInt())).thenReturn(type);
1028     }
1029 
setMinDataStallEvaluateInterval(int time)1030     private void setMinDataStallEvaluateInterval(int time) {
1031         when(mDependencies.getDeviceConfigPropertyInt(any(),
1032             eq(CONFIG_DATA_STALL_MIN_EVALUATE_INTERVAL), anyInt())).thenReturn(time);
1033     }
1034 
setValidDataStallDnsTimeThreshold(int time)1035     private void setValidDataStallDnsTimeThreshold(int time) {
1036         when(mDependencies.getDeviceConfigPropertyInt(any(),
1037             eq(CONFIG_DATA_STALL_VALID_DNS_TIME_THRESHOLD), anyInt())).thenReturn(time);
1038     }
1039 
setConsecutiveDnsTimeoutThreshold(int num)1040     private void setConsecutiveDnsTimeoutThreshold(int num) {
1041         when(mDependencies.getDeviceConfigPropertyInt(any(),
1042             eq(CONFIG_DATA_STALL_CONSECUTIVE_DNS_TIMEOUT_THRESHOLD), anyInt())).thenReturn(num);
1043     }
1044 
setFallbackUrl(String url)1045     private void setFallbackUrl(String url) {
1046         when(mDependencies.getSetting(any(),
1047                 eq(Settings.Global.CAPTIVE_PORTAL_FALLBACK_URL), any())).thenReturn(url);
1048     }
1049 
setOtherFallbackUrls(String urls)1050     private void setOtherFallbackUrls(String urls) {
1051         when(mDependencies.getDeviceConfigProperty(any(),
1052                 eq(CAPTIVE_PORTAL_OTHER_FALLBACK_URLS), any())).thenReturn(urls);
1053     }
1054 
setFallbackSpecs(String specs)1055     private void setFallbackSpecs(String specs) {
1056         when(mDependencies.getDeviceConfigProperty(any(),
1057                 eq(CAPTIVE_PORTAL_FALLBACK_PROBE_SPECS), any())).thenReturn(specs);
1058     }
1059 
setCaptivePortalMode(int mode)1060     private void setCaptivePortalMode(int mode) {
1061         when(mDependencies.getSetting(any(),
1062                 eq(Settings.Global.CAPTIVE_PORTAL_MODE), anyInt())).thenReturn(mode);
1063     }
1064 
runPortalNetworkTest(int result)1065     private void runPortalNetworkTest(int result) {
1066         runNetworkTest(result);
1067         assertEquals(1, mRegisteredReceivers.size());
1068         assertNotNull(mNetworkTestedRedirectUrlCaptor.getValue());
1069     }
1070 
runNotPortalNetworkTest()1071     private void runNotPortalNetworkTest() {
1072         runNetworkTest(VALIDATION_RESULT_VALID);
1073         assertEquals(0, mRegisteredReceivers.size());
1074         assertNull(mNetworkTestedRedirectUrlCaptor.getValue());
1075     }
1076 
runNoValidationNetworkTest()1077     private void runNoValidationNetworkTest() {
1078         runNetworkTest(NETWORK_VALIDATION_RESULT_VALID);
1079         assertEquals(0, mRegisteredReceivers.size());
1080         assertNull(mNetworkTestedRedirectUrlCaptor.getValue());
1081     }
1082 
runFailedNetworkTest()1083     private void runFailedNetworkTest() {
1084         runNetworkTest(VALIDATION_RESULT_INVALID);
1085         assertEquals(0, mRegisteredReceivers.size());
1086         assertNull(mNetworkTestedRedirectUrlCaptor.getValue());
1087     }
1088 
runPartialConnectivityNetworkTest(int result)1089     private void runPartialConnectivityNetworkTest(int result) {
1090         runNetworkTest(result);
1091         assertEquals(0, mRegisteredReceivers.size());
1092         assertNull(mNetworkTestedRedirectUrlCaptor.getValue());
1093     }
1094 
runValidatedNetworkTest()1095     private NetworkMonitor runValidatedNetworkTest() throws Exception {
1096         setStatus(mHttpsConnection, 204);
1097         setStatus(mHttpConnection, 204);
1098         // Expect to send HTTPs and evaluation results.
1099         return runNetworkTest(VALIDATION_RESULT_VALID);
1100     }
1101 
runNetworkTest(int testResult)1102     private NetworkMonitor runNetworkTest(int testResult) {
1103         return runNetworkTest(METERED_CAPABILITIES, testResult);
1104     }
1105 
runNetworkTest(NetworkCapabilities nc, int testResult)1106     private NetworkMonitor runNetworkTest(NetworkCapabilities nc, int testResult) {
1107         final NetworkMonitor monitor = makeMonitor(nc);
1108         monitor.notifyNetworkConnected(TEST_LINK_PROPERTIES, nc);
1109         try {
1110             verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS))
1111                     .notifyNetworkTested(eq(testResult), mNetworkTestedRedirectUrlCaptor.capture());
1112         } catch (RemoteException e) {
1113             fail("Unexpected exception: " + e);
1114         }
1115         waitForIdle(monitor.getHandler());
1116 
1117         return monitor;
1118     }
1119 
setSslException(HttpURLConnection connection)1120     private void setSslException(HttpURLConnection connection) throws IOException {
1121         doThrow(new SSLHandshakeException("Invalid cert")).when(connection).getResponseCode();
1122     }
1123 
set302(HttpURLConnection connection, String location)1124     private void set302(HttpURLConnection connection, String location) throws IOException {
1125         setStatus(connection, 302);
1126         doReturn(location).when(connection).getHeaderField(LOCATION_HEADER);
1127     }
1128 
setPortal302(HttpURLConnection connection)1129     private void setPortal302(HttpURLConnection connection) throws IOException {
1130         set302(connection, "http://login.example.com");
1131     }
1132 
setStatus(HttpURLConnection connection, int status)1133     private void setStatus(HttpURLConnection connection, int status) throws IOException {
1134         doReturn(status).when(connection).getResponseCode();
1135     }
1136 
generateTimeoutDnsEvent(DataStallDetectionStats.Builder stats, int num)1137     private void generateTimeoutDnsEvent(DataStallDetectionStats.Builder stats, int num) {
1138         for (int i = 0; i < num; i++) {
1139             stats.addDnsEvent(RETURN_CODE_DNS_TIMEOUT, 123456789 /* timeMs */);
1140         }
1141     }
1142 
1143 }
1144 
1145