Question

We are creating an app which uses the webview and will access a page where the user needs to upload a file. We are experiencing problems with Android 4.4 where the file chooser does not open and clicking the upload button causes nothing to happen. This functionality works with earlier versions using the openFileChooser method like so:

 webview.setWebChromeClient(new WebChromeClient() {
        //The undocumented magic method override
        //Eclipse will swear at you if you try to put @Override here
        // For Android 3.0+
        public void openFileChooser(ValueCallback<Uri> uploadMsg) {
            mUploadMessage = uploadMsg;
            Intent i = new Intent(Intent.ACTION_GET_CONTENT);
            i.addCategory(Intent.CATEGORY_OPENABLE);
            i.setType("image/*");
            MainActivity.this.startActivityForResult(Intent.createChooser(i, "File Chooser"), FILECHOOSER_RESULTCODE);
        }

        // For Android 3.0+
        public void openFileChooser(ValueCallback uploadMsg, String acceptType) {
            mUploadMessage = uploadMsg;
            Intent i = new Intent(Intent.ACTION_GET_CONTENT);
            i.addCategory(Intent.CATEGORY_OPENABLE);
            i.setType("*/*");
            MainActivity.this.startActivityForResult(
                    Intent.createChooser(i, "File Browser"),
                    FILECHOOSER_RESULTCODE);
        }

        //For Android 4.1
        public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture) {
            mUploadMessage = uploadMsg;
            Intent i = new Intent(Intent.ACTION_GET_CONTENT);
            i.addCategory(Intent.CATEGORY_OPENABLE);
            i.setType("image/*");
            MainActivity.this.startActivityForResult(Intent.createChooser(i, "File Chooser"), MainActivity.FILECHOOSER_RESULTCODE);
        }

    });

I have spent a good amount of time searching for a way to do it on 4.4 but have had no luck. Has anyone managed to do this?

Was it helpful?

Solution 2

WebView is working as intended

If I understand correctly what the above link says , you (me and probably some hundreds more developers) are looking for a hack

OTHER TIPS

This Code worked for me.

    private class MyWebChromeClient extends WebChromeClient {
    //The undocumented magic method override
    //Eclipse will swear at you if you try to put @Override here


    // For Android 3.0+
    public void openFileChooser(ValueCallback uploadMsg, String acceptType) {
        mUploadMessage = uploadMsg;
        Intent i = new Intent(Intent.ACTION_GET_CONTENT);
        i.addCategory(Intent.CATEGORY_OPENABLE);
        i.setType("*/*");
        MainActivity1.this.startActivityForResult(Intent.createChooser(i, "File Browser"), FILECHOOSER_RESULTCODE);
    }

    //For Android 4.1+ only
    protected void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture)
    {
        mUploadMessage = uploadMsg;
        Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
        intent.addCategory(Intent.CATEGORY_OPENABLE);
        intent.setType("*/*");
        startActivityForResult(Intent.createChooser(intent, "File Browser"), FILECHOOSER_RESULTCODE);
    }

    protected void openFileChooser(ValueCallback<Uri> uploadMsg)
    {
        mUploadMessage = uploadMsg;
        Intent i = new Intent(Intent.ACTION_GET_CONTENT);
        i.addCategory(Intent.CATEGORY_OPENABLE);
        i.setType("*/*");
        startActivityForResult(Intent.createChooser(i, "File Chooser"), FILECHOOSER_RESULTCODE);
    }

    // For Lollipop 5.0+ Devices
    public boolean onShowFileChooser(WebView mWebView, ValueCallback<Uri[]> filePathCallback, WebChromeClient.FileChooserParams fileChooserParams) {
        if (uploadMessage != null) {
            uploadMessage.onReceiveValue(null);
            uploadMessage = null;
        }

        uploadMessage = filePathCallback;
        Intent intent = null;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            intent = fileChooserParams.createIntent();
        }
        try {
            startActivityForResult(intent, REQUEST_SELECT_FILE);
        } catch (ActivityNotFoundException e) {
            uploadMessage = null;
            Toast.makeText(getApplicationContext(), "Cannot Open File Chooser", Toast.LENGTH_LONG).show();
            return false;
        }
        return true;
    }

    @Override
    public boolean onJsAlert(WebView view, String url, String message, JsResult result) {

        Log.d("LogTag", message);
        result.confirm();
        return true;
    }
}

  @Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (requestCode == REQUEST_SELECT_FILE) {
        if (uploadMessage == null) return;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            uploadMessage.onReceiveValue(WebChromeClient.FileChooserParams.parseResult(resultCode, data));
        }
        uploadMessage = null;
    } else if (requestCode == FILECHOOSER_RESULTCODE) {
        if (null == mUploadMessage)
            return;
        // Use MainActivity.RESULT_OK if you're implementing WebView inside Fragment
        // Use RESULT_OK only if you're implementing WebView inside an Activity
        Uri result = data == null || resultCode != MainActivity.RESULT_OK ? null : data.getData();
        mUploadMessage.onReceiveValue(result);
        mUploadMessage = null;
    }
}

