3

Android Studio / SDK 30 I need to upload into a input file in webview.

There is TONS of 6,7 to 8 years ago answers that don't work anymore :(

I have a working webview that can use .getUserMedia (webrtc), can use GPS, but is unable to upload in a regular <input type="file" >

This code does ask (when start the app) permissions for camera, storage, gps...

I want to trigger a prompt that ask the user take photo, take video or choose from gallery and that actually uploads it.

here my Manifest

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.apptheway.wexview">

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <uses-permission android:name="android.permission.ACCESS_GPS" />
    <uses-permission android:name="android.permission.ACCESS_ASSISTED_GPS" />
    <uses-permission android:name="android.permission.ACCESS_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_INTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28" />
    <uses-permission android:name="android.permission.WRITE_INTERNAL_STORAGE" android:maxSdkVersion="28" />
    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.CAMERA2" />
    <uses-permission android:name="android.webkit.PermissionRequest" />
    <uses-permission android:name="com.android.vending.BILLING" />
    <uses-permission android:name="com.android.vending.CHECK_LICENSE" />
    <uses-permission android:name="android.permission.RECORD_AUDIO" />
    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />

    <uses-feature android:name="android.hardware.camera" />
    <uses-feature android:name="android.hardware.camera2" />
    <uses-feature android:name="android.hardware.location.gps" />
    <uses-feature android:name="android.hardware.location.network" />

    <application
        android:allowBackup="true"
        android:fullBackupContent="true"
        android:hardwareAccelerated="true"
        android:requestLegacyExternalStorage="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.AppCompat.NoActionBar">
        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

Here MainActivity

import android.Manifest;
import android.annotation.SuppressLint;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.view.KeyEvent;
import android.view.Window;
import android.webkit.GeolocationPermissions;
import android.webkit.PermissionRequest;
import android.webkit.ValueCallback;
import android.webkit.WebChromeClient;
import android.webkit.WebResourceRequest;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import java.util.Random;

@SuppressLint("SetJavaScriptEnabled")

public class MainActivity extends AppCompatActivity {

    private WebView mWebView;

    private ValueCallback<Uri> mUploadMessage;
    private final static int FILECHOOSER_RESULTCODE = 1;

    @Override
    protected void onStart() {
        super.onStart();

        String[] permissions = {
                Manifest.permission.ACCESS_COARSE_LOCATION,
                Manifest.permission.ACCESS_FINE_LOCATION,
                Manifest.permission.CAMERA,
                Manifest.permission.RECORD_AUDIO,
                Manifest.permission.MODIFY_AUDIO_SETTINGS,
                Manifest.permission.WRITE_EXTERNAL_STORAGE
        };

        requestPermissions(permissions, 0);
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);

        for (int i = 0; i < permissions.length; i++) {
            String permission = permissions[i];
            boolean isGranted = grantResults[i] >= 0;
        }
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        getWindow().requestFeature(Window.FEATURE_NO_TITLE);

        mWebView = new WebView(this);
        WebSettings webSettings = mWebView.getSettings();
        webSettings.setLoadWithOverviewMode(true);
        webSettings.setUseWideViewPort(true);
        webSettings.setDomStorageEnabled(true);
        webSettings.setGeolocationEnabled(true);
        webSettings.setBuiltInZoomControls(true);
        webSettings.setDisplayZoomControls(false);
        webSettings.setSupportZoom(true);
        webSettings.setJavaScriptCanOpenWindowsAutomatically(true);
        webSettings.setJavaScriptEnabled(true);
        webSettings.setCacheMode(WebSettings.LOAD_DEFAULT);
        webSettings.setAllowFileAccess(true);
        webSettings.setAllowContentAccess(true);
        webSettings.setDatabaseEnabled(true);
        webSettings.setMediaPlaybackRequiresUserGesture(false);
        webSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);

        mWebView.setWebViewClient(new WebViewClient() {

            @Override
            public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {

                String url = request.getUrl().toString();

                int opensInWexView = 1;

                // DOMAINS
//                if ( !url.contains("stubfee.com") && !url.contains("stubfee.com") && !url.contains("stubfeedevent.com") ) {
//                    opensInWexView = 0;
//                }

                // IF FILE
                if (    url.contains(".pdf") || url.contains(".mov") || url.contains(".mp") ||
                        url.contains(".hv") || url.contains(".aif") || url.contains(".wav") ||
                        url.contains(".xls") || url.contains(".doc") || url.contains(".txt")
                ) {
                    opensInWexView = 0;
                }

                // URL SCHEME
                if (    url.startsWith("tel:") || url.startsWith("sms:") ||
                        url.startsWith("mms:") || url.startsWith("mailto:") ||
                        url.startsWith("fax:")
                ) {
                    opensInWexView = 0;
                }

                switch (opensInWexView) {
                    case 0:
                        Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
                        startActivity(intent);
                        break;
                    default:
                        view.loadUrl(url);
                        break;
                }

                return true;

            }
        });

        mWebView.setWebChromeClient(new WebChromeClient() {

            // CAMERA
            public void onPermissionRequest(final PermissionRequest request) {
                request.grant(request.getResources());
            }

            // GPS
            public void onGeolocationPermissionsShowPrompt(String origin, GeolocationPermissions.Callback callback) {
                callback.invoke(origin, true, false);
            }

            // FILE INPUT

        });

        mWebView.loadUrl("https://www.stubfee.com/account/" + (new Random().nextInt((1000000 - 1) + 1) + 1) + "/");

        this.setContentView(mWebView);

    }

    @Override
    public boolean onKeyDown(final int keyCode, final KeyEvent event) {
        if ((keyCode == KeyEvent.KEYCODE_BACK) && mWebView.canGoBack()) {
            mWebView.goBack();
            return true;
        }
        return super.onKeyDown(keyCode, event);
    }

}

