10

I was trying to download an image from a server and save it in the external memory, but in Android 11 it gives me an error when I try to create the file. I have granted permission to access the external storage.

i searched a bit on the internet and they suggested me to put this code in the manifest, but it didn't work for android 11

android:requestLegacyExternalStorage="true"

manifest

<uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WAKE_LOCK" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:requestLegacyExternalStorage="true"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.TestDwonloadImgApp"
        android:usesCleartextTraffic="true">
        <activity android:name=".MainActivity2">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name=".MainActivity">
        </activity>
    </application>

MainActivity

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        ImageView img = findViewById(R.id.img);

        ImmagineInterface ii = RetrofitManager.retrofit.create(ImmagineInterface.class);
        Call<ResponseBody> call = ii.downloadFile("/immaginimusei/arte-scienza.jpg");
        call.enqueue(new Callback<ResponseBody>() {
            @Override
            public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
                if (response.code() == 200) {
                    boolean result = writeResponseBody(response.body(), "/immaginimusei/arte-scienza.jpg");
                    if(result) {
                        Bitmap bitmap = BitmapFactory.decodeFile(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).toString() + "/ArtHunter/immaginimusei/arte-scienza.jpg");
                        img.setImageBitmap(bitmap);
                    }
                }
            }

            @Override
            public void onFailure(Call<ResponseBody> call, Throwable t) {
                Bitmap bitmap = BitmapFactory.decodeFile(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).toString() + "/ArtHunter/immaginimusei/arte-scienza.jpg");
                img.setImageBitmap(bitmap);
            }
        });
    }
}

writeResponseBody

public static boolean writeResponseBody(ResponseBody body, String dir1) {
        try {
            String state = Environment.getExternalStorageState();
            if (Environment.MEDIA_MOUNTED.equals(state)) {
                // todo change the file location/name according to your needs

                String path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).toString() + "/ArtHunter";

                String path1 = path + dir1;
                File f = new File(path1);
                String path2 = f.getPath();
                String nome = f.getName();
                path2 = path2.replaceAll("/" + nome, "");

                File directory = new File(path2);
                if (!directory.exists())
                    directory.mkdirs();

                File img = new File(path2, nome);
                if (img.exists())
                    return true;
                img.createNewFile();

                InputStream inputStream = null;
                FileOutputStream outputStream = null;

                try {
                    byte[] fileReader = new byte[4096];

                    inputStream = body.byteStream();
                    outputStream = new FileOutputStream(img); //error here!

                    while (true) {
                        int read = inputStream.read(fileReader);

                        if (read == -1) {
                            break;
                        }

                        outputStream.write(fileReader, 0, read);

                    }

                    outputStream.flush();

                    return true;
                } catch (IOException e) {
                    e.printStackTrace();
                    return false;
                } finally {
                    if (inputStream != null) {
                        inputStream.close();
                    }

                    if (outputStream != null) {
                        outputStream.close();
                    }
                }
            }
            return false;
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        }
    }

error

/System.err: java.io.FileNotFoundException: /storage/emulated/0/Download/ArtHunter/immaginimusei/arte-scienza.jpg: open failed: EEXIST (File exists)
W/System.err:     at libcore.io.IoBridge.open(IoBridge.java:492)
        at java.io.FileOutputStream.<init>(FileOutputStream.java:236)
        at java.io.FileOutputStream.<init>(FileOutputStream.java:186)
        at com.theapplegeek.testdwonloadimgapp.MainActivity.writeResponseBody(MainActivity.java:93)
        at com.theapplegeek.testdwonloadimgapp.MainActivity$1.onResponse(MainActivity.java:47)
        at retrofit2.DefaultCallAdapterFactory$ExecutorCallbackCall$1.lambda$onResponse$0$DefaultCallAdapterFactory$ExecutorCallbackCall$1(DefaultCallAdapterFactory.java:89)
        at retrofit2.-$$Lambda$DefaultCallAdapterFactory$ExecutorCallbackCall$1$hVGjmafRi6VitDIrPNdoFizVAdk.run(Unknown Source:6)
        at android.os.Handler.handleCallback(Handler.java:938)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loop(Looper.java:245)
        at android.app.ActivityThread.main(ActivityThread.java:8004)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:631)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:978)
    Caused by: android.system.ErrnoException: open failed: EEXIST (File exists)
        at libcore.io.Linux.open(Native Method)
        at libcore.io.ForwardingOs.open(ForwardingOs.java:166)
        at libcore.io.BlockGuardOs.open(BlockGuardOs.java:254)
W/System.err:     at libcore.io.ForwardingOs.open(ForwardingOs.java:166)
        at android.app.ActivityThread$AndroidOs.open(ActivityThread.java:7865)
        at libcore.io.IoBridge.open(IoBridge.java:478)
        ... 13 more
