From a1adea66db8b0db68aeeeb03063f3c013ab39348 Mon Sep 17 00:00:00 2001 From: Henrique Silva Date: Thu, 25 Oct 2018 11:12:40 +0100 Subject: [PATCH 01/14] update mockito version --- .idea/codeStyleSettings.xml | 240 ------------------ .idea/compiler.xml | 22 -- .idea/copyright/profiles_settings.xml | 3 - .../code/JUnit4 SetUp Method.java | 4 - .../code/JUnit4 TearDown Method.java | 4 - .../code/JUnit4 Test Method.java | 4 - .idea/fileTemplates/includes/File Header.java | 0 .idea/vcs.xml | 2 +- .../java/com/novoda/merlin/MerlinsBeard.java | 75 +++++- dependencies.gradle | 2 +- 10 files changed, 75 insertions(+), 281 deletions(-) delete mode 100644 .idea/codeStyleSettings.xml delete mode 100644 .idea/compiler.xml delete mode 100644 .idea/copyright/profiles_settings.xml delete mode 100644 .idea/fileTemplates/code/JUnit4 SetUp Method.java delete mode 100644 .idea/fileTemplates/code/JUnit4 TearDown Method.java delete mode 100644 .idea/fileTemplates/code/JUnit4 Test Method.java delete mode 100644 .idea/fileTemplates/includes/File Header.java diff --git a/.idea/codeStyleSettings.xml b/.idea/codeStyleSettings.xml deleted file mode 100644 index 686f0ff..0000000 --- a/.idea/codeStyleSettings.xml +++ /dev/null @@ -1,240 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml deleted file mode 100644 index 96cc43e..0000000 --- a/.idea/compiler.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml deleted file mode 100644 index e7bedf3..0000000 --- a/.idea/copyright/profiles_settings.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/.idea/fileTemplates/code/JUnit4 SetUp Method.java b/.idea/fileTemplates/code/JUnit4 SetUp Method.java deleted file mode 100644 index 153cb3c..0000000 --- a/.idea/fileTemplates/code/JUnit4 SetUp Method.java +++ /dev/null @@ -1,4 +0,0 @@ -@org.junit.Before -public void setUp() { - ${BODY} -} diff --git a/.idea/fileTemplates/code/JUnit4 TearDown Method.java b/.idea/fileTemplates/code/JUnit4 TearDown Method.java deleted file mode 100644 index d877e38..0000000 --- a/.idea/fileTemplates/code/JUnit4 TearDown Method.java +++ /dev/null @@ -1,4 +0,0 @@ -@org.junit.After -public void tearDown() { - ${BODY} -} diff --git a/.idea/fileTemplates/code/JUnit4 Test Method.java b/.idea/fileTemplates/code/JUnit4 Test Method.java deleted file mode 100644 index c06383b..0000000 --- a/.idea/fileTemplates/code/JUnit4 Test Method.java +++ /dev/null @@ -1,4 +0,0 @@ -@org.junit.Test -public void ${NAME}() { - ${BODY} -} diff --git a/.idea/fileTemplates/includes/File Header.java b/.idea/fileTemplates/includes/File Header.java deleted file mode 100644 index e69de29..0000000 diff --git a/.idea/vcs.xml b/.idea/vcs.xml index 35eb1dd..94a25f7 100644 --- a/.idea/vcs.xml +++ b/.idea/vcs.xml @@ -1,6 +1,6 @@ - + \ No newline at end of file diff --git a/core/src/main/java/com/novoda/merlin/MerlinsBeard.java b/core/src/main/java/com/novoda/merlin/MerlinsBeard.java index 7cf6ddd..10c5c75 100644 --- a/core/src/main/java/com/novoda/merlin/MerlinsBeard.java +++ b/core/src/main/java/com/novoda/merlin/MerlinsBeard.java @@ -5,7 +5,13 @@ import android.net.ConnectivityManager; import android.net.Network; import android.net.NetworkInfo; +import android.os.AsyncTask; import android.os.Build; +import android.util.Log; + +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.URL; /** * This class provides a mechanism for retrieving the current @@ -14,9 +20,12 @@ public class MerlinsBeard { private static final boolean IS_NOT_CONNECTED_TO_NETWORK_TYPE = false; + private static final String ENDPOINT = "http://clients3.google.com/generate_204"; + private static final int WALLED_GARDEN_SOCKET_TIMEOUT_MS = 10000; private final ConnectivityManager connectivityManager; private final AndroidVersion androidVersion; + private final EndpointPinger pinger; /** * Use this method to create a MerlinsBeard object, this is how you can retrieve the current network state. @@ -27,12 +36,14 @@ public class MerlinsBeard { public static MerlinsBeard from(Context context) { ConnectivityManager connectivityManager = (ConnectivityManager) context.getApplicationContext().getSystemService(Context.CONNECTIVITY_SERVICE); AndroidVersion androidVersion = new AndroidVersion(); - return new MerlinsBeard(connectivityManager, androidVersion); + EndpointPinger pinger = EndpointPinger.withCustomEndpointAndValidation(Endpoint.from(ENDPOINT), new ResponseCodeValidator.DefaultEndpointResponseCodeValidator()); + return new MerlinsBeard(connectivityManager, androidVersion, pinger); } - MerlinsBeard(ConnectivityManager connectivityManager, AndroidVersion androidVersion) { + MerlinsBeard(ConnectivityManager connectivityManager, AndroidVersion androidVersion, EndpointPinger pinger) { this.connectivityManager = connectivityManager; this.androidVersion = androidVersion; + this.pinger = pinger; } /** @@ -116,4 +127,64 @@ public String getMobileNetworkSubtypeName() { return networkInfo.getSubtypeName(); } + + /** + * Provides a boolean representing whether the device is behind a captive portal with restricted network access. + * + * @return boolean true if device is behind a captive portal. + */ + private static boolean isWalledGardenConnection(){ + + HttpURLConnection urlConnection = null; + try { + URL url = new URL(ENDPOINT); + urlConnection = (HttpURLConnection) url.openConnection(); + urlConnection.setInstanceFollowRedirects(false); + urlConnection.setConnectTimeout(WALLED_GARDEN_SOCKET_TIMEOUT_MS); + urlConnection.setReadTimeout(WALLED_GARDEN_SOCKET_TIMEOUT_MS); + urlConnection.setUseCaches(false); + urlConnection.getInputStream(); + // We got a valid response, but not from the real google + return urlConnection.getResponseCode() != 204; + } catch (IOException e) { + if (BuildConfig.DEBUG) { + Log.d("MerlinsBeard", "Captive Portal check - probably not a portal: exception ", e); + } + return false; + } finally { + if (urlConnection != null) { + urlConnection.disconnect(); + } + } + } + + + public static void isCaptivePortal(final EndpointPinger.PingerCallback onResultCallback){ + + new AsyncTask() { + @Override + protected Boolean doInBackground(Void... voids) { + return isWalledGardenConnection(); + } + + @Override + protected void onPostExecute(Boolean result) { + if(result) + onResultCallback.onSuccess(); + else + onResultCallback.onFailure(); + } + }.execute(); + } + + + /** + * Provides a boolean representing whether the device is behind a captive portal with restricted network access. + * + * @return boolean true if device is behind a captive portal. + */ + public void isCaptivePortal2(EndpointPinger.PingerCallback pingerCallback){ + pinger.ping(pingerCallback); + } + } diff --git a/dependencies.gradle b/dependencies.gradle index 888ff96..59e3fec 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -20,7 +20,7 @@ ext { ], test : [ jUnit : 'junit:junit:4.12', - mockito: 'org.mockito:mockito-core:2.15.0', + mockito: 'org.mockito:mockito-core:2.23.0', truth : 'com.google.truth:truth:0.39' ] ] From 9f56a62fa7248928ab3ada6be3b32912438256fd Mon Sep 17 00:00:00 2001 From: Henrique Silva Date: Thu, 25 Oct 2018 11:16:16 +0100 Subject: [PATCH 02/14] Detect captive portal --- .../java/com/novoda/merlin/MerlinsBeard.java | 55 +------------------ .../com/novoda/merlin/MerlinsBeardTest.java | 16 +++++- 2 files changed, 17 insertions(+), 54 deletions(-) diff --git a/core/src/main/java/com/novoda/merlin/MerlinsBeard.java b/core/src/main/java/com/novoda/merlin/MerlinsBeard.java index 10c5c75..f01908c 100644 --- a/core/src/main/java/com/novoda/merlin/MerlinsBeard.java +++ b/core/src/main/java/com/novoda/merlin/MerlinsBeard.java @@ -127,63 +127,12 @@ public String getMobileNetworkSubtypeName() { return networkInfo.getSubtypeName(); } - /** - * Provides a boolean representing whether the device is behind a captive portal with restricted network access. * - * @return boolean true if device is behind a captive portal. - */ - private static boolean isWalledGardenConnection(){ - - HttpURLConnection urlConnection = null; - try { - URL url = new URL(ENDPOINT); - urlConnection = (HttpURLConnection) url.openConnection(); - urlConnection.setInstanceFollowRedirects(false); - urlConnection.setConnectTimeout(WALLED_GARDEN_SOCKET_TIMEOUT_MS); - urlConnection.setReadTimeout(WALLED_GARDEN_SOCKET_TIMEOUT_MS); - urlConnection.setUseCaches(false); - urlConnection.getInputStream(); - // We got a valid response, but not from the real google - return urlConnection.getResponseCode() != 204; - } catch (IOException e) { - if (BuildConfig.DEBUG) { - Log.d("MerlinsBeard", "Captive Portal check - probably not a portal: exception ", e); - } - return false; - } finally { - if (urlConnection != null) { - urlConnection.disconnect(); - } - } - } - - - public static void isCaptivePortal(final EndpointPinger.PingerCallback onResultCallback){ - - new AsyncTask() { - @Override - protected Boolean doInBackground(Void... voids) { - return isWalledGardenConnection(); - } - - @Override - protected void onPostExecute(Boolean result) { - if(result) - onResultCallback.onSuccess(); - else - onResultCallback.onFailure(); - } - }.execute(); - } - - - /** - * Provides a boolean representing whether the device is behind a captive portal with restricted network access. * - * @return boolean true if device is behind a captive portal. + * */ - public void isCaptivePortal2(EndpointPinger.PingerCallback pingerCallback){ + public void isCaptivePortal(EndpointPinger.PingerCallback pingerCallback){ pinger.ping(pingerCallback); } diff --git a/core/src/test/java/com/novoda/merlin/MerlinsBeardTest.java b/core/src/test/java/com/novoda/merlin/MerlinsBeardTest.java index ad761ca..5d68288 100644 --- a/core/src/test/java/com/novoda/merlin/MerlinsBeardTest.java +++ b/core/src/test/java/com/novoda/merlin/MerlinsBeardTest.java @@ -5,12 +5,18 @@ import android.net.Network; import android.net.NetworkInfo; import android.os.Build; +import android.view.ViewOutlineProvider; import org.junit.Before; import org.junit.Test; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; import static com.google.common.truth.Truth.assertThat; import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.*; import static org.mockito.Mockito.mock; public class MerlinsBeardTest { @@ -24,12 +30,14 @@ public class MerlinsBeardTest { private final ConnectivityManager connectivityManager = mock(ConnectivityManager.class); private final NetworkInfo networkInfo = mock(NetworkInfo.class); private final AndroidVersion androidVersion = mock(AndroidVersion.class); + private final EndpointPinger endpointPinger = mock(EndpointPinger.class); + private final EndpointPinger.PingerCallback mockPingerCallback = mock(EndpointPinger.PingerCallback.class); private MerlinsBeard merlinsBeard; @Before public void setUp() { - merlinsBeard = new MerlinsBeard(connectivityManager, androidVersion); + merlinsBeard = new MerlinsBeard(connectivityManager, androidVersion, endpointPinger); } @Test @@ -138,6 +146,12 @@ public void givenNetworkIsDisconnected_andAndroidVersionIsBelowLollipop_whenChec assertThat(connectedToMobileNetwork).isFalse(); } + @Test + public void givenPingerCallback_whenCheckingCaptivePortal_thenCallsPingerCallback(){ + merlinsBeard.isCaptivePortal(mockPingerCallback); + then(endpointPinger).should().ping(mockPingerCallback); + } + @TargetApi(Build.VERSION_CODES.LOLLIPOP) @Test public void givenNetworkIsConnectedViaMobile_andAndroidVersionIsLollipopOrAbove_whenCheckingIfConnectedToMobile_thenReturnsTrue() { From 373e62b9feb732f6dbaf3e7d4cfa5225c5e92673 Mon Sep 17 00:00:00 2001 From: Henrique Silva Date: Thu, 25 Oct 2018 15:40:11 +0100 Subject: [PATCH 03/14] Captive portal detection (synchronous version) --- .../main/java/com/novoda/merlin/Endpoint.java | 2 +- .../java/com/novoda/merlin/MerlinsBeard.java | 26 ++++++++++++++----- .../com/novoda/merlin/MerlinsBeardTest.java | 17 +++++++++++- 3 files changed, 37 insertions(+), 8 deletions(-) diff --git a/core/src/main/java/com/novoda/merlin/Endpoint.java b/core/src/main/java/com/novoda/merlin/Endpoint.java index 8ba8c14..5eee7c5 100644 --- a/core/src/main/java/com/novoda/merlin/Endpoint.java +++ b/core/src/main/java/com/novoda/merlin/Endpoint.java @@ -5,7 +5,7 @@ public class Endpoint { - private static final Endpoint DEFAULT_ENDPOINT = Endpoint.from("http://connectivitycheck.android.com/generate_204"); + private static final Endpoint DEFAULT_ENDPOINT = Endpoint.from("https://connectivitycheck.android.com/generate_204"); private final String endpoint; diff --git a/core/src/main/java/com/novoda/merlin/MerlinsBeard.java b/core/src/main/java/com/novoda/merlin/MerlinsBeard.java index f01908c..23ca72c 100644 --- a/core/src/main/java/com/novoda/merlin/MerlinsBeard.java +++ b/core/src/main/java/com/novoda/merlin/MerlinsBeard.java @@ -7,6 +7,7 @@ import android.net.NetworkInfo; import android.os.AsyncTask; import android.os.Build; +import android.support.annotation.WorkerThread; import android.util.Log; import java.io.IOException; @@ -26,6 +27,7 @@ public class MerlinsBeard { private final ConnectivityManager connectivityManager; private final AndroidVersion androidVersion; private final EndpointPinger pinger; + private final Ping ping; /** * Use this method to create a MerlinsBeard object, this is how you can retrieve the current network state. @@ -36,14 +38,18 @@ public class MerlinsBeard { public static MerlinsBeard from(Context context) { ConnectivityManager connectivityManager = (ConnectivityManager) context.getApplicationContext().getSystemService(Context.CONNECTIVITY_SERVICE); AndroidVersion androidVersion = new AndroidVersion(); - EndpointPinger pinger = EndpointPinger.withCustomEndpointAndValidation(Endpoint.from(ENDPOINT), new ResponseCodeValidator.DefaultEndpointResponseCodeValidator()); - return new MerlinsBeard(connectivityManager, androidVersion, pinger); + ResponseCodeValidator validator = new ResponseCodeValidator.DefaultEndpointResponseCodeValidator(); + EndpointPinger pinger = EndpointPinger.withCustomEndpointAndValidation(Endpoint.from(ENDPOINT), validator); + Ping ping = new Ping(Endpoint.defaultEndpoint(), new EndpointPinger.ResponseCodeFetcher(), validator); + + return new MerlinsBeard(connectivityManager, androidVersion, pinger, ping); } - MerlinsBeard(ConnectivityManager connectivityManager, AndroidVersion androidVersion, EndpointPinger pinger) { + MerlinsBeard(ConnectivityManager connectivityManager, AndroidVersion androidVersion, EndpointPinger pinger, Ping ping) { this.connectivityManager = connectivityManager; this.androidVersion = androidVersion; this.pinger = pinger; + this.ping = ping; } /** @@ -128,12 +134,20 @@ public String getMobileNetworkSubtypeName() { } /** - * - * - * + * Detects if client is behind a captive portal. + * @param pingerCallback to call with success or failure. */ public void isCaptivePortal(EndpointPinger.PingerCallback pingerCallback){ pinger.ping(pingerCallback); } + /** + * Detects if client is behind a captive portal - synchronously. + * @return Boolean result representing if client is behind a captive portal. + */ + @WorkerThread + public boolean isCaptivePortal(){ + return !ping.doSynchronousPing(); + } + } diff --git a/core/src/test/java/com/novoda/merlin/MerlinsBeardTest.java b/core/src/test/java/com/novoda/merlin/MerlinsBeardTest.java index 5d68288..d98974d 100644 --- a/core/src/test/java/com/novoda/merlin/MerlinsBeardTest.java +++ b/core/src/test/java/com/novoda/merlin/MerlinsBeardTest.java @@ -32,12 +32,13 @@ public class MerlinsBeardTest { private final AndroidVersion androidVersion = mock(AndroidVersion.class); private final EndpointPinger endpointPinger = mock(EndpointPinger.class); private final EndpointPinger.PingerCallback mockPingerCallback = mock(EndpointPinger.PingerCallback.class); + private final Ping mockPing = mock(Ping.class); private MerlinsBeard merlinsBeard; @Before public void setUp() { - merlinsBeard = new MerlinsBeard(connectivityManager, androidVersion, endpointPinger); + merlinsBeard = new MerlinsBeard(connectivityManager, androidVersion, endpointPinger, mockPing); } @Test @@ -152,6 +153,20 @@ public void givenPingerCallback_whenCheckingCaptivePortal_thenCallsPingerCallbac then(endpointPinger).should().ping(mockPingerCallback); } + @Test + public void givenSuccessfulPing_whenCheckingCaptivePortal_thenReturnFailure() { + given(mockPing.doSynchronousPing()).willReturn(true); + boolean result = merlinsBeard.isCaptivePortal(); + assertThat(result).isFalse(); + } + + @Test + public void givenFailedPing_whenCheckingCaptivePortal_thenReturnSuccess() { + given(mockPing.doSynchronousPing()).willReturn(false); + boolean result = merlinsBeard.isCaptivePortal(); + assertThat(result).isTrue(); + } + @TargetApi(Build.VERSION_CODES.LOLLIPOP) @Test public void givenNetworkIsConnectedViaMobile_andAndroidVersionIsLollipopOrAbove_whenCheckingIfConnectedToMobile_thenReturnsTrue() { From bd6c08e13ba5d058526f5008efde577ca6332074 Mon Sep 17 00:00:00 2001 From: Henrique Silva Date: Thu, 25 Oct 2018 16:02:14 +0100 Subject: [PATCH 04/14] revert project files --- .idea/codeStyleSettings.xml | 240 ++++++++++++++++++ .idea/compiler.xml | 22 ++ .idea/copyright/profiles_settings.xml | 3 + .../code/JUnit4 SetUp Method.java | 4 + .../code/JUnit4 TearDown Method.java | 4 + .../code/JUnit4 Test Method.java | 4 + .idea/fileTemplates/includes/File Header.java | 0 .idea/vcs.xml | 2 +- 8 files changed, 278 insertions(+), 1 deletion(-) create mode 100644 .idea/codeStyleSettings.xml create mode 100644 .idea/compiler.xml create mode 100644 .idea/copyright/profiles_settings.xml create mode 100644 .idea/fileTemplates/code/JUnit4 SetUp Method.java create mode 100644 .idea/fileTemplates/code/JUnit4 TearDown Method.java create mode 100644 .idea/fileTemplates/code/JUnit4 Test Method.java create mode 100644 .idea/fileTemplates/includes/File Header.java diff --git a/.idea/codeStyleSettings.xml b/.idea/codeStyleSettings.xml new file mode 100644 index 0000000..686f0ff --- /dev/null +++ b/.idea/codeStyleSettings.xml @@ -0,0 +1,240 @@ + + + + + + \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..96cc43e --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml new file mode 100644 index 0000000..e7bedf3 --- /dev/null +++ b/.idea/copyright/profiles_settings.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/.idea/fileTemplates/code/JUnit4 SetUp Method.java b/.idea/fileTemplates/code/JUnit4 SetUp Method.java new file mode 100644 index 0000000..153cb3c --- /dev/null +++ b/.idea/fileTemplates/code/JUnit4 SetUp Method.java @@ -0,0 +1,4 @@ +@org.junit.Before +public void setUp() { + ${BODY} +} diff --git a/.idea/fileTemplates/code/JUnit4 TearDown Method.java b/.idea/fileTemplates/code/JUnit4 TearDown Method.java new file mode 100644 index 0000000..d877e38 --- /dev/null +++ b/.idea/fileTemplates/code/JUnit4 TearDown Method.java @@ -0,0 +1,4 @@ +@org.junit.After +public void tearDown() { + ${BODY} +} diff --git a/.idea/fileTemplates/code/JUnit4 Test Method.java b/.idea/fileTemplates/code/JUnit4 Test Method.java new file mode 100644 index 0000000..c06383b --- /dev/null +++ b/.idea/fileTemplates/code/JUnit4 Test Method.java @@ -0,0 +1,4 @@ +@org.junit.Test +public void ${NAME}() { + ${BODY} +} diff --git a/.idea/fileTemplates/includes/File Header.java b/.idea/fileTemplates/includes/File Header.java new file mode 100644 index 0000000..e69de29 diff --git a/.idea/vcs.xml b/.idea/vcs.xml index 94a25f7..35eb1dd 100644 --- a/.idea/vcs.xml +++ b/.idea/vcs.xml @@ -1,6 +1,6 @@ - + \ No newline at end of file From b6c2d0af251e6b25769c3db33e635a8cf46d6f29 Mon Sep 17 00:00:00 2001 From: Henrique Silva Date: Thu, 25 Oct 2018 16:10:59 +0100 Subject: [PATCH 05/14] removed unnecessary refs --- core/src/main/java/com/novoda/merlin/MerlinsBeard.java | 1 - 1 file changed, 1 deletion(-) diff --git a/core/src/main/java/com/novoda/merlin/MerlinsBeard.java b/core/src/main/java/com/novoda/merlin/MerlinsBeard.java index 23ca72c..38dc5cb 100644 --- a/core/src/main/java/com/novoda/merlin/MerlinsBeard.java +++ b/core/src/main/java/com/novoda/merlin/MerlinsBeard.java @@ -22,7 +22,6 @@ public class MerlinsBeard { private static final boolean IS_NOT_CONNECTED_TO_NETWORK_TYPE = false; private static final String ENDPOINT = "http://clients3.google.com/generate_204"; - private static final int WALLED_GARDEN_SOCKET_TIMEOUT_MS = 10000; private final ConnectivityManager connectivityManager; private final AndroidVersion androidVersion; From 442abc48f0b4307b6b69cc9e24f05d6f6928fb5b Mon Sep 17 00:00:00 2001 From: Ryan Feline Date: Thu, 25 Oct 2018 17:02:11 +0100 Subject: [PATCH 06/14] use default. --- .../main/java/com/novoda/merlin/MerlinsBeard.java | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/core/src/main/java/com/novoda/merlin/MerlinsBeard.java b/core/src/main/java/com/novoda/merlin/MerlinsBeard.java index 38dc5cb..629621d 100644 --- a/core/src/main/java/com/novoda/merlin/MerlinsBeard.java +++ b/core/src/main/java/com/novoda/merlin/MerlinsBeard.java @@ -5,14 +5,8 @@ import android.net.ConnectivityManager; import android.net.Network; import android.net.NetworkInfo; -import android.os.AsyncTask; import android.os.Build; import android.support.annotation.WorkerThread; -import android.util.Log; - -import java.io.IOException; -import java.net.HttpURLConnection; -import java.net.URL; /** * This class provides a mechanism for retrieving the current @@ -21,7 +15,6 @@ public class MerlinsBeard { private static final boolean IS_NOT_CONNECTED_TO_NETWORK_TYPE = false; - private static final String ENDPOINT = "http://clients3.google.com/generate_204"; private final ConnectivityManager connectivityManager; private final AndroidVersion androidVersion; @@ -38,7 +31,7 @@ public static MerlinsBeard from(Context context) { ConnectivityManager connectivityManager = (ConnectivityManager) context.getApplicationContext().getSystemService(Context.CONNECTIVITY_SERVICE); AndroidVersion androidVersion = new AndroidVersion(); ResponseCodeValidator validator = new ResponseCodeValidator.DefaultEndpointResponseCodeValidator(); - EndpointPinger pinger = EndpointPinger.withCustomEndpointAndValidation(Endpoint.from(ENDPOINT), validator); + EndpointPinger pinger = EndpointPinger.withCustomEndpointAndValidation(Endpoint.defaultEndpoint(), validator); Ping ping = new Ping(Endpoint.defaultEndpoint(), new EndpointPinger.ResponseCodeFetcher(), validator); return new MerlinsBeard(connectivityManager, androidVersion, pinger, ping); @@ -134,18 +127,20 @@ public String getMobileNetworkSubtypeName() { /** * Detects if client is behind a captive portal. + * * @param pingerCallback to call with success or failure. */ - public void isCaptivePortal(EndpointPinger.PingerCallback pingerCallback){ + public void isCaptivePortal(EndpointPinger.PingerCallback pingerCallback) { pinger.ping(pingerCallback); } /** * Detects if client is behind a captive portal - synchronously. + * * @return Boolean result representing if client is behind a captive portal. */ @WorkerThread - public boolean isCaptivePortal(){ + public boolean isCaptivePortal() { return !ping.doSynchronousPing(); } From 1e313b14c34b85a3543c1cae07957ec86194a84c Mon Sep 17 00:00:00 2001 From: Henrique Silva Date: Fri, 26 Oct 2018 12:09:13 +0100 Subject: [PATCH 07/14] introduce a captive portal interface and update demo --- .../java/com/novoda/merlin/MerlinsBeard.java | 20 ++++++++++++++++--- .../com/novoda/merlin/MerlinsBeardTest.java | 20 +++++++++++++------ .../demo/presentation/DemoActivity.java | 17 ++++++++++++++++ .../presentation/RxJava2DemoActivity.java | 18 +++++++++++++++++ .../demo/presentation/RxJavaDemoActivity.java | 17 ++++++++++++++++ demo/src/main/res/layout/main.xml | 7 +++++++ demo/src/main/res/values/strings.xml | 5 +++++ 7 files changed, 95 insertions(+), 9 deletions(-) diff --git a/core/src/main/java/com/novoda/merlin/MerlinsBeard.java b/core/src/main/java/com/novoda/merlin/MerlinsBeard.java index 38dc5cb..0b9c97e 100644 --- a/core/src/main/java/com/novoda/merlin/MerlinsBeard.java +++ b/core/src/main/java/com/novoda/merlin/MerlinsBeard.java @@ -134,10 +134,20 @@ public String getMobileNetworkSubtypeName() { /** * Detects if client is behind a captive portal. - * @param pingerCallback to call with success or failure. + * @param callback to call with boolean result representing if client is behind a captive portal. */ - public void isCaptivePortal(EndpointPinger.PingerCallback pingerCallback){ - pinger.ping(pingerCallback); + public void isCaptivePortal(final CaptivePortalDetectionCallback callback){ + pinger.ping(new EndpointPinger.PingerCallback() { + @Override + public void onSuccess() { + callback.onResult(false); + } + + @Override + public void onFailure() { + callback.onResult(true); + } + }); } /** @@ -149,4 +159,8 @@ public boolean isCaptivePortal(){ return !ping.doSynchronousPing(); } + public interface CaptivePortalDetectionCallback{ + void onResult(boolean isCaptivePortal); + } + } diff --git a/core/src/test/java/com/novoda/merlin/MerlinsBeardTest.java b/core/src/test/java/com/novoda/merlin/MerlinsBeardTest.java index d98974d..c329037 100644 --- a/core/src/test/java/com/novoda/merlin/MerlinsBeardTest.java +++ b/core/src/test/java/com/novoda/merlin/MerlinsBeardTest.java @@ -5,12 +5,9 @@ import android.net.Network; import android.net.NetworkInfo; import android.os.Build; -import android.view.ViewOutlineProvider; import org.junit.Before; import org.junit.Test; -import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; @@ -32,6 +29,7 @@ public class MerlinsBeardTest { private final AndroidVersion androidVersion = mock(AndroidVersion.class); private final EndpointPinger endpointPinger = mock(EndpointPinger.class); private final EndpointPinger.PingerCallback mockPingerCallback = mock(EndpointPinger.PingerCallback.class); + private final MerlinsBeard.CaptivePortalDetectionCallback mockCaptivePortalCallback = mock(MerlinsBeard.CaptivePortalDetectionCallback.class); private final Ping mockPing = mock(Ping.class); private MerlinsBeard merlinsBeard; @@ -148,9 +146,19 @@ public void givenNetworkIsDisconnected_andAndroidVersionIsBelowLollipop_whenChec } @Test - public void givenPingerCallback_whenCheckingCaptivePortal_thenCallsPingerCallback(){ - merlinsBeard.isCaptivePortal(mockPingerCallback); - then(endpointPinger).should().ping(mockPingerCallback); + public void givenCaptivePortalCallback_whenCheckingCaptivePortal_thenCallsCaptivePortalCallback(){ + willAnswer(new Answer() { + @Override + public EndpointPinger.PingerCallback answer(InvocationOnMock invocation) throws Throwable { + EndpointPinger.PingerCallback cb = invocation.getArgument(0); + cb.onSuccess(); + return cb; + } + }).given(endpointPinger).ping(any(EndpointPinger.PingerCallback.class)); + + merlinsBeard.isCaptivePortal(mockCaptivePortalCallback); + + then(mockCaptivePortalCallback).should().onResult(false); } @Test diff --git a/demo/src/main/java/com/novoda/merlin/demo/presentation/DemoActivity.java b/demo/src/main/java/com/novoda/merlin/demo/presentation/DemoActivity.java index 58d22b0..a53ce0a 100644 --- a/demo/src/main/java/com/novoda/merlin/demo/presentation/DemoActivity.java +++ b/demo/src/main/java/com/novoda/merlin/demo/presentation/DemoActivity.java @@ -30,6 +30,7 @@ protected void onCreate(Bundle savedInstanceState) { networkStatusDisplayer = new NetworkStatusDisplayer(getResources(), merlinsBeard); findViewById(R.id.current_status).setOnClickListener(networkStatusOnClick); + findViewById(R.id.is_captive_portal).setOnClickListener(captivePortalClick); findViewById(R.id.wifi_connected).setOnClickListener(wifiConnectedOnClick); findViewById(R.id.mobile_connected).setOnClickListener(mobileConnectedOnClick); findViewById(R.id.network_subtype).setOnClickListener(networkSubtypeOnClick); @@ -47,6 +48,22 @@ public void onClick(View v) { } }; + private final View.OnClickListener captivePortalClick = new View.OnClickListener() { + @Override + public void onClick(final View view) { + merlinsBeard.isCaptivePortal(new MerlinsBeard.CaptivePortalDetectionCallback() { + @Override + public void onResult(boolean isCaptivePortal) { + if (!isCaptivePortal) { + networkStatusDisplayer.displayPositiveMessage(R.string.is_captive_portal_false, viewToAttachDisplayerTo); + } else { + networkStatusDisplayer.displayNegativeMessage(R.string.is_captive_portal_true, viewToAttachDisplayerTo); + } + } + }); + } + }; + private final View.OnClickListener wifiConnectedOnClick = new View.OnClickListener() { @Override diff --git a/demo/src/main/java/com/novoda/merlin/demo/presentation/RxJava2DemoActivity.java b/demo/src/main/java/com/novoda/merlin/demo/presentation/RxJava2DemoActivity.java index 5140e93..b9c8815 100644 --- a/demo/src/main/java/com/novoda/merlin/demo/presentation/RxJava2DemoActivity.java +++ b/demo/src/main/java/com/novoda/merlin/demo/presentation/RxJava2DemoActivity.java @@ -23,6 +23,7 @@ public class RxJava2DemoActivity extends AppCompatActivity { private CompositeDisposable disposables; private View viewToAttachDisplayerTo; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -33,6 +34,7 @@ protected void onCreate(Bundle savedInstanceState) { disposables = new CompositeDisposable(); findViewById(R.id.current_status).setOnClickListener(networkStatusOnClick); + findViewById(R.id.is_captive_portal).setOnClickListener(captivePortalClick); findViewById(R.id.wifi_connected).setOnClickListener(wifiConnectedOnClick); findViewById(R.id.mobile_connected).setOnClickListener(mobileConnectedOnClick); findViewById(R.id.network_subtype).setOnClickListener(networkSubtypeOnClick); @@ -62,6 +64,22 @@ public void onClick(View view) { } }; + private final View.OnClickListener captivePortalClick = new View.OnClickListener() { + @Override + public void onClick(final View view) { + merlinsBeard.isCaptivePortal(new MerlinsBeard.CaptivePortalDetectionCallback() { + @Override + public void onResult(boolean isCaptivePortal) { + if (!isCaptivePortal) { + networkStatusDisplayer.displayPositiveMessage(R.string.is_captive_portal_false, viewToAttachDisplayerTo); + } else { + networkStatusDisplayer.displayNegativeMessage(R.string.is_captive_portal_true, viewToAttachDisplayerTo); + } + } + }); + } + }; + private final View.OnClickListener mobileConnectedOnClick = new View.OnClickListener() { @Override diff --git a/demo/src/main/java/com/novoda/merlin/demo/presentation/RxJavaDemoActivity.java b/demo/src/main/java/com/novoda/merlin/demo/presentation/RxJavaDemoActivity.java index df1ddf6..2251b32 100644 --- a/demo/src/main/java/com/novoda/merlin/demo/presentation/RxJavaDemoActivity.java +++ b/demo/src/main/java/com/novoda/merlin/demo/presentation/RxJavaDemoActivity.java @@ -32,6 +32,7 @@ protected void onCreate(Bundle savedInstanceState) { subscriptions = new CompositeSubscription(); findViewById(R.id.current_status).setOnClickListener(networkStatusOnClick); + findViewById(R.id.is_captive_portal).setOnClickListener(captivePortalClick); findViewById(R.id.wifi_connected).setOnClickListener(wifiConnectedOnClick); findViewById(R.id.mobile_connected).setOnClickListener(mobileConnectedOnClick); findViewById(R.id.network_subtype).setOnClickListener(networkSubtypeOnClick); @@ -49,6 +50,22 @@ public void onClick(View v) { } }; + private final View.OnClickListener captivePortalClick = new View.OnClickListener() { + @Override + public void onClick(final View view) { + merlinsBeard.isCaptivePortal(new MerlinsBeard.CaptivePortalDetectionCallback() { + @Override + public void onResult(boolean isCaptivePortal) { + if (!isCaptivePortal) { + networkStatusDisplayer.displayPositiveMessage(R.string.is_captive_portal_false, viewToAttachDisplayerTo); + } else { + networkStatusDisplayer.displayNegativeMessage(R.string.is_captive_portal_true, viewToAttachDisplayerTo); + } + } + }); + } + }; + private final View.OnClickListener wifiConnectedOnClick = new View.OnClickListener() { @Override diff --git a/demo/src/main/res/layout/main.xml b/demo/src/main/res/layout/main.xml index e39a7be..3065acb 100644 --- a/demo/src/main/res/layout/main.xml +++ b/demo/src/main/res/layout/main.xml @@ -21,6 +21,13 @@ android:layout_gravity="center_horizontal" android:text="@string/current_status" /> +