简体   繁体   中英

Android Webview image upload from gallery and camera, camera not working

I have been searching all related posts here regarding this problem, but I cannot figure out, why the image upload does not work (testing on device with Android 8), when taking a picture from my camera. Gallery upload works fine as expected.

In the activity result i get a path to the image in the cache directory (variable mCameraPhotoPath), but it seems that the result or the website cannot access this file or maybe there is an empty image file in there.

Taking and uploading the image via google chrome directly on the device works, so I think the problem is not on the website side.

Here are my permissions

<uses-feature android:name="android.hardware.camera"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.CAMERA"/>
<!-- for new versions api 21+-->
<uses-permission
    android:name="android.permission.WRITE_EXTERNAL_STORAGE"
    android:maxSdkVersion="18" />
<uses-permission android:name="android.permission.WRITE_INTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_INTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.CAMERA2" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />

this is my application definition

 <application
    android:name="***"
    android:allowBackup="true"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:roundIcon="@mipmap/ic_launcher_round"
    android:supportsRtl="true"
    android:requestLegacyExternalStorage="true"
    android:hardwareAccelerated="true"
    android:theme="@style/Theme.Android">

    <!-- added fileprovider support -->
    <provider
        android:name="androidx.core.content.FileProvider"
        android:authorities="${applicationId}.provider"
        android:exported="false"
        android:grantUriPermissions="true">
        <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/file_paths" />
    </provider>

Then I added an xml folder in the res directory with a file called file_path.xml and added the content

<paths>
  <cache-path
    name="mediaimages"
    path="./">
  </cache-path>
</paths>

And this is my activity code

private var mUploadCallbackAboveL: ValueCallback<Array<Uri>>? = null
private var mUploadCallbackBelow: ValueCallback<Uri?>? = null
private var imageUri: Uri? = null

override fun onCreate(savedInstanceState: Bundle?) {   
  super.onCreate(savedInstanceState)
  // webview definition 
  myWebView.webChromeClient = initChromeWebViewClient()
  myWebView.webViewClient = initWebViewClient()
  myWebView.settings.javaScriptEnabled = true
  myWebView.settings.domStorageEnabled = true
  myWebView.settings.allowFileAccess = true
  myWebView.settings.allowContentAccess = true
  myWebView.settings.supportZoom()
  myWebView.visibility = View.INVISIBLE
  myWebView.settings.userAgentString = "android"
  myWebView.setLayerType(View.LAYER_TYPE_HARDWARE, null)

  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
      myWebView.settings.mixedContentMode = 0
  }
}

private fun initChromeWebViewClient() : WebChromeClient {
    webChromeViewClient = object : WebChromeClient() {
        /**
         * API > = 21 (Android 5.0.1) calls back this method
         */
        override fun onShowFileChooser(
            webView: WebView?,
            valueCallback: ValueCallback<Array<Uri>>,
            fileChooserParams: FileChooserParams?
        ): Boolean {
            //(1) when the method calls back, it indicates that the version API > = 21. In this case, assign the result to muploadcallbackabovel to make it! = null
            mUploadCallbackAboveL = valueCallback
            takePhoto()
            return true
        }
    }
    return webChromeViewClient
}

@Override
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    super.onActivityResult(requestCode, resultCode, data)
    if (requestCode == REQUEST_CODE) {
        val file: File = File(imageUri!!.toString())
        val file_size = (file.length() / 1024).toString().toInt()
        //After the above two assignment operations (1) and (2), we can decide which processing method to adopt according to whether its value is empty
        if (mUploadCallbackBelow != null) {
            chooseBelow(resultCode, data);
        } else if (mUploadCallbackAboveL != null) {
            chooseAbove(resultCode, data);
        } else {
            Toast.makeText (this, "an error occurred.", Toast.LENGTH_SHORT).show();
        }
    }
}

Method which creates and starts the chooser Intent

private fun takePhoto() {
    //Adjust the camera in a way that specifies the storage location for taking pictures
    var photoFile : File? = null
    val authorities : String = applicationContext.packageName + ".provider"
    try {
        photoFile = createImageFile()
        imageUri = FileProvider.getUriForFile(this, authorities, photoFile)
    } catch(e: IOException) {
        e.printStackTrace()
    }

    val captureIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
    captureIntent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri)
    val Photo = Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI)
    val chooserIntent = Intent.createChooser(Photo, "Image Chooser")
    chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, arrayOf<Parcelable>(captureIntent))
    startActivityForResult(chooserIntent, REQUEST_CODE)
}

@Throws(IOException::class)
  private fun createImageFile(): File {
    val imageFileName = "JPEG_" + SimpleDateFormat("yyyyMMdd_HHmmss").format(Date())
    return File(applicationContext.cacheDir.toString() + File.separator + imageFileName + ".jpg")
}

method to handle Android L+ results

private fun chooseAbove(resultCode : Int, data : Intent?) {
    if (RESULT_OK == resultCode) {
        updatePhotos()

        if (data != null) {
            //Here is the processing of selecting pictures from a file
            val results : Array<Uri>
            val uriData : Uri? = data.data
            if (uriData != null) {
                results = arrayOf(uriData)
                mUploadCallbackAboveL!!.onReceiveValue(results)
            } else {
                mUploadCallbackAboveL!!.onReceiveValue(null)
            }
        } else {
            if (imageUri != null) {
                mUploadCallbackAboveL!!.onReceiveValue(arrayOf(imageUri!!))
            }
        }
    } else {
        mUploadCallbackAboveL!!.onReceiveValue(null)
    }
    mUploadCallbackAboveL = null
}

Edit: Updated code to working code up until Android 10, Android 11 does not work. Picture is taken, but not shown in Webviews Input. It seems the Fileprovider approach does not work or something is misconfigured for Android 11.

Question was updated with working code. Now chooser intent allows to select image from gallery or taken by the camera for Android +5 devices.

Main issue was, that no FileProvider was used and cache directory was not specified correctly in file_paths.xml file.

Thanks to @blackapps for help and guiding into the correct direction!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM