From d90ef66111945891014b091960dd8de81cb717ca Mon Sep 17 00:00:00 2001 From: Alex Babrykovich Date: Tue, 18 Apr 2023 15:58:39 +0200 Subject: [PATCH] fix(sdk): missing webview (#108) --- CHANGES.md | 4 +++ sdk/build.gradle | 4 +-- .../sdk/HCaptchaDialogFragmentTest.java | 4 +-- .../main/java/com/hcaptcha/sdk/HCaptcha.java | 32 ++++++++++++------- .../hcaptcha/sdk/HCaptchaDialogFragment.java | 4 +-- .../java/com/hcaptcha/sdk/HCaptchaTest.java | 30 +++++++++++++++++ 6 files changed, 60 insertions(+), 18 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 4c278fc0..cb91ccf0 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,9 @@ # Changelog +# 3.8.1 + +- Bugfix: report error when missing WebView provider + # 3.8.0 - Feat: new `HCaptcha.reset` to force stop verification and release all resources. diff --git a/sdk/build.gradle b/sdk/build.gradle index 322d8ad9..f496d761 100644 --- a/sdk/build.gradle +++ b/sdk/build.gradle @@ -22,11 +22,11 @@ android { // See https://developer.android.com/studio/publish/versioning // versionCode must be integer and be incremented by one for every new update // android system uses this to prevent downgrades - versionCode 32 + versionCode 33 // version number visible to the user // should follow semantic versioning (See https://semver.org) - versionName "3.8.0" + versionName "3.8.1" buildConfigField 'String', 'VERSION_NAME', "\"${defaultConfig.versionName}_${defaultConfig.versionCode}\"" diff --git a/sdk/src/androidTest/java/com/hcaptcha/sdk/HCaptchaDialogFragmentTest.java b/sdk/src/androidTest/java/com/hcaptcha/sdk/HCaptchaDialogFragmentTest.java index 2b5c4e04..1f7be016 100644 --- a/sdk/src/androidTest/java/com/hcaptcha/sdk/HCaptchaDialogFragmentTest.java +++ b/sdk/src/androidTest/java/com/hcaptcha/sdk/HCaptchaDialogFragmentTest.java @@ -25,7 +25,7 @@ import static org.mockito.Mockito.when; import android.os.Bundle; -import android.util.AndroidRuntimeException; +import android.view.InflateException; import android.view.LayoutInflater; import androidx.fragment.app.testing.FragmentScenario; import androidx.lifecycle.Lifecycle; @@ -187,7 +187,7 @@ void onFailure(HCaptchaException exception) { public void webViewNotInstalled() throws InterruptedException { final LayoutInflater inflater = mock(LayoutInflater.class); when(inflater.inflate(eq(R.layout.hcaptcha_fragment), any(), eq(false))) - .thenThrow(AndroidRuntimeException.class); + .thenThrow(InflateException.class); final CountDownLatch latch = new CountDownLatch(1); diff --git a/sdk/src/main/java/com/hcaptcha/sdk/HCaptcha.java b/sdk/src/main/java/com/hcaptcha/sdk/HCaptcha.java index 89ae4266..b89e6d73 100644 --- a/sdk/src/main/java/com/hcaptcha/sdk/HCaptcha.java +++ b/sdk/src/main/java/com/hcaptcha/sdk/HCaptcha.java @@ -4,6 +4,7 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.os.Bundle; +import android.util.AndroidRuntimeException; import androidx.annotation.Nullable; import androidx.fragment.app.FragmentActivity; @@ -91,16 +92,20 @@ void onFailure(final HCaptchaException exception) { setException(exception); } }; - if (inputConfig.getHideDialog()) { - // Overwrite certain config values in case the dialog is hidden to avoid behavior collision - this.config = inputConfig.toBuilder() - .size(HCaptchaSize.INVISIBLE) - .loading(false) - .build(); - captchaVerifier = new HCaptchaHeadlessWebView(activity, this.config, internalConfig, listener); - } else { - captchaVerifier = HCaptchaDialogFragment.newInstance(inputConfig, internalConfig, listener); - this.config = inputConfig; + try { + if (inputConfig.getHideDialog()) { + // Overwrite certain config values in case the dialog is hidden to avoid behavior collision + this.config = inputConfig.toBuilder() + .size(HCaptchaSize.INVISIBLE) + .loading(false) + .build(); + captchaVerifier = new HCaptchaHeadlessWebView(activity, this.config, internalConfig, listener); + } else { + captchaVerifier = HCaptchaDialogFragment.newInstance(inputConfig, internalConfig, listener); + this.config = inputConfig; + } + } catch (AndroidRuntimeException e) { + listener.onFailure(new HCaptchaException(HCaptchaError.ERROR)); } return this; } @@ -148,8 +153,11 @@ public void reset() { private HCaptcha startVerification() { HCaptchaLog.d("HCaptcha.startVerification"); handler.removeCallbacksAndMessages(null); - assert captchaVerifier != null; - captchaVerifier.startVerification(activity); + if (captchaVerifier == null) { + setException(new HCaptchaException(HCaptchaError.ERROR)); + } else { + captchaVerifier.startVerification(activity); + } return this; } } diff --git a/sdk/src/main/java/com/hcaptcha/sdk/HCaptchaDialogFragment.java b/sdk/src/main/java/com/hcaptcha/sdk/HCaptchaDialogFragment.java index f874b762..15469609 100644 --- a/sdk/src/main/java/com/hcaptcha/sdk/HCaptchaDialogFragment.java +++ b/sdk/src/main/java/com/hcaptcha/sdk/HCaptchaDialogFragment.java @@ -9,7 +9,7 @@ import android.os.Bundle; import android.os.Handler; import android.os.Looper; -import android.util.AndroidRuntimeException; +import android.view.InflateException; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.View; @@ -120,7 +120,7 @@ public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, loadingContainer.setVisibility(config.getLoading() ? View.VISIBLE : View.GONE); webViewHelper = new HCaptchaWebViewHelper(new Handler(Looper.getMainLooper()), requireContext(), config, internalConfig, this, listener, webView); - } catch (AndroidRuntimeException | ClassCastException e) { + } catch (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 diff --git a/sdk/src/test/java/com/hcaptcha/sdk/HCaptchaTest.java b/sdk/src/test/java/com/hcaptcha/sdk/HCaptchaTest.java index 04290aae..ac5136d5 100644 --- a/sdk/src/test/java/com/hcaptcha/sdk/HCaptchaTest.java +++ b/sdk/src/test/java/com/hcaptcha/sdk/HCaptchaTest.java @@ -7,15 +7,18 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockConstruction; import static org.mockito.Mockito.mockStatic; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static org.mockito.Mockito.withSettings; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.os.Bundle; +import android.util.AndroidRuntimeException; import androidx.fragment.app.FragmentActivity; import com.hcaptcha.sdk.tasks.OnFailureListener; @@ -28,6 +31,7 @@ import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; +import org.mockito.MockedConstruction; import org.mockito.MockedStatic; import org.mockito.MockitoAnnotations; import org.mockito.junit.MockitoJUnitRunner; @@ -248,4 +252,30 @@ public void test_clear_all_listener() { .addOnOpenListener(onOpenListener) .removeAllListeners(); } + + @Test + public void test_webview_not_installed() throws Exception { + final ApplicationInfo applicationInfo = mock(ApplicationInfo.class); + final Bundle bundle = mock(Bundle.class); + when(bundle.getString(META_SITE_KEY)).thenReturn(HCaptchaConfigTest.MOCK_SITE_KEY); + bundle.putString(META_SITE_KEY, HCaptchaConfigTest.MOCK_SITE_KEY); + applicationInfo.metaData = bundle; + + when(fragmentActivity.getPackageName()).thenReturn(TEST_PACKAGE_NAME); + when(fragmentActivity.getPackageManager()).thenReturn(packageManager); + when(packageManager.getApplicationInfo(TEST_PACKAGE_NAME, PackageManager.GET_META_DATA)) + .thenReturn(applicationInfo); + + try (MockedConstruction mock = mockConstruction(HCaptchaWebView.class, + withSettings().defaultAnswer(invocation -> { + throw new AndroidRuntimeException( + "android.webkit.WebViewFactory$MissingWebViewPackageException"); + }) + )) { + HCaptcha.getClient(fragmentActivity) + .setup(config.toBuilder().hideDialog(true).build()) + .verifyWithHCaptcha() + .addOnFailureListener(e -> assertEquals(HCaptchaError.ERROR, e.getHCaptchaError())); + } + } }