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 XXXXX in 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 XXXXX with 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