blob: ddb7030d314aad7ef4f517e9448561d85ab3e06b [file] [log] [blame]
Hugo Benichic894b122017-07-31 12:58:20 +09001/*
2 * Copyright (C) 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.server.connectivity;
18
Remi NGUYEN VANcfff01e2019-02-13 20:58:59 +090019import static android.net.CaptivePortal.APP_RETURN_DISMISSED;
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +090020import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_INVALID;
21import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_VALID;
22import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +090023
24import static junit.framework.Assert.assertEquals;
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +090025import static junit.framework.Assert.assertFalse;
26
27import static org.junit.Assert.assertTrue;
Hugo Benichic894b122017-07-31 12:58:20 +090028import static org.junit.Assert.fail;
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +090029import static org.mockito.ArgumentMatchers.anyString;
30import static org.mockito.ArgumentMatchers.eq;
Hugo Benichic894b122017-07-31 12:58:20 +090031import static org.mockito.Mockito.any;
32import static org.mockito.Mockito.anyInt;
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +090033import static org.mockito.Mockito.doAnswer;
34import static org.mockito.Mockito.doReturn;
35import static org.mockito.Mockito.doThrow;
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +090036import static org.mockito.Mockito.never;
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +090037import static org.mockito.Mockito.timeout;
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +090038import static org.mockito.Mockito.times;
Hugo Benichic894b122017-07-31 12:58:20 +090039import static org.mockito.Mockito.verify;
40import static org.mockito.Mockito.when;
41
Chiachang Wang95489ca2019-02-26 11:32:18 +080042import android.annotation.NonNull;
Hugo Benichic894b122017-07-31 12:58:20 +090043import android.content.Context;
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +090044import android.net.ConnectivityManager;
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +090045import android.net.INetworkMonitorCallbacks;
46import android.net.InetAddresses;
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +090047import android.net.LinkProperties;
Hugo Benichic894b122017-07-31 12:58:20 +090048import android.net.Network;
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +090049import android.net.NetworkCapabilities;
50import android.net.NetworkInfo;
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +090051import android.net.captiveportal.CaptivePortalProbeResult;
Chiachang Wang95489ca2019-02-26 11:32:18 +080052import android.net.metrics.DataStallDetectionStats;
Chiachang Wangf09e3e32019-02-22 11:13:07 +080053import android.net.metrics.DataStallStatsUtils;
Hugo Benichic894b122017-07-31 12:58:20 +090054import android.net.metrics.IpConnectivityLog;
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +090055import android.net.util.SharedLog;
Chiachang Wang95489ca2019-02-26 11:32:18 +080056import android.net.wifi.WifiInfo;
Hugo Benichic894b122017-07-31 12:58:20 +090057import android.net.wifi.WifiManager;
Remi NGUYEN VANcfff01e2019-02-13 20:58:59 +090058import android.os.Bundle;
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +090059import android.os.ConditionVariable;
Hugo Benichic894b122017-07-31 12:58:20 +090060import android.os.Handler;
Chiachang Wang7a70a7e2018-11-27 18:00:05 +080061import android.os.SystemClock;
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +090062import android.provider.Settings;
Hugo Benichic894b122017-07-31 12:58:20 +090063import android.support.test.filters.SmallTest;
64import android.support.test.runner.AndroidJUnit4;
Chiachang Wang95489ca2019-02-26 11:32:18 +080065import android.telephony.CellSignalStrength;
Hugo Benichic894b122017-07-31 12:58:20 +090066import android.telephony.TelephonyManager;
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +090067import android.util.ArrayMap;
Hugo Benichic894b122017-07-31 12:58:20 +090068
69import org.junit.Before;
70import org.junit.Test;
71import org.junit.runner.RunWith;
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +090072import org.mockito.ArgumentCaptor;
Hugo Benichic894b122017-07-31 12:58:20 +090073import org.mockito.Mock;
74import org.mockito.MockitoAnnotations;
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +090075import org.mockito.Spy;
Hugo Benichic894b122017-07-31 12:58:20 +090076
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +090077import java.io.IOException;
78import java.net.HttpURLConnection;
79import java.net.InetAddress;
80import java.net.URL;
81import java.util.Random;
82
83import javax.net.ssl.SSLHandshakeException;
84
Hugo Benichic894b122017-07-31 12:58:20 +090085
86@RunWith(AndroidJUnit4.class)
87@SmallTest
88public class NetworkMonitorTest {
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +090089 private static final String LOCATION_HEADER = "location";
Hugo Benichic894b122017-07-31 12:58:20 +090090
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +090091 private @Mock Context mContext;
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +090092 private @Mock IpConnectivityLog mLogger;
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +090093 private @Mock SharedLog mValidationLogger;
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +090094 private @Mock NetworkInfo mNetworkInfo;
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +090095 private @Mock ConnectivityManager mCm;
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +090096 private @Mock TelephonyManager mTelephony;
97 private @Mock WifiManager mWifi;
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +090098 private @Mock HttpURLConnection mHttpConnection;
99 private @Mock HttpURLConnection mHttpsConnection;
100 private @Mock HttpURLConnection mFallbackConnection;
101 private @Mock HttpURLConnection mOtherFallbackConnection;
102 private @Mock Random mRandom;
103 private @Mock NetworkMonitor.Dependencies mDependencies;
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900104 private @Mock INetworkMonitorCallbacks mCallbacks;
105 private @Spy Network mNetwork = new Network(TEST_NETID);
Chiachang Wangf09e3e32019-02-22 11:13:07 +0800106 private @Mock DataStallStatsUtils mDataStallStatsUtils;
Chiachang Wang95489ca2019-02-26 11:32:18 +0800107 private @Mock WifiInfo mWifiInfo;
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900108
109 private static final int TEST_NETID = 4242;
Hugo Benichic894b122017-07-31 12:58:20 +0900110
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +0900111 private static final String TEST_HTTP_URL = "http://www.google.com/gen_204";
112 private static final String TEST_HTTPS_URL = "https://www.google.com/gen_204";
113 private static final String TEST_FALLBACK_URL = "http://fallback.google.com/gen_204";
114 private static final String TEST_OTHER_FALLBACK_URL = "http://otherfallback.google.com/gen_204";
Chiachang Wang95489ca2019-02-26 11:32:18 +0800115 private static final String TEST_MCCMNC = "123456";
Hugo Benichic894b122017-07-31 12:58:20 +0900116
Chiachang Wang7a70a7e2018-11-27 18:00:05 +0800117 private static final int DATA_STALL_EVALUATION_TYPE_DNS = 1;
118 private static final int RETURN_CODE_DNS_SUCCESS = 0;
119 private static final int RETURN_CODE_DNS_TIMEOUT = 255;
Chiachang Wang95489ca2019-02-26 11:32:18 +0800120 private static final int DEFAULT_DNS_TIMEOUT_THRESHOLD = 5;
Chiachang Wang7a70a7e2018-11-27 18:00:05 +0800121
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900122 private static final int HANDLER_TIMEOUT_MS = 1000;
123
124 private static final LinkProperties TEST_LINKPROPERTIES = new LinkProperties();
125
126 private static final NetworkCapabilities METERED_CAPABILITIES = new NetworkCapabilities()
127 .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
128 .addCapability(NET_CAPABILITY_INTERNET);
129
130 private static final NetworkCapabilities NOT_METERED_CAPABILITIES = new NetworkCapabilities()
131 .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
132 .addCapability(NET_CAPABILITY_INTERNET)
133 .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
134
135 private static final NetworkCapabilities NO_INTERNET_CAPABILITIES = new NetworkCapabilities()
136 .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
137
Hugo Benichic894b122017-07-31 12:58:20 +0900138 @Before
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +0900139 public void setUp() throws IOException {
Hugo Benichic894b122017-07-31 12:58:20 +0900140 MockitoAnnotations.initMocks(this);
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900141 when(mDependencies.getPrivateDnsBypassNetwork(any())).thenReturn(mNetwork);
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +0900142 when(mDependencies.getRandom()).thenReturn(mRandom);
143 when(mDependencies.getSetting(any(), eq(Settings.Global.CAPTIVE_PORTAL_MODE), anyInt()))
144 .thenReturn(Settings.Global.CAPTIVE_PORTAL_MODE_PROMPT);
145 when(mDependencies.getSetting(any(), eq(Settings.Global.CAPTIVE_PORTAL_USE_HTTPS),
146 anyInt())).thenReturn(1);
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900147 when(mDependencies.getCaptivePortalServerHttpUrl(any())).thenReturn(TEST_HTTP_URL);
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +0900148 when(mDependencies.getSetting(any(), eq(Settings.Global.CAPTIVE_PORTAL_HTTPS_URL),
149 anyString())).thenReturn(TEST_HTTPS_URL);
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900150 doReturn(mNetwork).when(mNetwork).getPrivateDnsBypassingCopy();
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +0900151
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900152 when(mContext.getSystemService(Context.CONNECTIVITY_SERVICE)).thenReturn(mCm);
Hugo Benichic894b122017-07-31 12:58:20 +0900153 when(mContext.getSystemService(Context.TELEPHONY_SERVICE)).thenReturn(mTelephony);
154 when(mContext.getSystemService(Context.WIFI_SERVICE)).thenReturn(mWifi);
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +0900155
156 when(mNetworkInfo.getType()).thenReturn(ConnectivityManager.TYPE_WIFI);
157 setFallbackUrl(TEST_FALLBACK_URL);
158 setOtherFallbackUrls(TEST_OTHER_FALLBACK_URL);
159 setFallbackSpecs(null); // Test with no fallback spec by default
160 when(mRandom.nextInt()).thenReturn(0);
161
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900162 doAnswer((invocation) -> {
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +0900163 URL url = invocation.getArgument(0);
164 switch(url.toString()) {
165 case TEST_HTTP_URL:
166 return mHttpConnection;
167 case TEST_HTTPS_URL:
168 return mHttpsConnection;
169 case TEST_FALLBACK_URL:
170 return mFallbackConnection;
171 case TEST_OTHER_FALLBACK_URL:
172 return mOtherFallbackConnection;
173 default:
174 fail("URL not mocked: " + url.toString());
175 return null;
176 }
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900177 }).when(mNetwork).openConnection(any());
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +0900178 when(mHttpConnection.getRequestProperties()).thenReturn(new ArrayMap<>());
179 when(mHttpsConnection.getRequestProperties()).thenReturn(new ArrayMap<>());
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900180 doReturn(new InetAddress[] {
181 InetAddresses.parseNumericAddress("192.168.0.0")
182 }).when(mNetwork).getAllByName(any());
183
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900184 // Default values. Individual tests can override these.
185 when(mCm.getLinkProperties(any())).thenReturn(TEST_LINKPROPERTIES);
186 when(mCm.getNetworkCapabilities(any())).thenReturn(METERED_CAPABILITIES);
Chiachang Wang7a70a7e2018-11-27 18:00:05 +0800187
188 setMinDataStallEvaluateInterval(500);
189 setDataStallEvaluationType(1 << DATA_STALL_EVALUATION_TYPE_DNS);
190 setValidDataStallDnsTimeThreshold(500);
191 setConsecutiveDnsTimeoutThreshold(5);
192 }
193
194 private class WrappedNetworkMonitor extends NetworkMonitor {
195 private long mProbeTime = 0;
196
Remi NGUYEN VAN231b52b2019-01-29 15:38:52 +0900197 WrappedNetworkMonitor(Context context, Network network, IpConnectivityLog logger,
Chiachang Wangf09e3e32019-02-22 11:13:07 +0800198 Dependencies deps, DataStallStatsUtils statsUtils) {
Remi NGUYEN VAN231b52b2019-01-29 15:38:52 +0900199 super(context, mCallbacks, network, logger,
Chiachang Wangf09e3e32019-02-22 11:13:07 +0800200 new SharedLog("test_nm"), deps, statsUtils);
Chiachang Wang7a70a7e2018-11-27 18:00:05 +0800201 }
202
203 @Override
204 protected long getLastProbeTime() {
205 return mProbeTime;
206 }
207
208 protected void setLastProbeTime(long time) {
209 mProbeTime = time;
210 }
Chiachang Wang95489ca2019-02-26 11:32:18 +0800211
212 @Override
213 protected void addDnsEvents(@NonNull final DataStallDetectionStats.Builder stats) {
214 generateTimeoutDnsEvent(stats, DEFAULT_DNS_TIMEOUT_THRESHOLD);
215 }
Chiachang Wang7a70a7e2018-11-27 18:00:05 +0800216 }
217
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900218 private WrappedNetworkMonitor makeMeteredWrappedNetworkMonitor() {
219 final WrappedNetworkMonitor nm = new WrappedNetworkMonitor(
Chiachang Wangf09e3e32019-02-22 11:13:07 +0800220 mContext, mNetwork, mLogger, mDependencies, mDataStallStatsUtils);
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900221 when(mCm.getNetworkCapabilities(any())).thenReturn(METERED_CAPABILITIES);
222 nm.start();
223 waitForIdle(nm.getHandler());
224 return nm;
Chiachang Wang7a70a7e2018-11-27 18:00:05 +0800225 }
226
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900227 private WrappedNetworkMonitor makeNotMeteredWrappedNetworkMonitor() {
228 final WrappedNetworkMonitor nm = new WrappedNetworkMonitor(
Chiachang Wangf09e3e32019-02-22 11:13:07 +0800229 mContext, mNetwork, mLogger, mDependencies, mDataStallStatsUtils);
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900230 when(mCm.getNetworkCapabilities(any())).thenReturn(NOT_METERED_CAPABILITIES);
231 nm.start();
232 waitForIdle(nm.getHandler());
233 return nm;
Hugo Benichic894b122017-07-31 12:58:20 +0900234 }
235
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900236 private NetworkMonitor makeMonitor() {
237 final NetworkMonitor nm = new NetworkMonitor(
Remi NGUYEN VAN231b52b2019-01-29 15:38:52 +0900238 mContext, mCallbacks, mNetwork, mLogger, mValidationLogger,
Chiachang Wangf09e3e32019-02-22 11:13:07 +0800239 mDependencies, mDataStallStatsUtils);
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900240 nm.start();
241 waitForIdle(nm.getHandler());
242 return nm;
243 }
244
245 private void waitForIdle(Handler handler) {
246 final ConditionVariable cv = new ConditionVariable(false);
247 handler.post(cv::open);
248 if (!cv.block(HANDLER_TIMEOUT_MS)) {
249 fail("Timed out waiting for handler");
250 }
Hugo Benichic894b122017-07-31 12:58:20 +0900251 }
252
253 @Test
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +0900254 public void testIsCaptivePortal_HttpProbeIsPortal() throws IOException {
255 setSslException(mHttpsConnection);
256 setPortal302(mHttpConnection);
257
258 assertPortal(makeMonitor().isCaptivePortal());
259 }
260
261 @Test
262 public void testIsCaptivePortal_HttpsProbeIsNotPortal() throws IOException {
263 setStatus(mHttpsConnection, 204);
264 setStatus(mHttpConnection, 500);
265
266 assertNotPortal(makeMonitor().isCaptivePortal());
267 }
268
269 @Test
270 public void testIsCaptivePortal_HttpsProbeFailedHttpSuccessNotUsed() throws IOException {
271 setSslException(mHttpsConnection);
272 // Even if HTTP returns a 204, do not use the result unless HTTPS succeeded
273 setStatus(mHttpConnection, 204);
274 setStatus(mFallbackConnection, 500);
275
276 assertFailed(makeMonitor().isCaptivePortal());
277 }
278
279 @Test
280 public void testIsCaptivePortal_FallbackProbeIsPortal() throws IOException {
281 setSslException(mHttpsConnection);
282 setStatus(mHttpConnection, 500);
283 setPortal302(mFallbackConnection);
284
285 assertPortal(makeMonitor().isCaptivePortal());
286 }
287
288 @Test
289 public void testIsCaptivePortal_FallbackProbeIsNotPortal() throws IOException {
290 setSslException(mHttpsConnection);
291 setStatus(mHttpConnection, 500);
292 setStatus(mFallbackConnection, 204);
293
294 // Fallback probe did not see portal, HTTPS failed -> inconclusive
295 assertFailed(makeMonitor().isCaptivePortal());
296 }
297
298 @Test
299 public void testIsCaptivePortal_OtherFallbackProbeIsPortal() throws IOException {
300 // Set all fallback probes but one to invalid URLs to verify they are being skipped
301 setFallbackUrl(TEST_FALLBACK_URL);
302 setOtherFallbackUrls(TEST_FALLBACK_URL + "," + TEST_OTHER_FALLBACK_URL);
303
304 setSslException(mHttpsConnection);
305 setStatus(mHttpConnection, 500);
306 setStatus(mFallbackConnection, 500);
307 setPortal302(mOtherFallbackConnection);
308
309 // TEST_OTHER_FALLBACK_URL is third
310 when(mRandom.nextInt()).thenReturn(2);
311
312 final NetworkMonitor monitor = makeMonitor();
313
314 // First check always uses the first fallback URL: inconclusive
315 assertFailed(monitor.isCaptivePortal());
316 verify(mFallbackConnection, times(1)).getResponseCode();
317 verify(mOtherFallbackConnection, never()).getResponseCode();
318
319 // Second check uses the URL chosen by Random
320 assertPortal(monitor.isCaptivePortal());
321 verify(mOtherFallbackConnection, times(1)).getResponseCode();
322 }
323
324 @Test
325 public void testIsCaptivePortal_AllProbesFailed() throws IOException {
326 setSslException(mHttpsConnection);
327 setStatus(mHttpConnection, 500);
328 setStatus(mFallbackConnection, 404);
329
330 assertFailed(makeMonitor().isCaptivePortal());
331 verify(mFallbackConnection, times(1)).getResponseCode();
332 verify(mOtherFallbackConnection, never()).getResponseCode();
333 }
334
335 @Test
336 public void testIsCaptivePortal_InvalidUrlSkipped() throws IOException {
337 setFallbackUrl("invalid");
338 setOtherFallbackUrls("otherinvalid," + TEST_OTHER_FALLBACK_URL + ",yetanotherinvalid");
339
340 setSslException(mHttpsConnection);
341 setStatus(mHttpConnection, 500);
342 setPortal302(mOtherFallbackConnection);
343
344 assertPortal(makeMonitor().isCaptivePortal());
345 verify(mOtherFallbackConnection, times(1)).getResponseCode();
346 verify(mFallbackConnection, never()).getResponseCode();
347 }
348
349 private void setupFallbackSpec() throws IOException {
350 setFallbackSpecs("http://example.com@@/@@204@@/@@"
351 + "@@,@@"
352 + TEST_OTHER_FALLBACK_URL + "@@/@@30[12]@@/@@https://(www\\.)?google.com/?.*");
353
354 setSslException(mHttpsConnection);
355 setStatus(mHttpConnection, 500);
356
357 // Use the 2nd fallback spec
358 when(mRandom.nextInt()).thenReturn(1);
359 }
360
361 @Test
362 public void testIsCaptivePortal_FallbackSpecIsNotPortal() throws IOException {
363 setupFallbackSpec();
364 set302(mOtherFallbackConnection, "https://www.google.com/test?q=3");
365
366 // HTTPS failed, fallback spec did not see a portal -> inconclusive
367 assertFailed(makeMonitor().isCaptivePortal());
368 verify(mOtherFallbackConnection, times(1)).getResponseCode();
369 verify(mFallbackConnection, never()).getResponseCode();
370 }
371
372 @Test
373 public void testIsCaptivePortal_FallbackSpecIsPortal() throws IOException {
374 setupFallbackSpec();
375 set302(mOtherFallbackConnection, "http://login.portal.example.com");
376
377 assertPortal(makeMonitor().isCaptivePortal());
378 }
379
Chiachang Wang7a70a7e2018-11-27 18:00:05 +0800380 @Test
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900381 public void testIsCaptivePortal_IgnorePortals() throws IOException {
382 setCaptivePortalMode(Settings.Global.CAPTIVE_PORTAL_MODE_IGNORE);
383 setSslException(mHttpsConnection);
384 setPortal302(mHttpConnection);
385
386 assertNotPortal(makeMonitor().isCaptivePortal());
387 }
388
389 @Test
Chiachang Wang7a70a7e2018-11-27 18:00:05 +0800390 public void testIsDataStall_EvaluationDisabled() {
391 setDataStallEvaluationType(0);
392 WrappedNetworkMonitor wrappedMonitor = makeMeteredWrappedNetworkMonitor();
393 wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 100);
394 assertFalse(wrappedMonitor.isDataStall());
395 }
396
397 @Test
398 public void testIsDataStall_EvaluationDnsOnNotMeteredNetwork() {
399 WrappedNetworkMonitor wrappedMonitor = makeNotMeteredWrappedNetworkMonitor();
400 wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 100);
Chiachang Wang95489ca2019-02-26 11:32:18 +0800401 makeDnsTimeoutEvent(wrappedMonitor, DEFAULT_DNS_TIMEOUT_THRESHOLD);
Chiachang Wang7a70a7e2018-11-27 18:00:05 +0800402 assertTrue(wrappedMonitor.isDataStall());
403 }
404
405 @Test
406 public void testIsDataStall_EvaluationDnsOnMeteredNetwork() {
407 WrappedNetworkMonitor wrappedMonitor = makeMeteredWrappedNetworkMonitor();
408 wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 100);
409 assertFalse(wrappedMonitor.isDataStall());
410
411 wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 1000);
Chiachang Wang95489ca2019-02-26 11:32:18 +0800412 makeDnsTimeoutEvent(wrappedMonitor, DEFAULT_DNS_TIMEOUT_THRESHOLD);
Chiachang Wang7a70a7e2018-11-27 18:00:05 +0800413 assertTrue(wrappedMonitor.isDataStall());
414 }
415
416 @Test
417 public void testIsDataStall_EvaluationDnsWithDnsTimeoutCount() {
418 WrappedNetworkMonitor wrappedMonitor = makeMeteredWrappedNetworkMonitor();
419 wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 1000);
420 makeDnsTimeoutEvent(wrappedMonitor, 3);
421 assertFalse(wrappedMonitor.isDataStall());
422 // Reset consecutive timeout counts.
423 makeDnsSuccessEvent(wrappedMonitor, 1);
424 makeDnsTimeoutEvent(wrappedMonitor, 2);
425 assertFalse(wrappedMonitor.isDataStall());
426
427 makeDnsTimeoutEvent(wrappedMonitor, 3);
428 assertTrue(wrappedMonitor.isDataStall());
429
430 // Set the value to larger than the default dns log size.
431 setConsecutiveDnsTimeoutThreshold(51);
432 wrappedMonitor = makeMeteredWrappedNetworkMonitor();
433 wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 1000);
434 makeDnsTimeoutEvent(wrappedMonitor, 50);
435 assertFalse(wrappedMonitor.isDataStall());
436
437 makeDnsTimeoutEvent(wrappedMonitor, 1);
438 assertTrue(wrappedMonitor.isDataStall());
439 }
440
441 @Test
442 public void testIsDataStall_EvaluationDnsWithDnsTimeThreshold() {
443 // Test dns events happened in valid dns time threshold.
444 WrappedNetworkMonitor wrappedMonitor = makeMeteredWrappedNetworkMonitor();
445 wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 100);
Chiachang Wang95489ca2019-02-26 11:32:18 +0800446 makeDnsTimeoutEvent(wrappedMonitor, DEFAULT_DNS_TIMEOUT_THRESHOLD);
Chiachang Wang7a70a7e2018-11-27 18:00:05 +0800447 assertFalse(wrappedMonitor.isDataStall());
448 wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 1000);
449 assertTrue(wrappedMonitor.isDataStall());
450
451 // Test dns events happened before valid dns time threshold.
452 setValidDataStallDnsTimeThreshold(0);
453 wrappedMonitor = makeMeteredWrappedNetworkMonitor();
454 wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 100);
Chiachang Wang95489ca2019-02-26 11:32:18 +0800455 makeDnsTimeoutEvent(wrappedMonitor, DEFAULT_DNS_TIMEOUT_THRESHOLD);
Chiachang Wang7a70a7e2018-11-27 18:00:05 +0800456 assertFalse(wrappedMonitor.isDataStall());
457 wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 1000);
458 assertFalse(wrappedMonitor.isDataStall());
459 }
460
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900461 @Test
462 public void testBrokenNetworkNotValidated() throws Exception {
463 setSslException(mHttpsConnection);
464 setStatus(mHttpConnection, 500);
465 setStatus(mFallbackConnection, 404);
466 when(mCm.getNetworkCapabilities(any())).thenReturn(METERED_CAPABILITIES);
467
468 final NetworkMonitor nm = makeMonitor();
469 nm.notifyNetworkConnected();
470
471 verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
472 .notifyNetworkTested(NETWORK_TEST_RESULT_INVALID, null);
473 }
474
475 @Test
476 public void testNoInternetCapabilityValidated() throws Exception {
477 when(mCm.getNetworkCapabilities(any())).thenReturn(NO_INTERNET_CAPABILITIES);
478
479 final NetworkMonitor nm = makeMonitor();
480 nm.notifyNetworkConnected();
481
482 verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
483 .notifyNetworkTested(NETWORK_TEST_RESULT_VALID, null);
484 verify(mNetwork, never()).openConnection(any());
485 }
486
487 @Test
488 public void testLaunchCaptivePortalApp() throws Exception {
489 setSslException(mHttpsConnection);
490 setPortal302(mHttpConnection);
491
492 final NetworkMonitor nm = makeMonitor();
493 nm.notifyNetworkConnected();
494
495 verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
Remi NGUYEN VAN9c5d9642019-02-07 21:29:57 +0900496 .showProvisioningNotification(any(), any());
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900497
498 // Check that startCaptivePortalApp sends the expected intent.
499 nm.launchCaptivePortalApp();
500
Remi NGUYEN VANcfff01e2019-02-13 20:58:59 +0900501 final ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
502 final ArgumentCaptor<Network> networkCaptor = ArgumentCaptor.forClass(Network.class);
503 verify(mCm, timeout(HANDLER_TIMEOUT_MS).times(1))
504 .startCaptivePortalApp(networkCaptor.capture(), bundleCaptor.capture());
505 final Bundle bundle = bundleCaptor.getValue();
506 final Network bundleNetwork = bundle.getParcelable(ConnectivityManager.EXTRA_NETWORK);
507 assertEquals(TEST_NETID, bundleNetwork.netId);
508 // network is passed both in bundle and as parameter, as the bundle is opaque to the
509 // framework and only intended for the captive portal app, but the framework needs
510 // the network to identify the right NetworkMonitor.
511 assertEquals(TEST_NETID, networkCaptor.getValue().netId);
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900512
513 // Have the app report that the captive portal is dismissed, and check that we revalidate.
514 setStatus(mHttpsConnection, 204);
515 setStatus(mHttpConnection, 204);
Remi NGUYEN VANcfff01e2019-02-13 20:58:59 +0900516
517 nm.notifyCaptivePortalAppFinished(APP_RETURN_DISMISSED);
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900518 verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
519 .notifyNetworkTested(NETWORK_TEST_RESULT_VALID, null);
520 }
521
Chiachang Wangf09e3e32019-02-22 11:13:07 +0800522 @Test
523 public void testDataStall_StallSuspectedAndSendMetrics() throws IOException {
524 WrappedNetworkMonitor wrappedMonitor = makeNotMeteredWrappedNetworkMonitor();
525 wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 1000);
526 makeDnsTimeoutEvent(wrappedMonitor, 5);
527 assertTrue(wrappedMonitor.isDataStall());
Chiachang Wang95489ca2019-02-26 11:32:18 +0800528 verify(mDataStallStatsUtils, times(1)).write(any(), any());
Chiachang Wangf09e3e32019-02-22 11:13:07 +0800529 }
530
531 @Test
532 public void testDataStall_NoStallSuspectedAndSendMetrics() throws IOException {
533 WrappedNetworkMonitor wrappedMonitor = makeNotMeteredWrappedNetworkMonitor();
534 wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 1000);
535 makeDnsTimeoutEvent(wrappedMonitor, 3);
536 assertFalse(wrappedMonitor.isDataStall());
Chiachang Wang95489ca2019-02-26 11:32:18 +0800537 verify(mDataStallStatsUtils, never()).write(any(), any());
Chiachang Wangf09e3e32019-02-22 11:13:07 +0800538 }
Chiachang Wang95489ca2019-02-26 11:32:18 +0800539
540 @Test
541 public void testCollectDataStallMetrics() {
542 WrappedNetworkMonitor wrappedMonitor = makeNotMeteredWrappedNetworkMonitor();
543
544 when(mTelephony.getDataNetworkType()).thenReturn(TelephonyManager.NETWORK_TYPE_LTE);
545 when(mTelephony.getNetworkOperator()).thenReturn(TEST_MCCMNC);
546 when(mTelephony.getSimOperator()).thenReturn(TEST_MCCMNC);
547
548 DataStallDetectionStats.Builder stats =
549 new DataStallDetectionStats.Builder()
550 .setEvaluationType(DATA_STALL_EVALUATION_TYPE_DNS)
551 .setNetworkType(NetworkCapabilities.TRANSPORT_CELLULAR)
552 .setCellData(TelephonyManager.NETWORK_TYPE_LTE /* radioType */,
553 true /* roaming */,
554 TEST_MCCMNC /* networkMccmnc */,
555 TEST_MCCMNC /* simMccmnc */,
556 CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN /* signalStrength */);
557 generateTimeoutDnsEvent(stats, DEFAULT_DNS_TIMEOUT_THRESHOLD);
558
559 assertEquals(wrappedMonitor.buildDataStallDetectionStats(
560 NetworkCapabilities.TRANSPORT_CELLULAR), stats.build());
561
562 when(mWifi.getConnectionInfo()).thenReturn(mWifiInfo);
563
564 stats = new DataStallDetectionStats.Builder()
565 .setEvaluationType(DATA_STALL_EVALUATION_TYPE_DNS)
566 .setNetworkType(NetworkCapabilities.TRANSPORT_WIFI)
567 .setWiFiData(mWifiInfo);
568 generateTimeoutDnsEvent(stats, DEFAULT_DNS_TIMEOUT_THRESHOLD);
569
570 assertEquals(
571 wrappedMonitor.buildDataStallDetectionStats(NetworkCapabilities.TRANSPORT_WIFI),
572 stats.build());
573 }
574
Chiachang Wang7a70a7e2018-11-27 18:00:05 +0800575 private void makeDnsTimeoutEvent(WrappedNetworkMonitor wrappedMonitor, int count) {
576 for (int i = 0; i < count; i++) {
577 wrappedMonitor.getDnsStallDetector().accumulateConsecutiveDnsTimeoutCount(
578 RETURN_CODE_DNS_TIMEOUT);
579 }
580 }
581
582 private void makeDnsSuccessEvent(WrappedNetworkMonitor wrappedMonitor, int count) {
583 for (int i = 0; i < count; i++) {
584 wrappedMonitor.getDnsStallDetector().accumulateConsecutiveDnsTimeoutCount(
585 RETURN_CODE_DNS_SUCCESS);
586 }
587 }
588
589 private void setDataStallEvaluationType(int type) {
590 when(mDependencies.getSetting(any(),
591 eq(Settings.Global.DATA_STALL_EVALUATION_TYPE), anyInt())).thenReturn(type);
592 }
593
594 private void setMinDataStallEvaluateInterval(int time) {
595 when(mDependencies.getSetting(any(),
596 eq(Settings.Global.DATA_STALL_MIN_EVALUATE_INTERVAL), anyInt())).thenReturn(time);
597 }
598
599 private void setValidDataStallDnsTimeThreshold(int time) {
600 when(mDependencies.getSetting(any(),
601 eq(Settings.Global.DATA_STALL_VALID_DNS_TIME_THRESHOLD), anyInt())).thenReturn(time);
602 }
603
604 private void setConsecutiveDnsTimeoutThreshold(int num) {
605 when(mDependencies.getSetting(any(),
606 eq(Settings.Global.DATA_STALL_CONSECUTIVE_DNS_TIMEOUT_THRESHOLD), anyInt()))
607 .thenReturn(num);
608 }
609
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +0900610 private void setFallbackUrl(String url) {
611 when(mDependencies.getSetting(any(),
612 eq(Settings.Global.CAPTIVE_PORTAL_FALLBACK_URL), any())).thenReturn(url);
613 }
614
615 private void setOtherFallbackUrls(String urls) {
616 when(mDependencies.getSetting(any(),
617 eq(Settings.Global.CAPTIVE_PORTAL_OTHER_FALLBACK_URLS), any())).thenReturn(urls);
618 }
619
620 private void setFallbackSpecs(String specs) {
621 when(mDependencies.getSetting(any(),
622 eq(Settings.Global.CAPTIVE_PORTAL_FALLBACK_PROBE_SPECS), any())).thenReturn(specs);
623 }
624
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900625 private void setCaptivePortalMode(int mode) {
626 when(mDependencies.getSetting(any(),
627 eq(Settings.Global.CAPTIVE_PORTAL_MODE), anyInt())).thenReturn(mode);
628 }
629
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +0900630 private void assertPortal(CaptivePortalProbeResult result) {
631 assertTrue(result.isPortal());
632 assertFalse(result.isFailed());
633 assertFalse(result.isSuccessful());
634 }
635
636 private void assertNotPortal(CaptivePortalProbeResult result) {
637 assertFalse(result.isPortal());
638 assertFalse(result.isFailed());
639 assertTrue(result.isSuccessful());
640 }
641
642 private void assertFailed(CaptivePortalProbeResult result) {
643 assertFalse(result.isPortal());
644 assertTrue(result.isFailed());
645 assertFalse(result.isSuccessful());
646 }
647
648 private void setSslException(HttpURLConnection connection) throws IOException {
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900649 doThrow(new SSLHandshakeException("Invalid cert")).when(connection).getResponseCode();
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +0900650 }
651
652 private void set302(HttpURLConnection connection, String location) throws IOException {
653 setStatus(connection, 302);
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900654 doReturn(location).when(connection).getHeaderField(LOCATION_HEADER);
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +0900655 }
656
657 private void setPortal302(HttpURLConnection connection) throws IOException {
658 set302(connection, "http://login.example.com");
659 }
660
661 private void setStatus(HttpURLConnection connection, int status) throws IOException {
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900662 doReturn(status).when(connection).getResponseCode();
Hugo Benichic894b122017-07-31 12:58:20 +0900663 }
Chiachang Wang95489ca2019-02-26 11:32:18 +0800664
665 private void generateTimeoutDnsEvent(DataStallDetectionStats.Builder stats, int num) {
666 for (int i = 0; i < num; i++) {
667 stats.addDnsEvent(RETURN_CODE_DNS_TIMEOUT, 123456789 /* timeMs */);
668 }
669 }
Hugo Benichic894b122017-07-31 12:58:20 +0900670}
671