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