In many answers they talk about adding code inside

mWebView.setWebChromeClient(new WebChromeClient() {}

But any answer from the web I try; it does work :(

in one of the answers, I get the gallery to open, but when select a file, nothing happens :(

is there a more recent approach ?

what I'm I missing ?

Tried those without success

Upload an Image from camera or gallery in WebView

Android Webview Upload Image from gallery or camera

Saving photos and videos using Android FileProvider to the gallery

File Upload in WebView Android Studio

How to Upload Files with WebView in Android Studio?

How to make upload button work in android app?

Thanks

Jintor
  • 607
  • 8
  • 32

1 Answers1

0

I had the same problem, and was having problems with WebChromeClient, too, and I recently found the answer. onShowFileChooser is the key event. (I had trouble trying to pass mWebView as a param to a new class constructor, but it did work when I kept the definition inside MainActivity.)

Also, this technique avoids the now-deprecated startActivityForResult and onActivityResult functions.

In Kotlin

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    supportActionBar!!.hide()
    val progressBarView = findViewById<ProgressBar>(R.id.webviewProgress)
    mWebview.settings.allowContentAccess = true
    mWebview.settings.allowFileAccess = true
    mWebview.settings.domStorageEnabled = true
    mWebview.settings.javaScriptEnabled = true
    mWebview.webViewClient = AppWebViewClient(progressBarView)
    mWebview.webChromeClient = object:WebChromeClient() {
        override fun onShowFileChooser(mWebView:WebView,
                                       filePathCallback:ValueCallback<Array<Uri>>,
                                       fileChooserParams:FileChooserParams):Boolean {
            if (mUploadMessage != null) {
                mUploadMessage!!.onReceiveValue(null)
                mUploadMessage = null
            }
            mUploadMessage = filePathCallback
            val contentSelectionIntent = Intent(Intent.ACTION_GET_CONTENT)
            contentSelectionIntent.addCategory(Intent.CATEGORY_OPENABLE)
            contentSelectionIntent.type = "*/*"
            val intent = Intent(Intent.ACTION_CHOOSER)
            intent.putExtra(Intent.EXTRA_INTENT, contentSelectionIntent)
            intent.putExtra(Intent.EXTRA_TITLE, "File Chooser")
            try {
                getFileResultLauncher.launch(intent)
            } catch (e: ActivityNotFoundException) {
                mUploadMessage = null
                Toast.makeText(getApplicationContext(), "Cannot Open File Chooser", Toast.LENGTH_LONG).show()
                return false
            }
            return true
        }
    }
    mWebview.loadUrl(getString(R.string.app_url))

}

val getFileResultLauncher = registerForActivityResult(
    ActivityResultContracts.StartActivityForResult()
) {
    ar: ActivityResult ->
    val intent: Intent? = ar.data
    val result = if (intent == null || ar.resultCode != RESULT_OK) null else arrayOf(Uri.parse(intent.dataString))
    mUploadMessage!!.onReceiveValue(result);
    mUploadMessage = null;
}

override fun onBackPressed() {
    _webview.goBack()
}
David
  • 2,782
  • 4
  • 31
  • 47
  • NOTE: this is tweaked from my own personal implementation, because you are declaring mWebView as a class field, whereas I declare it inside the `onCreate` function itself. `onShowFileChooser` doesn't reference it anyway, so it shouldn't be a problem, but if you are having trouble, you might look into that. – David Nov 01 '21 at 20:29
  • Cool, I'll test it soon. I'll get back with results of the test – Jintor Nov 01 '21 at 23:30
  • how would it be in java? – Popularfan Mar 12 '22 at 13:28