Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Handling File Upload Prompts in Android WebView

Tech 2

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.

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.