Have a look this. Because api 19 4.4, webview has been migrated to use Chromium, not WebChromeClient anymore.

I was working on this issue too, and the problem and here is my solution

private class MyWebChromeClient extends WebChromeClient {


    /**
     * This is the method used by Android 5.0+ to upload files towards a web form in a Webview
     *
     * @param webView
     * @param filePathCallback
     * @param fileChooserParams
     * @return
     */
    @Override
    public boolean onShowFileChooser(
            WebView webView, ValueCallback<Uri[]> filePathCallback,
            WebChromeClient.FileChooserParams fileChooserParams) {

        if (mFilePathCallback != null) {
            mFilePathCallback.onReceiveValue(null);
        }
        mFilePathCallback = filePathCallback;

        Intent contentSelectionIntent = new Intent(Intent.ACTION_GET_CONTENT);
        contentSelectionIntent.addCategory(Intent.CATEGORY_OPENABLE);
        contentSelectionIntent.setType("image/*");

        Intent[] intentArray = getCameraIntent();

        Intent chooserIntent = new Intent(Intent.ACTION_CHOOSER);
        chooserIntent.putExtra(Intent.EXTRA_INTENT, contentSelectionIntent);
        chooserIntent.putExtra(Intent.EXTRA_TITLE, "Seleccionar Fuente");
        chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, intentArray);

        startActivityForResult(chooserIntent, INPUT_FILE_REQUEST_CODE);

        return true;
    }

    @Override
    public void onProgressChanged(WebView view, int newProgress) {

        mProgressBar.setVisibility(View.VISIBLE);
        WebActivity.this.setValue(newProgress);
        super.onProgressChanged(view, newProgress);
    }

    @Override
    public boolean onJsAlert(WebView view, String url, String message, JsResult result) {

        Log.d("LogTag", message);
        result.confirm();
        return true;
    }

    /**
     * Despite that there is not a Override annotation, this method overrides the open file
     * chooser function present in Android 3.0+
     *
     * @param uploadMsg
     * @author Tito_Leiva
     */
    public void openFileChooser(ValueCallback<Uri> uploadMsg) {

        mUploadMessage = uploadMsg;
        Intent i = getChooserIntent(getCameraIntent(), getGalleryIntent("image/*"));
        i.addCategory(Intent.CATEGORY_OPENABLE);
        WebActivity.this.startActivityForResult(Intent.createChooser(i, "Selecciona la imagen"), FILECHOOSER_RESULTCODE);

    }

    public void openFileChooser(ValueCallback uploadMsg, String acceptType) {
        mUploadMessage = uploadMsg;
        Intent i = getChooserIntent(getCameraIntent(), getGalleryIntent("*/*"));
        i.addCategory(Intent.CATEGORY_OPENABLE);
        WebActivity.this.startActivityForResult(
                Intent.createChooser(i, "Selecciona la imagen"),
                FILECHOOSER_RESULTCODE);
    }

    /**
     * Despite that there is not a Override annotation, this method overrides the open file
     * chooser function present in Android 4.1+
     *
     * @param uploadMsg
     * @author Tito_Leiva
     */
    public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture) {
        mUploadMessage = uploadMsg;
        Intent i = getChooserIntent(getCameraIntent(), getGalleryIntent("image/*"));
        WebActivity.this.startActivityForResult(Intent.createChooser(i, "Selecciona la imagen"), FILECHOOSER_RESULTCODE);

    }

    private Intent[] getCameraIntent() {

        // Determine Uri of camera image to save.
        Intent takePictureIntent = new Intent(WebActivity.this, CameraActivity.class);
        if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
            // Create the File where the photo should go
            File photoFile = null;
            try {
                photoFile = createImageFile();
                takePictureIntent.putExtra("PhotoPath", mCameraPhotoPath);
            } catch (IOException ex) {
                // Error occurred while creating the File
                Log.e(TAG, "Unable to create Image File", ex);
            }

            // Continue only if the File was successfully created
            if (photoFile != null) {
                mCameraPhotoPath = "file:" + photoFile.getAbsolutePath();
                takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT,
                        Uri.fromFile(photoFile));
            } else {
                takePictureIntent = null;
            }
        }

        Intent[] intentArray;
        if (takePictureIntent != null) {
            intentArray = new Intent[]{takePictureIntent};
        } else {
            intentArray = new Intent[0];
        }

        return intentArray;

    }

    private Intent getGalleryIntent(String type) {

        // Filesystem.
        final Intent galleryIntent = new Intent();
        galleryIntent.setType(type);
        galleryIntent.addCategory(Intent.CATEGORY_OPENABLE);
        galleryIntent.setAction(Intent.ACTION_GET_CONTENT);

        return galleryIntent;
    }

    private Intent getChooserIntent(Intent[] cameraIntents, Intent galleryIntent) {

        // Chooser of filesystem options.
        final Intent chooserIntent = Intent.createChooser(galleryIntent, "Seleccionar Fuente");

        // Add the camera options.
        chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, cameraIntents);

        return chooserIntent;
    }
}

