14

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'
WaqasArshad
  • 237
  • 1
  • 3
  • 12

3 Answers3

24

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();
}
Aakash Sharma
  • 427
  • 1
  • 6
  • 15
Rahul Khurana
  • 8,577
  • 7
  • 33
  • 60
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(this@MainActivity.getPackageManager()) != 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 = "*/*"
    this@MainActivity.startActivityForResult(
        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*/
  • 2
    While this code may answer the question, [including an explanation](https://meta.stackexchange.com/q/114762) 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. – herrbischoff Jul 12 '20 at 19:46
  • Yeah so true this provides better result than the above ones – g7pro Oct 12 '20 at 08:15
  • worked liked a charm. Thank you so much. You saved so much of my time. – dennisrufigill Mar 28 '22 at 10:21
6

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)
         }
      }
   }
}
OzgurG
  • 1,634
  • 1
  • 16
  • 21
  • 1
    This answer is missing information. You need to complete your answer with the missing functions and variables. – Khris Vandal Dec 07 '22 at 12:22
  • 1
    I thought it was understandable already, but how is it now? – OzgurG Dec 07 '22 at 12:39
  • Seem to work now! Best answer! – Khris Vandal Dec 07 '22 at 13:11
  • The only problem I see, is that is not asking for permissions for the media files, camera... – Khris Vandal Dec 07 '22 at 13:23
  • No need to ask for permission. Android takes care of the rest. – OzgurG Dec 07 '22 at 13:25
  • Hi @OzgurG , 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. – Khris Vandal Dec 12 '22 at 13:47
  • 1
    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. – OzgurG Dec 12 '22 at 19:08
  • 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. – Waqas Younis Jan 12 '23 at 18:21
  • Above solution worked for me in kotlin , – Rabia Tabasam May 04 '23 at 07:27