TheAppleGeeK
  • 139
  • 1
  • 2
  • 7
  • 1
    `path2 = path2.replaceAll("/" + nome, "");` You are messing around with building up a path. Nobody can follow you and i think you lost sight too. Further check the return value of mkdirs and if false handle accordingly. Remove the .createNewFile call. Rewrite your code please. Also here. – blackapps Mar 15 '21 at 19:47
  • did you get solution?? I don't want to use android:requestLegacyExternalStorage – Shweta Chauhan May 07 '21 at 12:26

3 Answers3

13

In Android 11 android:requestLegacyExternalStorage="true" will simply be ignored, since it was an ad-hoc solution for Android < 11 to not break old apps. Now, you must use

<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>

Also you could just use SAF to avoid all this 'permissions' hassle. This is what Google recommends for apps that do not need to manage most internal storage data. Refer to: https://developer.android.com/guide/topics/providers/document-provider

However, if you don't want to break you app and lose all your hard work, consider

if(Environment.isExternalStorageManager())
{
    internal = new File("/sdcard");
    internalContents = internal.listFiles();
}
else
{
    Intent permissionIntent = new Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION);
    startActivity(permissionIntent);
}

This will bring up a settings page where you will be able to give storage access to your app. If the app already has permission, then you will be able to access the directory. Place this at the very beginning of onCreate() method after setting layout resource.

It's best not to do this for any future apps you build.

mindoverflow
  • 730
  • 4
  • 13
  • 1
    As far as I understand... a subdirectory of /Download is shared storage and shared storage should be possible to access without MANAGE_EXTERNAL_STORAGE permission or SAF. I currently have a similar issue for subdirectories of shared storage and I'm looking for a solution... – Taifun Feb 16 '22 at 17:38
  • It's been a while since I worked on Android, but accessing the download storage without SAF would require us to use the ROOM persistence framework, which would be a great hassle if i'm only accessing very rarely. the last time i had worked on android, there was this very odd thing happening that if you called ```getFilesDir()``` to access ```Downloads```, it would simply create a folder names such in the app's internal storage directory. i would probably advise anyone who isn't working on file manager apps to move away from accessing local directly as with every iteration Android messes it up – mindoverflow Jun 29 '22 at 14:21
  • I have the same issue: I would like to create a file into a folder, for which I have granted the persmission before (by `ACTION_OPEN_DOCUMENT_TREE`), but it works upredictable. For example with file name: "demoobstacle.kml" it is ok, but with file name: "DemoObstacle.kml" it throws the same exception as in the question. By using the permission `ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION` it works, but I don't understand what the problem is. (I tried it on other devices (other android versions) and it worked perfectly with any file name.) Any idea? – jack-wilson Sep 02 '22 at 13:11
  • i believe for single file creation we should use ```ACTION_OPEN_DOCUMENT```. but at one point in time i used to be heavily involved in android, and i would suggest you use their room persistence api to do everything. i dont knkw what their end goal is, but theyre trying to restrict everything like IOS in the name of security. as of now file access has 2 methods. 1 applies to pre 10 and one post. but overall i'll bet future iterations will get rid of SAF as well any manual non-MIME type access. if we want apps to not get ruined after each android release best to use what they recommend. :( – mindoverflow Sep 03 '22 at 16:27
3

Go to your mobile setting -> apps -> select your app -> permissions -> storage -> select Allow managment of all files

It works for me.

1

Android has become too complex when it comes to creating folders.

If you want to avoid so many problems and not use things like android:requestLegacyExternalStorage="true" and adding many permissions like MANAGE_EXTERNAL_STORAGE, READ_EXTERNAL_STORAGE, WRITE_EXTERNAL_STORAGE which can be a nightmare, even when it comes to publishing your app to the marketplace.

In addition to that, from Android version 30 onwards, you ca NOT extend android:requestLegacyExternalStorage="true". So you will have problems again.

What is recommended is to start to save your recordings inside applications dedicated folder, because in the future Android will no longer let you create folders anymore, even with legacy methods. You can use standard directories in which to place any file like DIRECTORY_MUSIC, DIRECTORY_PICTURES, etc.

For instance, I created my own method for saving audio recording in Android 33 and it works perfectly. I don't need to add anything to my Manifest.

override fun startRecording(): Boolean {
    try {
        val recordingStoragePath = "${app.getExternalFilesDir(Environment.DIRECTORY_MUSIC)}"
        recordingFilePath = "$recordingStoragePath/${fileRecordingUtils.generateFileName()}"
        mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC)
        mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP)
        // Audio Quality Normal
        mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB)
        mediaRecorder.setAudioSamplingRate(8000)
        // Set path
        mediaRecorder.setOutputFile(recordingFilePath)
        mediaRecorder.prepare()
        mediaRecorder.start()
        Toast.makeText(app, "Recording Started", Toast.LENGTH_SHORT).show()
    } catch (e: IOException) {
        Toast.makeText(app, "Recording Failed. Problem accessing storage.", Toast.LENGTH_SHORT).show()
        mediaRecorder.reset()
        return false
    } catch (e: Exception) {
        mediaRecorder.reset()
        return false
    }
    return true
}