Integrating Popin WebView into Your Android App
This guide walks you through the complete setup for embedding the Popin WebView with camera and microphone access into your Android application — now with automatic permission handling through onPermissionRequest.
🛠️ Prerequisites
Before you begin, make sure you have the following:
Android Studio (latest stable version)
Minimum SDK: 21 (Android 5.0, Lollipop)
A valid Popin token (Replace
XXXXXin the sample code with your brand’s actual Popin token)
🔐 Add Required Permissions
To enable video calling, add the following permissions in your AndroidManifest.xml:
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<!-- Optional: for smoother video calls -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />🧱 Create the Call Activity
Create a new file named CallActivity.java in your app package, for example:
package to.popin.popinwebviewdemo;This activity embeds the Popin widget in a secure WebView and automatically handles camera and microphone permissions using onPermissionRequest.
📄 CallActivity.java
package to.popin.popinwebviewdemo;
import android.Manifest;
import android.annotation.TargetApi;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.MotionEvent;
import android.webkit.ConsoleMessage;
import android.webkit.PermissionRequest;
import android.webkit.WebChromeClient;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.ImageButton;
import android.widget.Toast;
import androidx.activity.EdgeToEdge;
import androidx.activity.OnBackPressedCallback;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.ContextCompat;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
public class CallActivity extends AppCompatActivity {
private static final String[] REQUIRED_PERMISSIONS = {
Manifest.permission.CAMERA,
Manifest.permission.RECORD_AUDIO
};
private WebView webView;
private ImageButton closeButton;
private ActivityResultLauncher<String[]> requestPermissionLauncher;
private PermissionRequest pendingPermissionRequest;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_call);
webView = findViewById(R.id.webview);
closeButton = findViewById(R.id.closeButton);
// Initialize permission launcher
requestPermissionLauncher = registerForActivityResult(
new ActivityResultContracts.RequestMultiplePermissions(),
result -> {
boolean allGranted = true;
for (Boolean granted : result.values()) {
if (!granted) {
allGranted = false;
break;
}
}
if (allGranted && pendingPermissionRequest != null) {
Log.d("CallActivity", "Permissions granted, granting WebView access");
pendingPermissionRequest.grant(pendingPermissionRequest.getResources());
pendingPermissionRequest = null;
} else {
Log.w("CallActivity", "Permissions denied, denying WebView access");
if (pendingPermissionRequest != null) {
pendingPermissionRequest.deny();
pendingPermissionRequest = null;
}
Toast.makeText(this,
"Camera and microphone permissions are required for video calls",
Toast.LENGTH_LONG).show();
}
}
);
configureWebView();
// Load Popin widget (replace XXXXX with your Popin token)
webView.loadUrl("https://widget01.popin.to/standalone?token=XXXXX&popin=open");
closeButton.setOnClickListener(v -> finish());
// Handle back button navigation
getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(true) {
@Override
public void handleOnBackPressed() {
if (webView.canGoBack()) webView.goBack();
else finish();
}
});
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
Insets bars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(bars.left, bars.top, bars.right, bars.bottom);
return insets;
});
}
private void configureWebView() {
WebSettings webSettings = webView.getSettings();
webSettings.setJavaScriptEnabled(true);
webSettings.setDomStorageEnabled(true);
webSettings.setAllowFileAccess(true);
webSettings.setAllowContentAccess(true);
webSettings.setCacheMode(WebSettings.LOAD_NO_CACHE);
webSettings.setAllowFileAccessFromFileURLs(true);
webSettings.setAllowUniversalAccessFromFileURLs(true);
webSettings.setJavaScriptCanOpenWindowsAutomatically(true);
webSettings.setMediaPlaybackRequiresUserGesture(false);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
WebView.setWebContentsDebuggingEnabled(true);
}
webView.setWebViewClient(new WebViewClient());
// Handle media permission requests from WebRTC
webView.setWebChromeClient(new WebChromeClient() {
@Override
public boolean onConsoleMessage(ConsoleMessage msg) {
Log.d("CallActivity WebView", msg.message());
return true;
}
@Override
public void onPermissionRequest(final PermissionRequest request) {
Log.d("CallActivity", "Permission request from: " + request.getOrigin());
if (hasRequiredPermissions()) {
runOnUiThread(() -> {
Log.d("CallActivity", "Granting permissions to WebView");
request.grant(request.getResources());
});
} else {
Log.d("CallActivity", "Requesting Android permissions");
pendingPermissionRequest = request;
requestPermissionLauncher.launch(REQUIRED_PERMISSIONS);
}
}
});
}
private boolean hasRequiredPermissions() {
return ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED &&
ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED;
}
}🧱 Layout XML
Create res/layout/activity_call.xml to define a fullscreen WebView with a close button.
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent">
<WebView
android:id="@+id/webview"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<ImageButton
android:id="@+id/closeButton"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_alignParentEnd="true"
android:layout_margin="16dp"
android:background="@android:color/transparent"
android:src="@android:drawable/ic_menu_close_clear_cancel" />
</RelativeLayout>🚀 Launching a Popin Call
Start the call by launching the CallActivity from any part of your app:
Intent intent = new Intent(this, CallActivity.class);
startActivity(intent);📝 Final Notes
✅ No need to manually request permissions — they’re automatically handled through
onPermissionRequest.🔐 Replace
XXXXXwith your actual Popin token in the WebView URL.🎯 If the user denies permissions, the WebView will automatically prevent access to the camera/mic.
🧪 Test across Android versions to ensure smooth permission flows.
Last updated