Android File Chooser not calling from Android Webview
Asked Answered
U

3

16

Its a simple WebApp with a Webview in it and there is a registration page with upload file option. trying to open file chooser when click on browse button but there is not response. i assume that there is some issue in my gradle files. please help me debug it. Here is my Code.

here is my project gradle

    buildscript {

        repositories {
            google()
            jcenter()
            mavenCentral()
        }
        dependencies {
            classpath 'com.android.tools.build:gradle:3.1.3'
            classpath 'com.google.gms:google-services:3.2.1'
        }
    }
    allprojects {
        repositories {
            google()
            jcenter()
        }
    }

    task clean(type: Delete) {
        delete rootProject.buildDir
    }

and here is my app gradle

android {
    compileSdkVersion 27
    defaultConfig {
        applicationId "com.androidapp.myApp"
        minSdkVersion 15
        targetSdkVersion 27
        versionCode 2
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
        useLibrary 'org.apache.http.legacy'

        multiDexEnabled true
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:27.1.1'
    implementation 'com.android.support:support-v4:27.1.1'
    implementation 'com.google.firebase:firebase-invites:16.0.1'
    implementation 'com.google.firebase:firebase-messaging:17.1.0'

    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
    implementation 'com.squareup.okhttp3:okhttp:3.8.0'

    implementation 'com.android.support:multidex:1.0.3'


}

apply plugin: 'com.google.gms.google-services'
Unidirectional answered 6/8, 2018 at 9:33 Comment(1)
Use TWA developers.google.com/web/updates/2019/02/using-twaChauffeur
D
25

It is not the gradle file error. You just need to provide custom WebChromeClient like below.

class MyWebChromeClient extends WebChromeClient {
    // For 3.0+ Devices (Start)
    // onActivityResult attached before constructor
    protected void openFileChooser(ValueCallback uploadMsg, String acceptType) {
        mUploadMessage = uploadMsg;
        Intent i = new Intent(Intent.ACTION_GET_CONTENT);
        i.addCategory(Intent.CATEGORY_OPENABLE);
        i.setType("image/*");
        startActivityForResult(Intent.createChooser(i, "File Chooser"), FILECHOOSER_RESULTCODE);
    }


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

        uploadMessage = filePathCallback;

        Intent intent = fileChooserParams.createIntent();
        try {
            startActivityForResult(intent, REQUEST_SELECT_FILE);
        } catch (ActivityNotFoundException e) {
            uploadMessage = null;
            Toast.makeText(WebLink.this, "Cannot Open File Chooser", Toast.LENGTH_LONG).show();
            return false;
        }
        return true;
    }

    //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("image/*");
        startActivityForResult(Intent.createChooser(intent, "File Chooser"), 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("image/*");
        startActivityForResult(Intent.createChooser(i, "File Chooser"), FILECHOOSER_RESULTCODE);
    }
}

and it in your webview like below

webview.setWebChromeClient(new MyWebChromeClient());

some other useful stuff/variables to be declared globally.

public ValueCallback<Uri[]> uploadMessage;
private ValueCallback<Uri> mUploadMessage;
public static final int REQUEST_SELECT_FILE = 100;
private final static int FILECHOOSER_RESULTCODE = 1;

make sure you have all the read/write permissions

UPDATE

Use below lines to provide access to files from storage.

webview.getSettings().setDomStorageEnabled(true);
webview.getSettings().setAllowContentAccess(true);
webview.getSettings().setAllowFileAccess(true);

// EDIT, add this line also
webview.getSettings().setJavaScriptEnabled(true);

UPDATE 2

Get the result in onActivityResult method. You can use the result given like below.

