java.lang.IllegalArgumentException: Failed to find configured root that contains /data/data/**/cache/storage/CAPTURE-20210427_125521.jpg
Asked Answered
S

1

0

I am developing a flutter app in which I'm using webview_flutter plugin. Now inside the webview I want upload a file from the device. The problem is when I try to select browse button to choose the files from my device (or use Camera directly), I get IllegalArgumentException.

I found other issues like this but it didn't solve my problem:

Below is the code for the package I'm using: (You can also go to this LINK to get the exact code which I'm using)

packages/webview_flutter/android/src/main/AndroidManifest.xml:

<manifest package="io.flutter.plugins.webviewflutter"
xmlns:android="http://schemas.android.com/apk/res/android">
<application>
    <activity
        android:name="io.flutter.plugins.webviewflutter.RequestCameraPermissionActivity"
        android:theme="@android:style/Theme.Translucent.NoTitleBar.Fullscreen" />
    <activity
        android:name="io.flutter.plugins.webviewflutter.FileChooserActivity"
        android:theme="@android:style/Theme.Translucent.NoTitleBar.Fullscreen" />

    <provider
        android:name="io.flutter.plugins.webviewflutter.GenericFileProvider"
        android:authorities="${applicationId}.generic.provider"
        android:exported="false"
        android:grantUriPermissions="true"
        >
        <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/provider_paths"
            />
    </provider>
</application>

packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/Constants.java:

package io.flutter.plugins.webviewflutter;

public class Constants {
  static final String ACTION_REQUEST_CAMERA_PERMISSION_FINISHED =
      "action_request_camera_permission_denied";
  static final String ACTION_FILE_CHOOSER_FINISHED = "action_file_chooser_completed";

  static final String EXTRA_TITLE = "extra_title";
  static final String EXTRA_ACCEPT_TYPES = "extra_types";
  static final String EXTRA_SHOW_VIDEO_OPTION = "extra_show_video_option";
  static final String EXTRA_SHOW_IMAGE_OPTION = "extra_show_image_option";
  static final String EXTRA_FILE_URIS = "extra_file_uris";
  static final String EXTRA_ALLOW_MULTIPLE_FILES = "extra_allow_multiple_files";

  static final String WEBVIEW_STORAGE_DIRECTORY = "storage";
}

packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/FileChooserActivity.java:

package io.flutter.plugins.webviewflutter;

import static io.flutter.plugins.webviewflutter.Constants.ACTION_FILE_CHOOSER_FINISHED;
import static io.flutter.plugins.webviewflutter.Constants.EXTRA_ACCEPT_TYPES;
import static io.flutter.plugins.webviewflutter.Constants.EXTRA_ALLOW_MULTIPLE_FILES;
import static io.flutter.plugins.webviewflutter.Constants.EXTRA_FILE_URIS;
import static io.flutter.plugins.webviewflutter.Constants.EXTRA_SHOW_IMAGE_OPTION;
import static io.flutter.plugins.webviewflutter.Constants.EXTRA_SHOW_VIDEO_OPTION;
import static io.flutter.plugins.webviewflutter.Constants.EXTRA_TITLE;
import static io.flutter.plugins.webviewflutter.Constants.WEBVIEW_STORAGE_DIRECTORY;

import android.app.Activity;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.provider.MediaStore;
import android.provider.OpenableColumns;
import android.util.Log;
import androidx.annotation.Nullable;
import androidx.core.content.FileProvider;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;

public class FileChooserActivity extends Activity {

  private static final int FILE_CHOOSER_REQUEST_CODE = 12322;
  private static final SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMdd_HHmmss");

  // List of Uris that point to files where there MIGHT be the output of the capture. At most one of these can be valid
  private final ArrayList<Uri> potentialCaptureOutputUris = new ArrayList<>();

