7

My code was working perfectly until my phone updated last night. I'm reading a CSV file from the phones storage into an array in my app using opencsv. This is the code...

    public static String[] getFileContents(File file) {
    String[] contentsArray = new String[500];
    if(file.exists()) {
        try {
            CSVReader reader = new CSVReader(new FileReader(file));
            String[] nextLine;
            while ((nextLine = reader.readNext()) != null) {
                System.arraycopy(nextLine, 0, contentsArray, 0, nextLine.length);
            }
        } catch (Exception e) {
            System.out.println("Failed file: " + file);
            System.out.println("You are here and sad but at least the file exists");
            e.printStackTrace();
        }
    } else {
        System.out.println("File does not exist");
    }

    return contentsArray;
}

and this is the error I'm getting...

I/System.out: Failed file: /storage/emulated/0/Documents/text/36-12644-Test cert-2021.csv
I/System.out: You are here and sad but at least the file exists
W/System.err: java.io.FileNotFoundException: /storage/emulated/0/Documents/text/36-12644-Test cert-2021.csv: open failed: EACCES (Permission denied)

I have changed nothing. Can anyone point me in the right direction to resolve this please? Has something changed in the new update (Android 11) with permissions that is now blocking openCSV from reading the file?

Edit: This is the code that brings in the selected file from the fileExplorer...

                    Uri fileUri = data.getData();
                String filePath;
                try {
                    filePath = Utils.getRealPathFromURI(this.getContext(), fileUri);
                    selectedFile = new File(filePath);
                    contentsFromFile = Utils.getFileContents(selectedFile.getAbsoluteFile());

and this is the code that gets the path...

    public static String getRealPathFromURI(Context context, Uri contentUri) {
    Cursor cursor = null;
    try {
        String[] proj = {MediaStore.Images.Media.DATA};
        cursor = context.getContentResolver().query(contentUri, proj, null, null, null);
        int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
        cursor.moveToFirst();
        return cursor.getString(column_index);
    } catch (Exception e) {
        Log.e(TAG, "getRealPathFromURI Exception : " + e.toString());
        return;
    } finally {
        if (cursor != null) {
            cursor.close();
        }
    }
}
Alan Haden
  • 147
  • 14
  • Since it's about permissions, maybe you need to re-install your app to fix permissions. – AntiqTech Jan 31 '21 at 18:01
  • Thanks for replying. I've uninstalled and reinstalled the app dozens of times during the last couple of days. The problem persists. – Alan Haden Jan 31 '21 at 18:51
  • Sorry, When I didn't see anything about reinstalling, I thought it was worth a shot. – AntiqTech Jan 31 '21 at 19:06
  • I^ve found this : https://developer.android.com/about/versions/11/privacy/storage It mentions setting requestLegacyExternalStorage to true in your app's manifest file. That way, your app can continue to behave as expected on devices that run Android 10. If you haven't already done so already. – AntiqTech Jan 31 '21 at 19:22
  • 1
    Thanks again but I have that in the manifest already. – Alan Haden Jan 31 '21 at 20:07
  • How are you getting the value of `file` that you are passing into `getFileContents()`? – CommonsWare Feb 01 '21 at 12:09
  • @CommonsWare. I have edited the post to show more. The code fails at the point where I attempt to assign the new fileReader(file) – Alan Haden Feb 01 '21 at 13:37
  • 2
    Delete `getRealPathFromURI()`. Use `ContentResolver` and `openInputStream()` to get an `InputStream` on the content. Wrap that `InputStream` in an `InputStreamReader`. Pass that `InputStreamReader` to `CSVReader`. – CommonsWare Feb 01 '21 at 13:45
  • Duplicates include [this](https://stackoverflow.com/questions/59123162/android-kotlin-getting-a-filenotfoundexception-with-filename-chosen-from-file-p) and [this](https://stackoverflow.com/questions/49221312/android-get-real-path-of-a-txt-file-selected-from-the-file-explorer), among others. – CommonsWare Feb 01 '21 at 13:46
  • @CommonsWare. Thank you very much for your reply and I'm sure you're absolutely right. However, I'm an electrician primarily and this is something I do as a hobby. I've spent the last 3hrs trying to figure how to do this as you've kindly suggested but I'm not getting very far. Could you please point me towards an example? I've explored the two links you provided and many others but I can't get anything to work. – Alan Haden Feb 01 '21 at 17:03
  • 1
    `InputStream input = this.getContext().getContentResolver().openInputStream(fileUri);` gets you the `InputStream`. `CSVReader reader = new CSVReader(new InputStreamReader(input))`; gives you your `CSVReader`. – CommonsWare Feb 01 '21 at 17:17
  • @CommonsWare. Thank you so much for your patience with me. I have got it working again. If I haven't taken too much of your time already, could you please tell me why why method simply stopped working after the update when it had been fine for a couple of months previous? – Alan Haden Feb 01 '21 at 18:27
  • 1
    "when it had been fine for a couple of months previous?" -- your existing code would have failed for lots of people in lots of situations. With respect to your device, Android 11 locked down filesystem access further, and there are locations that you cannot access by any `File` means. `getRealPathFromURI()` was always a bad solution; it has just gotten worse in recent years. – CommonsWare Feb 01 '21 at 18:56
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/228165/discussion-between-alan-haden-and-commonsware). – Alan Haden Feb 02 '21 at 15:48

1 Answers1

4

Thanks to CommonsWare I have now got the code working again. Their solution was to

delete getRealPathFromURI(). Use ContentResolver and openInputStream() to get an InputStream on the content. Wrap that InputStream in an InputStreamReader. Pass that InputStreamReader to CSVReader.

Specifically...

InputStream input = this.getContext().getContentResolver().openInputStream(fileUri);
CSVReader reader = new CSVReader(new InputStreamReader(input));
Alan Haden
  • 147
  • 14