@Override
public void onActivityResult(int requestCode, int resultCode, Intent intent) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        if (requestCode == REQUEST_SELECT_FILE) {
            if (uploadMessage == null)
                return;
            uploadMessage.onReceiveValue(WebChromeClient.FileChooserParams.parseResult(resultCode, intent));
            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 = intent == null || resultCode != WebLink.RESULT_OK ? null : intent.getData();
        mUploadMessage.onReceiveValue(result);
        mUploadMessage = null;
    } else
        Toast.makeText(WebLink.this, "Failed to Upload Image", Toast.LENGTH_LONG).show();
}
Dwightdwindle answered 6/8, 2018 at 9:38 Comment(21)
thank you for reply, but this code works first time only and didnt even select a file.Unidirectional
which android version you are using?Dwightdwindle
you mean on my device??Unidirectional
android 7.0 on my deviceUnidirectional
Glad to know that :)Dwightdwindle
Are you sure setDomStorageEnabled and allowContentAccess are necessary?Meeks
@Meeks Did you try removing them?Dwightdwindle
its only giving option to select images from Folder but there must be options available for image capture and then select image or images upload from sd card , how to achieve this ?Expire
@Expire you can modify openFileChooser or onShowFileChooser method according to your needs.Dwightdwindle
@RahulKhurana is it possible to have both options either Camera or Select file ? by overriding openFileChooser or openFileChooserExpire
followed this androidexample.com/… but still its not opening any cameraExpire
@Expire My suggestion is: You can show a custom dialog to ask the user whether they want to take a picture or choose from the gallery and then use the correct intent according to the option chosen.Dwightdwindle
@RahulKhurana custom dialog will be from JAVA code , by handling click option of javascript button with the help of Id ?Expire
@Expire yes many example of custom dialogs are available on google. inside openFileChooser and onShowFileChooser call your custom dialog code.Dwightdwindle
Let us continue this discussion in chat.Expire
Thank you so much for this solution. How can I add a Camera to the file chooser? I need the user to be able to choose from the gallery or take a photo using the camera. it is in the website code but doesn't work on Android WebView.Lasser
@Lasser You can show a custom dialog to ask the user whether they want to take a picture or choose from the gallery and then use the correct intent according to the option chosen.Dwightdwindle
Where does WebLink exist?Herder
@RobertdeW WebLink refers to the current activity name.Dwightdwindle
what is the code in the java scipt to call the file chooserExemplificative
@RohitSharma try this <input type="file">Dwightdwindle
M
10

This Kotlin code worked for all versions:

webView.settings.javaScriptEnabled = true
webView.settings.loadWithOverviewMode = true
webView.settings.useWideViewPort = true
webView.settings.domStorageEnabled = true
webView.settings.allowFileAccess=true
webView.settings.allowContentAccess=true
webView.settings.allowUniversalAccessFromFileURLs=true
webView.settings.allowFileAccessFromFileURLs=true
webView.settings.javaScriptCanOpenWindowsAutomatically=true
webView.loadUrl(Constants.URL)
webView.webChromeClient = object : WebChromeClient() {
        override fun onShowFileChooser(
            webView: WebView,
            filePathCallback: ValueCallback<Array<Uri>>,
            fileChooserParams: FileChooserParams
        ): Boolean {
            if (mUMA != null) {
                mUMA!!.onReceiveValue(null)
            }
            mUMA = filePathCallback
            var takePictureIntent: Intent? = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
            if (takePictureIntent!!.resolveActivity([email protected]()) != null) {
                var photoFile: File? = null
                try {
                    photoFile = createImageFile()
                    takePictureIntent.putExtra("PhotoPath", mCM)
                } catch (ex: IOException) {
                    Log.e("Webview", "Image file creation failed", ex)
                }
                if (photoFile != null) {
                    mCM = "file:" + photoFile.getAbsolutePath()
                    takePictureIntent.putExtra(
                        MediaStore.EXTRA_OUTPUT,
                        Uri.fromFile(photoFile)
                    )
                } else {
                    takePictureIntent = null
                }
            }
            val contentSelectionIntent = Intent(Intent.ACTION_GET_CONTENT)
            contentSelectionIntent.addCategory(Intent.CATEGORY_OPENABLE)
            contentSelectionIntent.type = "*/*"
            val intentArray: Array<Intent>
            intentArray = takePictureIntent?.let { arrayOf(it) } ?: arrayOf<Intent>()
            val chooserIntent = Intent(Intent.ACTION_CHOOSER)
            chooserIntent.putExtra(Intent.EXTRA_INTENT, contentSelectionIntent)
            chooserIntent.putExtra(Intent.EXTRA_TITLE, "Image Chooser")
            chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, intentArray)
            startActivityForResult(chooserIntent, FCR)
            return true
        }
    }

// Create an image file
@Throws(IOException::class)
private fun createImageFile(): File? {
    @SuppressLint("SimpleDateFormat") val timeStamp: String =
        SimpleDateFormat("yyyyMMdd_HHmmss").format(Date())
    val imageFileName = "img_" + timeStamp + "_"
    val storageDir: File =
        Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)
    return File.createTempFile(imageFileName, ".jpg", storageDir)
}
fun openFileChooser(uploadMsg: ValueCallback<Uri?>?) {
    this.openFileChooser(uploadMsg, "*/*")
}

