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.DnsResolver.TYPE_A;
21 import static android.net.DnsResolver.TYPE_AAAA;
22 import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_DNS;
23 import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_FALLBACK;
24 import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_HTTP;
25 import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_HTTPS;
26 import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_PRIVDNS;
27 import static android.net.INetworkMonitor.NETWORK_VALIDATION_RESULT_PARTIAL;
28 import static android.net.INetworkMonitor.NETWORK_VALIDATION_RESULT_VALID;
29 import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
30 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
31 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
32 import static android.net.NetworkCapabilities.TRANSPORT_VPN;
33 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
34 import static android.net.metrics.ValidationProbeEvent.PROBE_HTTP;
35 import static android.net.util.DataStallUtils.CONFIG_DATA_STALL_CONSECUTIVE_DNS_TIMEOUT_THRESHOLD;
36 import static android.net.util.DataStallUtils.CONFIG_DATA_STALL_EVALUATION_TYPE;
37 import static android.net.util.DataStallUtils.CONFIG_DATA_STALL_MIN_EVALUATE_INTERVAL;
38 import static android.net.util.DataStallUtils.CONFIG_DATA_STALL_TCP_POLLING_INTERVAL;
39 import static android.net.util.DataStallUtils.CONFIG_DATA_STALL_VALID_DNS_TIME_THRESHOLD;
40 import static android.net.util.DataStallUtils.DATA_STALL_EVALUATION_TYPE_DNS;
41 import static android.net.util.DataStallUtils.DATA_STALL_EVALUATION_TYPE_TCP;
42 import static android.net.util.DataStallUtils.DEFAULT_DATA_STALL_EVALUATION_TYPES;
43 import static android.net.util.NetworkStackUtils.CAPTIVE_PORTAL_FALLBACK_PROBE_SPECS;
44 import static android.net.util.NetworkStackUtils.CAPTIVE_PORTAL_OTHER_FALLBACK_URLS;
45 import static android.net.util.NetworkStackUtils.CAPTIVE_PORTAL_USE_HTTPS;
46 import static android.net.util.NetworkStackUtils.DEFAULT_CAPTIVE_PORTAL_DNS_PROBE_TIMEOUT;
47 import static android.net.util.NetworkStackUtils.DISMISS_PORTAL_IN_VALIDATED_NETWORK;
48 import static android.net.util.NetworkStackUtils.DNS_PROBE_PRIVATE_IP_NO_INTERNET_VERSION;
49 import static android.net.util.NetworkStackUtils.TEST_CAPTIVE_PORTAL_HTTPS_URL;
50 import static android.net.util.NetworkStackUtils.TEST_CAPTIVE_PORTAL_HTTP_URL;
51 import static android.net.util.NetworkStackUtils.TEST_URL_EXPIRATION_TIME;
52 import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY;
53 
54 import static com.android.networkstack.util.DnsUtils.PRIVATE_DNS_PROBE_HOST_SUFFIX;
55 import static com.android.server.connectivity.NetworkMonitor.INITIAL_REEVALUATE_DELAY_MS;
56 import static com.android.server.connectivity.NetworkMonitor.extractCharset;
57 
58 import static junit.framework.Assert.assertEquals;
59 import static junit.framework.Assert.assertFalse;
60 
61 import static org.junit.Assert.assertArrayEquals;
62 import static org.junit.Assert.assertNotEquals;
63 import static org.junit.Assert.assertNotNull;
64 import static org.junit.Assert.assertNull;
65 import static org.junit.Assert.assertTrue;
66 import static org.junit.Assert.fail;
67 import static org.junit.Assume.assumeFalse;
68 import static org.junit.Assume.assumeTrue;
69 import static org.mockito.ArgumentMatchers.anyBoolean;
70 import static org.mockito.ArgumentMatchers.argThat;
71 import static org.mockito.ArgumentMatchers.eq;
72 import static org.mockito.Mockito.after;
73 import static org.mockito.Mockito.any;
74 import static org.mockito.Mockito.anyInt;
75 import static org.mockito.Mockito.atLeastOnce;
76 import static org.mockito.Mockito.atMost;
77 import static org.mockito.Mockito.doAnswer;
78 import static org.mockito.Mockito.doReturn;
79 import static org.mockito.Mockito.doThrow;
80 import static org.mockito.Mockito.mock;
81 import static org.mockito.Mockito.never;
82 import static org.mockito.Mockito.reset;
83 import static org.mockito.Mockito.spy;
84 import static org.mockito.Mockito.timeout;
85 import static org.mockito.Mockito.times;
86 import static org.mockito.Mockito.verify;
87 import static org.mockito.Mockito.when;
88 
89 import static java.lang.System.currentTimeMillis;
90 import static java.util.Collections.singletonList;
91 import static java.util.stream.Collectors.toList;
92 
93 import android.annotation.NonNull;
94 import android.content.BroadcastReceiver;
95 import android.content.Context;
96 import android.content.ContextWrapper;
97 import android.content.Intent;
98 import android.content.pm.PackageManager;
99 import android.content.res.Configuration;
100 import android.content.res.Resources;
101 import android.net.CaptivePortalData;
102 import android.net.ConnectivityManager;
103 import android.net.DataStallReportParcelable;
104 import android.net.DnsResolver;
105 import android.net.INetd;
106 import android.net.INetworkMonitorCallbacks;
107 import android.net.InetAddresses;
108 import android.net.LinkProperties;
109 import android.net.Network;
110 import android.net.NetworkCapabilities;
111 import android.net.NetworkTestResultParcelable;
112 import android.net.Uri;
113 import android.net.captiveportal.CaptivePortalProbeResult;
114 import android.net.metrics.IpConnectivityLog;
115 import android.net.shared.PrivateDnsConfig;
116 import android.net.util.SharedLog;
117 import android.net.wifi.WifiInfo;
118 import android.net.wifi.WifiManager;
119 import android.os.Build;
120 import android.os.Bundle;
121 import android.os.ConditionVariable;
122 import android.os.Handler;
123 import android.os.IBinder;
124 import android.os.Looper;
125 import android.os.Process;
126 import android.os.RemoteException;
127 import android.os.SystemClock;
128 import android.provider.Settings;
129 import android.telephony.CellIdentityGsm;
130 import android.telephony.CellIdentityLte;
131 import android.telephony.CellInfo;
132 import android.telephony.CellInfoGsm;
133 import android.telephony.CellInfoLte;
134 import android.telephony.CellSignalStrength;
135 import android.telephony.TelephonyManager;
136 import android.util.ArrayMap;
137 
138 import androidx.test.filters.SmallTest;
139 import androidx.test.runner.AndroidJUnit4;
140 
141 import com.android.networkstack.NetworkStackNotifier;
142 import com.android.networkstack.R;
143 import com.android.networkstack.apishim.CaptivePortalDataShimImpl;
144 import com.android.networkstack.apishim.ConstantsShim;
145 import com.android.networkstack.apishim.common.ShimUtils;
146 import com.android.networkstack.metrics.DataStallDetectionStats;
147 import com.android.networkstack.metrics.DataStallStatsUtils;
148 import com.android.networkstack.netlink.TcpSocketTracker;
149 import com.android.server.NetworkStackService.NetworkStackServiceManager;
150 import com.android.server.connectivity.nano.CellularData;
151 import com.android.server.connectivity.nano.DnsEvent;
152 import com.android.server.connectivity.nano.WifiData;
153 import com.android.testutils.DevSdkIgnoreRule;
154 import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
155 import com.android.testutils.HandlerUtilsKt;
156 
157 import com.google.protobuf.nano.MessageNano;
158 
159 import junit.framework.AssertionFailedError;
160 
161 import org.junit.After;
162 import org.junit.Before;
163 import org.junit.Rule;
164 import org.junit.Test;
165 import org.junit.runner.RunWith;
166 import org.mockito.ArgumentCaptor;
167 import org.mockito.Mock;
168 import org.mockito.MockitoAnnotations;
169 import org.mockito.Spy;
170 import org.mockito.invocation.InvocationOnMock;
171 import org.mockito.stubbing.Answer;
172 
173 import java.io.ByteArrayInputStream;
174 import java.io.IOException;
175 import java.io.InputStream;
176 import java.lang.reflect.Constructor;
177 import java.net.HttpURLConnection;
178 import java.net.Inet6Address;
179 import java.net.InetAddress;
180 import java.net.URL;
181 import java.net.UnknownHostException;
182 import java.nio.charset.Charset;
183 import java.nio.charset.StandardCharsets;
184 import java.util.ArrayList;
185 import java.util.Arrays;
186 import java.util.Collections;
187 import java.util.HashMap;
188 import java.util.HashSet;
189 import java.util.List;
190 import java.util.Map;
191 import java.util.Objects;
192 import java.util.Random;
193 import java.util.concurrent.Executor;
194 import java.util.concurrent.TimeUnit;
195 
196 import javax.net.ssl.SSLHandshakeException;
197 
198 @RunWith(AndroidJUnit4.class)
199 @SmallTest
200 public class NetworkMonitorTest {
201     private static final String LOCATION_HEADER = "location";
202     private static final String CONTENT_TYPE_HEADER = "Content-Type";
203 
204     @Rule
205     public final DevSdkIgnoreRule mIgnoreRule = new DevSdkIgnoreRule();
206 
207     private @Mock Context mContext;
208     private @Mock Configuration mConfiguration;
209     private @Mock Resources mResources;
210     private @Mock IpConnectivityLog mLogger;
211     private @Mock SharedLog mValidationLogger;
212     private @Mock DnsResolver mDnsResolver;
213     private @Mock ConnectivityManager mCm;
214     private @Mock TelephonyManager mTelephony;
215     private @Mock WifiManager mWifi;
216     private @Mock NetworkStackServiceManager mServiceManager;
217     private @Mock NetworkStackNotifier mNotifier;
218     private @Mock HttpURLConnection mHttpConnection;
219     private @Mock HttpURLConnection mOtherHttpConnection1;
220     private @Mock HttpURLConnection mOtherHttpConnection2;
221     private @Mock HttpURLConnection mHttpsConnection;
222     private @Mock HttpURLConnection mOtherHttpsConnection1;
223     private @Mock HttpURLConnection mOtherHttpsConnection2;
224     private @Mock HttpURLConnection mFallbackConnection;
225     private @Mock HttpURLConnection mOtherFallbackConnection;
226     private @Mock HttpURLConnection mTestOverriddenUrlConnection;
227     private @Mock HttpURLConnection mCapportApiConnection;
228     private @Mock HttpURLConnection mSpeedTestConnection;
229     private @Mock Random mRandom;
230     private @Mock NetworkMonitor.Dependencies mDependencies;
231     // Mockito can't create a mock of INetworkMonitorCallbacks on Q because it can't find
232     // CaptivePortalData on notifyCaptivePortalDataChanged. Use a spy on a mock IBinder instead.
233     private INetworkMonitorCallbacks mCallbacks = spy(
234             INetworkMonitorCallbacks.Stub.asInterface(mock(IBinder.class)));
235     private @Spy Network mCleartextDnsNetwork = new Network(TEST_NETID);
236     private @Mock Network mNetwork;
237     private @Mock DataStallStatsUtils mDataStallStatsUtils;
238     private @Mock TcpSocketTracker.Dependencies mTstDependencies;
239     private @Mock INetd mNetd;
240     private @Mock TcpSocketTracker mTst;
241     private HashSet<WrappedNetworkMonitor> mCreatedNetworkMonitors;
242     private HashSet<BroadcastReceiver> mRegisteredReceivers;
243     private @Mock Context mMccContext;
244     private @Mock Resources mMccResource;
245     private @Mock WifiInfo mWifiInfo;
246 
247     private static final int TEST_NETID = 4242;
248     private static final String TEST_HTTP_URL = "http://www.google.com/gen_204";
249     private static final String TEST_HTTP_OTHER_URL1 = "http://other1.google.com/gen_204";
250     private static final String TEST_HTTP_OTHER_URL2 = "http://other2.google.com/gen_204";
251     private static final String TEST_HTTPS_URL = "https://www.google.com/gen_204";
252     private static final String TEST_HTTPS_OTHER_URL1 = "https://other1.google.com/gen_204";
253     private static final String TEST_HTTPS_OTHER_URL2 = "https://other2.google.com/gen_204";
254     private static final String TEST_FALLBACK_URL = "http://fallback.google.com/gen_204";
255     private static final String TEST_OTHER_FALLBACK_URL = "http://otherfallback.google.com/gen_204";
256     private static final String TEST_INVALID_OVERRIDE_URL = "https://override.example.com/test";
257     private static final String TEST_OVERRIDE_URL = "http://localhost:12345/test";
258     private static final String TEST_CAPPORT_API_URL = "https://capport.example.com/api";
259     private static final String TEST_LOGIN_URL = "https://testportal.example.com/login";
260     private static final String TEST_VENUE_INFO_URL = "https://venue.example.com/info";
261     private static final String TEST_SPEED_TEST_URL = "https://speedtest.example.com";
262     private static final String TEST_RELATIVE_URL = "/test/relative/gen_204";
263     private static final String TEST_MCCMNC = "123456";
264     private static final String[] TEST_HTTP_URLS = {TEST_HTTP_OTHER_URL1, TEST_HTTP_OTHER_URL2};
265     private static final String[] TEST_HTTPS_URLS = {TEST_HTTPS_OTHER_URL1, TEST_HTTPS_OTHER_URL2};
266     private static final int TEST_TCP_FAIL_RATE = 99;
267     private static final int TEST_TCP_PACKET_COUNT = 50;
268     private static final long TEST_ELAPSED_TIME_MS = 123456789L;
269     private static final int TEST_SIGNAL_STRENGTH = -100;
270     private static final int VALIDATION_RESULT_INVALID = 0;
271     private static final int VALIDATION_RESULT_PORTAL = 0;
272     private static final String TEST_REDIRECT_URL = "android.com";
273     private static final int PROBES_PRIVDNS_VALID = NETWORK_VALIDATION_PROBE_DNS
274             | NETWORK_VALIDATION_PROBE_HTTPS | NETWORK_VALIDATION_PROBE_PRIVDNS;
275 
276     private static final int RETURN_CODE_DNS_SUCCESS = 0;
277     private static final int RETURN_CODE_DNS_TIMEOUT = 255;
278     private static final int DEFAULT_DNS_TIMEOUT_THRESHOLD = 5;
279 
280     private static final int HANDLER_TIMEOUT_MS = 1000;
281 
282     private static final LinkProperties TEST_LINK_PROPERTIES = new LinkProperties();
283 
284     // Cannot have a static member for the LinkProperties with captive portal API information, as
285     // the initializer would crash on Q (the members in LinkProperties were introduced in R).
makeCapportLPs()286     private static LinkProperties makeCapportLPs() {
287         final LinkProperties lp = new LinkProperties(TEST_LINK_PROPERTIES);
288         lp.setCaptivePortalApiUrl(Uri.parse(TEST_CAPPORT_API_URL));
289         return lp;
290     }
291 
292     private static final NetworkCapabilities CELL_METERED_CAPABILITIES = new NetworkCapabilities()
293             .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
294             .addCapability(NET_CAPABILITY_INTERNET);
295 
296     private static final NetworkCapabilities CELL_NOT_METERED_CAPABILITIES =
297             new NetworkCapabilities()
298                 .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
299                 .addCapability(NET_CAPABILITY_INTERNET)
300                 .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
301 
302     private static final NetworkCapabilities WIFI_NOT_METERED_CAPABILITIES =
303             new NetworkCapabilities()
304                 .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
305                 .addCapability(NET_CAPABILITY_INTERNET)
306                 .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
307 
308     private static final NetworkCapabilities CELL_NO_INTERNET_CAPABILITIES =
309             new NetworkCapabilities().addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
310 
311     /**
312      * Fakes DNS responses.
313      *
314      * Allows test methods to configure the IP addresses that will be resolved by
315      * Network#getAllByName and by DnsResolver#query.
316      */
317     class FakeDns {
318         /** Data class to record the Dns entry. */
319         class DnsEntry {
320             final String mHostname;
321             final int mType;
322             final List<InetAddress> mAddresses;
DnsEntry(String host, int type, List<InetAddress> addr)323             DnsEntry(String host, int type, List<InetAddress> addr) {
324                 mHostname = host;
325                 mType = type;
326                 mAddresses = addr;
327             }
328             // Full match or partial match that target host contains the entry hostname to support
329             // random private dns probe hostname.
matches(String hostname, int type)330             private boolean matches(String hostname, int type) {
331                 return hostname.endsWith(mHostname) && type == mType;
332             }
333         }
334         private final ArrayList<DnsEntry> mAnswers = new ArrayList<DnsEntry>();
335         private boolean mNonBypassPrivateDnsWorking = true;
336 
337         /** Whether DNS queries on mNonBypassPrivateDnsWorking should succeed. */
setNonBypassPrivateDnsWorking(boolean working)338         private void setNonBypassPrivateDnsWorking(boolean working) {
339             mNonBypassPrivateDnsWorking = working;
340         }
341 
342         /** Clears all DNS entries. */
clearAll()343         private synchronized void clearAll() {
344             mAnswers.clear();
345         }
346 
347         /** Returns the answer for a given name and type on the given mock network. */
getAnswer(Object mock, String hostname, int type)348         private synchronized List<InetAddress> getAnswer(Object mock, String hostname, int type) {
349             if (mock == mNetwork && !mNonBypassPrivateDnsWorking) {
350                 return null;
351             }
352 
353             return mAnswers.stream().filter(e -> e.matches(hostname, type))
354                     .map(answer -> answer.mAddresses).findFirst().orElse(null);
355         }
356 
357         /** Sets the answer for a given name and type. */
setAnswer(String hostname, String[] answer, int type)358         private synchronized void setAnswer(String hostname, String[] answer, int type)
359                 throws UnknownHostException {
360             DnsEntry record = new DnsEntry(hostname, type, generateAnswer(answer));
361             // Remove the existing one.
362             mAnswers.removeIf(entry -> entry.matches(hostname, type));
363             // Add or replace a new record.
364             mAnswers.add(record);
365         }
366 
generateAnswer(String[] answer)367         private List<InetAddress> generateAnswer(String[] answer) {
368             if (answer == null) return new ArrayList<>();
369             return Arrays.stream(answer).map(addr -> InetAddress.parseNumericAddress(addr))
370                     .collect(toList());
371         }
372 
373         /** Simulates a getAllByName call for the specified name on the specified mock network. */
getAllByName(Object mock, String hostname)374         private InetAddress[] getAllByName(Object mock, String hostname)
375                 throws UnknownHostException {
376             List<InetAddress> answer = queryAllTypes(mock, hostname);
377             if (answer == null || answer.size() == 0) {
378                 throw new UnknownHostException(hostname);
379             }
380             return answer.toArray(new InetAddress[0]);
381         }
382 
383         // Regardless of the type, depends on what the responses contained in the network.
queryAllTypes(Object mock, String hostname)384         private List<InetAddress> queryAllTypes(Object mock, String hostname) {
385             List<InetAddress> answer = new ArrayList<>();
386             addAllIfNotNull(answer, getAnswer(mock, hostname, TYPE_A));
387             addAllIfNotNull(answer, getAnswer(mock, hostname, TYPE_AAAA));
388             return answer;
389         }
390 
addAllIfNotNull(List<InetAddress> list, List<InetAddress> c)391         private void addAllIfNotNull(List<InetAddress> list, List<InetAddress> c) {
392             if (c != null) {
393                 list.addAll(c);
394             }
395         }
396 
397         /** Starts mocking DNS queries. */
startMocking()398         private void startMocking() throws UnknownHostException {
399             // Queries on mNetwork using getAllByName.
400             doAnswer(invocation -> {
401                 return getAllByName(invocation.getMock(), invocation.getArgument(0));
402             }).when(mNetwork).getAllByName(any());
403 
404             // Queries on mCleartextDnsNetwork using DnsResolver#query.
405             doAnswer(invocation -> {
406                 return mockQuery(invocation, 1 /* posHostname */, 3 /* posExecutor */,
407                         5 /* posCallback */, -1 /* posType */);
408             }).when(mDnsResolver).query(any(), any(), anyInt(), any(), any(), any());
409 
410             // Queries on mCleartextDnsNetwork using DnsResolver#query with QueryType.
411             doAnswer(invocation -> {
412                 return mockQuery(invocation, 1 /* posHostname */, 4 /* posExecutor */,
413                         6 /* posCallback */, 2 /* posType */);
414             }).when(mDnsResolver).query(any(), any(), anyInt(), anyInt(), any(), any(), any());
415         }
416 
417         // Mocking queries on DnsResolver#query.
mockQuery(InvocationOnMock invocation, int posHostname, int posExecutor, int posCallback, int posType)418         private Answer mockQuery(InvocationOnMock invocation, int posHostname, int posExecutor,
419                 int posCallback, int posType) {
420             String hostname = (String) invocation.getArgument(posHostname);
421             Executor executor = (Executor) invocation.getArgument(posExecutor);
422             DnsResolver.Callback<List<InetAddress>> callback = invocation.getArgument(posCallback);
423             List<InetAddress> answer;
424 
425             answer = posType != -1
426                     ? getAnswer(invocation.getMock(), hostname, invocation.getArgument(posType)) :
427                     queryAllTypes(invocation.getMock(), hostname);
428 
429             if (answer != null && answer.size() > 0) {
430                 new Handler(Looper.getMainLooper()).post(() -> {
431                     executor.execute(() -> callback.onAnswer(answer, 0));
432                 });
433             }
434             // If no answers, do nothing. sendDnsProbeWithTimeout will time out and throw UHE.
435             return null;
436         }
437     }
438 
439     private FakeDns mFakeDns;
440 
441     @Before
setUp()442     public void setUp() throws Exception {
443         MockitoAnnotations.initMocks(this);
444         when(mDependencies.getPrivateDnsBypassNetwork(any())).thenReturn(mCleartextDnsNetwork);
445         when(mDependencies.getDnsResolver()).thenReturn(mDnsResolver);
446         when(mDependencies.getRandom()).thenReturn(mRandom);
447         when(mDependencies.getSetting(any(), eq(Settings.Global.CAPTIVE_PORTAL_MODE), anyInt()))
448                 .thenReturn(Settings.Global.CAPTIVE_PORTAL_MODE_PROMPT);
449         when(mDependencies.getDeviceConfigPropertyInt(any(), eq(CAPTIVE_PORTAL_USE_HTTPS),
450                 anyInt())).thenReturn(1);
451         when(mDependencies.getSetting(any(), eq(Settings.Global.CAPTIVE_PORTAL_HTTP_URL), any()))
452                 .thenReturn(TEST_HTTP_URL);
453         when(mDependencies.getSetting(any(), eq(Settings.Global.CAPTIVE_PORTAL_HTTPS_URL), any()))
454                 .thenReturn(TEST_HTTPS_URL);
455 
456         doReturn(mCleartextDnsNetwork).when(mNetwork).getPrivateDnsBypassingCopy();
457 
458         when(mContext.getSystemService(Context.CONNECTIVITY_SERVICE)).thenReturn(mCm);
459         when(mContext.getSystemService(Context.TELEPHONY_SERVICE)).thenReturn(mTelephony);
460         when(mContext.getSystemService(Context.WIFI_SERVICE)).thenReturn(mWifi);
461         when(mContext.getResources()).thenReturn(mResources);
462 
463         when(mServiceManager.getNotifier()).thenReturn(mNotifier);
464 
465         when(mTelephony.getDataNetworkType()).thenReturn(TelephonyManager.NETWORK_TYPE_LTE);
466         when(mTelephony.getNetworkOperator()).thenReturn(TEST_MCCMNC);
467         when(mTelephony.getSimOperator()).thenReturn(TEST_MCCMNC);
468 
469         when(mResources.getString(anyInt())).thenReturn("");
470         when(mResources.getStringArray(anyInt())).thenReturn(new String[0]);
471         doReturn(mConfiguration).when(mResources).getConfiguration();
472         when(mMccContext.getResources()).thenReturn(mMccResource);
473 
474         setFallbackUrl(TEST_FALLBACK_URL);
475         setOtherFallbackUrls(TEST_OTHER_FALLBACK_URL);
476         setFallbackSpecs(null); // Test with no fallback spec by default
477         when(mRandom.nextInt()).thenReturn(0);
478 
479         when(mTstDependencies.getNetd()).thenReturn(mNetd);
480         // DNS probe timeout should not be defined more than half of HANDLER_TIMEOUT_MS. Otherwise,
481         // it will fail the test because of timeout expired for querying AAAA and A sequentially.
482         when(mResources.getInteger(eq(R.integer.config_captive_portal_dns_probe_timeout)))
483                 .thenReturn(200);
484 
485         doAnswer((invocation) -> {
486             URL url = invocation.getArgument(0);
487             switch(url.toString()) {
488                 case TEST_HTTP_URL:
489                     return mHttpConnection;
490                 case TEST_HTTP_OTHER_URL1:
491                     return mOtherHttpConnection1;
492                 case TEST_HTTP_OTHER_URL2:
493                     return mOtherHttpConnection2;
494                 case TEST_HTTPS_URL:
495                     return mHttpsConnection;
496                 case TEST_HTTPS_OTHER_URL1:
497                     return mOtherHttpsConnection1;
498                 case TEST_HTTPS_OTHER_URL2:
499                     return mOtherHttpsConnection2;
500                 case TEST_FALLBACK_URL:
501                     return mFallbackConnection;
502                 case TEST_OTHER_FALLBACK_URL:
503                     return mOtherFallbackConnection;
504                 case TEST_OVERRIDE_URL:
505                 case TEST_INVALID_OVERRIDE_URL:
506                     return mTestOverriddenUrlConnection;
507                 case TEST_CAPPORT_API_URL:
508                     return mCapportApiConnection;
509                 case TEST_SPEED_TEST_URL:
510                     return mSpeedTestConnection;
511                 default:
512                     fail("URL not mocked: " + url.toString());
513                     return null;
514             }
515         }).when(mCleartextDnsNetwork).openConnection(any());
516         when(mHttpConnection.getRequestProperties()).thenReturn(new ArrayMap<>());
517         when(mHttpsConnection.getRequestProperties()).thenReturn(new ArrayMap<>());
518 
519         mFakeDns = new FakeDns();
520         mFakeDns.startMocking();
521         // Set private dns suffix answer. sendPrivateDnsProbe() in NetworkMonitor send probe with
522         // one time hostname. The hostname will be [random generated UUID] + HOST_SUFFIX differently
523         // each time. That means the host answer cannot be pre-set into the answer list. Thus, set
524         // the host suffix and use partial match in FakeDns to match the target host and reply the
525         // intended answer.
526         mFakeDns.setAnswer(PRIVATE_DNS_PROBE_HOST_SUFFIX, new String[]{"192.0.2.2"}, TYPE_A);
527         mFakeDns.setAnswer(PRIVATE_DNS_PROBE_HOST_SUFFIX, new String[]{"2001:db8::1"}, TYPE_AAAA);
528 
529         when(mContext.registerReceiver(any(BroadcastReceiver.class), any())).then((invocation) -> {
530             mRegisteredReceivers.add(invocation.getArgument(0));
531             return new Intent();
532         });
533 
534         doAnswer((invocation) -> {
535             mRegisteredReceivers.remove(invocation.getArgument(0));
536             return null;
537         }).when(mContext).unregisterReceiver(any());
538 
539         resetCallbacks();
540 
541         setMinDataStallEvaluateInterval(500);
542         setDataStallEvaluationType(DATA_STALL_EVALUATION_TYPE_DNS);
543         setValidDataStallDnsTimeThreshold(500);
544         setConsecutiveDnsTimeoutThreshold(5);
545         mCreatedNetworkMonitors = new HashSet<>();
546         mRegisteredReceivers = new HashSet<>();
547         setDismissPortalInValidatedNetwork(false);
548     }
549 
550     @After
tearDown()551     public void tearDown() {
552         mFakeDns.clearAll();
553         // Make a local copy of mCreatedNetworkMonitors because during the iteration below,
554         // WrappedNetworkMonitor#onQuitting will delete elements from it on the handler threads.
555         WrappedNetworkMonitor[] networkMonitors = mCreatedNetworkMonitors.toArray(
556                 new WrappedNetworkMonitor[0]);
557         for (WrappedNetworkMonitor nm : networkMonitors) {
558             nm.notifyNetworkDisconnected();
559             nm.awaitQuit();
560         }
561         assertEquals("NetworkMonitor still running after disconnect",
562                 0, mCreatedNetworkMonitors.size());
563         assertEquals("BroadcastReceiver still registered after disconnect",
564                 0, mRegisteredReceivers.size());
565     }
566 
resetCallbacks()567     private void resetCallbacks() {
568         resetCallbacks(6);
569     }
570 
resetCallbacks(int interfaceVersion)571     private void resetCallbacks(int interfaceVersion) {
572         reset(mCallbacks);
573         try {
574             doReturn(interfaceVersion).when(mCallbacks).getInterfaceVersion();
575         } catch (RemoteException e) {
576             // Can't happen as mCallbacks is a mock
577             fail("Error mocking getInterfaceVersion" + e);
578         }
579     }
580 
getTcpSocketTrackerOrNull(NetworkMonitor.Dependencies dp)581     private TcpSocketTracker getTcpSocketTrackerOrNull(NetworkMonitor.Dependencies dp) {
582         return ((dp.getDeviceConfigPropertyInt(
583                 NAMESPACE_CONNECTIVITY,
584                 CONFIG_DATA_STALL_EVALUATION_TYPE,
585                 DEFAULT_DATA_STALL_EVALUATION_TYPES)
586                 & DATA_STALL_EVALUATION_TYPE_TCP) != 0) ? mTst : null;
587     }
588 
589     private class WrappedNetworkMonitor extends NetworkMonitor {
590         private long mProbeTime = 0;
591         private final ConditionVariable mQuitCv = new ConditionVariable(false);
592 
WrappedNetworkMonitor()593         WrappedNetworkMonitor() {
594             super(mContext, mCallbacks, mNetwork, mLogger, mValidationLogger, mServiceManager,
595                     mDependencies, getTcpSocketTrackerOrNull(mDependencies));
596         }
597 
598         @Override
getLastProbeTime()599         protected long getLastProbeTime() {
600             return mProbeTime;
601         }
602 
setLastProbeTime(long time)603         protected void setLastProbeTime(long time) {
604             mProbeTime = time;
605         }
606 
607         @Override
addDnsEvents(@onNull final DataStallDetectionStats.Builder stats)608         protected void addDnsEvents(@NonNull final DataStallDetectionStats.Builder stats) {
609             if ((getDataStallEvaluationType() & DATA_STALL_EVALUATION_TYPE_DNS) != 0) {
610                 generateTimeoutDnsEvent(stats, DEFAULT_DNS_TIMEOUT_THRESHOLD);
611             }
612         }
613 
614         @Override
onQuitting()615         protected void onQuitting() {
616             assertTrue(mCreatedNetworkMonitors.remove(this));
617             mQuitCv.open();
618         }
619 
awaitQuit()620         protected void awaitQuit() {
621             assertTrue("NetworkMonitor did not quit after " + HANDLER_TIMEOUT_MS + "ms",
622                     mQuitCv.block(HANDLER_TIMEOUT_MS));
623         }
624 
getContext()625         protected Context getContext() {
626             return mContext;
627         }
628     }
629 
makeMonitor(NetworkCapabilities nc)630     private WrappedNetworkMonitor makeMonitor(NetworkCapabilities nc) {
631         final WrappedNetworkMonitor nm = new WrappedNetworkMonitor();
632         nm.start();
633         setNetworkCapabilities(nm, nc);
634         HandlerUtilsKt.waitForIdle(nm.getHandler(), HANDLER_TIMEOUT_MS);
635         mCreatedNetworkMonitors.add(nm);
636         when(mTstDependencies.isTcpInfoParsingSupported()).thenReturn(false);
637 
638         return nm;
639     }
640 
makeCellMeteredNetworkMonitor()641     private WrappedNetworkMonitor makeCellMeteredNetworkMonitor() {
642         final WrappedNetworkMonitor nm = makeMonitor(CELL_METERED_CAPABILITIES);
643         return nm;
644     }
645 
makeCellNotMeteredNetworkMonitor()646     private WrappedNetworkMonitor makeCellNotMeteredNetworkMonitor() {
647         final WrappedNetworkMonitor nm = makeMonitor(CELL_NOT_METERED_CAPABILITIES);
648         return nm;
649     }
650 
makeWifiNotMeteredNetworkMonitor()651     private WrappedNetworkMonitor makeWifiNotMeteredNetworkMonitor() {
652         final WrappedNetworkMonitor nm = makeMonitor(WIFI_NOT_METERED_CAPABILITIES);
653         return nm;
654     }
655 
setNetworkCapabilities(NetworkMonitor nm, NetworkCapabilities nc)656     private void setNetworkCapabilities(NetworkMonitor nm, NetworkCapabilities nc) {
657         nm.notifyNetworkCapabilitiesChanged(nc);
658         HandlerUtilsKt.waitForIdle(nm.getHandler(), HANDLER_TIMEOUT_MS);
659     }
660 
661     @Test
testOnlyWifiTransport()662     public void testOnlyWifiTransport() {
663         final WrappedNetworkMonitor wnm = makeMonitor(CELL_METERED_CAPABILITIES);
664         assertFalse(wnm.onlyWifiTransport());
665         final NetworkCapabilities nc = new NetworkCapabilities()
666                 .addTransportType(TRANSPORT_WIFI)
667                 .addTransportType(TRANSPORT_VPN);
668         setNetworkCapabilities(wnm, nc);
669         assertFalse(wnm.onlyWifiTransport());
670         nc.removeTransportType(TRANSPORT_VPN);
671         setNetworkCapabilities(wnm, nc);
672         assertTrue(wnm.onlyWifiTransport());
673     }
674 
675     @Test
testNeedEvaluatingBandwidth()676     public void testNeedEvaluatingBandwidth() throws Exception {
677         // Make metered network first, the transport type is TRANSPORT_CELLULAR. That means the
678         // test cannot pass the condition check in needEvaluatingBandwidth().
679         final WrappedNetworkMonitor wnm1 = makeCellMeteredNetworkMonitor();
680         // Don't set the config_evaluating_bandwidth_url to make
681         // the condition check fail in needEvaluatingBandwidth().
682         assertFalse(wnm1.needEvaluatingBandwidth());
683         // Make the NetworkCapabilities to have the TRANSPORT_WIFI but it still cannot meet the
684         // condition check.
685         final NetworkCapabilities nc = new NetworkCapabilities()
686                 .addTransportType(TRANSPORT_WIFI);
687         setNetworkCapabilities(wnm1, nc);
688         assertFalse(wnm1.needEvaluatingBandwidth());
689         // Make the network to be non-metered wifi but it still cannot meet the condition check
690         // since the config_evaluating_bandwidth_url is not set.
691         nc.addCapability(NET_CAPABILITY_NOT_METERED);
692         setNetworkCapabilities(wnm1, nc);
693         assertFalse(wnm1.needEvaluatingBandwidth());
694         // All configurations are set correctly.
695         doReturn(TEST_SPEED_TEST_URL).when(mResources).getString(
696                 R.string.config_evaluating_bandwidth_url);
697         final WrappedNetworkMonitor wnm2 = makeCellMeteredNetworkMonitor();
698         setNetworkCapabilities(wnm2, nc);
699         assertTrue(wnm2.needEvaluatingBandwidth());
700         // Set mIsBandwidthCheckPassedOrIgnored to true and expect needEvaluatingBandwidth() will
701         // return false.
702         wnm2.mIsBandwidthCheckPassedOrIgnored = true;
703         assertFalse(wnm2.needEvaluatingBandwidth());
704         // Reset mIsBandwidthCheckPassedOrIgnored back to false.
705         wnm2.mIsBandwidthCheckPassedOrIgnored = false;
706         // Shouldn't evaluate network bandwidth on the metered wifi.
707         nc.removeCapability(NET_CAPABILITY_NOT_METERED);
708         setNetworkCapabilities(wnm2, nc);
709         assertFalse(wnm2.needEvaluatingBandwidth());
710         // Shouldn't evaluate network bandwidth on the unmetered cellular.
711         nc.addCapability(NET_CAPABILITY_NOT_METERED);
712         nc.removeTransportType(TRANSPORT_WIFI);
713         nc.addTransportType(TRANSPORT_CELLULAR);
714         assertFalse(wnm2.needEvaluatingBandwidth());
715     }
716 
717     @Test
testEvaluatingBandwidthState_meteredNetwork()718     public void testEvaluatingBandwidthState_meteredNetwork() throws Exception {
719         setStatus(mHttpsConnection, 204);
720         setStatus(mHttpConnection, 204);
721         final NetworkCapabilities meteredCap = new NetworkCapabilities()
722                 .addTransportType(TRANSPORT_WIFI)
723                 .addCapability(NET_CAPABILITY_INTERNET);
724         doReturn(TEST_SPEED_TEST_URL).when(mResources).getString(
725                 R.string.config_evaluating_bandwidth_url);
726         final NetworkMonitor nm = runNetworkTest(TEST_LINK_PROPERTIES, meteredCap,
727                 NETWORK_VALIDATION_RESULT_VALID, NETWORK_VALIDATION_PROBE_DNS
728                 | NETWORK_VALIDATION_PROBE_HTTPS, null /* redirectUrl */);
729         // Evaluating bandwidth process won't be executed when the network is metered wifi.
730         // Check that the connection hasn't been opened and the state should transition to validated
731         // state directly.
732         verify(mCleartextDnsNetwork, never()).openConnection(new URL(TEST_SPEED_TEST_URL));
733         assertEquals(NETWORK_VALIDATION_RESULT_VALID,
734                 nm.getEvaluationState().getEvaluationResult());
735     }
736 
737     @Test
testEvaluatingBandwidthState_nonMeteredNetworkWithWrongConfig()738     public void testEvaluatingBandwidthState_nonMeteredNetworkWithWrongConfig() throws Exception {
739         setStatus(mHttpsConnection, 204);
740         setStatus(mHttpConnection, 204);
741         final NetworkCapabilities nonMeteredCap = new NetworkCapabilities()
742                 .addTransportType(TRANSPORT_WIFI)
743                 .addCapability(NET_CAPABILITY_INTERNET)
744                 .addCapability(NET_CAPABILITY_NOT_METERED);
745         doReturn("").when(mResources).getString(R.string.config_evaluating_bandwidth_url);
746         final NetworkMonitor nm = runNetworkTest(TEST_LINK_PROPERTIES, nonMeteredCap,
747                 NETWORK_VALIDATION_RESULT_VALID, NETWORK_VALIDATION_PROBE_DNS
748                 | NETWORK_VALIDATION_PROBE_HTTPS, null /* redirectUrl */);
749         // Non-metered network with wrong configuration(the config_evaluating_bandwidth_url is
750         // empty). Check that the connection hasn't been opened and the state should transition to
751         // validated state directly.
752         verify(mCleartextDnsNetwork, never()).openConnection(new URL(TEST_SPEED_TEST_URL));
753         assertEquals(NETWORK_VALIDATION_RESULT_VALID,
754                 nm.getEvaluationState().getEvaluationResult());
755     }
756 
757     @Test
testMatchesHttpContent()758     public void testMatchesHttpContent() throws Exception {
759         final WrappedNetworkMonitor wnm = makeCellNotMeteredNetworkMonitor();
760         doReturn("[\\s\\S]*line2[\\s\\S]*").when(mResources).getString(
761                 R.string.config_network_validation_failed_content_regexp);
762         assertTrue(wnm.matchesHttpContent("This is line1\nThis is line2\nThis is line3",
763                 R.string.config_network_validation_failed_content_regexp));
764         assertFalse(wnm.matchesHttpContent("hello",
765                 R.string.config_network_validation_failed_content_regexp));
766         // Set an invalid regex and expect to get the false even though the regex is the same as the
767         // content.
768         doReturn("[").when(mResources).getString(
769                 R.string.config_network_validation_failed_content_regexp);
770         assertFalse(wnm.matchesHttpContent("[",
771                 R.string.config_network_validation_failed_content_regexp));
772     }
773 
774     @Test
testMatchesHttpContentLength()775     public void testMatchesHttpContentLength() throws Exception {
776         final WrappedNetworkMonitor wnm = makeCellNotMeteredNetworkMonitor();
777         // Set the range of content length.
778         doReturn(100).when(mResources).getInteger(R.integer.config_min_matches_http_content_length);
779         doReturn(1000).when(mResources).getInteger(
780                 R.integer.config_max_matches_http_content_length);
781         assertFalse(wnm.matchesHttpContentLength(100));
782         assertFalse(wnm.matchesHttpContentLength(1000));
783         assertTrue(wnm.matchesHttpContentLength(500));
784 
785         // Test the invalid value.
786         assertFalse(wnm.matchesHttpContentLength(-1));
787         assertFalse(wnm.matchesHttpContentLength(0));
788         assertFalse(wnm.matchesHttpContentLength(Integer.MAX_VALUE + 1L));
789 
790         // Set the wrong value for min and max config to make sure the function is working even
791         // though the config is wrong.
792         doReturn(1000).when(mResources).getInteger(
793                 R.integer.config_min_matches_http_content_length);
794         doReturn(100).when(mResources).getInteger(
795                 R.integer.config_max_matches_http_content_length);
796         assertFalse(wnm.matchesHttpContentLength(100));
797         assertFalse(wnm.matchesHttpContentLength(1000));
798         assertFalse(wnm.matchesHttpContentLength(500));
799     }
800 
801     @Test
testGetResStringConfig()802     public void testGetResStringConfig() throws Exception {
803         final WrappedNetworkMonitor wnm = makeCellNotMeteredNetworkMonitor();
804         // Set the config and expect to get the customized value.
805         final String regExp = ".*HTTP.*200.*not a captive portal.*";
806         doReturn(regExp).when(mResources).getString(
807                 R.string.config_network_validation_failed_content_regexp);
808         assertEquals(regExp, wnm.getResStringConfig(mContext,
809                 R.string.config_network_validation_failed_content_regexp, null));
810         doThrow(new Resources.NotFoundException()).when(mResources).getString(eq(
811                 R.string.config_network_validation_failed_content_regexp));
812         // If the config is not found, then expect to get the default value - null.
813         assertNull(wnm.getResStringConfig(mContext,
814                 R.string.config_network_validation_failed_content_regexp, null));
815     }
816 
817     @Test
testGetResIntConfig()818     public void testGetResIntConfig() throws Exception {
819         final WrappedNetworkMonitor wnm = makeCellNotMeteredNetworkMonitor();
820         // Set the config and expect to get the customized value.
821         doReturn(100).when(mResources).getInteger(R.integer.config_min_matches_http_content_length);
822         doReturn(1000).when(mResources).getInteger(
823                 R.integer.config_max_matches_http_content_length);
824         assertEquals(100, wnm.getResIntConfig(mContext,
825                 R.integer.config_min_matches_http_content_length, Integer.MAX_VALUE));
826         assertEquals(1000, wnm.getResIntConfig(mContext,
827                 R.integer.config_max_matches_http_content_length, 0));
828         doThrow(new Resources.NotFoundException())
829                 .when(mResources).getInteger(
830                         eq(R.integer.config_min_matches_http_content_length));
831         doThrow(new Resources.NotFoundException())
832                 .when(mResources).getInteger(eq(R.integer.config_max_matches_http_content_length));
833         // If the config is not found, then expect to get the default value.
834         assertEquals(Integer.MAX_VALUE, wnm.getResIntConfig(mContext,
835                 R.integer.config_min_matches_http_content_length, Integer.MAX_VALUE));
836         assertEquals(0, wnm.getResIntConfig(mContext,
837                 R.integer.config_max_matches_http_content_length, 0));
838     }
839 
840     @Test
testGetHttpProbeUrl()841     public void testGetHttpProbeUrl() {
842         final WrappedNetworkMonitor wnm = makeCellNotMeteredNetworkMonitor();
843         // If config_captive_portal_http_url is set and the global setting is set, the config is
844         // used.
845         doReturn(TEST_HTTP_URL).when(mResources).getString(R.string.config_captive_portal_http_url);
846         doReturn(TEST_HTTP_OTHER_URL2).when(mResources).getString(
847                 R.string.default_captive_portal_http_url);
848         when(mDependencies.getSetting(any(), eq(Settings.Global.CAPTIVE_PORTAL_HTTP_URL), any()))
849                 .thenReturn(TEST_HTTP_OTHER_URL1);
850         assertEquals(TEST_HTTP_URL, wnm.getCaptivePortalServerHttpUrl());
851         // If config_captive_portal_http_url is unset and the global setting is set, the global
852         // setting is used.
853         doReturn(null).when(mResources).getString(R.string.config_captive_portal_http_url);
854         assertEquals(TEST_HTTP_OTHER_URL1, wnm.getCaptivePortalServerHttpUrl());
855         // If both config_captive_portal_http_url and global setting are unset,
856         // default_captive_portal_http_url is used.
857         when(mDependencies.getSetting(any(), eq(Settings.Global.CAPTIVE_PORTAL_HTTP_URL), any()))
858                 .thenReturn(null);
859         assertEquals(TEST_HTTP_OTHER_URL2, wnm.getCaptivePortalServerHttpUrl());
860     }
861 
862     @Test
testGetLocationMcc()863     public void testGetLocationMcc() throws Exception {
864         final WrappedNetworkMonitor wnm = makeCellNotMeteredNetworkMonitor();
865         doReturn(PackageManager.PERMISSION_DENIED).when(mContext).checkPermission(
866                 eq(android.Manifest.permission.ACCESS_FINE_LOCATION),  anyInt(), anyInt());
867         assertNull(wnm.getLocationMcc());
868         doReturn(PackageManager.PERMISSION_GRANTED).when(mContext).checkPermission(
869                 eq(android.Manifest.permission.ACCESS_FINE_LOCATION),  anyInt(), anyInt());
870         doReturn(new ContextWrapper(mContext)).when(mContext).createConfigurationContext(any());
871         doReturn(null).when(mTelephony).getAllCellInfo();
872         assertNull(wnm.getLocationMcc());
873         // Prepare CellInfo and check if the vote mechanism is working or not.
874         final List<CellInfo> cellList = new ArrayList<CellInfo>();
875         doReturn(cellList).when(mTelephony).getAllCellInfo();
876         assertNull(wnm.getLocationMcc());
877         cellList.add(makeTestCellInfoGsm("460"));
878         cellList.add(makeTestCellInfoGsm("460"));
879         cellList.add(makeTestCellInfoLte("466"));
880         // The count of 460 is 2 and the count of 466 is 1, so the getLocationMcc() should return
881         // 460.
882         assertEquals("460", wnm.getLocationMcc());
883         // getCustomizedContextOrDefault() shouldn't return mContext when using neighbor mcc
884         // is enabled and the sim is not ready.
885         doReturn(true).when(mResources).getBoolean(R.bool.config_no_sim_card_uses_neighbor_mcc);
886         doReturn(TelephonyManager.SIM_STATE_ABSENT).when(mTelephony).getSimState();
887         assertEquals(460,
888                 wnm.getCustomizedContextOrDefault().getResources().getConfiguration().mcc);
889         doReturn(false).when(mResources).getBoolean(R.bool.config_no_sim_card_uses_neighbor_mcc);
890         assertEquals(wnm.getContext(), wnm.getCustomizedContextOrDefault());
891     }
892 
893     @Test
testGetMccMncOverrideInfo()894     public void testGetMccMncOverrideInfo() {
895         final WrappedNetworkMonitor wnm = makeCellNotMeteredNetworkMonitor();
896         doReturn(new ContextWrapper(mContext)).when(mContext).createConfigurationContext(any());
897         // 1839 is VZW's carrier id.
898         doReturn(1839).when(mTelephony).getSimCarrierId();
899         assertNull(wnm.getMccMncOverrideInfo());
900         // 1854 is CTC's carrier id.
901         doReturn(1854).when(mTelephony).getSimCarrierId();
902         assertNotNull(wnm.getMccMncOverrideInfo());
903         // Check if the mcc & mnc has changed as expected.
904         assertEquals(460,
905                 wnm.getCustomizedContextOrDefault().getResources().getConfiguration().mcc);
906         assertEquals(03,
907                 wnm.getCustomizedContextOrDefault().getResources().getConfiguration().mnc);
908         // Every mcc and mnc should be set in sCarrierIdToMccMnc.
909         // Check if there is any unset value in mcc or mnc.
910         for (int i = 0; i < wnm.sCarrierIdToMccMnc.size(); i++) {
911             assertNotEquals(-1, wnm.sCarrierIdToMccMnc.valueAt(i).mcc);
912             assertNotEquals(-1, wnm.sCarrierIdToMccMnc.valueAt(i).mnc);
913         }
914     }
915 
makeTestCellInfoGsm(String mcc)916     private CellInfoGsm makeTestCellInfoGsm(String mcc) throws Exception {
917         final CellInfoGsm info = new CellInfoGsm();
918         final CellIdentityGsm ci = makeCellIdentityGsm(0, 0, 0, 0, mcc, "01", "", "");
919         info.setCellIdentity(ci);
920         return info;
921     }
922 
makeTestCellInfoLte(String mcc)923     private CellInfoLte makeTestCellInfoLte(String mcc) throws Exception {
924         final CellInfoLte info = new CellInfoLte();
925         final CellIdentityLte ci = makeCellIdentityLte(0, 0, 0, 0, 0, mcc, "01", "", "");
926         info.setCellIdentity(ci);
927         return info;
928     }
929 
setupNoSimCardNeighborMcc()930     private void setupNoSimCardNeighborMcc() throws Exception {
931         // Enable using neighbor resource by camping mcc feature.
932         doReturn(true).when(mResources).getBoolean(R.bool.config_no_sim_card_uses_neighbor_mcc);
933         final List<CellInfo> cellList = new ArrayList<CellInfo>();
934         final int testMcc = 460;
935         cellList.add(makeTestCellInfoGsm(Integer.toString(testMcc)));
936         doReturn(cellList).when(mTelephony).getAllCellInfo();
937         final Configuration config = mResources.getConfiguration();
938         config.mcc = testMcc;
939         doReturn(mMccContext).when(mContext).createConfigurationContext(eq(config));
940     }
941 
942     @Test
testMakeFallbackUrls()943     public void testMakeFallbackUrls() throws Exception {
944         final WrappedNetworkMonitor wnm = makeCellNotMeteredNetworkMonitor();
945         // Value exist in setting provider.
946         URL[] urls = wnm.makeCaptivePortalFallbackUrls();
947         assertEquals(urls[0].toString(), TEST_FALLBACK_URL);
948 
949         // Clear setting provider value. Verify it to get configuration from resource instead.
950         setFallbackUrl(null);
951         // Verify that getting resource with exception.
952         when(mResources.getStringArray(R.array.config_captive_portal_fallback_urls))
953                 .thenThrow(Resources.NotFoundException.class);
954         urls = wnm.makeCaptivePortalFallbackUrls();
955         assertEquals(urls.length, 0);
956 
957         // Verify resource return 2 different URLs.
958         doReturn(new String[] {"http://testUrl1.com", "http://testUrl2.com"}).when(mResources)
959                 .getStringArray(R.array.config_captive_portal_fallback_urls);
960         urls = wnm.makeCaptivePortalFallbackUrls();
961         assertEquals(urls.length, 2);
962         assertEquals("http://testUrl1.com", urls[0].toString());
963         assertEquals("http://testUrl2.com", urls[1].toString());
964 
965         // Even though the using neighbor resource by camping mcc feature is enabled, the
966         // customized context has been assigned and won't change. So calling
967         // makeCaptivePortalFallbackUrls() still gets the original value.
968         setupNoSimCardNeighborMcc();
969         doReturn(new String[] {"http://testUrl3.com"}).when(mMccResource)
970                 .getStringArray(R.array.config_captive_portal_fallback_urls);
971         urls = wnm.makeCaptivePortalFallbackUrls();
972         assertEquals(urls.length, 2);
973         assertEquals("http://testUrl1.com", urls[0].toString());
974         assertEquals("http://testUrl2.com", urls[1].toString());
975     }
976 
977     @Test
testMakeFallbackUrlsWithCustomizedContext()978     public void testMakeFallbackUrlsWithCustomizedContext() throws Exception {
979         // Value is expected to be replaced by location resource.
980         setupNoSimCardNeighborMcc();
981         doReturn(new String[] {"http://testUrl.com"}).when(mMccResource)
982                 .getStringArray(R.array.config_captive_portal_fallback_urls);
983         final WrappedNetworkMonitor wnm = makeCellNotMeteredNetworkMonitor();
984         final URL[] urls = wnm.makeCaptivePortalFallbackUrls();
985         assertEquals(urls.length, 1);
986         assertEquals("http://testUrl.com", urls[0].toString());
987     }
988 
makeCellIdentityGsm(int lac, int cid, int arfcn, int bsic, String mccStr, String mncStr, String alphal, String alphas)989     private static CellIdentityGsm makeCellIdentityGsm(int lac, int cid, int arfcn, int bsic,
990             String mccStr, String mncStr, String alphal, String alphas)
991             throws ReflectiveOperationException {
992         if (ShimUtils.isAtLeastR()) {
993             return new CellIdentityGsm(lac, cid, arfcn, bsic, mccStr, mncStr, alphal, alphas,
994                     Collections.emptyList() /* additionalPlmns */);
995         } else {
996             // API <= Q does not have the additionalPlmns parameter
997             final Constructor<CellIdentityGsm> constructor = CellIdentityGsm.class.getConstructor(
998                     int.class, int.class, int.class, int.class, String.class, String.class,
999                     String.class, String.class);
1000             return constructor.newInstance(lac, cid, arfcn, bsic, mccStr, mncStr, alphal, alphas);
1001         }
1002     }
1003 
makeCellIdentityLte(int ci, int pci, int tac, int earfcn, int bandwidth, String mccStr, String mncStr, String alphal, String alphas)1004     private static CellIdentityLte makeCellIdentityLte(int ci, int pci, int tac, int earfcn,
1005             int bandwidth, String mccStr, String mncStr, String alphal, String alphas)
1006             throws ReflectiveOperationException {
1007         if (ShimUtils.isAtLeastR()) {
1008             return new CellIdentityLte(ci, pci, tac, earfcn, new int[] {} /* bands */,
1009                     bandwidth, mccStr, mncStr, alphal, alphas,
1010                     Collections.emptyList() /* additionalPlmns */, null /* csgInfo */);
1011         } else {
1012             // API <= Q does not have the additionalPlmns and csgInfo parameters
1013             final Constructor<CellIdentityLte> constructor = CellIdentityLte.class.getConstructor(
1014                     int.class, int.class, int.class, int.class, int.class, String.class,
1015                     String.class, String.class, String.class);
1016             return constructor.newInstance(ci, pci, tac, earfcn, bandwidth, mccStr, mncStr, alphal,
1017                     alphas);
1018         }
1019     }
1020 
1021     @Test
testGetIntSetting()1022     public void testGetIntSetting() throws Exception {
1023         WrappedNetworkMonitor wnm = makeCellNotMeteredNetworkMonitor();
1024 
1025         // No config resource, no device config. Expect to get default resource.
1026         doThrow(new Resources.NotFoundException())
1027                 .when(mResources).getInteger(eq(R.integer.config_captive_portal_dns_probe_timeout));
1028         doAnswer(invocation -> {
1029             int defaultValue = invocation.getArgument(2);
1030             return defaultValue;
1031         }).when(mDependencies).getDeviceConfigPropertyInt(any(),
1032                 eq(NetworkMonitor.CONFIG_CAPTIVE_PORTAL_DNS_PROBE_TIMEOUT),
1033                 anyInt());
1034         assertEquals(DEFAULT_CAPTIVE_PORTAL_DNS_PROBE_TIMEOUT, wnm.getIntSetting(mContext,
1035                 R.integer.config_captive_portal_dns_probe_timeout,
1036                 NetworkMonitor.CONFIG_CAPTIVE_PORTAL_DNS_PROBE_TIMEOUT,
1037                 DEFAULT_CAPTIVE_PORTAL_DNS_PROBE_TIMEOUT));
1038 
1039         // Set device config. Expect to get device config.
1040         when(mDependencies.getDeviceConfigPropertyInt(any(),
1041                 eq(NetworkMonitor.CONFIG_CAPTIVE_PORTAL_DNS_PROBE_TIMEOUT), anyInt()))
1042                         .thenReturn(1234);
1043         assertEquals(1234, wnm.getIntSetting(mContext,
1044                 R.integer.config_captive_portal_dns_probe_timeout,
1045                 NetworkMonitor.CONFIG_CAPTIVE_PORTAL_DNS_PROBE_TIMEOUT,
1046                 DEFAULT_CAPTIVE_PORTAL_DNS_PROBE_TIMEOUT));
1047 
1048         // Set config resource. Expect to get config resource.
1049         when(mResources.getInteger(eq(R.integer.config_captive_portal_dns_probe_timeout)))
1050                 .thenReturn(5678);
1051         assertEquals(5678, wnm.getIntSetting(mContext,
1052                 R.integer.config_captive_portal_dns_probe_timeout,
1053                 NetworkMonitor.CONFIG_CAPTIVE_PORTAL_DNS_PROBE_TIMEOUT,
1054                 DEFAULT_CAPTIVE_PORTAL_DNS_PROBE_TIMEOUT));
1055     }
1056 
1057     @Test
testIsCaptivePortal_HttpProbeIsPortal()1058     public void testIsCaptivePortal_HttpProbeIsPortal() throws Exception {
1059         setSslException(mHttpsConnection);
1060         setPortal302(mHttpConnection);
1061         runPortalNetworkTest();
1062     }
1063 
setupPrivateIpResponse(String privateAddr)1064     private void setupPrivateIpResponse(String privateAddr) throws Exception {
1065         setSslException(mHttpsConnection);
1066         setPortal302(mHttpConnection);
1067         final String httpHost = new URL(TEST_HTTP_URL).getHost();
1068         mFakeDns.setAnswer(httpHost, new String[] { "2001:db8::123" }, TYPE_AAAA);
1069         final InetAddress parsedPrivateAddr = InetAddresses.parseNumericAddress(privateAddr);
1070         mFakeDns.setAnswer(httpHost, new String[] { privateAddr },
1071                 (parsedPrivateAddr instanceof Inet6Address) ? TYPE_AAAA : TYPE_A);
1072     }
1073 
1074     @Test
testIsCaptivePortal_PrivateIpNotPortal_Enabled_IPv4()1075     public void testIsCaptivePortal_PrivateIpNotPortal_Enabled_IPv4() throws Exception {
1076         when(mDependencies.isFeatureEnabled(any(), eq(DNS_PROBE_PRIVATE_IP_NO_INTERNET_VERSION)))
1077                 .thenReturn(true);
1078         setupPrivateIpResponse("192.168.1.1");
1079         runFailedNetworkTest();
1080     }
1081 
1082     @Test
testIsCaptivePortal_PrivateIpNotPortal_Enabled_IPv6()1083     public void testIsCaptivePortal_PrivateIpNotPortal_Enabled_IPv6() throws Exception {
1084         when(mDependencies.isFeatureEnabled(any(), eq(DNS_PROBE_PRIVATE_IP_NO_INTERNET_VERSION)))
1085                 .thenReturn(true);
1086         setupPrivateIpResponse("fec0:1234::1");
1087         runFailedNetworkTest();
1088     }
1089 
1090     @Test
testIsCaptivePortal_PrivateIpNotPortal_Disabled()1091     public void testIsCaptivePortal_PrivateIpNotPortal_Disabled() throws Exception {
1092         setupPrivateIpResponse("192.168.1.1");
1093         runPortalNetworkTest();
1094     }
1095 
1096     @Test
testIsCaptivePortal_HttpsProbeIsNotPortal()1097     public void testIsCaptivePortal_HttpsProbeIsNotPortal() throws Exception {
1098         setStatus(mHttpsConnection, 204);
1099         setStatus(mHttpConnection, 500);
1100 
1101         runValidatedNetworkTest();
1102     }
1103 
1104     @Test
testIsCaptivePortal_FallbackProbeIsPortal()1105     public void testIsCaptivePortal_FallbackProbeIsPortal() throws Exception {
1106         setSslException(mHttpsConnection);
1107         setStatus(mHttpConnection, 500);
1108         setPortal302(mFallbackConnection);
1109         runPortalNetworkTest();
1110     }
1111 
1112     @Test
testIsCaptivePortal_FallbackProbeIsNotPortal()1113     public void testIsCaptivePortal_FallbackProbeIsNotPortal() throws Exception {
1114         setSslException(mHttpsConnection);
1115         setStatus(mHttpConnection, 500);
1116         setStatus(mFallbackConnection, 500);
1117 
1118         // Fallback probe did not see portal, HTTPS failed -> inconclusive
1119         runFailedNetworkTest();
1120     }
1121 
1122     @Test
testIsCaptivePortal_OtherFallbackProbeIsPortal()1123     public void testIsCaptivePortal_OtherFallbackProbeIsPortal() throws Exception {
1124         // Set all fallback probes but one to invalid URLs to verify they are being skipped
1125         setFallbackUrl(TEST_FALLBACK_URL);
1126         setOtherFallbackUrls(TEST_FALLBACK_URL + "," + TEST_OTHER_FALLBACK_URL);
1127 
1128         setSslException(mHttpsConnection);
1129         setStatus(mHttpConnection, 500);
1130         setStatus(mFallbackConnection, 500);
1131         setPortal302(mOtherFallbackConnection);
1132 
1133         // TEST_OTHER_FALLBACK_URL is third
1134         when(mRandom.nextInt()).thenReturn(2);
1135 
1136         // First check always uses the first fallback URL: inconclusive
1137         final NetworkMonitor monitor = runFailedNetworkTest();
1138         verify(mFallbackConnection, times(1)).getResponseCode();
1139         verify(mOtherFallbackConnection, never()).getResponseCode();
1140 
1141         // Second check should be triggered automatically after the reevaluate delay, and uses the
1142         // URL chosen by mRandom
1143         // Ensure that the reevaluate delay is not changed to a large value, otherwise this test
1144         // would block for too long and a different test strategy should be used.
1145         assertTrue(INITIAL_REEVALUATE_DELAY_MS < 2000);
1146         verify(mOtherFallbackConnection, timeout(INITIAL_REEVALUATE_DELAY_MS + HANDLER_TIMEOUT_MS))
1147                 .getResponseCode();
1148         verifyNetworkTested(VALIDATION_RESULT_PORTAL, 0 /* probesSucceeded */, TEST_LOGIN_URL);
1149     }
1150 
1151     @Test
1152     public void testIsCaptivePortal_AllProbesFailed() throws Exception {
1153         setSslException(mHttpsConnection);
1154         setStatus(mHttpConnection, 500);
1155         setStatus(mFallbackConnection, 404);
1156 
1157         runFailedNetworkTest();
1158         verify(mFallbackConnection, times(1)).getResponseCode();
1159         verify(mOtherFallbackConnection, never()).getResponseCode();
1160     }
1161 
1162     @Test
1163     public void testIsCaptivePortal_InvalidUrlSkipped() throws Exception {
1164         setFallbackUrl("invalid");
1165         setOtherFallbackUrls("otherinvalid," + TEST_OTHER_FALLBACK_URL + ",yetanotherinvalid");
1166 
1167         setSslException(mHttpsConnection);
1168         setStatus(mHttpConnection, 500);
1169         setPortal302(mOtherFallbackConnection);
1170         runPortalNetworkTest();
1171         verify(mOtherFallbackConnection, times(1)).getResponseCode();
1172         verify(mFallbackConnection, never()).getResponseCode();
1173     }
1174 
1175     @Test
1176     public void testIsCaptivePortal_CapportApiIsPortalWithNullPortalUrl() throws Exception {
1177         assumeTrue(CaptivePortalDataShimImpl.isSupported());
1178         setSslException(mHttpsConnection);
1179         final long bytesRemaining = 10_000L;
1180         final long secondsRemaining = 500L;
1181         // Set content without partal url.
1182         setApiContent(mCapportApiConnection, "{'captive': true,"
1183                 + "'venue-info-url': '" + TEST_VENUE_INFO_URL + "',"
1184                 + "'bytes-remaining': " + bytesRemaining + ","
1185                 + "'seconds-remaining': " + secondsRemaining + "}");
1186         setPortal302(mHttpConnection);
1187 
1188         runNetworkTest(makeCapportLPs(), CELL_METERED_CAPABILITIES, VALIDATION_RESULT_PORTAL,
1189                 0 /* probesSucceeded*/, TEST_LOGIN_URL);
1190 
1191         verify(mCapportApiConnection).getResponseCode();
1192 
1193         verify(mHttpConnection, times(1)).getResponseCode();
1194         verify(mCallbacks, never()).notifyCaptivePortalDataChanged(any());
1195     }
1196 
1197     @Test
1198     public void testIsCaptivePortal_CapportApiIsPortalWithValidPortalUrl() throws Exception {
1199         assumeTrue(CaptivePortalDataShimImpl.isSupported());
1200         setSslException(mHttpsConnection);
1201         final long bytesRemaining = 10_000L;
1202         final long secondsRemaining = 500L;
1203 
1204         setApiContent(mCapportApiConnection, "{'captive': true,"
1205                 + "'user-portal-url': '" + TEST_LOGIN_URL + "',"
1206                 + "'venue-info-url': '" + TEST_VENUE_INFO_URL + "',"
1207                 + "'bytes-remaining': " + bytesRemaining + ","
1208                 + "'seconds-remaining': " + secondsRemaining + "}");
1209 
1210         runNetworkTest(makeCapportLPs(), CELL_METERED_CAPABILITIES, VALIDATION_RESULT_PORTAL,
1211                 0 /* probesSucceeded*/, TEST_LOGIN_URL);
1212 
1213         verify(mHttpConnection, never()).getResponseCode();
1214         verify(mCapportApiConnection).getResponseCode();
1215 
1216         final ArgumentCaptor<CaptivePortalData> capportDataCaptor =
1217                 ArgumentCaptor.forClass(CaptivePortalData.class);
1218         verify(mCallbacks).notifyCaptivePortalDataChanged(capportDataCaptor.capture());
1219         final CaptivePortalData p = capportDataCaptor.getValue();
1220         assertTrue(p.isCaptive());
1221         assertEquals(Uri.parse(TEST_LOGIN_URL), p.getUserPortalUrl());
1222         assertEquals(Uri.parse(TEST_VENUE_INFO_URL), p.getVenueInfoUrl());
1223         assertEquals(bytesRemaining, p.getByteLimit());
1224         final long expectedExpiry = currentTimeMillis() + secondsRemaining * 1000;
1225         // Actual expiry will be slightly lower as some time as passed
1226         assertTrue(p.getExpiryTimeMillis() <= expectedExpiry);
1227         assertTrue(p.getExpiryTimeMillis() > expectedExpiry - 30_000);
1228     }
1229 
1230     @Test
1231     public void testIsCaptivePortal_CapportApiRevalidation() throws Exception {
1232         assumeTrue(CaptivePortalDataShimImpl.isSupported());
1233         setValidProbes();
1234         final NetworkMonitor nm = runValidatedNetworkTest();
1235 
1236         setApiContent(mCapportApiConnection, "{'captive': true, "
1237                 + "'user-portal-url': '" + TEST_LOGIN_URL + "'}");
1238         nm.notifyLinkPropertiesChanged(makeCapportLPs());
1239 
1240         verifyNetworkTested(VALIDATION_RESULT_PORTAL, 0 /* probesSucceeded */,
1241                 TEST_LOGIN_URL);
1242         final ArgumentCaptor<CaptivePortalData> capportCaptor = ArgumentCaptor.forClass(
1243                 CaptivePortalData.class);
1244         verify(mCallbacks).notifyCaptivePortalDataChanged(capportCaptor.capture());
1245         assertEquals(Uri.parse(TEST_LOGIN_URL), capportCaptor.getValue().getUserPortalUrl());
1246 
1247         // HTTP probe was sent on first validation but not re-sent when there was a portal URL.
1248         verify(mHttpConnection, times(1)).getResponseCode();
1249         verify(mCapportApiConnection, times(1)).getResponseCode();
1250     }
1251 
1252     @Test
1253     public void testIsCaptivePortal_NoRevalidationBeforeNetworkConnected() throws Exception {
1254         assumeTrue(CaptivePortalDataShimImpl.isSupported());
1255 
1256         final NetworkMonitor nm = makeCellMeteredNetworkMonitor();
1257 
1258         final LinkProperties lp = makeCapportLPs();
1259 
1260         // LinkProperties changed, but NM should not revalidate before notifyNetworkConnected
1261         nm.notifyLinkPropertiesChanged(lp);
1262         verify(mHttpConnection, after(100).never()).getResponseCode();
1263         verify(mHttpsConnection, never()).getResponseCode();
1264         verify(mCapportApiConnection, never()).getResponseCode();
1265 
1266         setValidProbes();
1267         setApiContent(mCapportApiConnection, "{'captive': true, "
1268                 + "'user-portal-url': '" + TEST_LOGIN_URL + "'}");
1269 
1270         // After notifyNetworkConnected, validation uses the capport API contents
1271         nm.notifyNetworkConnected(lp, CELL_METERED_CAPABILITIES);
1272         verifyNetworkTested(VALIDATION_RESULT_PORTAL, 0 /* probesSucceeded */, TEST_LOGIN_URL);
1273 
1274         verify(mHttpConnection, never()).getResponseCode();
1275         verify(mCapportApiConnection).getResponseCode();
1276     }
1277 
1278     @Test
1279     public void testIsCaptivePortal_CapportApiNotPortalNotValidated() throws Exception {
1280         assumeTrue(CaptivePortalDataShimImpl.isSupported());
1281         setSslException(mHttpsConnection);
1282         setStatus(mHttpConnection, 500);
1283         setApiContent(mCapportApiConnection, "{'captive': false,"
1284                 + "'venue-info-url': '" + TEST_VENUE_INFO_URL + "'}");
1285         runNetworkTest(makeCapportLPs(), CELL_METERED_CAPABILITIES, VALIDATION_RESULT_INVALID,
1286                 0 /* probesSucceeded */, null /* redirectUrl */);
1287 
1288         final ArgumentCaptor<CaptivePortalData> capportCaptor = ArgumentCaptor.forClass(
1289                 CaptivePortalData.class);
1290         verify(mCallbacks).notifyCaptivePortalDataChanged(capportCaptor.capture());
1291         assertEquals(Uri.parse(TEST_VENUE_INFO_URL), capportCaptor.getValue().getVenueInfoUrl());
1292     }
1293 
1294     @Test
1295     public void testIsCaptivePortal_CapportApiNotPortalPartial() throws Exception {
1296         assumeTrue(CaptivePortalDataShimImpl.isSupported());
1297         setSslException(mHttpsConnection);
1298         setStatus(mHttpConnection, 204);
1299         setApiContent(mCapportApiConnection, "{'captive': false,"
1300                 + "'venue-info-url': '" + TEST_VENUE_INFO_URL + "'}");
1301         runNetworkTest(makeCapportLPs(), CELL_METERED_CAPABILITIES,
1302                 NETWORK_VALIDATION_RESULT_PARTIAL,
1303                 NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTP,
1304                 null /* redirectUrl */);
1305 
1306         final ArgumentCaptor<CaptivePortalData> capportCaptor = ArgumentCaptor.forClass(
1307                 CaptivePortalData.class);
1308         verify(mCallbacks).notifyCaptivePortalDataChanged(capportCaptor.capture());
1309         assertEquals(Uri.parse(TEST_VENUE_INFO_URL), capportCaptor.getValue().getVenueInfoUrl());
1310     }
1311 
1312     @Test
1313     public void testIsCaptivePortal_CapportApiNotPortalValidated() throws Exception {
1314         assumeTrue(CaptivePortalDataShimImpl.isSupported());
1315         setStatus(mHttpsConnection, 204);
1316         setStatus(mHttpConnection, 204);
1317         setApiContent(mCapportApiConnection, "{'captive': false,"
1318                 + "'venue-info-url': '" + TEST_VENUE_INFO_URL + "'}");
1319         runNetworkTest(makeCapportLPs(), CELL_METERED_CAPABILITIES,
1320                 NETWORK_VALIDATION_RESULT_VALID,
1321                 NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTP
1322                         | NETWORK_VALIDATION_PROBE_HTTPS,
1323                 null /* redirectUrl */);
1324 
1325         final ArgumentCaptor<CaptivePortalData> capportCaptor = ArgumentCaptor.forClass(
1326                 CaptivePortalData.class);
1327         verify(mCallbacks).notifyCaptivePortalDataChanged(capportCaptor.capture());
1328         assertEquals(Uri.parse(TEST_VENUE_INFO_URL), capportCaptor.getValue().getVenueInfoUrl());
1329     }
1330 
1331     @Test
1332     public void testIsCaptivePortal_CapportApiInvalidContent() throws Exception {
1333         assumeTrue(CaptivePortalDataShimImpl.isSupported());
1334         setSslException(mHttpsConnection);
1335         setPortal302(mHttpConnection);
1336         setApiContent(mCapportApiConnection, "{SomeInvalidText");
1337         runNetworkTest(makeCapportLPs(), CELL_METERED_CAPABILITIES,
1338                 VALIDATION_RESULT_PORTAL, 0 /* probesSucceeded */,
1339                 TEST_LOGIN_URL);
1340 
1341         verify(mCallbacks, never()).notifyCaptivePortalDataChanged(any());
1342         verify(mHttpConnection).getResponseCode();
1343     }
1344 
1345     private void runCapportApiInvalidUrlTest(String url) throws Exception {
1346         assumeTrue(CaptivePortalDataShimImpl.isSupported());
1347         setSslException(mHttpsConnection);
1348         setPortal302(mHttpConnection);
1349         final LinkProperties lp = new LinkProperties(TEST_LINK_PROPERTIES);
1350         lp.setCaptivePortalApiUrl(Uri.parse(url));
1351         runNetworkTest(makeCapportLPs(), CELL_METERED_CAPABILITIES,
1352                 VALIDATION_RESULT_PORTAL, 0 /* probesSucceeded */,
1353                 TEST_LOGIN_URL);
1354 
1355         verify(mCallbacks, never()).notifyCaptivePortalDataChanged(any());
1356         verify(mCapportApiConnection, never()).getInputStream();
1357         verify(mHttpConnection).getResponseCode();
1358     }
1359 
1360     @Test
1361     public void testIsCaptivePortal_HttpIsInvalidCapportApiScheme() throws Exception {
1362         runCapportApiInvalidUrlTest("http://capport.example.com");
1363     }
1364 
1365     @Test
1366     public void testIsCaptivePortal_FileIsInvalidCapportApiScheme() throws Exception {
1367         runCapportApiInvalidUrlTest("file://localhost/myfile");
1368     }
1369 
1370     @Test
1371     public void testIsCaptivePortal_InvalidUrlFormat() throws Exception {
1372         runCapportApiInvalidUrlTest("ThisIsNotAValidUrl");
1373     }
1374 
1375     @Test @IgnoreUpTo(Build.VERSION_CODES.Q)
1376     public void testIsCaptivePortal_CapportApiNotSupported() throws Exception {
1377         // Test that on a R+ device, if NetworkStack was compiled without CaptivePortalData support
1378         // (built against Q), NetworkMonitor behaves as expected.
1379         assumeFalse(CaptivePortalDataShimImpl.isSupported());
1380         setSslException(mHttpsConnection);
1381         setPortal302(mHttpConnection);
1382         setApiContent(mCapportApiConnection, "{'captive': false,"
1383                 + "'venue-info-url': '" + TEST_VENUE_INFO_URL + "'}");
1384         runNetworkTest(makeCapportLPs(), CELL_METERED_CAPABILITIES, VALIDATION_RESULT_PORTAL,
1385                 0 /* probesSucceeded */,
1386                 TEST_LOGIN_URL);
1387 
1388         verify(mCallbacks, never()).notifyCaptivePortalDataChanged(any());
1389         verify(mHttpConnection).getResponseCode();
1390     }
1391 
1392     @Test
1393     public void testIsCaptivePortal_HttpsProbeMatchesFailRegex() throws Exception {
1394         setStatus(mHttpsConnection, 200);
1395         setStatus(mHttpConnection, 500);
1396         final String content = "test";
1397         doReturn(new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8)))
1398                 .when(mHttpsConnection).getInputStream();
1399         doReturn(Long.valueOf(content.length())).when(mHttpsConnection).getContentLengthLong();
1400         doReturn(1).when(mResources).getInteger(R.integer.config_min_matches_http_content_length);
1401         doReturn(10).when(mResources).getInteger(
1402                 R.integer.config_max_matches_http_content_length);
1403         doReturn("te.t").when(mResources).getString(
1404                 R.string.config_network_validation_failed_content_regexp);
1405         runFailedNetworkTest();
1406     }
1407 
1408     @Test
1409     public void testIsCaptivePortal_HttpProbeMatchesSuccessRegex() throws Exception {
1410         setStatus(mHttpsConnection, 500);
1411         setStatus(mHttpConnection, 200);
1412         final String content = "test";
1413         doReturn(new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8)))
1414                 .when(mHttpConnection).getInputStream();
1415         doReturn(Long.valueOf(content.length())).when(mHttpConnection).getContentLengthLong();
1416         doReturn(1).when(mResources).getInteger(R.integer.config_min_matches_http_content_length);
1417         doReturn(10).when(mResources).getInteger(
1418                 R.integer.config_max_matches_http_content_length);
1419         doReturn("te.t").when(mResources).getString(
1420                 R.string.config_network_validation_success_content_regexp);
1421         runPartialConnectivityNetworkTest(
1422                 NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTP);
1423     }
1424 
1425     private void setupFallbackSpec() throws IOException {
1426         setFallbackSpecs("http://example.com@@/@@204@@/@@"
1427                 + "@@,@@"
1428                 + TEST_OTHER_FALLBACK_URL + "@@/@@30[12]@@/@@https://(www\\.)?google.com/?.*");
1429 
1430         setSslException(mHttpsConnection);
1431         setStatus(mHttpConnection, 500);
1432 
1433         // Use the 2nd fallback spec
1434         when(mRandom.nextInt()).thenReturn(1);
1435     }
1436 
1437     @Test
1438     public void testIsCaptivePortal_FallbackSpecIsPartial() throws Exception {
1439         setupFallbackSpec();
1440         set302(mOtherFallbackConnection, "https://www.google.com/test?q=3");
1441 
1442         // HTTPS failed, fallback spec went through -> partial connectivity
1443         runPartialConnectivityNetworkTest(
1444                 NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_FALLBACK);
1445         verify(mOtherFallbackConnection, times(1)).getResponseCode();
1446         verify(mFallbackConnection, never()).getResponseCode();
1447     }
1448 
1449     @Test
1450     public void testIsCaptivePortal_FallbackSpecIsPortal() throws Exception {
1451         setupFallbackSpec();
1452         setPortal302(mOtherFallbackConnection);
1453         runPortalNetworkTest();
1454     }
1455 
1456     @Test
1457     public void testIsCaptivePortal_IgnorePortals() throws Exception {
1458         setCaptivePortalMode(Settings.Global.CAPTIVE_PORTAL_MODE_IGNORE);
1459         setSslException(mHttpsConnection);
1460         setPortal302(mHttpConnection);
1461 
1462         runNoValidationNetworkTest();
1463     }
1464 
1465     @Test
1466     public void testIsCaptivePortal_OverriddenHttpsUrlValid() throws Exception {
1467         setDeviceConfig(TEST_URL_EXPIRATION_TIME,
1468                 String.valueOf(currentTimeMillis() + TimeUnit.MINUTES.toMillis(9)));
1469         setDeviceConfig(TEST_CAPTIVE_PORTAL_HTTPS_URL, TEST_OVERRIDE_URL);
1470         setStatus(mTestOverriddenUrlConnection, 204);
1471         setStatus(mHttpConnection, 204);
1472 
1473         runValidatedNetworkTest();
1474         verify(mHttpsConnection, never()).getResponseCode();
1475         verify(mTestOverriddenUrlConnection).getResponseCode();
1476     }
1477 
1478     @Test
1479     public void testIsCaptivePortal_OverriddenHttpUrlPortal() throws Exception {
1480         setDeviceConfig(TEST_URL_EXPIRATION_TIME,
1481                 String.valueOf(currentTimeMillis() + TimeUnit.MINUTES.toMillis(9)));
1482         setDeviceConfig(TEST_CAPTIVE_PORTAL_HTTP_URL, TEST_OVERRIDE_URL);
1483         setStatus(mHttpsConnection, 500);
1484         setPortal302(mTestOverriddenUrlConnection);
1485 
1486         runPortalNetworkTest();
1487         verify(mHttpConnection, never()).getResponseCode();
1488         verify(mTestOverriddenUrlConnection).getResponseCode();
1489     }
1490 
1491     @Test
1492     public void testIsCaptivePortal_InvalidHttpOverrideUrl() throws Exception {
1493         setDeviceConfig(TEST_URL_EXPIRATION_TIME,
1494                 String.valueOf(currentTimeMillis() + TimeUnit.MINUTES.toMillis(9)));
1495         setDeviceConfig(TEST_CAPTIVE_PORTAL_HTTP_URL, TEST_INVALID_OVERRIDE_URL);
1496         setStatus(mHttpsConnection, 500);
1497         setPortal302(mHttpConnection);
1498 
1499         runPortalNetworkTest();
1500         verify(mTestOverriddenUrlConnection, never()).getResponseCode();
1501         verify(mHttpConnection).getResponseCode();
1502     }
1503 
1504     @Test
1505     public void testIsCaptivePortal_InvalidHttpsOverrideUrl() throws Exception {
1506         setDeviceConfig(TEST_URL_EXPIRATION_TIME,
1507                 String.valueOf(currentTimeMillis() + TimeUnit.MINUTES.toMillis(9)));
1508         setDeviceConfig(TEST_CAPTIVE_PORTAL_HTTPS_URL, TEST_INVALID_OVERRIDE_URL);
1509         setStatus(mHttpsConnection, 204);
1510         setStatus(mHttpConnection, 204);
1511 
1512         runValidatedNetworkTest();
1513         verify(mTestOverriddenUrlConnection, never()).getResponseCode();
1514         verify(mHttpsConnection).getResponseCode();
1515     }
1516 
1517     @Test
1518     public void testIsCaptivePortal_ExpiredHttpsOverrideUrl() throws Exception {
1519         setDeviceConfig(TEST_URL_EXPIRATION_TIME,
1520                 String.valueOf(currentTimeMillis() - TimeUnit.MINUTES.toMillis(1)));
1521         setDeviceConfig(TEST_CAPTIVE_PORTAL_HTTPS_URL, TEST_OVERRIDE_URL);
1522         setStatus(mHttpsConnection, 204);
1523         setStatus(mHttpConnection, 204);
1524 
1525         runValidatedNetworkTest();
1526         verify(mTestOverriddenUrlConnection, never()).getResponseCode();
1527         verify(mHttpsConnection).getResponseCode();
1528     }
1529 
1530     @Test
1531     public void testIsCaptivePortal_TestHttpUrlExpirationTooLarge() throws Exception {
1532         setDeviceConfig(TEST_URL_EXPIRATION_TIME,
1533                 String.valueOf(currentTimeMillis() + TimeUnit.MINUTES.toMillis(20)));
1534         setDeviceConfig(TEST_CAPTIVE_PORTAL_HTTP_URL, TEST_OVERRIDE_URL);
1535         setStatus(mHttpsConnection, 500);
1536         setPortal302(mHttpConnection);
1537 
1538         runPortalNetworkTest();
1539         verify(mTestOverriddenUrlConnection, never()).getResponseCode();
1540         verify(mHttpConnection).getResponseCode();
1541     }
1542 
1543     @Test
1544     public void testIsDataStall_EvaluationDisabled() {
1545         setDataStallEvaluationType(0);
1546         WrappedNetworkMonitor wrappedMonitor = makeCellMeteredNetworkMonitor();
1547         wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 100);
1548         assertFalse(wrappedMonitor.isDataStall());
1549     }
1550 
1551     @Test
1552     public void testIsDataStall_EvaluationDnsOnNotMeteredNetwork() throws Exception {
1553         WrappedNetworkMonitor wrappedMonitor = makeCellNotMeteredNetworkMonitor();
1554         wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 100);
1555         makeDnsTimeoutEvent(wrappedMonitor, DEFAULT_DNS_TIMEOUT_THRESHOLD);
1556         assertTrue(wrappedMonitor.isDataStall());
1557         verify(mCallbacks).notifyDataStallSuspected(
1558                 matchDnsDataStallParcelable(DEFAULT_DNS_TIMEOUT_THRESHOLD));
1559     }
1560 
1561     @Test
1562     public void testIsDataStall_EvaluationDnsOnMeteredNetwork() throws Exception {
1563         WrappedNetworkMonitor wrappedMonitor = makeCellMeteredNetworkMonitor();
1564         wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 100);
1565         assertFalse(wrappedMonitor.isDataStall());
1566 
1567         wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 1000);
1568         makeDnsTimeoutEvent(wrappedMonitor, DEFAULT_DNS_TIMEOUT_THRESHOLD);
1569         assertTrue(wrappedMonitor.isDataStall());
1570         verify(mCallbacks).notifyDataStallSuspected(
1571                 matchDnsDataStallParcelable(DEFAULT_DNS_TIMEOUT_THRESHOLD));
1572     }
1573 
1574     @Test
1575     public void testIsDataStall_EvaluationDnsWithDnsTimeoutCount() throws Exception {
1576         WrappedNetworkMonitor wrappedMonitor = makeCellMeteredNetworkMonitor();
1577         wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 1000);
1578         makeDnsTimeoutEvent(wrappedMonitor, 3);
1579         assertFalse(wrappedMonitor.isDataStall());
1580         // Reset consecutive timeout counts.
1581         makeDnsSuccessEvent(wrappedMonitor, 1);
1582         makeDnsTimeoutEvent(wrappedMonitor, 2);
1583         assertFalse(wrappedMonitor.isDataStall());
1584 
1585         makeDnsTimeoutEvent(wrappedMonitor, 3);
1586         assertTrue(wrappedMonitor.isDataStall());
1587 
1588         // The expected timeout count is the previous 2 DNS timeouts + the most recent 3 timeouts
1589         verify(mCallbacks).notifyDataStallSuspected(
1590                 matchDnsDataStallParcelable(5 /* timeoutCount */));
1591 
1592         // Set the value to larger than the default dns log size.
1593         setConsecutiveDnsTimeoutThreshold(51);
1594         wrappedMonitor = makeCellMeteredNetworkMonitor();
1595         wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 1000);
1596         makeDnsTimeoutEvent(wrappedMonitor, 50);
1597         assertFalse(wrappedMonitor.isDataStall());
1598 
1599         makeDnsTimeoutEvent(wrappedMonitor, 1);
1600         assertTrue(wrappedMonitor.isDataStall());
1601 
1602         // The expected timeout count is the previous 50 DNS timeouts + the most recent timeout
1603         verify(mCallbacks).notifyDataStallSuspected(
1604                 matchDnsDataStallParcelable(51 /* timeoutCount */));
1605     }
1606 
1607     @Test
1608     public void testIsDataStall_SkipEvaluateOnValidationNotRequiredNetwork() {
1609         // Make DNS and TCP stall condition satisfied.
1610         setDataStallEvaluationType(DATA_STALL_EVALUATION_TYPE_DNS | DATA_STALL_EVALUATION_TYPE_TCP);
1611         when(mTstDependencies.isTcpInfoParsingSupported()).thenReturn(true);
1612         when(mTst.getLatestReceivedCount()).thenReturn(0);
1613         when(mTst.isDataStallSuspected()).thenReturn(true);
1614         final WrappedNetworkMonitor nm = makeMonitor(CELL_NO_INTERNET_CAPABILITIES);
1615         nm.setLastProbeTime(SystemClock.elapsedRealtime() - 1000);
1616         makeDnsTimeoutEvent(nm, DEFAULT_DNS_TIMEOUT_THRESHOLD);
1617         assertFalse(nm.isDataStall());
1618     }
1619 
1620     @Test
1621     public void testIsDataStall_EvaluationDnsWithDnsTimeThreshold() throws Exception {
1622         // Test dns events happened in valid dns time threshold.
1623         WrappedNetworkMonitor wrappedMonitor = makeCellMeteredNetworkMonitor();
1624         wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 100);
1625         makeDnsTimeoutEvent(wrappedMonitor, DEFAULT_DNS_TIMEOUT_THRESHOLD);
1626         assertFalse(wrappedMonitor.isDataStall());
1627         wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 1000);
1628         assertTrue(wrappedMonitor.isDataStall());
1629         verify(mCallbacks).notifyDataStallSuspected(
1630                 matchDnsDataStallParcelable(DEFAULT_DNS_TIMEOUT_THRESHOLD));
1631 
1632         // Test dns events happened before valid dns time threshold.
1633         setValidDataStallDnsTimeThreshold(0);
1634         wrappedMonitor = makeCellMeteredNetworkMonitor();
1635         wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 100);
1636         makeDnsTimeoutEvent(wrappedMonitor, DEFAULT_DNS_TIMEOUT_THRESHOLD);
1637         assertFalse(wrappedMonitor.isDataStall());
1638         wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 1000);
1639         assertFalse(wrappedMonitor.isDataStall());
1640     }
1641 
1642     @Test
1643     public void testIsDataStall_EvaluationTcp() throws Exception {
1644         // Evaluate TCP only. Expect ignoring DNS signal.
1645         setDataStallEvaluationType(DATA_STALL_EVALUATION_TYPE_TCP);
1646         WrappedNetworkMonitor wrappedMonitor = makeMonitor(CELL_METERED_CAPABILITIES);
1647         assertFalse(wrappedMonitor.isDataStall());
1648         // Packet received.
1649         when(mTstDependencies.isTcpInfoParsingSupported()).thenReturn(true);
1650         when(mTst.getLatestReceivedCount()).thenReturn(5);
1651         // Trigger a tcp event immediately.
1652         setTcpPollingInterval(0);
1653         wrappedMonitor.sendTcpPollingEvent();
1654         HandlerUtilsKt.waitForIdle(wrappedMonitor.getHandler(), HANDLER_TIMEOUT_MS);
1655         assertFalse(wrappedMonitor.isDataStall());
1656 
1657         when(mTst.getLatestReceivedCount()).thenReturn(0);
1658         when(mTst.isDataStallSuspected()).thenReturn(true);
1659         // Trigger a tcp event immediately.
1660         setTcpPollingInterval(0);
1661         wrappedMonitor.sendTcpPollingEvent();
1662         HandlerUtilsKt.waitForIdle(wrappedMonitor.getHandler(), HANDLER_TIMEOUT_MS);
1663         assertTrue(wrappedMonitor.isDataStall());
1664         verify(mCallbacks).notifyDataStallSuspected(matchTcpDataStallParcelable());
1665     }
1666 
1667     @Test
1668     public void testIsDataStall_EvaluationDnsAndTcp() throws Exception {
1669         setDataStallEvaluationType(DATA_STALL_EVALUATION_TYPE_DNS | DATA_STALL_EVALUATION_TYPE_TCP);
1670         setupTcpDataStall();
1671         final WrappedNetworkMonitor nm = makeMonitor(CELL_METERED_CAPABILITIES);
1672         nm.setLastProbeTime(SystemClock.elapsedRealtime() - 1000);
1673         makeDnsTimeoutEvent(nm, DEFAULT_DNS_TIMEOUT_THRESHOLD);
1674         assertTrue(nm.isDataStall());
1675         verify(mCallbacks).notifyDataStallSuspected(
1676                 matchDnsAndTcpDataStallParcelable(DEFAULT_DNS_TIMEOUT_THRESHOLD));
1677 
1678         when(mTst.getLatestReceivedCount()).thenReturn(5);
1679         // Trigger a tcp event immediately.
1680         setTcpPollingInterval(0);
1681         nm.sendTcpPollingEvent();
1682         HandlerUtilsKt.waitForIdle(nm.getHandler(), HANDLER_TIMEOUT_MS);
1683         assertFalse(nm.isDataStall());
1684     }
1685 
1686     @Test
1687     public void testIsDataStall_DisableTcp() {
1688         // Disable tcp detection with only DNS detect. keep the tcp signal but set to no DNS signal.
1689         setDataStallEvaluationType(DATA_STALL_EVALUATION_TYPE_DNS);
1690         WrappedNetworkMonitor wrappedMonitor = makeMonitor(CELL_METERED_CAPABILITIES);
1691         makeDnsSuccessEvent(wrappedMonitor, 1);
1692         wrappedMonitor.sendTcpPollingEvent();
1693         HandlerUtilsKt.waitForIdle(wrappedMonitor.getHandler(), HANDLER_TIMEOUT_MS);
1694         assertFalse(wrappedMonitor.isDataStall());
1695         verify(mTst, never()).isDataStallSuspected();
1696         verify(mTst, never()).pollSocketsInfo();
1697     }
1698 
1699     @Test
1700     public void testBrokenNetworkNotValidated() throws Exception {
1701         setSslException(mHttpsConnection);
1702         setStatus(mHttpConnection, 500);
1703         setStatus(mFallbackConnection, 404);
1704 
1705         runFailedNetworkTest();
1706     }
1707 
1708     @Test
1709     public void testNoInternetCapabilityValidated() throws Exception {
1710         runNetworkTest(TEST_LINK_PROPERTIES, CELL_NO_INTERNET_CAPABILITIES,
1711                 NETWORK_VALIDATION_RESULT_VALID, 0 /* probesSucceeded */, null /* redirectUrl */);
1712         verify(mCleartextDnsNetwork, never()).openConnection(any());
1713     }
1714 
1715     @Test
1716     public void testLaunchCaptivePortalApp() throws Exception {
1717         setSslException(mHttpsConnection);
1718         setPortal302(mHttpConnection);
1719         when(mHttpConnection.getHeaderField(eq("location"))).thenReturn(TEST_LOGIN_URL);
1720         final NetworkMonitor nm = makeMonitor(CELL_METERED_CAPABILITIES);
1721         notifyNetworkConnected(nm, CELL_METERED_CAPABILITIES);
1722 
1723         verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
1724                 .showProvisioningNotification(any(), any());
1725 
1726         assertEquals(1, mRegisteredReceivers.size());
1727 
1728         // Check that startCaptivePortalApp sends the expected intent.
1729         nm.launchCaptivePortalApp();
1730 
1731         final ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
1732         final ArgumentCaptor<Network> networkCaptor = ArgumentCaptor.forClass(Network.class);
1733         verify(mCm, timeout(HANDLER_TIMEOUT_MS).times(1))
1734                 .startCaptivePortalApp(networkCaptor.capture(), bundleCaptor.capture());
1735         verify(mNotifier).notifyCaptivePortalValidationPending(networkCaptor.getValue());
1736         final Bundle bundle = bundleCaptor.getValue();
1737         final Network bundleNetwork = bundle.getParcelable(ConnectivityManager.EXTRA_NETWORK);
1738         assertEquals(TEST_NETID, bundleNetwork.netId);
1739         // network is passed both in bundle and as parameter, as the bundle is opaque to the
1740         // framework and only intended for the captive portal app, but the framework needs
1741         // the network to identify the right NetworkMonitor.
1742         assertEquals(TEST_NETID, networkCaptor.getValue().netId);
1743         // Portal URL should be detection URL.
1744         final String redirectUrl = bundle.getString(ConnectivityManager.EXTRA_CAPTIVE_PORTAL_URL);
1745         assertEquals(TEST_HTTP_URL, redirectUrl);
1746 
1747         // Have the app report that the captive portal is dismissed, and check that we revalidate.
1748         setStatus(mHttpsConnection, 204);
1749         setStatus(mHttpConnection, 204);
1750 
1751         resetCallbacks();
1752         nm.notifyCaptivePortalAppFinished(APP_RETURN_DISMISSED);
1753         verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).atLeastOnce())
1754                 .notifyNetworkTestedWithExtras(matchNetworkTestResultParcelable(
1755                         NETWORK_VALIDATION_RESULT_VALID,
1756                         NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTP));
1757         assertEquals(0, mRegisteredReceivers.size());
1758     }
1759 
1760     @Test
1761     public void testPrivateDnsSuccess() throws Exception {
1762         setStatus(mHttpsConnection, 204);
1763         setStatus(mHttpConnection, 204);
1764 
1765         // Verify dns query only get v6 address.
1766         mFakeDns.setAnswer("dns6.google", new String[]{"2001:db8::53"}, TYPE_AAAA);
1767         WrappedNetworkMonitor wnm = makeCellNotMeteredNetworkMonitor();
1768         wnm.notifyPrivateDnsSettingsChanged(new PrivateDnsConfig("dns6.google",
1769                 new InetAddress[0]));
1770         notifyNetworkConnected(wnm, CELL_NOT_METERED_CAPABILITIES);
1771         verifyNetworkTested(NETWORK_VALIDATION_RESULT_VALID, PROBES_PRIVDNS_VALID);
1772         verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1)).notifyProbeStatusChanged(
1773                 eq(PROBES_PRIVDNS_VALID), eq(PROBES_PRIVDNS_VALID));
1774 
1775         // Verify dns query only get v4 address.
1776         resetCallbacks();
1777         mFakeDns.setAnswer("dns4.google", new String[]{"192.0.2.1"}, TYPE_A);
1778         wnm.notifyPrivateDnsSettingsChanged(new PrivateDnsConfig("dns4.google",
1779                 new InetAddress[0]));
1780         verifyNetworkTested(NETWORK_VALIDATION_RESULT_VALID, PROBES_PRIVDNS_VALID);
1781         // NetworkMonitor will check if the probes has changed or not, if the probes has not
1782         // changed, the callback won't be fired.
1783         verify(mCallbacks, never()).notifyProbeStatusChanged(
1784                 eq(PROBES_PRIVDNS_VALID), eq(PROBES_PRIVDNS_VALID));
1785 
1786         // Verify dns query get both v4 and v6 address.
1787         resetCallbacks();
1788         mFakeDns.setAnswer("dns.google", new String[]{"2001:db8::54"}, TYPE_AAAA);
1789         mFakeDns.setAnswer("dns.google", new String[]{"192.0.2.3"}, TYPE_A);
1790         wnm.notifyPrivateDnsSettingsChanged(new PrivateDnsConfig("dns.google", new InetAddress[0]));
1791         verifyNetworkTested(NETWORK_VALIDATION_RESULT_VALID, PROBES_PRIVDNS_VALID);
1792         verify(mCallbacks, never()).notifyProbeStatusChanged(
1793                 eq(PROBES_PRIVDNS_VALID), eq(PROBES_PRIVDNS_VALID));
1794     }
1795 
1796     @Test
1797     public void testProbeStatusChanged() throws Exception {
1798         // Set no record in FakeDns and expect validation to fail.
1799         setStatus(mHttpsConnection, 204);
1800         setStatus(mHttpConnection, 204);
1801 
1802         WrappedNetworkMonitor wnm = makeCellNotMeteredNetworkMonitor();
1803         wnm.notifyPrivateDnsSettingsChanged(new PrivateDnsConfig("dns.google", new InetAddress[0]));
1804         wnm.notifyNetworkConnected(TEST_LINK_PROPERTIES, CELL_NOT_METERED_CAPABILITIES);
1805         verifyNetworkTested(VALIDATION_RESULT_INVALID,
1806                 NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTPS);
1807         verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS)).notifyProbeStatusChanged(
1808                 eq(PROBES_PRIVDNS_VALID), eq(NETWORK_VALIDATION_PROBE_DNS
1809                 | NETWORK_VALIDATION_PROBE_HTTPS));
1810         // Fix DNS and retry, expect validation to succeed.
1811         resetCallbacks();
1812         mFakeDns.setAnswer("dns.google", new String[]{"2001:db8::1"}, TYPE_AAAA);
1813 
1814         wnm.forceReevaluation(Process.myUid());
1815         // ProbeCompleted should be reset to 0
1816         HandlerUtilsKt.waitForIdle(wnm.getHandler(), HANDLER_TIMEOUT_MS);
1817         assertEquals(wnm.getEvaluationState().getProbeCompletedResult(), 0);
1818         verifyNetworkTested(NETWORK_VALIDATION_RESULT_VALID, PROBES_PRIVDNS_VALID);
1819         verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS)).notifyProbeStatusChanged(
1820                 eq(PROBES_PRIVDNS_VALID), eq(PROBES_PRIVDNS_VALID));
1821     }
1822 
1823     @Test
1824     public void testPrivateDnsResolutionRetryUpdate() throws Exception {
1825         // Set no record in FakeDns and expect validation to fail.
1826         setStatus(mHttpsConnection, 204);
1827         setStatus(mHttpConnection, 204);
1828 
1829         WrappedNetworkMonitor wnm = makeCellNotMeteredNetworkMonitor();
1830         wnm.notifyPrivateDnsSettingsChanged(new PrivateDnsConfig("dns.google", new InetAddress[0]));
1831         wnm.notifyNetworkConnected(TEST_LINK_PROPERTIES, CELL_NOT_METERED_CAPABILITIES);
1832         verifyNetworkTested(VALIDATION_RESULT_INVALID,
1833                 NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTPS);
1834         verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1)).notifyProbeStatusChanged(
1835                 eq(PROBES_PRIVDNS_VALID), eq(NETWORK_VALIDATION_PROBE_DNS
1836                 | NETWORK_VALIDATION_PROBE_HTTPS));
1837 
1838         // Fix DNS and retry, expect validation to succeed.
1839         resetCallbacks();
1840         mFakeDns.setAnswer("dns.google", new String[]{"2001:db8::1"}, TYPE_AAAA);
1841 
1842         wnm.forceReevaluation(Process.myUid());
1843         verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).atLeastOnce())
1844                 .notifyNetworkTestedWithExtras(matchNetworkTestResultParcelable(
1845                         NETWORK_VALIDATION_RESULT_VALID, PROBES_PRIVDNS_VALID));
1846         verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1)).notifyProbeStatusChanged(
1847                 eq(PROBES_PRIVDNS_VALID), eq(PROBES_PRIVDNS_VALID));
1848 
1849         // Change configuration to an invalid DNS name, expect validation to fail.
1850         resetCallbacks();
1851         mFakeDns.setAnswer("dns.bad", new String[0], TYPE_A);
1852         mFakeDns.setAnswer("dns.bad", new String[0], TYPE_AAAA);
1853         wnm.notifyPrivateDnsSettingsChanged(new PrivateDnsConfig("dns.bad", new InetAddress[0]));
1854         // Strict mode hostname resolve fail. Expect only notification for evaluation fail. No probe
1855         // notification.
1856         verifyNetworkTested(VALIDATION_RESULT_INVALID,
1857                 NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTPS);
1858         verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1)).notifyProbeStatusChanged(
1859                 eq(PROBES_PRIVDNS_VALID), eq(NETWORK_VALIDATION_PROBE_DNS
1860                 | NETWORK_VALIDATION_PROBE_HTTPS));
1861 
1862         // Change configuration back to working again, but make private DNS not work.
1863         // Expect validation to fail.
1864         resetCallbacks();
1865         mFakeDns.setNonBypassPrivateDnsWorking(false);
1866         wnm.notifyPrivateDnsSettingsChanged(new PrivateDnsConfig("dns.google",
1867                 new InetAddress[0]));
1868         verifyNetworkTested(VALIDATION_RESULT_INVALID,
1869                 NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTPS);
1870         // NetworkMonitor will check if the probes has changed or not, if the probes has not
1871         // changed, the callback won't be fired.
1872         verify(mCallbacks, never()).notifyProbeStatusChanged(
1873                 eq(PROBES_PRIVDNS_VALID), eq(NETWORK_VALIDATION_PROBE_DNS
1874                 | NETWORK_VALIDATION_PROBE_HTTPS));
1875 
1876         // Make private DNS work again. Expect validation to succeed.
1877         resetCallbacks();
1878         mFakeDns.setNonBypassPrivateDnsWorking(true);
1879         wnm.forceReevaluation(Process.myUid());
1880         verifyNetworkTested(NETWORK_VALIDATION_RESULT_VALID, PROBES_PRIVDNS_VALID);
1881         verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1)).notifyProbeStatusChanged(
1882                 eq(PROBES_PRIVDNS_VALID), eq(PROBES_PRIVDNS_VALID));
1883     }
1884 
1885     @Test
1886     public void testDataStall_StallDnsSuspectedAndSendMetricsOnCell() throws Exception {
1887         testDataStall_StallDnsSuspectedAndSendMetrics(NetworkCapabilities.TRANSPORT_CELLULAR,
1888                 CELL_METERED_CAPABILITIES);
1889     }
1890 
1891     @Test
1892     public void testDataStall_StallDnsSuspectedAndSendMetricsOnWifi() throws Exception {
1893         testDataStall_StallDnsSuspectedAndSendMetrics(NetworkCapabilities.TRANSPORT_WIFI,
1894                 WIFI_NOT_METERED_CAPABILITIES);
1895     }
1896 
1897     private void testDataStall_StallDnsSuspectedAndSendMetrics(int transport,
1898             NetworkCapabilities nc) throws Exception {
1899         // NM suspects data stall from DNS signal and sends data stall metrics.
1900         final WrappedNetworkMonitor nm = prepareNetworkMonitorForVerifyDataStall(nc);
1901         makeDnsTimeoutEvent(nm, 5);
1902         // Trigger a dns signal to start evaluate data stall and upload metrics.
1903         nm.notifyDnsResponse(RETURN_CODE_DNS_TIMEOUT);
1904         // Verify data sent as expected.
1905         verifySendDataStallDetectionStats(nm, DATA_STALL_EVALUATION_TYPE_DNS, transport);
1906     }
1907 
1908     @Test
1909     public void testDataStall_NoStallSuspectedAndSendMetrics() throws Exception {
1910         final WrappedNetworkMonitor nm = prepareNetworkMonitorForVerifyDataStall(
1911                 CELL_METERED_CAPABILITIES);
1912         // Setup no data stall dns signal.
1913         makeDnsTimeoutEvent(nm, 3);
1914         assertFalse(nm.isDataStall());
1915         // Trigger a dns signal to start evaluate data stall.
1916         nm.notifyDnsResponse(RETURN_CODE_DNS_SUCCESS);
1917         verify(mDependencies, never()).writeDataStallDetectionStats(any(), any());
1918     }
1919 
1920     @Test
1921     public void testDataStall_StallTcpSuspectedAndSendMetricsOnCell() throws Exception {
1922         testDataStall_StallTcpSuspectedAndSendMetrics(CELL_METERED_CAPABILITIES);
1923     }
1924 
1925     @Test
1926     public void testDataStall_StallTcpSuspectedAndSendMetricsOnWifi() throws Exception {
1927         testDataStall_StallTcpSuspectedAndSendMetrics(WIFI_NOT_METERED_CAPABILITIES);
1928     }
1929 
1930     private void testDataStall_StallTcpSuspectedAndSendMetrics(NetworkCapabilities nc)
1931             throws Exception {
1932         assumeTrue(ShimUtils.isReleaseOrDevelopmentApiAbove(Build.VERSION_CODES.Q));
1933         setupTcpDataStall();
1934         setTcpPollingInterval(0);
1935         // NM suspects data stall from TCP signal and sends data stall metrics.
1936         setDataStallEvaluationType(DATA_STALL_EVALUATION_TYPE_TCP);
1937         final WrappedNetworkMonitor nm = prepareNetworkMonitorForVerifyDataStall(nc);
1938         // Trigger a tcp event immediately.
1939         nm.sendTcpPollingEvent();
1940         // Allow only one transport type in the context of this test for simplification.
1941         final int[] transports = nc.getTransportTypes();
1942         assertEquals(1, transports.length);
1943         verifySendDataStallDetectionStats(nm, DATA_STALL_EVALUATION_TYPE_TCP, transports[0]);
1944     }
1945 
1946     private WrappedNetworkMonitor prepareNetworkMonitorForVerifyDataStall(NetworkCapabilities nc)
1947             throws Exception {
1948         // Connect a VALID network to simulate the data stall detection because data stall
1949         // evaluation will only start from validated state.
1950         setStatus(mHttpsConnection, 204);
1951         final WrappedNetworkMonitor nm;
1952         // Allow only one transport type in the context of this test for simplification.
1953         if (nc.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
1954             nm = makeCellMeteredNetworkMonitor();
1955         } else if (nc.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
1956             nm = makeWifiNotMeteredNetworkMonitor();
1957             setupTestWifiInfo();
1958         } else {
1959             nm = null;
1960             fail("Undefined transport type");
1961         }
1962         nm.notifyNetworkConnected(TEST_LINK_PROPERTIES, nc);
1963         verifyNetworkTested(NETWORK_VALIDATION_RESULT_VALID,
1964                 NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTPS);
1965         nm.setLastProbeTime(SystemClock.elapsedRealtime() - 1000);
1966         return nm;
1967     }
1968 
1969     private void setupTcpDataStall() {
1970         when(mTstDependencies.isTcpInfoParsingSupported()).thenReturn(true);
1971         when(mTst.getLatestReceivedCount()).thenReturn(0);
1972         when(mTst.getLatestPacketFailPercentage()).thenReturn(TEST_TCP_FAIL_RATE);
1973         when(mTst.getSentSinceLastRecv()).thenReturn(TEST_TCP_PACKET_COUNT);
1974         when(mTst.isDataStallSuspected()).thenReturn(true);
1975         when(mTst.pollSocketsInfo()).thenReturn(true);
1976     }
1977 
1978     private void verifySendDataStallDetectionStats(WrappedNetworkMonitor nm, int evalType,
1979             int transport) {
1980         // Verify data sent as expectated.
1981         final ArgumentCaptor<CaptivePortalProbeResult> probeResultCaptor =
1982                 ArgumentCaptor.forClass(CaptivePortalProbeResult.class);
1983         final ArgumentCaptor<DataStallDetectionStats> statsCaptor =
1984                 ArgumentCaptor.forClass(DataStallDetectionStats.class);
1985         verify(mDependencies, timeout(HANDLER_TIMEOUT_MS).times(1))
1986                 .writeDataStallDetectionStats(statsCaptor.capture(), probeResultCaptor.capture());
1987         assertTrue(nm.isDataStall());
1988         assertTrue(probeResultCaptor.getValue().isSuccessful());
1989         verifyTestDataStallDetectionStats(evalType, transport, statsCaptor.getValue());
1990     }
1991 
1992     private void verifyTestDataStallDetectionStats(int evalType, int transport,
1993             DataStallDetectionStats stats) {
1994         assertEquals(transport, stats.mNetworkType);
1995         switch (transport) {
1996             case NetworkCapabilities.TRANSPORT_WIFI:
1997                 assertArrayEquals(makeTestWifiDataNano(), stats.mWifiInfo);
1998                 // Expedient way to check stats.mCellularInfo contains the neutral byte array that
1999                 // is sent to represent a lack of data, as stats.mCellularInfo is not supposed to
2000                 // contain null.
2001                 assertArrayEquals(DataStallDetectionStats.emptyCellDataIfNull(null),
2002                         stats.mCellularInfo);
2003                 break;
2004             case NetworkCapabilities.TRANSPORT_CELLULAR:
2005                 // Expedient way to check stats.mWifiInfo contains the neutral byte array that is
2006                 // sent to represent a lack of data, as stats.mWifiInfo is not supposed to contain
2007                 // null.
2008                 assertArrayEquals(DataStallDetectionStats.emptyWifiInfoIfNull(null),
2009                         stats.mWifiInfo);
2010                 assertArrayEquals(makeTestCellDataNano(), stats.mCellularInfo);
2011                 break;
2012             default:
2013                 // Add other cases.
2014                 fail("Unexpected transport type");
2015         }
2016 
2017         assertEquals(evalType, stats.mEvaluationType);
2018         if ((evalType & DATA_STALL_EVALUATION_TYPE_TCP) != 0) {
2019             assertEquals(TEST_TCP_FAIL_RATE, stats.mTcpFailRate);
2020             assertEquals(TEST_TCP_PACKET_COUNT, stats.mTcpSentSinceLastRecv);
2021         } else {
2022             assertEquals(DataStallDetectionStats.UNSPECIFIED_TCP_FAIL_RATE, stats.mTcpFailRate);
2023             assertEquals(DataStallDetectionStats.UNSPECIFIED_TCP_PACKETS_COUNT,
2024                     stats.mTcpSentSinceLastRecv);
2025         }
2026 
2027         if ((evalType & DATA_STALL_EVALUATION_TYPE_DNS) != 0) {
2028             assertArrayEquals(stats.mDns, makeTestDnsTimeoutNano(DEFAULT_DNS_TIMEOUT_THRESHOLD));
2029         } else {
2030             assertArrayEquals(stats.mDns, makeTestDnsTimeoutNano(0 /* times */));
2031         }
2032     }
2033 
2034     private DataStallDetectionStats makeTestDataStallDetectionStats(int evaluationType,
2035             int transportType) {
2036         final DataStallDetectionStats.Builder stats = new DataStallDetectionStats.Builder()
2037                 .setEvaluationType(evaluationType)
2038                 .setNetworkType(transportType);
2039         switch (transportType) {
2040             case NetworkCapabilities.TRANSPORT_CELLULAR:
2041                 stats.setCellData(TelephonyManager.NETWORK_TYPE_LTE /* radioType */,
2042                         true /* roaming */,
2043                         TEST_MCCMNC /* networkMccmnc */,
2044                         TEST_MCCMNC /* simMccmnc */,
2045                         CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN /* signalStrength */);
2046                 break;
2047             case NetworkCapabilities.TRANSPORT_WIFI:
2048                 setupTestWifiInfo();
2049                 stats.setWiFiData(mWifiInfo);
2050                 break;
2051             default:
2052                 break;
2053         }
2054 
2055         if ((evaluationType & DATA_STALL_EVALUATION_TYPE_TCP) != 0) {
2056             generateTestTcpStats(stats);
2057         }
2058 
2059         if ((evaluationType & DATA_STALL_EVALUATION_TYPE_DNS) != 0) {
2060             generateTimeoutDnsEvent(stats, DEFAULT_DNS_TIMEOUT_THRESHOLD);
2061         }
2062 
2063         return stats.build();
2064     }
2065 
2066     private byte[] makeTestDnsTimeoutNano(int timeoutCount) {
2067         // Make a expected nano dns message.
2068         final DnsEvent event = new DnsEvent();
2069         event.dnsReturnCode = new int[timeoutCount];
2070         event.dnsTime = new long[timeoutCount];
2071         Arrays.fill(event.dnsReturnCode, RETURN_CODE_DNS_TIMEOUT);
2072         Arrays.fill(event.dnsTime, TEST_ELAPSED_TIME_MS);
2073         return MessageNano.toByteArray(event);
2074     }
2075 
2076     private byte[] makeTestCellDataNano() {
2077         final CellularData data = new CellularData();
2078         data.ratType = DataStallEventProto.RADIO_TECHNOLOGY_LTE;
2079         data.networkMccmnc = TEST_MCCMNC;
2080         data.simMccmnc = TEST_MCCMNC;
2081         data.isRoaming = true;
2082         data.signalStrength = 0;
2083         return MessageNano.toByteArray(data);
2084     }
2085 
2086     private byte[] makeTestWifiDataNano() {
2087         final WifiData data = new WifiData();
2088         data.wifiBand = DataStallEventProto.AP_BAND_2GHZ;
2089         data.signalStrength = TEST_SIGNAL_STRENGTH;
2090         return MessageNano.toByteArray(data);
2091     }
2092 
2093     private void setupTestWifiInfo() {
2094         when(mWifi.getConnectionInfo()).thenReturn(mWifiInfo);
2095         when(mWifiInfo.getRssi()).thenReturn(TEST_SIGNAL_STRENGTH);
2096         // Set to 2.4G band. Map to DataStallEventProto.AP_BAND_2GHZ proto definition.
2097         when(mWifiInfo.getFrequency()).thenReturn(2450);
2098     }
2099 
2100     private void testDataStallMetricsWithCellular(int evalType) {
2101         testDataStallMetrics(evalType, NetworkCapabilities.TRANSPORT_CELLULAR);
2102     }
2103 
2104     private void testDataStallMetricsWithWiFi(int evalType) {
2105         testDataStallMetrics(evalType, NetworkCapabilities.TRANSPORT_WIFI);
2106     }
2107 
2108     private void testDataStallMetrics(int evalType, int transportType) {
2109         setDataStallEvaluationType(evalType);
2110         final NetworkCapabilities nc = new NetworkCapabilities()
2111                 .addTransportType(transportType)
2112                 .addCapability(NET_CAPABILITY_INTERNET);
2113         final WrappedNetworkMonitor wrappedMonitor = makeMonitor(nc);
2114         setupTestWifiInfo();
2115         final DataStallDetectionStats stats =
2116                 makeTestDataStallDetectionStats(evalType, transportType);
2117         assertEquals(wrappedMonitor.buildDataStallDetectionStats(transportType, evalType), stats);
2118 
2119         if ((evalType & DATA_STALL_EVALUATION_TYPE_TCP) != 0) {
2120             verify(mTst, timeout(HANDLER_TIMEOUT_MS).atLeastOnce()).getLatestPacketFailPercentage();
2121         } else {
2122             verify(mTst, never()).getLatestPacketFailPercentage();
2123         }
2124     }
2125 
2126     @Test
2127     public void testCollectDataStallMetrics_DnsWithCellular() {
2128         testDataStallMetricsWithCellular(DATA_STALL_EVALUATION_TYPE_DNS);
2129     }
2130 
2131     @Test
2132     public void testCollectDataStallMetrics_DnsWithWiFi() {
2133         testDataStallMetricsWithWiFi(DATA_STALL_EVALUATION_TYPE_DNS);
2134     }
2135 
2136     @Test
2137     public void testCollectDataStallMetrics_TcpWithCellular() {
2138         assumeTrue(ShimUtils.isReleaseOrDevelopmentApiAbove(Build.VERSION_CODES.Q));
2139         testDataStallMetricsWithCellular(DATA_STALL_EVALUATION_TYPE_TCP);
2140     }
2141 
2142     @Test
2143     public void testCollectDataStallMetrics_TcpWithWiFi() {
2144         assumeTrue(ShimUtils.isReleaseOrDevelopmentApiAbove(Build.VERSION_CODES.Q));
2145         testDataStallMetricsWithWiFi(DATA_STALL_EVALUATION_TYPE_TCP);
2146     }
2147 
2148     @Test
2149     public void testCollectDataStallMetrics_TcpAndDnsWithWifi() {
2150         assumeTrue(ShimUtils.isReleaseOrDevelopmentApiAbove(Build.VERSION_CODES.Q));
2151         testDataStallMetricsWithWiFi(
2152                 DATA_STALL_EVALUATION_TYPE_TCP | DATA_STALL_EVALUATION_TYPE_DNS);
2153     }
2154 
2155     @Test
2156     public void testCollectDataStallMetrics_TcpAndDnsWithCellular() {
2157         assumeTrue(ShimUtils.isReleaseOrDevelopmentApiAbove(Build.VERSION_CODES.Q));
2158         testDataStallMetricsWithCellular(
2159                 DATA_STALL_EVALUATION_TYPE_TCP | DATA_STALL_EVALUATION_TYPE_DNS);
2160     }
2161 
2162     @Test
2163     public void testIgnoreHttpsProbe() throws Exception {
2164         setSslException(mHttpsConnection);
2165         setStatus(mHttpConnection, 204);
2166         // Expect to send HTTP, HTTPS, FALLBACK probe and evaluation result notifications to CS.
2167         final NetworkMonitor nm = runNetworkTest(NETWORK_VALIDATION_RESULT_PARTIAL,
2168                 NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTP,
2169                 null /* redirectUrl */);
2170 
2171         resetCallbacks();
2172         nm.setAcceptPartialConnectivity();
2173         // Expect to update evaluation result notifications to CS.
2174         verifyNetworkTested(NETWORK_VALIDATION_RESULT_PARTIAL | NETWORK_VALIDATION_RESULT_VALID,
2175                 NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTP);
2176     }
2177 
2178     @Test
2179     public void testIsPartialConnectivity() throws Exception {
2180         setStatus(mHttpsConnection, 500);
2181         setStatus(mHttpConnection, 204);
2182         setStatus(mFallbackConnection, 500);
2183         runPartialConnectivityNetworkTest(
2184                 NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTP);
2185 
2186         resetCallbacks();
2187         setStatus(mHttpsConnection, 500);
2188         setStatus(mHttpConnection, 500);
2189         setStatus(mFallbackConnection, 204);
2190         runPartialConnectivityNetworkTest(
2191                 NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_FALLBACK);
2192     }
2193 
2194     private void assertIpAddressArrayEquals(String[] expected, InetAddress[] actual) {
2195         String[] actualStrings = new String[actual.length];
2196         for (int i = 0; i < actual.length; i++) {
2197             actualStrings[i] = actual[i].getHostAddress();
2198         }
2199         assertArrayEquals("Array of IP addresses differs", expected, actualStrings);
2200     }
2201 
2202     @Test
2203     public void testSendDnsProbeWithTimeout() throws Exception {
2204         WrappedNetworkMonitor wnm = makeCellNotMeteredNetworkMonitor();
2205         final int shortTimeoutMs = 200;
2206         // v6 only.
2207         String[] expected = new String[]{"2001:db8::"};
2208         mFakeDns.setAnswer("www.google.com", expected, TYPE_AAAA);
2209         InetAddress[] actual = wnm.sendDnsProbeWithTimeout("www.google.com", shortTimeoutMs);
2210         assertIpAddressArrayEquals(expected, actual);
2211         // v4 only.
2212         expected = new String[]{"192.0.2.1"};
2213         mFakeDns.setAnswer("www.android.com", expected, TYPE_A);
2214         actual = wnm.sendDnsProbeWithTimeout("www.android.com", shortTimeoutMs);
2215         assertIpAddressArrayEquals(expected, actual);
2216         // Both v4 & v6.
2217         expected = new String[]{"192.0.2.1", "2001:db8::"};
2218         mFakeDns.setAnswer("www.googleapis.com", new String[]{"192.0.2.1"}, TYPE_A);
2219         mFakeDns.setAnswer("www.googleapis.com", new String[]{"2001:db8::"}, TYPE_AAAA);
2220         actual = wnm.sendDnsProbeWithTimeout("www.googleapis.com", shortTimeoutMs);
2221         assertIpAddressArrayEquals(expected, actual);
2222         // Clear DNS response.
2223         mFakeDns.setAnswer("www.android.com", new String[0], TYPE_A);
2224         try {
2225             actual = wnm.sendDnsProbeWithTimeout("www.android.com", shortTimeoutMs);
2226             fail("No DNS results, expected UnknownHostException");
2227         } catch (UnknownHostException e) {
2228         }
2229 
2230         mFakeDns.setAnswer("www.android.com", null, TYPE_A);
2231         mFakeDns.setAnswer("www.android.com", null, TYPE_AAAA);
2232         try {
2233             wnm.sendDnsProbeWithTimeout("www.android.com", shortTimeoutMs);
2234             fail("DNS query timed out, expected UnknownHostException");
2235         } catch (UnknownHostException e) {
2236         }
2237     }
2238 
2239     @Test
2240     public void testNotifyNetwork_WithforceReevaluation() throws Exception {
2241         setValidProbes();
2242         final NetworkMonitor nm = runValidatedNetworkTest();
2243         // Verify forceReevaluation will not reset the validation result but only probe result until
2244         // getting the validation result.
2245         resetCallbacks();
2246         setSslException(mHttpsConnection);
2247         setStatus(mHttpConnection, 500);
2248         setStatus(mFallbackConnection, 204);
2249         nm.forceReevaluation(Process.myUid());
2250         // Expect to send HTTP, HTTPs, FALLBACK and evaluation results.
2251         verifyNetworkTested(NETWORK_VALIDATION_RESULT_PARTIAL,
2252                 NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_FALLBACK);
2253     }
2254 
2255     @Test
2256     public void testNotifyNetwork_NotifyNetworkTestedOldInterfaceVersion() throws Exception {
2257         // Use old interface version so notifyNetworkTested is used over
2258         // notifyNetworkTestedWithExtras
2259         resetCallbacks(4);
2260 
2261         // Trigger Network validation
2262         setStatus(mHttpsConnection, 204);
2263         setStatus(mHttpConnection, 204);
2264         final NetworkMonitor nm = makeMonitor(CELL_METERED_CAPABILITIES);
2265         nm.notifyNetworkConnected(TEST_LINK_PROPERTIES, CELL_METERED_CAPABILITIES);
2266 
2267         verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS))
2268                 .notifyNetworkTested(eq(NETWORK_VALIDATION_RESULT_VALID
2269                         | NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTPS),
2270                         eq(null) /* redirectUrl */);
2271     }
2272 
2273     @Test
2274     public void testDismissPortalInValidatedNetworkEnabledOsSupported() throws Exception {
2275         assumeTrue(ShimUtils.isAtLeastR());
2276         testDismissPortalInValidatedNetworkEnabled(TEST_LOGIN_URL, TEST_LOGIN_URL);
2277     }
2278 
2279     @Test
2280     public void testDismissPortalInValidatedNetworkEnabledOsSupported_NullLocationUrl()
2281             throws Exception {
2282         assumeTrue(ShimUtils.isAtLeastR());
2283         testDismissPortalInValidatedNetworkEnabled(TEST_HTTP_URL, null /* locationUrl */);
2284     }
2285 
2286     @Test
2287     public void testDismissPortalInValidatedNetworkEnabledOsSupported_InvalidLocationUrl()
2288             throws Exception {
2289         assumeTrue(ShimUtils.isAtLeastR());
2290         testDismissPortalInValidatedNetworkEnabled(TEST_HTTP_URL, TEST_RELATIVE_URL);
2291     }
2292 
2293     @Test
2294     public void testDismissPortalInValidatedNetworkEnabledOsNotSupported() throws Exception {
2295         assumeFalse(ShimUtils.isAtLeastR());
2296         testDismissPortalInValidatedNetworkEnabled(TEST_HTTP_URL, TEST_LOGIN_URL);
2297     }
2298 
2299     private void testDismissPortalInValidatedNetworkEnabled(String expectedUrl, String locationUrl)
2300             throws Exception {
2301         setDismissPortalInValidatedNetwork(true);
2302         setSslException(mHttpsConnection);
2303         setPortal302(mHttpConnection);
2304         when(mHttpConnection.getHeaderField(eq("location"))).thenReturn(locationUrl);
2305         final NetworkMonitor nm = makeMonitor(CELL_METERED_CAPABILITIES);
2306         notifyNetworkConnected(nm, CELL_METERED_CAPABILITIES);
2307 
2308         verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
2309             .showProvisioningNotification(any(), any());
2310 
2311         assertEquals(1, mRegisteredReceivers.size());
2312         // Check that startCaptivePortalApp sends the expected intent.
2313         nm.launchCaptivePortalApp();
2314 
2315         final ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
2316         final ArgumentCaptor<Network> networkCaptor = ArgumentCaptor.forClass(Network.class);
2317         verify(mCm, timeout(HANDLER_TIMEOUT_MS).times(1))
2318             .startCaptivePortalApp(networkCaptor.capture(), bundleCaptor.capture());
2319         verify(mNotifier).notifyCaptivePortalValidationPending(networkCaptor.getValue());
2320         final Bundle bundle = bundleCaptor.getValue();
2321         final Network bundleNetwork = bundle.getParcelable(ConnectivityManager.EXTRA_NETWORK);
2322         assertEquals(TEST_NETID, bundleNetwork.netId);
2323         // Network is passed both in bundle and as parameter, as the bundle is opaque to the
2324         // framework and only intended for the captive portal app, but the framework needs
2325         // the network to identify the right NetworkMonitor.
2326         assertEquals(TEST_NETID, networkCaptor.getValue().netId);
2327         // Portal URL should be redirect URL.
2328         final String redirectUrl = bundle.getString(ConnectivityManager.EXTRA_CAPTIVE_PORTAL_URL);
2329         assertEquals(expectedUrl, redirectUrl);
2330     }
2331 
2332     @Test
2333     public void testEvaluationState_clearProbeResults() throws Exception {
2334         setValidProbes();
2335         final NetworkMonitor nm = runValidatedNetworkTest();
2336         nm.getEvaluationState().clearProbeResults();
2337         // Verify probe results are all reset and only evaluation result left.
2338         assertEquals(NETWORK_VALIDATION_RESULT_VALID,
2339                 nm.getEvaluationState().getEvaluationResult());
2340         assertEquals(0, nm.getEvaluationState().getProbeResults());
2341     }
2342 
2343     @Test
2344     public void testEvaluationState_reportProbeResult() throws Exception {
2345         setValidProbes();
2346         final NetworkMonitor nm = runValidatedNetworkTest();
2347 
2348         resetCallbacks();
2349 
2350         nm.reportHttpProbeResult(NETWORK_VALIDATION_PROBE_HTTP,
2351                 CaptivePortalProbeResult.success(1 << PROBE_HTTP));
2352         // Verify result should be appended and notifyNetworkTestedWithExtras callback is triggered
2353         // once.
2354         assertEquals(NETWORK_VALIDATION_RESULT_VALID,
2355                 nm.getEvaluationState().getEvaluationResult());
2356         assertEquals(NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTPS
2357                 | NETWORK_VALIDATION_PROBE_HTTP, nm.getEvaluationState().getProbeResults());
2358 
2359         nm.reportHttpProbeResult(NETWORK_VALIDATION_PROBE_HTTP,
2360                 CaptivePortalProbeResult.failed(1 << PROBE_HTTP));
2361         // Verify DNS probe result should not be cleared.
2362         assertEquals(NETWORK_VALIDATION_PROBE_DNS,
2363                 nm.getEvaluationState().getProbeResults() & NETWORK_VALIDATION_PROBE_DNS);
2364     }
2365 
2366     @Test
2367     public void testEvaluationState_reportEvaluationResult() throws Exception {
2368         setStatus(mHttpsConnection, 500);
2369         setStatus(mHttpConnection, 204);
2370         final NetworkMonitor nm = runNetworkTest(NETWORK_VALIDATION_RESULT_PARTIAL,
2371                 NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTP,
2372                 null /* redirectUrl */);
2373 
2374         nm.getEvaluationState().reportEvaluationResult(NETWORK_VALIDATION_RESULT_VALID,
2375                 null /* redirectUrl */);
2376         verifyNetworkTested(NETWORK_VALIDATION_RESULT_VALID,
2377                 NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTP);
2378 
2379         nm.getEvaluationState().reportEvaluationResult(
2380                 NETWORK_VALIDATION_RESULT_VALID | NETWORK_VALIDATION_RESULT_PARTIAL,
2381                 null /* redirectUrl */);
2382         verifyNetworkTested(
2383                 NETWORK_VALIDATION_RESULT_VALID | NETWORK_VALIDATION_RESULT_PARTIAL,
2384                 NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTP);
2385 
2386         nm.getEvaluationState().reportEvaluationResult(VALIDATION_RESULT_INVALID,
2387                 TEST_REDIRECT_URL);
2388         verifyNetworkTested(VALIDATION_RESULT_INVALID,
2389                 NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTP,
2390                 TEST_REDIRECT_URL);
2391     }
2392 
2393     @Test
2394     public void testExtractCharset() {
2395         assertEquals(StandardCharsets.UTF_8, extractCharset(null));
2396         assertEquals(StandardCharsets.UTF_8, extractCharset("text/html;charset=utf-8"));
2397         assertEquals(StandardCharsets.UTF_8, extractCharset("text/html;charset=UtF-8"));
2398         assertEquals(StandardCharsets.UTF_8, extractCharset("text/html; Charset=\"utf-8\""));
2399         assertEquals(StandardCharsets.UTF_8, extractCharset("image/png"));
2400         assertEquals(StandardCharsets.UTF_8, extractCharset("Text/HTML;"));
2401         assertEquals(StandardCharsets.UTF_8, extractCharset("multipart/form-data; boundary=-aa*-"));
2402         assertEquals(StandardCharsets.UTF_8, extractCharset("text/plain;something=else"));
2403         assertEquals(StandardCharsets.UTF_8, extractCharset("text/plain;charset=ImNotACharset"));
2404 
2405         assertEquals(StandardCharsets.ISO_8859_1, extractCharset("text/plain; CharSeT=ISO-8859-1"));
2406         assertEquals(Charset.forName("Shift_JIS"), extractCharset("text/plain;charset=Shift_JIS"));
2407         assertEquals(Charset.forName("Windows-1251"), extractCharset(
2408                 "text/plain;charset=Windows-1251 ; somethingelse"));
2409     }
2410 
2411     @Test
2412     public void testReadAsString() throws IOException {
2413         final String repeatedString = "1aテスト-?";
2414         // Infinite stream repeating characters
2415         class TestInputStream extends InputStream {
2416             private final byte[] mBytes = repeatedString.getBytes(StandardCharsets.UTF_8);
2417             private int mPosition = -1;
2418 
2419             @Override
2420             public int read() {
2421                 mPosition = (mPosition + 1) % mBytes.length;
2422                 return mBytes[mPosition];
2423             }
2424         }
2425 
2426         final String readString = NetworkMonitor.readAsString(new TestInputStream(),
2427                 1500 /* maxLength */, StandardCharsets.UTF_8);
2428 
2429         assertEquals(1500, readString.length());
2430         for (int i = 0; i < readString.length(); i++) {
2431             assertEquals(repeatedString.charAt(i % repeatedString.length()), readString.charAt(i));
2432         }
2433     }
2434 
2435     @Test
2436     public void testReadAsString_StreamShorterThanLimit() throws Exception {
2437         final WrappedNetworkMonitor wnm = makeCellNotMeteredNetworkMonitor();
2438         final byte[] content = "The HTTP response code is 200 but it is not a captive portal."
2439                 .getBytes(StandardCharsets.UTF_8);
2440         assertEquals(new String(content), wnm.readAsString(new ByteArrayInputStream(content),
2441                 content.length, StandardCharsets.UTF_8));
2442         // Test the case that the stream ends earlier than the limit.
2443         assertEquals(new String(content), wnm.readAsString(new ByteArrayInputStream(content),
2444                 content.length + 10, StandardCharsets.UTF_8));
2445     }
2446 
2447     @Test
2448     public void testMultipleProbesOnPortalNetwork() throws Exception {
2449         setupResourceForMultipleProbes();
2450         // One of the http probes is portal, then result is portal.
2451         setPortal302(mOtherHttpConnection1);
2452         runPortalNetworkTest();
2453         // Get conclusive result from one of the HTTP probe. Expect to create 2 HTTP and 2 HTTPS
2454         // probes as resource configuration, but the portal can be detected before other probes
2455         // start.
2456         verify(mCleartextDnsNetwork, atMost(4)).openConnection(any());
2457         verify(mCleartextDnsNetwork, atLeastOnce()).openConnection(any());
2458         verify(mOtherHttpConnection1).getResponseCode();
2459     }
2460 
2461     @Test
2462     public void testMultipleProbesOnValidNetwork() throws Exception {
2463         setupResourceForMultipleProbes();
2464         // One of the https probes succeeds, then it's validated.
2465         setStatus(mOtherHttpsConnection2, 204);
2466         runValidatedNetworkTest();
2467         // Get conclusive result from one of the HTTPS probe. Expect to create 2 HTTP and 2 HTTPS
2468         // probes as resource configuration, but the network may validate from the HTTPS probe
2469         // before other probes start.
2470         verify(mCleartextDnsNetwork, atMost(4)).openConnection(any());
2471         verify(mCleartextDnsNetwork, atLeastOnce()).openConnection(any());
2472         verify(mOtherHttpsConnection2).getResponseCode();
2473     }
2474 
2475     @Test
2476     public void testMultipleProbesOnInValidNetworkForPrioritizedResource() throws Exception {
2477         setupResourceForMultipleProbes();
2478         // The configuration resource is prioritized. Only use configurations from resource.(i.e
2479         // Only configuration for mOtherHttpsConnection2, mOtherHttpsConnection2,
2480         // mOtherHttpConnection2, mOtherHttpConnection2 will affect the result.)
2481         // Configure mHttpsConnection is no-op.
2482         setStatus(mHttpsConnection, 204);
2483         runFailedNetworkTest();
2484         // No conclusive result from both HTTP and HTTPS probes. Expect to create 2 HTTP and 2 HTTPS
2485         // probes as resource configuration. All probes are expected to have been run because this
2486         // network is set to never validate (no probe has a success or portal result), so NM tests
2487         // all probes to completion.
2488         verify(mCleartextDnsNetwork, times(4)).openConnection(any());
2489         verify(mHttpsConnection, never()).getResponseCode();
2490     }
2491 
2492     @Test
2493     public void testMultipleProbesOnInValidNetwork() throws Exception {
2494         setupResourceForMultipleProbes();
2495         runFailedNetworkTest();
2496         // No conclusive result from both HTTP and HTTPS probes. Expect to create 2 HTTP and 2 HTTPS
2497         // probes as resource configuration.
2498         verify(mCleartextDnsNetwork, times(4)).openConnection(any());
2499     }
2500 
2501     private void setupResourceForMultipleProbes() {
2502         // Configure the resource to send multiple probe.
2503         when(mResources.getStringArray(R.array.config_captive_portal_https_urls))
2504                 .thenReturn(TEST_HTTPS_URLS);
2505         when(mResources.getStringArray(R.array.config_captive_portal_http_urls))
2506                 .thenReturn(TEST_HTTP_URLS);
2507     }
2508 
2509     private void makeDnsTimeoutEvent(WrappedNetworkMonitor wrappedMonitor, int count) {
2510         for (int i = 0; i < count; i++) {
2511             wrappedMonitor.getDnsStallDetector().accumulateConsecutiveDnsTimeoutCount(
2512                     RETURN_CODE_DNS_TIMEOUT);
2513         }
2514     }
2515 
2516     private void makeDnsSuccessEvent(WrappedNetworkMonitor wrappedMonitor, int count) {
2517         for (int i = 0; i < count; i++) {
2518             wrappedMonitor.getDnsStallDetector().accumulateConsecutiveDnsTimeoutCount(
2519                     RETURN_CODE_DNS_SUCCESS);
2520         }
2521     }
2522 
2523     private DataStallDetectionStats makeEmptyDataStallDetectionStats() {
2524         return new DataStallDetectionStats.Builder().build();
2525     }
2526 
2527     private void setDataStallEvaluationType(int type) {
2528         when(mDependencies.getDeviceConfigPropertyInt(any(),
2529             eq(CONFIG_DATA_STALL_EVALUATION_TYPE), anyInt())).thenReturn(type);
2530     }
2531 
2532     private void setMinDataStallEvaluateInterval(int time) {
2533         when(mDependencies.getDeviceConfigPropertyInt(any(),
2534             eq(CONFIG_DATA_STALL_MIN_EVALUATE_INTERVAL), anyInt())).thenReturn(time);
2535     }
2536 
2537     private void setValidDataStallDnsTimeThreshold(int time) {
2538         when(mDependencies.getDeviceConfigPropertyInt(any(),
2539             eq(CONFIG_DATA_STALL_VALID_DNS_TIME_THRESHOLD), anyInt())).thenReturn(time);
2540     }
2541 
2542     private void setConsecutiveDnsTimeoutThreshold(int num) {
2543         when(mDependencies.getDeviceConfigPropertyInt(any(),
2544             eq(CONFIG_DATA_STALL_CONSECUTIVE_DNS_TIMEOUT_THRESHOLD), anyInt())).thenReturn(num);
2545     }
2546 
2547     private void setTcpPollingInterval(int time) {
2548         doReturn(time).when(mDependencies).getDeviceConfigPropertyInt(any(),
2549                 eq(CONFIG_DATA_STALL_TCP_POLLING_INTERVAL), anyInt());
2550     }
2551 
2552     private void setFallbackUrl(String url) {
2553         when(mDependencies.getSetting(any(),
2554                 eq(Settings.Global.CAPTIVE_PORTAL_FALLBACK_URL), any())).thenReturn(url);
2555     }
2556 
2557     private void setOtherFallbackUrls(String urls) {
2558         when(mDependencies.getDeviceConfigProperty(any(),
2559                 eq(CAPTIVE_PORTAL_OTHER_FALLBACK_URLS), any())).thenReturn(urls);
2560     }
2561 
2562     private void setFallbackSpecs(String specs) {
2563         when(mDependencies.getDeviceConfigProperty(any(),
2564                 eq(CAPTIVE_PORTAL_FALLBACK_PROBE_SPECS), any())).thenReturn(specs);
2565     }
2566 
2567     private void setCaptivePortalMode(int mode) {
2568         when(mDependencies.getSetting(any(),
2569                 eq(Settings.Global.CAPTIVE_PORTAL_MODE), anyInt())).thenReturn(mode);
2570     }
2571 
2572     private void setDismissPortalInValidatedNetwork(boolean enabled) {
2573         when(mDependencies.isFeatureEnabled(any(), any(),
2574                 eq(DISMISS_PORTAL_IN_VALIDATED_NETWORK), anyBoolean())).thenReturn(enabled);
2575     }
2576 
2577     private void setDeviceConfig(String key, String value) {
2578         doReturn(value).when(mDependencies).getDeviceConfigProperty(eq(NAMESPACE_CONNECTIVITY),
2579                 eq(key), any() /* defaultValue */);
2580     }
2581 
2582     private NetworkMonitor runPortalNetworkTest() throws RemoteException {
2583         final NetworkMonitor nm = runNetworkTest(VALIDATION_RESULT_PORTAL,
2584                 0 /* probesSucceeded */, TEST_LOGIN_URL);
2585         assertEquals(1, mRegisteredReceivers.size());
2586         return nm;
2587     }
2588 
2589     private NetworkMonitor runNoValidationNetworkTest() throws RemoteException {
2590         final NetworkMonitor nm = runNetworkTest(NETWORK_VALIDATION_RESULT_VALID,
2591                 0 /* probesSucceeded */, null /* redirectUrl */);
2592         assertEquals(0, mRegisteredReceivers.size());
2593         return nm;
2594     }
2595 
2596     private NetworkMonitor runFailedNetworkTest() throws RemoteException {
2597         final NetworkMonitor nm = runNetworkTest(
2598                 VALIDATION_RESULT_INVALID, 0 /* probesSucceeded */, null /* redirectUrl */);
2599         assertEquals(0, mRegisteredReceivers.size());
2600         return nm;
2601     }
2602 
2603     private NetworkMonitor runPartialConnectivityNetworkTest(int probesSucceeded)
2604             throws RemoteException {
2605         final NetworkMonitor nm = runNetworkTest(NETWORK_VALIDATION_RESULT_PARTIAL,
2606                 probesSucceeded, null /* redirectUrl */);
2607         assertEquals(0, mRegisteredReceivers.size());
2608         return nm;
2609     }
2610 
2611     private NetworkMonitor runValidatedNetworkTest() throws RemoteException {
2612         // Expect to send HTTPS and evaluation results.
2613         return runNetworkTest(NETWORK_VALIDATION_RESULT_VALID,
2614                 NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTPS,
2615                 null /* redirectUrl */);
2616     }
2617 
2618     private NetworkMonitor runNetworkTest(int testResult, int probesSucceeded, String redirectUrl)
2619             throws RemoteException {
2620         return runNetworkTest(TEST_LINK_PROPERTIES, CELL_METERED_CAPABILITIES, testResult,
2621                 probesSucceeded, redirectUrl);
2622     }
2623 
2624     private NetworkMonitor runNetworkTest(LinkProperties lp, NetworkCapabilities nc,
2625             int testResult, int probesSucceeded, String redirectUrl) throws RemoteException {
2626         final NetworkMonitor monitor = makeMonitor(nc);
2627         monitor.notifyNetworkConnected(lp, nc);
2628         verifyNetworkTested(testResult, probesSucceeded, redirectUrl);
2629         HandlerUtilsKt.waitForIdle(monitor.getHandler(), HANDLER_TIMEOUT_MS);
2630 
2631         return monitor;
2632     }
2633 
2634     private void verifyNetworkTested(int testResult, int probesSucceeded) throws RemoteException {
2635         verifyNetworkTested(testResult, probesSucceeded, null /* redirectUrl */);
2636     }
2637 
2638     private void verifyNetworkTested(int testResult, int probesSucceeded, String redirectUrl)
2639             throws RemoteException {
2640         try {
2641             verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS)).notifyNetworkTestedWithExtras(
2642                     matchNetworkTestResultParcelable(testResult, probesSucceeded, redirectUrl));
2643         } catch (AssertionFailedError e) {
2644             // Capture the callbacks up to now to give a better error message
2645             final ArgumentCaptor<NetworkTestResultParcelable> captor =
2646                     ArgumentCaptor.forClass(NetworkTestResultParcelable.class);
2647 
2648             // Call verify() again to verify the same method call verified by the previous verify
2649             // call which failed, but this time use a captor to log the exact parcel sent by
2650             // NetworkMonitor.
2651             // This assertion will fail if notifyNetworkTested was not called at all.
2652             verify(mCallbacks).notifyNetworkTestedWithExtras(captor.capture());
2653 
2654             final NetworkTestResultParcelable lastResult = captor.getValue();
2655             fail(String.format("notifyNetworkTestedWithExtras was not called with the "
2656                     + "expected result within timeout. "
2657                     + "Expected result %d, probes succeeded %d, redirect URL %s, "
2658                     + "last result was (%d, %d, %s).",
2659                     testResult, probesSucceeded, redirectUrl,
2660                     lastResult.result, lastResult.probesSucceeded, lastResult.redirectUrl));
2661         }
2662     }
2663 
2664     private void notifyNetworkConnected(NetworkMonitor nm, NetworkCapabilities nc) {
2665         nm.notifyNetworkConnected(TEST_LINK_PROPERTIES, nc);
2666     }
2667 
2668     private void setSslException(HttpURLConnection connection) throws IOException {
2669         doThrow(new SSLHandshakeException("Invalid cert")).when(connection).getResponseCode();
2670     }
2671 
2672     private void setValidProbes() throws IOException {
2673         setStatus(mHttpsConnection, 204);
2674         setStatus(mHttpConnection, 204);
2675     }
2676 
2677     private void set302(HttpURLConnection connection, String location) throws IOException {
2678         setStatus(connection, 302);
2679         doReturn(location).when(connection).getHeaderField(LOCATION_HEADER);
2680     }
2681 
2682     private void setPortal302(HttpURLConnection connection) throws IOException {
2683         set302(connection, TEST_LOGIN_URL);
2684     }
2685 
2686     private void setApiContent(HttpURLConnection connection, String content) throws IOException {
2687         setStatus(connection, 200);
2688         final Map<String, List<String>> headerFields = new HashMap<>();
2689         headerFields.put(
2690                 CONTENT_TYPE_HEADER, singletonList("application/captive+json;charset=UTF-8"));
2691         doReturn(headerFields).when(connection).getHeaderFields();
2692         doReturn(new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8)))
2693                 .when(connection).getInputStream();
2694     }
2695 
2696     private void setStatus(HttpURLConnection connection, int status) throws IOException {
2697         doReturn(status).when(connection).getResponseCode();
2698     }
2699 
2700     private void generateTimeoutDnsEvent(DataStallDetectionStats.Builder stats, int num) {
2701         for (int i = 0; i < num; i++) {
2702             stats.addDnsEvent(RETURN_CODE_DNS_TIMEOUT, TEST_ELAPSED_TIME_MS /* timeMs */);
2703         }
2704     }
2705 
2706     private void generateTestTcpStats(DataStallDetectionStats.Builder stats) {
2707         when(mTst.getLatestPacketFailPercentage()).thenReturn(TEST_TCP_FAIL_RATE);
2708         when(mTst.getSentSinceLastRecv()).thenReturn(TEST_TCP_PACKET_COUNT);
2709         stats.setTcpFailRate(TEST_TCP_FAIL_RATE).setTcpSentSinceLastRecv(TEST_TCP_PACKET_COUNT);
2710     }
2711 
2712     private NetworkTestResultParcelable matchNetworkTestResultParcelable(final int result,
2713             final int probesSucceeded) {
2714         return matchNetworkTestResultParcelable(result, probesSucceeded, null /* redirectUrl */);
2715     }
2716 
2717     private NetworkTestResultParcelable matchNetworkTestResultParcelable(final int result,
2718             final int probesSucceeded, String redirectUrl) {
2719         // TODO: also verify probesAttempted
2720         return argThat(p -> p.result == result && p.probesSucceeded == probesSucceeded
2721                 && Objects.equals(p.redirectUrl, redirectUrl));
2722     }
2723 
2724     private DataStallReportParcelable matchDnsAndTcpDataStallParcelable(final int timeoutCount) {
2725         return argThat(p ->
2726                 (p.detectionMethod & ConstantsShim.DETECTION_METHOD_DNS_EVENTS) != 0
2727                 && (p.detectionMethod & ConstantsShim.DETECTION_METHOD_TCP_METRICS) != 0
2728                 && p.dnsConsecutiveTimeouts == timeoutCount);
2729     }
2730 
2731     private DataStallReportParcelable matchDnsDataStallParcelable(final int timeoutCount) {
2732         return argThat(p -> (p.detectionMethod & ConstantsShim.DETECTION_METHOD_DNS_EVENTS) != 0
2733                 && p.dnsConsecutiveTimeouts == timeoutCount);
2734     }
2735 
2736     private DataStallReportParcelable matchTcpDataStallParcelable() {
2737         return argThat(p -> (p.detectionMethod & ConstantsShim.DETECTION_METHOD_TCP_METRICS) != 0);
2738     }
2739 }
2740 
2741