1

I really need your help because I'm stuck <.< I have already tryed all solutions I've found here in "stackoverflow" for this problem, but any of them worked for me..

I have updated my application to follow the new Google Play Store policies, so actually my app is using only "Scoped Storage" without "Shared Storage" behaviour, so I've removed from the manifest the "requestLegacyExternalStorage".

In my app I need to send to the server some file selected by the user, these file can be stored in the "internal storage" or in the "external storage". They are always stored outside of the app-specific directory, so the directory in: "/storage/emulated/0/Android/data/[APP_PACKAGE]/files"

My biggest problem is that I can't open any file stored outside of the app-specific directory! I really need to open the files that the user has selected and convert its content to "base64" to send it to the server. But when I use this method to get the file content encoded in base64:

public static String fileToBase64(@NonNull String filePath) {
    String ret = null;
    byte[] buffer = new byte[8192];
    int bytesRead;
    try(ByteArrayOutputStream bAOS = new ByteArrayOutputStream();
        Base64OutputStream b64OS = new Base64OutputStream(bAOS, Base64.DEFAULT);
        InputStream iS = new FileInputStream(filePath)){
        while ((bytesRead = iS.read(buffer)) != -0x1) {
            b64OS.write(buffer, 0x0, bytesRead);
        }
        b64OS.flush();
        ret = bAOS.toString();
    } catch (IOException ioE) {
        Logger.onException(TAG, ioE);
    }
    return ret;
}

I always get an ACCESS PERMISSION DENIED.

Is there any way to solve this problem without using "requestLegacyStorage" in the manifest? I know that Google will remove all applications that don't use only "SCOPED STORAGE" from the store starting from 5 of july, so I can't use the "reqestLegacyStorage" to solve this problem...

I'm already requesting and giving the "READ_EXTERNAL_STORAGE" and "WRITE_EXTERNAL_STORAGE" permissions, but I still can't open the file content if it is stored outside of the dedicated application directory... I can only open and encode file content if it is in the app directory (so: "/storage/emulated/0/Android/data/[APP_PACKAGE]/files"), but I need to upload to my server files choosed by the user so these files were never stored inside the app directory, they are always stored inside the internal storage or the external storage in directories like the "Download" dir or the "Pictures" dir (I need to upload every type of files, so pictures, documents, pdfs ecc.. ecc.)

Is there any solution to this problem? I have already tryed all the solutions I found online, but I always get an ACCESS EXCEPTION if I don't add "requestLegacyStorage" to the manifest (and I can't add it to notg go against Google's policies...) Please I really need to solve this problem because this is one of the most important feature of my app..

Thank you so much! I hope anybody can help me solve this problem T_T

Have a nice day and nice coding!

(Ask if you need more informations and I will add them!)

##########################################################################

