From 9868c61f06d1b342527dc7ddeccd7f547b067d62 Mon Sep 17 00:00:00 2001 From: Alex Babrykovich Date: Wed, 17 May 2023 10:55:02 +0200 Subject: [PATCH] feat(sdk): config to control webview hardware acceleration (#119) --- CHANGES.md | 5 ++ README.md | 47 ++++++++++--------- example-app/build.gradle | 2 +- .../com/hcaptcha/example/MainActivity.java | 3 ++ .../src/main/res/layout/activity_main.xml | 16 +++++-- example-app/src/main/res/values/strings.xml | 1 + sdk/build.gradle | 4 +- .../java/com/hcaptcha/sdk/HCaptchaConfig.java | 7 +++ .../hcaptcha/sdk/HCaptchaWebViewHelper.java | 6 ++- .../com/hcaptcha/sdk/HCaptchaConfigTest.java | 4 ++ .../hcaptcha/sdk/HCaptchaJSInterfaceTest.java | 3 ++ 11 files changed, 67 insertions(+), 31 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 883256f5..aafc7644 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,10 @@ # Changelog +# 3.9.0 + +- Feature: add config to control WebView hardware acceleration `HCaptchaConfig.disableHardwareAcceleration` +- Fix: removed unsafe cast with improved public api + # 3.8.2 - Bugfix: handle BadParcelableException when hCaptcha fragment needs to be recreated due to app resume diff --git a/README.md b/README.md index e7c949be..125c7ffb 100644 --- a/README.md +++ b/README.md @@ -151,29 +151,30 @@ To release all resources you may call `HCaptcha.reset()`. The following list contains configuration properties to allows customization of the hCaptcha verification flow. -| Name | Values/Type | Required | Default | Description | -|-------------------|-------------------------|----------|----------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `siteKey` | String | **Yes** | - | This is your sitekey, this allows you to load challenges. If you need a sitekey, please visit [hCaptcha](https://www.hcaptcha.com), and sign up to get your sitekey. | -| `size` | Enum | No | INVISIBLE | This specifies the "size" of the checkbox component. By default, the checkbox is invisible and the challenge is shown automatically. | -| `orientation` | Enum | No | PORTRAIT | This specifies the "orientation" of the challenge. | -| `theme` | Enum | No | LIGHT | hCaptcha supports light, dark, and contrast themes. | -| `locale` | String (ISO 639-1 code) | No | AUTO | You can enforce a specific language or let hCaptcha auto-detect the local language based on user's device. | -| `resetOnTimeout` | Boolean | No | False | (DEPRECATED, use `retryPredicate`) Automatically reload to fetch new challenge if user does not submit challenge. (Matches iOS SDK behavior.) | -| `retryPredicate` | Lambda | No | - | Automatically trigger a new verification when some error occurs. | -| `jsSrc` | String (URL) | No | https://js.hcaptcha.com/1/api.js | See Enterprise docs. | -| `sentry` | Boolean | No | True | See Enterprise docs. | -| `rqdata` | String | No | - | See Enterprise docs. | -| `apiEndpoint` | String (URL) | No | - | (DEPRECATED, use `jsSrc`) See Enterprise docs. | -| `endpoint` | String (URL) | No | - | See Enterprise docs. | -| `reportapi` | String (URL) | No | - | See Enterprise docs. | -| `assethost` | String (URL) | No | - | See Enterprise docs. | -| `imghost` | String (URL) | No | - | See Enterprise docs. | -| `customTheme` | Stringified JSON | No | - | See Enterprise docs. | -| `host` | String (URL) | No | - | See Enterprise docs. | -| `loading` | Boolean | No | True | Show or hide the loading dialog. | -| `hideDialog` | Boolean | No | False | To be used in combination with a passive sitekey when no user interaction is required. See Enterprise docs. | -| `tokenExpiration` | long | No | 120 | hCaptcha token expiration timeout (seconds). | -| `diagnosticLog` | Boolean | No | False | Emit detailed console logs for debugging | +| Name | Values/Type | Required | Default | Description | +|-------------------------------|-------------------------|----------|----------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `siteKey` | String | **Yes** | - | This is your sitekey, this allows you to load challenges. If you need a sitekey, please visit [hCaptcha](https://www.hcaptcha.com), and sign up to get your sitekey. | +| `size` | Enum | No | INVISIBLE | This specifies the "size" of the checkbox component. By default, the checkbox is invisible and the challenge is shown automatically. | +| `orientation` | Enum | No | PORTRAIT | This specifies the "orientation" of the challenge. | +| `theme` | Enum | No | LIGHT | hCaptcha supports light, dark, and contrast themes. | +| `locale` | String (ISO 639-1 code) | No | AUTO | You can enforce a specific language or let hCaptcha auto-detect the local language based on user's device. | +| `resetOnTimeout` | Boolean | No | False | (DEPRECATED, use `retryPredicate`) Automatically reload to fetch new challenge if user does not submit challenge. (Matches iOS SDK behavior.) | +| `retryPredicate` | Lambda | No | - | Automatically trigger a new verification when some error occurs. | +| `jsSrc` | String (URL) | No | https://js.hcaptcha.com/1/api.js | See Enterprise docs. | +| `sentry` | Boolean | No | True | See Enterprise docs. | +| `rqdata` | String | No | - | See Enterprise docs. | +| `apiEndpoint` | String (URL) | No | - | (DEPRECATED, use `jsSrc`) See Enterprise docs. | +| `endpoint` | String (URL) | No | - | See Enterprise docs. | +| `reportapi` | String (URL) | No | - | See Enterprise docs. | +| `assethost` | String (URL) | No | - | See Enterprise docs. | +| `imghost` | String (URL) | No | - | See Enterprise docs. | +| `customTheme` | Stringified JSON | No | - | See Enterprise docs. | +| `host` | String (URL) | No | - | See Enterprise docs. | +| `loading` | Boolean | No | True | Show or hide the loading dialog. | +| `hideDialog` | Boolean | No | False | To be used in combination with a passive sitekey when no user interaction is required. See Enterprise docs. | +| `tokenExpiration` | long | No | 120 | hCaptcha token expiration timeout (seconds). | +| `diagnosticLog` | Boolean | No | False | Emit detailed console logs for debugging | +| `disableHardwareAcceleration` | Boolean | No | True | Disable WebView hardware acceleration | ### Config Examples diff --git a/example-app/build.gradle b/example-app/build.gradle index d1a229ac..f57c097c 100644 --- a/example-app/build.gradle +++ b/example-app/build.gradle @@ -44,7 +44,7 @@ android { dependencies { //noinspection GradleDependency implementation "androidx.appcompat:appcompat:${prop('exampleAppcompatVersion', '1.3.1')}" - + implementation "com.google.android.flexbox:flexbox:3.0.0" implementation project(path: ':sdk') testImplementation 'junit:junit:4.13.2' diff --git a/example-app/src/main/java/com/hcaptcha/example/MainActivity.java b/example-app/src/main/java/com/hcaptcha/example/MainActivity.java index b13a6031..81758f0b 100644 --- a/example-app/src/main/java/com/hcaptcha/example/MainActivity.java +++ b/example-app/src/main/java/com/hcaptcha/example/MainActivity.java @@ -24,6 +24,7 @@ public class MainActivity extends AppCompatActivity { private Spinner sizeSpinner; private CheckBox hideDialog; private CheckBox loading; + private CheckBox disableHardwareAccel; private TextView tokenTextView; private TextView errorTextView; private HCaptcha hCaptcha; @@ -39,6 +40,7 @@ protected void onCreate(Bundle savedInstanceState) { errorTextView = findViewById(R.id.errorTextView); hideDialog = findViewById(R.id.hide_dialog); loading = findViewById(R.id.loading); + disableHardwareAccel = findViewById(R.id.hwAccel); final ArrayAdapter adapter = new ArrayAdapter<>(this, android.R.layout.simple_spinner_dropdown_item, Arrays.asList(HCaptchaSize.NORMAL, HCaptchaSize.INVISIBLE, HCaptchaSize.COMPACT)); @@ -63,6 +65,7 @@ private HCaptchaConfig getConfig() { .size(size) .loading(loading.isChecked()) .hideDialog(hideDialog.isChecked()) + .disableHardwareAcceleration(disableHardwareAccel.isChecked()) .tokenExpiration(10) .diagnosticLog(true) .retryPredicate((config, exception) -> exception.getHCaptchaError() == HCaptchaError.SESSION_TIMEOUT) diff --git a/example-app/src/main/res/layout/activity_main.xml b/example-app/src/main/res/layout/activity_main.xml index ae01d37a..15d07486 100644 --- a/example-app/src/main/res/layout/activity_main.xml +++ b/example-app/src/main/res/layout/activity_main.xml @@ -1,16 +1,18 @@ - + app:flexDirection="row" + app:flexWrap="wrap"> - + + Mark used Loading Web Debug + Disable HW Accel Hide Dialog diff --git a/sdk/build.gradle b/sdk/build.gradle index d9a45d03..9ea14427 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 34 + versionCode 35 // version number visible to the user // should follow semantic versioning (See https://semver.org) - versionName "3.8.2" + versionName "3.9.0" buildConfigField 'String', 'VERSION_NAME', "\"${defaultConfig.versionName}_${defaultConfig.versionCode}\"" diff --git a/sdk/src/main/java/com/hcaptcha/sdk/HCaptchaConfig.java b/sdk/src/main/java/com/hcaptcha/sdk/HCaptchaConfig.java index fcb97a07..32ff11ad 100644 --- a/sdk/src/main/java/com/hcaptcha/sdk/HCaptchaConfig.java +++ b/sdk/src/main/java/com/hcaptcha/sdk/HCaptchaConfig.java @@ -157,6 +157,13 @@ public class HCaptchaConfig implements Serializable { @Builder.Default private Boolean diagnosticLog = false; + /** + * Disable hardware acceleration for WebView + */ + @Builder.Default + @NonNull + private Boolean disableHardwareAcceleration = true; + /** * @deprecated use {@link #getJsSrc()} getter instead */ diff --git a/sdk/src/main/java/com/hcaptcha/sdk/HCaptchaWebViewHelper.java b/sdk/src/main/java/com/hcaptcha/sdk/HCaptchaWebViewHelper.java index b28d5343..d6e70564 100644 --- a/sdk/src/main/java/com/hcaptcha/sdk/HCaptchaWebViewHelper.java +++ b/sdk/src/main/java/com/hcaptcha/sdk/HCaptchaWebViewHelper.java @@ -85,11 +85,13 @@ private void setupWebView(@NonNull final Handler handler) { webView.setWebChromeClient(new HCaptchaWebChromeClient()); } webView.setBackgroundColor(Color.TRANSPARENT); - webView.setLayerType(View.LAYER_TYPE_SOFTWARE, null); + if (config.getDisableHardwareAcceleration()) { + webView.setLayerType(View.LAYER_TYPE_SOFTWARE, null); + } webView.addJavascriptInterface(jsInterface, HCaptchaJSInterface.JS_INTERFACE_TAG); webView.addJavascriptInterface(debugInfo, HCaptchaDebugInfo.JS_INTERFACE_TAG); webView.loadDataWithBaseURL(config.getHost(), htmlProvider.getHtml(), "text/html", "UTF-8", null); - HCaptchaLog.d("WebViewHelper.loadData"); + HCaptchaLog.d("WebViewHelper.loadData. Hardware acceleration enabled: %b", webView.isHardwareAccelerated()); } public void destroy() { diff --git a/sdk/src/test/java/com/hcaptcha/sdk/HCaptchaConfigTest.java b/sdk/src/test/java/com/hcaptcha/sdk/HCaptchaConfigTest.java index e9e6832e..0af2e079 100644 --- a/sdk/src/test/java/com/hcaptcha/sdk/HCaptchaConfigTest.java +++ b/sdk/src/test/java/com/hcaptcha/sdk/HCaptchaConfigTest.java @@ -32,6 +32,7 @@ public void default_config() { assertEquals(Locale.getDefault().getLanguage(), config.getLocale()); assertEquals("https://js.hcaptcha.com/1/api.js", config.getJsSrc()); assertEquals(null, config.getCustomTheme()); + assertEquals(true, config.getDisableHardwareAcceleration()); assertNull(config.getRqdata()); } @@ -44,6 +45,7 @@ public void custom_config() { final String customEndpoint = "https://local/api.js"; final String customLocale = "ro"; final Boolean sentry = false; + final Boolean disableHWAccel = false; final String customTheme = "{ \"palette\": {" + "\"mode\": \"light\", \"primary\": { \"main\": \"#F16622\" }," + "\"warn\": { \"main\": \"#F16622\" }," @@ -59,6 +61,7 @@ public void custom_config() { .size(hCaptchaSize) .orientation(hCaptchaOrientation) .customTheme(customTheme) + .disableHardwareAcceleration(disableHWAccel) .build(); assertEquals(MOCK_SITE_KEY, config.getSiteKey()); assertEquals(sentry, config.getSentry()); @@ -69,6 +72,7 @@ public void custom_config() { assertEquals(customRqdata, config.getRqdata()); assertEquals(customEndpoint, config.getJsSrc()); assertEquals(customTheme, config.getCustomTheme()); + assertEquals(disableHWAccel, config.getDisableHardwareAcceleration()); } @Test diff --git a/sdk/src/test/java/com/hcaptcha/sdk/HCaptchaJSInterfaceTest.java b/sdk/src/test/java/com/hcaptcha/sdk/HCaptchaJSInterfaceTest.java index a7daf63f..7c721d90 100644 --- a/sdk/src/test/java/com/hcaptcha/sdk/HCaptchaJSInterfaceTest.java +++ b/sdk/src/test/java/com/hcaptcha/sdk/HCaptchaJSInterfaceTest.java @@ -77,6 +77,7 @@ public void full_config_serialization() throws JSONException { .hideDialog(true) .tokenExpiration(timeout) .diagnosticLog(true) + .disableHardwareAcceleration(false) .build(); final HCaptchaJSInterface jsInterface = new HCaptchaJSInterface(handler, config, captchaVerifier); @@ -100,6 +101,7 @@ public void full_config_serialization() throws JSONException { expected.put("hideDialog", true); expected.put("tokenExpiration", timeout); expected.put("diagnosticLog", true); + expected.put("disableHardwareAcceleration", false); JSONAssert.assertEquals(jsInterface.getConfig(), expected, false); } @@ -142,6 +144,7 @@ public void subset_config_serialization() throws JSONException { expected.put("hideDialog", false); expected.put("tokenExpiration", defaultTimeout); expected.put("diagnosticLog", false); + expected.put("disableHardwareAcceleration", true); JSONAssert.assertEquals(jsInterface.getConfig(), expected, false); }