One problem that I solved is the uri delivered for the onActivityResult() method does not have extension. To solve this I use this method

public static Uri savePicture(Context context, Bitmap bitmap, int maxSize) {

    int cropWidth = bitmap.getWidth();
    int cropHeight = bitmap.getHeight();

    if (cropWidth > maxSize) {
        cropHeight = cropHeight * maxSize / cropWidth;
        cropWidth = maxSize;

    }

    if (cropHeight > maxSize) {
        cropWidth = cropWidth * maxSize / cropHeight;
        cropHeight = maxSize;

    }

    bitmap = ThumbnailUtils.extractThumbnail(bitmap, cropWidth, cropHeight, ThumbnailUtils.OPTIONS_RECYCLE_INPUT);

    File mediaStorageDir = new File(
            Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES),
            context.getString(R.string.app_name)
    );

    if (!mediaStorageDir.exists()) {
        if (!mediaStorageDir.mkdirs()) {
            return null;
        }
    }

    String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
    File mediaFile = new File(
            mediaStorageDir.getPath() + File.separator + "IMG_" + timeStamp + ".jpg"
    );

    // Saving the bitmap
    try {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        bitmap.compress(Bitmap.CompressFormat.JPEG, 80, out);

        FileOutputStream stream = new FileOutputStream(mediaFile);
        stream.write(out.toByteArray());
        stream.close();

    } catch (IOException exception) {
        exception.printStackTrace();
    }

    // Mediascanner need to scan for the image saved
    Intent mediaScannerIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
    Uri fileContentUri = Uri.fromFile(mediaFile);
    mediaScannerIntent.setData(fileContentUri);
    context.sendBroadcast(mediaScannerIntent);

    return fileContentUri;
} 

Finally, onActivityResult() method is

