Two layers have to agree before a feature works
A web page asking for the camera inside an Android app has to pass through two separate gates, and people usually only configure one of them:
- The Android permission. Your app declares
CAMERAin the manifest and asks the user at runtime. Without it, the app has no camera access, full stop. - The WebView permission. Even with the OS permission granted, the WebView asks your code whether this page may use the camera, via
onPermissionRequest. The default implementation denies everything.
Miss the second and the symptom is maddening: the app has permission, the page calls getUserMedia(), and nothing happens — no prompt, no error dialog, just a rejected promise. That is the single most common WebView support question, and it is why the code above always generates the client alongside the manifest.
Ask for less than you think you need
Every permission you declare shows up on your Play listing, appears in the install dialog, and needs a justification in the Data Safety form. Location in particular triggers extra scrutiny — and if you declare it and never use it, you have made your listing scarier for no benefit. Declare what your pages actually call, and nothing else.
| Web API | Android permission | Also needs |
|---|---|---|
navigator.geolocation | ACCESS_FINE_LOCATION | onGeolocationPermissionsShowPrompt |
getUserMedia({video}) | CAMERA | onPermissionRequest |
getUserMedia({audio}) | RECORD_AUDIO | onPermissionRequest |
<input type="file"> | none on modern Android | onShowFileChooser |
| Download links | none on Android 10+ | setDownloadListener |
| Web push | POST_NOTIFICATIONS (Android 13+) | a messaging SDK |
The settings that decide how your site feels
javaScriptEnabled— off by default in a raw WebView, which surprises everyone. Almost every site needs it on.domStorageEnabled— also off by default. Without itlocalStoragethrows, and any site that stores a session or a cart silently breaks.useWideViewPortandloadWithOverviewMode— make the WebView honour your responsive viewport instead of assuming a desktop width.- Zoom — leave pinch zoom off for an app-like feel, on for accessibility. If your layout is genuinely responsive, off is defensible.
- External links — without
shouldOverrideUrlLoading, tapping a link to another site loads it inside your app, chrome-less and with no way back. Your users end up trapped on someone else's page.
File upload: the one that needs real code
An <input type="file"> in a plain WebView does nothing at all — tapping it is inert, because the WebView has no idea how to show a file picker. You have to implement onShowFileChooser, launch the intent, and hand the resulting URIs back through the callback. Forgetting the callback on a cancelled picker is a classic bug: the input stays permanently dead until the page reloads, because the WebView is still waiting for a result that never came.
Cleartext traffic
Since Android 9, plain http:// requests are blocked unless you explicitly opt in. Leave the block in place: if your site still serves anything over http, fix the site rather than reopening the door. The one legitimate exception is bundled content served from a local http://localhost server inside the app, which is why the generator adjusts that setting when you tick the offline option.