fun openFileChooser(
    uploadMsg: ValueCallback<Uri?>?,
    acceptType: String?
) {
    this.openFileChooser(uploadMsg, acceptType, null)
}

fun openFileChooser(
    uploadMsg: ValueCallback<Uri?>?,
    acceptType: String?,
    capture: String?
) {
    val i = Intent(Intent.ACTION_GET_CONTENT)
    i.addCategory(Intent.CATEGORY_OPENABLE)
    i.type = "*/*"
    [email protected](
        Intent.createChooser(i, "File Browser"),
        FILECHOOSER_RESULTCODE
    )
}

override fun onActivityResult(
    requestCode: Int,
    resultCode: Int,
    intent: Intent?
) {
    super.onActivityResult(requestCode, resultCode, intent)
    if (Build.VERSION.SDK_INT >= 21) {
        var results: Array<Uri>? = null
        //Check if response is positive
        if (resultCode == Activity.RESULT_OK) {
            if (requestCode == FCR) {
                if (null == mUMA) {
                    return
                }
                if (intent == null) { //Capture Photo if no image available
                    if (mCM != null) {
                        results = arrayOf(Uri.parse(mCM))
                    }
                } else {
                    val dataString = intent.dataString
                    if (dataString != null) {
                        results = arrayOf(Uri.parse(dataString))
                    }
                }
            }
        }
        mUMA!!.onReceiveValue(results)
        mUMA = null
    } else {
        if (requestCode == FCR) {
            if (null == mUM) return
            val result =
                if (intent == null || resultCode != Activity.RESULT_OK) null else intent.data
            mUM!!.onReceiveValue(result)
            mUM = null
        }
    }
}

/*needed fileds 
 private var mCM: String? = null
 private var mUM: ValueCallback<Uri>? = null
 private var mUMA: ValueCallback<Array<Uri>>? = null
 private const val FCR = 1*/
Mantissa answered 12/7, 2020 at 19:25 Comment(3)
While this code may answer the question, including an explanation of how and why this solves the problem would help to improve the quality of your answer. Remember that you are also answering the question for readers in the future, not just the person asking now.Wilton
Yeah so true this provides better result than the above onesAmary
worked liked a charm. Thank you so much. You saved so much of my time.Vivianaviviane
G
9

It got so easy in 2022-23 with Kotlin.

class MainActivity : AppCompatActivity() {
   private var fileChooserResultLauncher = createFileChooserResultLauncher()
   private var fileChooserValueCallback: ValueCallback<Array<Uri>>? = null

   webView.webChromeClient = object : WebChromeClient() {
      override fun onShowFileChooser(webView: WebView?, filePathCallback: ValueCallback<Array<Uri>>?, fileChooserParams: FileChooserParams?): Boolean {
         try {
            fileChooserValueCallback = filePathCallback;
            fileChooserResultLauncher.launch(fileChooserParams?.createIntent())
         } catch (e: ActivityNotFoundException) {
            // You may handle "No activity found to handle intent" error
         }
         return true
      }
   }

   private fun createFileChooserResultLauncher(): ActivityResultLauncher<Intent> {
      return registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
         if (it.resultCode == Activity.RESULT_OK) {
            fileChooserValueCallback?.onReceiveValue(arrayOf(Uri.parse(it?.data?.dataString)));
         } else {
            fileChooserValueCallback?.onReceiveValue(null)
         }
      }
   }
}
Guardianship answered 6/12, 2022 at 11:55 Comment(9)
This answer is missing information. You need to complete your answer with the missing functions and variables.Herodotus
I thought it was understandable already, but how is it now?Guardianship
Seem to work now! Best answer!Herodotus
The only problem I see, is that is not asking for permissions for the media files, camera...Herodotus
No need to ask for permission. Android takes care of the rest.Guardianship
Hi @Guardianship , actually Android is not handling the permission for you, so you need to first ask for the permission in the code, and then if the user accept, run this... So, this is not working.Herodotus
I know Android is not handling the permission for me/us. But this code does not require any permission. It's just open the built-in file chooser and pass the chosen file to the WebView. But it might requires register a file provider in AndroidManifest.xml and I'm not calling that "permission". I'm using it in production.Guardianship
It does not work if you open the file chooser once, don't select any file, cancel the file chooser by tapping the back button, and then try to open the file chooser again.Creativity
Above solution worked for me in kotlin ,Junker

© 2022 - 2024 — McMap. All rights reserved.