If anyone needs it I found a "solution" but works only by using "ACTION_GET_CONTENT" (and probably by using "ACTION_OPEN_DOCUMENT", but I didn't try it yet). When you select a file (stored outside the app-specific directory) using "ACTION_GET_CONTENT" this file is copied inside the app-specific directory ("/storage/emulated/0/Android/data/[APP_PACKAGE]/files") so you can open it because it agrees with the "SCOPED STORAGE" policy.

# "ACTION_GET_CONTENT" code:

Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
String[] mimes = {
      "application/*",
      "audio/*",
      "font/*",
      "image/*",
      "message/*",
      "model/*",
      "multipart/*",
      "text/*",
      "video/*"
};
intent.setType("*/*");
intent.putExtra(Intent.EXTRA_MIME_TYPES, mimes);
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
startActivityForResult(Intent.createChooser(intent, getString(R.string.msg_select_file_to_upload)), REQ_CODE_OPEN_DOCUMENT);

Theoretically it also works without passing the "mimes" array to the intent extra "EXTRA_MIME_TYPES".

To get the path inside the "onActivityResult":

String path = FilesUtils.onActivityResultOpenDocument(this, data);


public static String onActivityResultOpenDocument(Context context, Intent data){
    String selectedPath, fileName;
    Uri uri = data.getData();
    String mimeType = context.getContentResolver().getType(uri);
    if (mimeType == null) {
        String path = getPathFromOpenDocumentUri(context, uri);
        if (path == null) {
            fileName = FilenameUtils.getName(uri.toString());
        } else {
            File file = new File(path);
            fileName = file.getName();
        }
    } else {
        Uri returnUri = data.getData();
        Cursor returnCursor = context.getContentResolver().query(returnUri, null, null, null, null);
        int nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
        int sizeIndex = returnCursor.getColumnIndex(OpenableColumns.SIZE);
        returnCursor.moveToFirst();
        fileName = returnCursor.getString(nameIndex);
    }
    String sourcePath = context.getExternalFilesDir(null).toString();
    selectedPath = formatFilepath(sourcePath, fileName);
    File fileSave = new File(selectedPath);
    try {
        copyUriStreamToFile(context, uri, fileSave);
    } catch (Exception e) {
        Logger.onException(TAG, e);
        Toast.makeText(context, R.string.error_impossibile_recuperare_file, Toast.LENGTH_SHORT).show();
        selectedPath = null;
    }
    return selectedPath;
}

So summarizing by using "ACTION_GET_CONTENT" (and maybe "ACTION_OPEN_DOCUMENT" too, but I didn't tryed this) the selected file is copied inside the app-specific directory (so: "/storage/emulated/0/Android/data/[APP_PACKAGE]/files") in this way the file can be opened because it agrees with the "Scoped Storage" policy.

Thank you all for your answers and your time! (: <3

I still don't know how to read a file if it is stored outside the app-specific directory, so if anybody know it please share your solution (:

Z3R0
  • 1,011
  • 10
  • 19
  • "What could be the problem?" -- it will be difficult to comment on your `ACTION_GET_CONTENT`/`ACTION_OPEN_DOCUMENT` code, as your question does not contain it. "I always get an ACCESS PERMISSION DENIED" -- where does `filePath` come from, and are you sure that it is a filesystem path? – CommonsWare May 23 '21 at 17:54
  • Hello, thanks for your comment. The file paths I'm trying are, for example : "/storage/emulated/0/Pictures/1621628200521.jpg" So the "Pictures" folder in the external storage. I add the "ACTION_GET_CONTENT" code I'm using, – Z3R0 May 23 '21 at 17:59
  • @CommonsWare Updated (: Ask if you need more info, thank you so much! – Z3R0 May 23 '21 at 18:03
  • @CommonsWare I removed the part with the problem about viewing the files because I first need to solve the problem about opening them. Also I found that by using "ACTION_GET_CONTENT" I can see the files, they are in the "internal memory" and not in the "external" (I don't know why lol) Anyway my biggest problem that I must solve is how to open the files in Android 10/11 :l If I can't open them a "must have" feature of my app is useless.. Thank you! – Z3R0 May 23 '21 at 18:13
  • `InputStream iS = new FileInputStream(filePath)` Replace by `InputStream iS = getContentResolver().openInputStream(data,getData();` Where data is a parameter of onActivityResult. – blackapps May 23 '21 at 18:19
  • 1
    On the whole, I recommend that you stick with `ACTION_OPEN_DOCUMENT` (or possibly `ACTION_GET_CONTENT`). Your app will not have read access to arbitrary filesystem paths, and I do not know where your test `filePath` value is coming from. Note that I would expect your app to crash frequently with an `OutOfMemoryError`, as you are trying to read the entire content into memory, then convert the entire thing into base64, and that will only work for small pieces of content. Converting content to base64 has been obsolete for a couple of decades; perhaps you could consider avoiding that step. – CommonsWare May 23 '21 at 18:21
  • 1
    If you can get rid of the base64 part, you could then look into things like OkHttp/Retrofit [and implementations of `InputStreamRequestBody`](https://stackoverflow.com/a/56308643/115145) to upload straight from the `Uri` that you get from `ACTION_OPEN_DOCUMENT` or `ACTION_GET_CONTENT`, without having to load the entire content into memory at once. – CommonsWare May 23 '21 at 18:23
  • @CommonsWare What should I use and do instead of this way to encode the file content and send it to the server? Can you better explain me this please? Any way I'm encoding to base64 to send it to the server, using classic hex encoding won't working. – Z3R0 May 23 '21 at 18:29
  • @blackapps Thank you for your answer, I try it and I let you know if it worked (: – Z3R0 May 23 '21 at 18:30
  • @CommonsWare mh for how the server is done I can't just send the file content , I must format my request by adding a token to it and I must put the file content inside a "params" argument. Also, actually, the server sometimes has some problem with classic hex encoded files – Z3R0 May 23 '21 at 18:33
  • @blackapps Sorry it isnt' working, I still get: java.io.FileNotFoundException: /storage/emulated/0/Pictures/JPEG_1621793180593.jpg: open failed: EACCES (Permission denied) – Z3R0 May 23 '21 at 18:34
  • @CommonsWare Actually I never got an "OutOfMemoryError" :/ – Z3R0 May 23 '21 at 18:37
  • Show your onActivityResult code. And the code i proposed. Post reproducable code so every body can see what you do. At the moment that is a mistery. – blackapps May 23 '21 at 18:50
  • @blackapps My "onActivityResult" code is correct, anyway I'm adding it to my question because I found a solution by using "ACTION_GET_CONTENT". The file selected using "ACTION_GET_CONTENT" it's copied inside the app-specific directory so after the user selects the file I can open it because it agrees with the "Scoped Storage" policy. Anyway I'll like to know how to open a file stored outside the app-specific directory if the app agrees with the "Scoped Storage" policy, so the code used isn't a "must have" to solve this problem because there are different ways to select and open a file – Z3R0 May 23 '21 at 20:43
  • @blackapps and I need to know how to open it, without get an ACCESS PERMISSION exception, if it is stored outisde the app-specific directory. Actually I didn't find a way to do it :/ Thank you for your answer and your time, if you and "CommonsWare" wants to add an answer to this question I will give you some points ^^ – Z3R0 May 23 '21 at 20:46
  • @CommonsWare I'd like to know what are the best practices you were talking about (: Thank you! – Z3R0 May 23 '21 at 20:46
  • 1
    You do not need to do all that copying, unless you actually need a long-term copy of the content. If all you are doing is uploading it, even with your current base64-based approach, just use a `ContentResolver` and `openInputStream()`, and use that instead of your `FileInputStream` in your original code snippet. And, with regards to the base64 stuff, talk to whoever is maintaining your Web service and ask them why that is needed. – CommonsWare May 23 '21 at 21:19
  • @CommonsWare The token passed to the server is a "must have" to verify the user and this behaviour won't ever be changed xD (a lot of things on the server are wrong, but nobody cares lol I'm sure you can feel my pain about that xD) I wrote the function to get the file on the back-end but the request is pre-processed so I must follow this server "requirements", so the arguments of my request must be formatted in a specific way. Also I can't just send the file content as you wrote before because I need to add other data (If I understand correctly what you meant, otherwise please correct me). – Z3R0 May 23 '21 at 21:29
  • @CommonsWare Anyway I will try with the "ContentResolver" and "openInputStream" as you wrote, but what are the differences from my current implementation? Really Thank you for your time and your answers! I really appreciate it! If you want to add an answer to my question I will give you some points (: Have a nice evening and a nice coding! – Z3R0 May 23 '21 at 21:31

0 Answers0