Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -468,6 +468,16 @@ The `retryPredicate` is part of `HCaptchaConfig` that may get persist during app
So pay attention to this aspect and make sure that `retryPredicate` is serializable to avoid
`android.os.BadParcelableException` in run-time.

### Camera Permission

Challenges that use the camera require the host app to declare and request the `CAMERA` permission at runtime (Android 6.0+):

```xml
<uses-permission android:name="android.permission.CAMERA" />
```

Requires Android 5.0+ (API 21).

### User Journeys (Enterprise)

You can optionally enable user journeys to send recent interaction events alongside your verification request.
Expand Down
5 changes: 5 additions & 0 deletions example-app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

<uses-permission android:name="android.permission.CAMERA" />
<uses-feature
android:name="android.hardware.camera"
android:required="false" />

<application
android:name=".App"
android:allowBackup="true"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package com.hcaptcha.example;

import android.Manifest;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.text.InputType;
import android.util.Log;
Expand All @@ -17,6 +19,7 @@

import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.SwitchCompat;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
Expand Down Expand Up @@ -101,6 +104,12 @@ protected void onCreate(final Bundle savedInstanceState) {
setContentView(R.layout.activity_main);
applySystemBarStyle();

// Ensure the WebView's required runtime permission is granted.
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, 0);
}

modeGroup = findViewById(R.id.challenge_mode_group);
sizeGroup = findViewById(R.id.challenge_size_group);
sitekeyGroup = findViewById(R.id.sitekey_group);
Expand Down
5 changes: 5 additions & 0 deletions example-compose-app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

<uses-permission android:name="android.permission.CAMERA" />
<uses-feature
android:name="android.hardware.camera"
android:required="false" />

<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.hcaptcha.example.compose

import android.Manifest
import android.content.pm.PackageManager
import android.os.Bundle
import android.webkit.WebView
import android.widget.Toast
Expand Down Expand Up @@ -60,6 +62,8 @@ import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import com.hcaptcha.sdk.HCaptchaCompose
import com.hcaptcha.sdk.HCaptchaConfig
import com.hcaptcha.sdk.HCaptchaError
Expand All @@ -86,6 +90,12 @@ class ComposeActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
actionBar?.hide()

// Ensure the WebView's required runtime permission is granted.
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.CAMERA), 0)
}
setContent {
AnalyticsScreen("ComposeActivity") {
val compactTypography = Typography(
Expand Down
29 changes: 29 additions & 0 deletions sdk/src/main/java/com/hcaptcha/sdk/HCaptchaWebViewHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import android.view.ViewGroup;
import android.view.ViewParent;
import android.webkit.ConsoleMessage;
import android.webkit.PermissionRequest;
import android.webkit.WebChromeClient;
import android.webkit.WebResourceRequest;
import android.webkit.WebResourceResponse;
Expand Down Expand Up @@ -79,6 +80,10 @@ private void setupWebView(@NonNull final Handler handler) {
settings.setAllowFileAccess(false);
settings.setAllowContentAccess(false);
settings.setSupportMultipleWindows(true);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
// Allow media playback to start without a user gesture.
settings.setMediaPlaybackRequiresUserGesture(false);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
webView.setWebViewClient(new HCaptchaWebClient(handler));
}
Expand Down Expand Up @@ -260,5 +265,29 @@ public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
public void onProgressChanged(WebView view, int newProgress) {
HCaptchaLog.d("[webview] onProgressChanged %d%%", newProgress);
}

@Override
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
public void onPermissionRequest(final PermissionRequest request) {
// Grant only the requested capture resource; audio is never requested, which
// avoids forcing host apps to declare RECORD_AUDIO.
boolean wantsCapture = false;
for (final String resource : request.getResources()) {
if (PermissionRequest.RESOURCE_VIDEO_CAPTURE.equals(resource)) {
wantsCapture = true;
break;
}
}
if (wantsCapture) {
// The requested surface only composites on a hardware layer; the default
// software layer (disableHardwareAcceleration) would render it blank.
webView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
request.grant(new String[]{PermissionRequest.RESOURCE_VIDEO_CAPTURE});
HCaptchaLog.d("[webview] onPermissionRequest granted");
} else {
request.deny();
HCaptchaLog.d("[webview] onPermissionRequest denied");
}
}
}
}
Loading