From a75cc049f42173d3778e8fd20e46dc9c18f56336 Mon Sep 17 00:00:00 2001 From: Alex Babrykovich Date: Tue, 24 Oct 2023 09:22:42 +0200 Subject: [PATCH] docs: how to prevent closing challenge upon back press (#132) --- README.md | 13 +++++ .../sdk/HCaptchaDialogFragmentTest.java | 47 ++++++++++++++++++- .../hcaptcha/sdk/HCaptchaDialogFragment.java | 2 +- 3 files changed, 60 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c1d0c18..f107c2a 100644 --- a/README.md +++ b/README.md @@ -315,6 +315,19 @@ No: the SDK depends on WebView, which is a UI component and cannot be instantiat However, the SDK provides a completely silent (invisible to the end-user) mechanism with `hideDialog=true` config + "passive" site key (this is an Enterprise feature). But note that the token request still has to be called from the UI thread. +> How can I prevent the hCaptcha verification from being canceled when the back button is pressed? + +It is possible by specifying `HCaptchaConfig.retryPredicate` as shown in the following code snippet: + +```java +final HCaptchaConfig config = HCaptchaConfig.builder() + .siteKey("YOUR_API_SITE_KEY") + .retryPredicate((config, hCaptchaException) -> { + return hCaptchaException.getHCaptchaError() == HCaptchaError.CHALLENGE_CLOSED; + }) + .build(); +``` + ## For maintainers If you plan to contribute to the repo, please see [MAINTAINERS.md](./MAINTAINERS.md) for detailed build, test, and release instructions. diff --git a/sdk/src/androidTest/java/com/hcaptcha/sdk/HCaptchaDialogFragmentTest.java b/sdk/src/androidTest/java/com/hcaptcha/sdk/HCaptchaDialogFragmentTest.java index e025343..3cd4f57 100644 --- a/sdk/src/androidTest/java/com/hcaptcha/sdk/HCaptchaDialogFragmentTest.java +++ b/sdk/src/androidTest/java/com/hcaptcha/sdk/HCaptchaDialogFragmentTest.java @@ -17,6 +17,7 @@ import static com.hcaptcha.sdk.HCaptchaDialogFragment.KEY_LISTENER; import static org.hamcrest.Matchers.not; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; @@ -32,6 +33,7 @@ import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.MotionEvent; +import android.view.View; import androidx.fragment.app.testing.FragmentScenario; import androidx.lifecycle.Lifecycle; import androidx.test.core.app.ActivityScenario; @@ -394,8 +396,10 @@ public void testBackShouldNotCloseCaptchaWithoutDefaultLoadingIndicator() { scenario.onFragment(fragment -> { final Dialog dialog = fragment.getDialog(); assertNotNull(dialog); + final View rootView = dialog.getWindow().getDecorView().getRootView().findFocus(); + assertNotNull(rootView); final KeyEvent keyEvent = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK); - assertTrue(dialog.dispatchKeyEvent(keyEvent)); + assertTrue(rootView.dispatchKeyEvent(keyEvent)); }); } } @@ -426,4 +430,45 @@ public void testTouchShouldNotCloseCaptchaWithoutDefaultLoadingIndicator() { }); } } + + @Test + public void testBackShouldNotCloseCaptchaWithCustomRetry() { + try (FragmentScenario scenario = launch( + config.toBuilder() + .loading(true) + .retryPredicate((c, e) -> e.getHCaptchaError() == HCaptchaError.CHALLENGE_CLOSED) + .build(), + internalConfig.toBuilder() + .htmlProvider(new HCaptchaTestHtml(false)) + .build(), + new HCaptchaStateTestAdapter())) { + + scenario.onFragment(fragment -> { + final Dialog dialog = fragment.getDialog(); + assertNotNull(dialog); + final View rootView = dialog.getWindow().getDecorView().getRootView().findFocus(); + assertNotNull(rootView); + final KeyEvent keyEvent = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK); + assertTrue(rootView.dispatchKeyEvent(keyEvent)); + }); + } + } + + @Test + public void testNonBackShouldNotCloseCaptcha() throws InterruptedException { + try (FragmentScenario scenario = launch( + config, + internalConfig.toBuilder() + .htmlProvider(new HCaptchaTestHtml(false)) + .build(), + new HCaptchaStateTestAdapter())) { + + scenario.onFragment(fragment -> { + final Dialog dialog = fragment.getDialog(); + assertNotNull(dialog); + final KeyEvent keyEvent = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER); + assertFalse(dialog.dispatchKeyEvent(keyEvent)); + }); + } + } } diff --git a/sdk/src/main/java/com/hcaptcha/sdk/HCaptchaDialogFragment.java b/sdk/src/main/java/com/hcaptcha/sdk/HCaptchaDialogFragment.java index f3222f9..bebd733 100644 --- a/sdk/src/main/java/com/hcaptcha/sdk/HCaptchaDialogFragment.java +++ b/sdk/src/main/java/com/hcaptcha/sdk/HCaptchaDialogFragment.java @@ -122,7 +122,7 @@ public View onCreateView(@Nullable LayoutInflater inflater, requireContext(), config, internalConfig, this, listener, webView); readyForInteraction = false; return rootView; - } catch (BadParcelableException | InflateException | ClassCastException e) { + } catch (AssertionError | BadParcelableException | InflateException | ClassCastException e) { HCaptchaLog.w("Cannot create view. Dismissing dialog..."); // Happens when fragment tries to reconstruct because the activity was killed // And thus there is no way of communicating back