  @Override
  protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    showFileChooser(
        getIntent().getBooleanExtra(EXTRA_SHOW_IMAGE_OPTION, false),
        getIntent().getBooleanExtra(EXTRA_SHOW_VIDEO_OPTION, false));
  }

  private void showFileChooser(boolean showImageIntent, boolean showVideoIntent) {
    Intent getContentIntent = createGetContentIntent();
    Intent captureImageIntent =
        showImageIntent ? createCaptureIntent(MediaStore.ACTION_IMAGE_CAPTURE, "jpg") : null;
    Intent captureVideoIntent =
        showVideoIntent ? createCaptureIntent(MediaStore.ACTION_VIDEO_CAPTURE, "mp4") : null;

    if (getContentIntent == null && captureImageIntent == null && captureVideoIntent == null) {
      // cannot open anything: cancel file chooser
      sendBroadcast(new Intent(ACTION_FILE_CHOOSER_FINISHED));
      finish();
    } else {
      ArrayList<Intent> intentList = new ArrayList<>();

      if (getContentIntent != null) {
        intentList.add(getContentIntent);
      }

      if (captureImageIntent != null) {
        intentList.add(captureImageIntent);
      }
      if (captureVideoIntent != null) {
        intentList.add(captureVideoIntent);
      }

      Intent chooserIntent = new Intent(Intent.ACTION_CHOOSER);
      chooserIntent.putExtra(Intent.EXTRA_TITLE, getIntent().getStringExtra(EXTRA_TITLE));

      chooserIntent.putExtra(Intent.EXTRA_INTENT, intentList.get(0));
      intentList.remove(0);
      if (intentList.size() > 0) {
        chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, intentList.toArray(new Intent[0]));
      }

      startActivityForResult(chooserIntent, FILE_CHOOSER_REQUEST_CODE);
    }
  }

  private Intent createGetContentIntent() {
    Intent filesIntent = new Intent(Intent.ACTION_GET_CONTENT);

    if (getIntent().getBooleanExtra(EXTRA_ALLOW_MULTIPLE_FILES, false)) {
      filesIntent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
    }

    String[] acceptTypes = getIntent().getStringArrayExtra(EXTRA_ACCEPT_TYPES);

    if (acceptTypes.length == 0 || (acceptTypes.length == 1 && acceptTypes[0].length() == 0)) {
      // empty array or only 1 empty string? -> accept all types
      filesIntent.setType("*/*");
    } else if (acceptTypes.length == 1) {
      filesIntent.setType(acceptTypes[0]);
    } else {
      // acceptTypes.length > 1
      filesIntent.setType("*/*");
      filesIntent.putExtra(Intent.EXTRA_MIME_TYPES, acceptTypes);
    }

    return (filesIntent.resolveActivity(getPackageManager()) != null) ? filesIntent : null;
  }

  private Intent createCaptureIntent(String type, String fileFormat) {
    Intent captureIntent = new Intent(type);
    if (captureIntent.resolveActivity(getPackageManager()) == null) {
      return null;
    }

    // Create the File where the output should go
    Uri captureOutputUri = getTempUri(fileFormat);
    potentialCaptureOutputUris.add(captureOutputUri);

    captureIntent.putExtra(MediaStore.EXTRA_OUTPUT, captureOutputUri);

    return captureIntent;
  }

  private File getStorageDirectory() {
    File imageDirectory = new File(getCacheDir(), WEBVIEW_STORAGE_DIRECTORY);
    if (!imageDirectory.exists() && !imageDirectory.mkdir()) {
      Log.e("WEBVIEW", "Unable to create storage directory");
    }
    return imageDirectory;
  }

  private Uri getTempUri(String format) {
    String fileName = "CAPTURE-" + simpleDateFormat.format(new Date()) + "." + format;
    File file = new File(getStorageDirectory(), fileName);
    return FileProvider.getUriForFile(
        this, getApplicationContext().getPackageName() + ".generic.provider", file);
  }

  private String getFileNameFromUri(Uri uri) {
    Cursor returnCursor = getContentResolver().query(uri, null, null, null, null);
    assert returnCursor != null;
    int nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
    returnCursor.moveToFirst();
    String name = returnCursor.getString(nameIndex);
    returnCursor.close();
    return name;
  }

  private Uri copyToLocalUri(Uri uri) {
    File destination = new File(getStorageDirectory(), getFileNameFromUri(uri));

    try (InputStream in = getContentResolver().openInputStream(uri);
        OutputStream out = new FileOutputStream(destination)) {
      byte[] buffer = new byte[1024];
      int len;
      while ((len = in.read(buffer)) != -1) {
        out.write(buffer, 0, len);
      }
      return FileProvider.getUriForFile(
          this, getApplicationContext().getPackageName() + ".generic.provider", destination);
    } catch (IOException e) {
      Log.e("WEBVIEW", "Unable to copy selected image", e);
      e.printStackTrace();
      return null;
    }
  }

  @Override
  protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (requestCode == FILE_CHOOSER_REQUEST_CODE) {
      Intent fileChooserFinishedIntent = new Intent(ACTION_FILE_CHOOSER_FINISHED);
      if (resultCode == Activity.RESULT_OK) {
        if (data != null && (data.getDataString() != null || data.getClipData() != null)) {
          if (data.getDataString() != null) {
            // single result from file browser OR video from camera
            Uri localUri = copyToLocalUri(data.getData());
            if (localUri != null) {
              fileChooserFinishedIntent.putExtra(
                  EXTRA_FILE_URIS, new String[] {localUri.toString()});
            }
          } else if (data.getClipData() != null) {
            // multiple results from file browser
            int uriCount = data.getClipData().getItemCount();
            String[] uriStrings = new String[uriCount];

            for (int i = 0; i < uriCount; i++) {
              Uri localUri = copyToLocalUri(data.getClipData().getItemAt(i).getUri());
              if (localUri != null) {
                uriStrings[i] = localUri.toString();
              }
            }
            fileChooserFinishedIntent.putExtra(EXTRA_FILE_URIS, uriStrings);
          }
        } else {
          // image result from camera (videos from the camera are handled above, but this if-branch could handle them too if this varies from device to device)
          for (Uri captureOutputUri : potentialCaptureOutputUris) {
            try {
              // just opening an input stream (and closing immediately) to test if the Uri points to a valid file
              // if it's not a real file, the below catch-clause gets executed and we continue with the next Uri in the loop.
              getContentResolver().openInputStream(captureOutputUri).close();
              fileChooserFinishedIntent.putExtra(
                  EXTRA_FILE_URIS, new String[] {captureOutputUri.toString()});
              // leave the loop, as only one of the potentialCaptureOutputUris is valid and we just found it
              break;
            } catch (IOException ignored) {
            }
          }
        }
      }
      sendBroadcast(fileChooserFinishedIntent);
      finish();
    } else {
      super.onActivityResult(requestCode, resultCode, data);
    }
  }
}

