Handling File Upload Prompts in Android WebView
When embedding third‑party HTML pages in an Android WebView, file inputs (for example, selecting an image) are not handled automatically. The app must intercept the request, show a picker UI, and return the selected file’s URI back to the page.
High‑level flow:
- Detect that the page requested a file chooser via WebChromeClient callbacks
- Launch an intent so the user can pick a file
- Receive the chosen file(s) URI in onActivityResult
- Deliver the URI(s) to the WebView using the provided ValueCallback
WebView setup
Enable JavaScript so HTML can invoke file inputs and other interactive features:
webView.getSettings().setJavaScriptEnabled(true);
Attach a WebChromeClient and implement the file chooser hooks. Android exposes different callbacsk across API levels, so handle all of them:
public class WebUploadActivity extends Activity {
private static final int REQ_PICK_FILE = 1001;
private WebView webView;
private ProgressBar progressBar;
// Legacy (single file) callback for pre‑Lollipop devices
private ValueCallback<Uri> legacyFileCallback;
// Modern (multiple files) callback for Lollipop+ devices
private ValueCallback<Uri[]> modernFileCallback;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_web_upload);
webView = findViewById(R.id.web_view);
progressBar = findViewById(R.id.progress_bar);
webView.getSettings().setJavaScriptEnabled(true);
webView.setWebChromeClient(new WebChromeClient() {
@Override
public void onProgressChanged(WebView view, int newProgress) {
if (newProgress == 100) {
progressBar.setVisibility(View.GONE);
} else {
progressBar.setVisibility(View.VISIBLE);
progressBar.setProgress(newProgress);
}
}
// Android < 3.0
public void openFileChooser(ValueCallback<Uri> callback) {
legacyFileCallback = callback;
launchFilePicker();
}
// Android 3.0–4.4
public void openFileChooser(ValueCallback<Uri> callback, String acceptType, String capture) {
legacyFileCallback = callback;
launchFilePicker();
}
// Android 5.0+
@Override
public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) {
modernFileCallback = filePathCallback;
launchFilePicker();
return true;
}
});
// Load your page
// webView.loadUrl("https://example.com");
}
private void launchFilePicker() {
Intent pick = new Intent(Intent.ACTION_GET_CONTENT);
pick.addCategory(Intent.CATEGORY_OPENABLE);
// Limit to images; change to "*/*" or other MIME types as needed
pick.setType("image/*");
startActivityForResult(Intent.createChooser(pick, "Select Image"), REQ_PICK_FILE);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode != REQ_PICK_FILE) return;
// If both callbacks are null, there is nothing to deliver
if (legacyFileCallback == null && modernFileCallback == null) return;
if (modernFileCallback != null) {
handleResultApi21Plus(resultCode, data);
} else if (legacyFileCallback != null) {
Uri uri = (resultCode == RESULT_OK && data != null) ? data.getData() : null;
legacyFileCallback.onReceiveValue(uri);
legacyFileCallback = null;
}
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private void handleResultApi21Plus(int resultCode, Intent data) {
Uri[] uris = new Uri[]{};
if (resultCode == Activity.RESULT_OK && data != null) {
ClipData clip = data.getClipData();
if (clip != null && clip.getItemCount() > 0) {
Uri[] out = new Uri[clip.getItemCount()];
for (int i = 0; i < clip.getItemCount(); i++) {
out[i] = clip.getItemAt(i).getUri();
}
uris = out;
} else if (data.getData() != null) {
uris = new Uri[]{data.getData()};
}
}
modernFileCallback.onReceiveValue(uris);
modernFileCallback = null;
}
}
Notes:
- Adjust the MIME type in setType based on the input’s accept attribute (for example, image/, video/, applicatoin/pdf, or /).
- For camera capture or broader scenarios, consider merging ACTION_GET_CONTENT with other intents and granting URI permissions as needed.
- The Activity Result API can replace onActivityResult in newer projects, but the above is compatible with the legacy callback model many WebView integrations still use.