@Override
protected void onActivityResult(int requestCode, int resultCode,
                                Intent intent) {

    if (resultCode == RESULT_OK) {

        // This is for Android 4.4.4- (JellyBean & KitKat)
        if (requestCode == FILECHOOSER_RESULTCODE) {

            if (null == mUploadMessage) {
                super.onActivityResult(requestCode, resultCode, intent);
                return;
            }

            final boolean isCamera;

            if (intent == null) {
                isCamera = true;
            } else {
                final String action = intent.getAction();
                if (action == null) {
                    isCamera = false;
                } else {
                    isCamera = action.equals(android.provider.MediaStore.ACTION_IMAGE_CAPTURE);
                }
            }

            Uri selectedImageUri;

            if (isCamera) {

                selectedImageUri = mOutputFileUri;
                mUploadMessage.onReceiveValue(selectedImageUri);
                mUploadMessage = null;

                return;

            } else {

                try {

                    Bitmap bitmap = MediaStore.Images.Media.getBitmap(this.getContentResolver(), intent.getData());
                    selectedImageUri = intent == null ? null : ImageUtility.savePicture(this, bitmap, 1400);

                    mUploadMessage.onReceiveValue(selectedImageUri);
                    mUploadMessage = null;

                    return;

                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            // And this is for Android 5.0+ (Lollipop)
        } else if (requestCode == INPUT_FILE_REQUEST_CODE) {

            Uri[] results = null;

            // Check that the response is a good one
            if (resultCode == Activity.RESULT_OK) {
                if (intent == null) {
                    // If there is not data, then we may have taken a photo
                    if (mCameraPhotoPath != null) {
                        results = new Uri[]{Uri.parse(mCameraPhotoPath)};
                    }
                } else {

                    Bitmap bitmap = null;

                    try {
                        bitmap = MediaStore.Images.Media.getBitmap(this.getContentResolver(), intent.getData());
                    } catch (IOException e) {
                        e.printStackTrace();
                    }

                    Uri dataUri = ImageUtility.savePicture(this, bitmap, 1400);

                    if (dataUri != null) {
                        results = new Uri[]{dataUri};
                    }
                }
            }

            mFilePathCallback.onReceiveValue(results);
            mFilePathCallback = null;

            return;
        }
    } else {

        super.onActivityResult(requestCode, resultCode, intent);
        return;
    }
} 

Found a solution that worked for me. I've added one more rule in proguard-android.txt:

-keepclassmembers class * extends android.webkit.WebChromeClient {
     public void openFileChooser(...);
}

Android Kotlin,

MyWebViewChromeClient class:

class MyWebViewChromeClient(private val mContext: MyMainActivity): WebChromeClient() {

    override fun onShowFileChooser(webView: WebView, filePathCallback: ValueCallback<Array<Uri>>, fileChooserParams: FileChooserParams): Boolean {
        mContext.mUploadMessageArray?.onReceiveValue(null)
        mContext.mUploadMessageArray = filePathCallback

        val contentSelectionIntent = Intent(Intent.ACTION_GET_CONTENT)
        contentSelectionIntent.addCategory(Intent.CATEGORY_OPENABLE)
        contentSelectionIntent.type = "*/*"
        val intentArray: Array<Intent?> = arrayOfNulls(0)

        val chooserIntent = Intent(Intent.ACTION_CHOOSER)
        chooserIntent.putExtra(Intent.EXTRA_INTENT, contentSelectionIntent)
        chooserIntent.putExtra(Intent.EXTRA_TITLE, "File Chooser")
        chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, intentArray)
        mContext.startActivityForResult(chooserIntent, mContext.FILECHOOSER_RESULTCODE)
        return true
    }

MyMainActivity class:

class MyMainActivity : MyBaseActivity() {

    val FILECHOOSER_RESULTCODE = 1001
    var mUploadMessageArray: ValueCallback<Array<Uri>>? = null

    private lateinit var mWebView: MyWebView
    private lateinit var mContext: Context

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        if (requestCode == FILECHOOSER_RESULTCODE) {
            if (mUploadMessageArray == null) {
                return
            }
            val result = if (intent == null || resultCode != Activity.RESULT_OK) null else data?.data
            result?.let {
                val uriArray: Array<Uri> = arrayOf(it)
                mUploadMessageArray?.onReceiveValue(uriArray)
                mUploadMessageArray = null
            } ?: kotlin.run {
                mUploadMessageArray?.onReceiveValue(null)
                mUploadMessageArray = null
            }
        }
    }
}

What I have done to fix this problem it is to implement CrossWalk into my project. I know the size of CrossWalk is significant (adds Chromium to your project) but I really needed this to work fine inside a native app. I documented how I implemented this in here: http://maurizionapoleoni.de/blog/implementing-a-file-input-with-camera-support-in-android-with-crosswalk/

I have tried the File Chooser in a webview using the latest Android release 4.4.3, and it's working fine. I guess Google has fixed the issue.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top