packages/webview_flutter/android/src/main/res/values/strings.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="webview_file_chooser_title">Choose a file</string>
    <string name="webview_image_chooser_title">Choose an image</string>
    <string name="webview_video_chooser_title">Choose a video</string>
</resources>

packages/webview_flutter/android/src/main/res/xml/provider_paths.xml:

<?xml version="1.0" encoding="utf-8"?>
<paths>
    <cache-path name="storage"/>
</paths>

The app is crashing with this error:

I/Timeline(26709): Timeline: Activity_launch_request time:162255045 intent:Intent { flg=0x10000000 cmp=com.XXXX(Package)/io.flutter.plugins.webviewflutter.FileChooserActivity (has extras) }
D/OneSignal(26709): curActivity is NOW: null
W/ActivityThread(26709): handleWindowVisibility: no activity for token android.os.BinderProxy@1811995
D/AndroidRuntime(26709): Shutting down VM
E/AndroidRuntime(26709): FATAL EXCEPTION: main
E/AndroidRuntime(26709): Process: com.XXXX, PID: 26709
E/AndroidRuntime(26709): java.lang.RuntimeException: Unable to start activity ComponentInfo{com.XXXX/io.flutter.plugins.webviewflutter.FileChooserActivity}: java.lang.IllegalArgumentException: Failed to find configured root that contains /data/data/com.XXXX(Package)/cache/storage/CAPTURE-20210426_203521.jpg
E/AndroidRuntime(26709):    at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2946)
E/AndroidRuntime(26709):    at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3081)
E/AndroidRuntime(26709):    at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78)
E/AndroidRuntime(26709):    at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)
E/AndroidRuntime(26709):    at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)
E/AndroidRuntime(26709):    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1831)
E/AndroidRuntime(26709):    at android.os.Handler.dispatchMessage(Handler.java:106)
E/AndroidRuntime(26709):    at android.os.Looper.loop(Looper.java:201)
E/AndroidRuntime(26709):    at android.app.ActivityThread.main(ActivityThread.java:6810)
E/AndroidRuntime(26709):    at java.lang.reflect.Method.invoke(Native Method)
E/AndroidRuntime(26709):    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:547)
E/AndroidRuntime(26709):    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:873)
E/AndroidRuntime(26709): Caused by: java.lang.IllegalArgumentException: Failed to find configured root that contains /data/data/com.XXXX(Package)/cache/storage/CAPTURE-20210426_203521.jpg
E/AndroidRuntime(26709):    at androidx.core.content.FileProvider$SimplePathStrategy.getUriForFile(FileProvider.java:744)
E/AndroidRuntime(26709):    at androidx.core.content.FileProvider.getUriForFile(FileProvider.java:418)
E/AndroidRuntime(26709):    at io.flutter.plugins.webviewflutter.FileChooserActivity.getTempUri(FileChooserActivity.java:138)
E/AndroidRuntime(26709):    at io.flutter.plugins.webviewflutter.FileChooserActivity.createCaptureIntent(FileChooserActivity.java:119)
E/AndroidRuntime(26709):    at io.flutter.plugins.webviewflutter.FileChooserActivity.showFileChooser(FileChooserActivity.java:54)
E/AndroidRuntime(26709):    at io.flutter.plugins.webviewflutter.FileChooserActivity.onCreate(FileChooserActivity.java:46)
E/AndroidRuntime(26709):    at android.app.Activity.performCreate(Activity.java:7224)
E/AndroidRuntime(26709):    at android.app.Activity.performCreate(Activity.java:7213)
E/AndroidRuntime(26709):    at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1272)
E/AndroidRuntime(26709):    at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2926)
E/AndroidRuntime(26709):    ... 11 more
D/libcrashlytics(26709): Initializing libcrashlytics version 3.0.0
D/libcrashlytics(26709): Initializing native crash handling successful.

Can you help me with what I may be missing?

Selfridge answered 27/4, 2021 at 8:42 Comment(0)
S
0

I found out a way to fix the problem.

Below are the changes I made in the files.

packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/Constants.java:

...
...
static final String WEBVIEW_STORAGE_DIRECTORY = "images";

packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/FileChooserActivity.java:

private File getStorageDirectory() {
File imageDirectory = new File(this.getExternalFilesDir(null), WEBVIEW_STORAGE_DIRECTORY);
if(!imageDirectory.isDirectory()){
            imageDirectory.mkdir();
        }
...
...

packages/webview_flutter/android/src/main/res/xml/provider_paths.xml:

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-files-path name="safetyapp_images" path="images" />
</paths>
Selfridge answered 29/4, 2021 at 4:53 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.