Android 10: What are my options to save files on external storage into a directory called "/sdcard/my-app/"
Asked Answered
G

3

43

Up until Android Pie I always stored files which the app needed to get stored on /sdcard/my-app/, which I got via

File fBaseDir = new File(Environment.getExternalStorageDirectory(), "my-app");

One of my App stores hundreds (up to multiple thousands) of favicons in /sdcard/my-app/favicons, and when the user creates a backup of the app, it gets stored in /sdcard/my-app/backups. Webpage-Screenshots taken by Google Chrome when shared with my app are stored in /sdcard/my-app/screenshots, and so on.

When I uninstall the app, I can reinstall it later and still have all the favicons, screenshots and the backups available for restoring the state and not having to re-download all the favicons (and possibly having lost all the other data).

I can also easily create a backup of the /sdcard/my-app/ directory by copying it to my PC, and restore it on another phone, for example when I migrate to a new device.

I can also use a file explorer to see which files are in that directory, and select and email them or copy them to Google Drive, or delete some of them specifically (like old backup files).

With Android 10 this approach has collapsed. I cannot add the favicons to the Images Media-Folder, because they are no real images, they would clutter the view unnecessarily and possibly ban my developer account from the Play Store for doing this. I also don't want to store all this data in an App-Private directory, because it will get deleted when the app gets uninstalled, and because other apps like an explorer cannot access them.

What are my options? Manually selecting files or directories is not an option, unless the directory only needs to be selected once and then access is granted forever (to read and write files).

I never read or write outside of /sdcard/my-app/.

Gisarme answered 4/9, 2019 at 12:54 Comment(8)
for starters there’s still android:requestLegacyExternalStorage mode, though that’s temporary, second way would be using SAF, let the user select the root directory and grant your app the access, then you can pretty much handle copy, moving and other things using DocumentFileFontana
@DarShan Thanks, that android:requestLegacyExternalStorage will be useful during development. Is granting access with the SAF permament?Gisarme
android:requestLegacyExternalStorage will work on production too, untill the next major release. About the SAF permission being permanent, you can save the Uri that you get in onActivityResult() and use grantPermission() and getContentResolver().takePersistableUriPermission()Fontana
@DarShan "until the next major release" is that Android 11? I'm asking because the documentations states that "Warning: Apps will be required to use scoped storage in next year's major platform release for all apps, independent of target SDK level. Therefore, you should ensure that your app works with scoped storage well in advance. To do so, make sure that the behavior is enabled for devices that run Android 10 (API level 29) and higher." without specifying explicitly which the next year is (they don't care about timestamping their documentation).Gisarme
Yes, Android 11/R, whatever. :)Fontana
Your use case is absolutely legitimate, and it's mind-blowing that no one at Google cares about how many apps this "feature" will irreversibly break.Rotman
Did you find any solution or workaround this? I've the same issue, I need to keep the images created by my app.Cupulate
@ParagPawar No. ATM I'm using android:requestLegacyExternalStorage, but I know that I can't do this in the future. I will store the hundreds of favicons which require fast access in the App's private storage, while seldom-generated/accessed files like backup files and others will have to go through the storage framework.Gisarme
S
28

In Android Q direct File access is disabled by default for the apps outside their private folders. Here few strategies you can use in your case:

  1. Use the manifest option requestLegacyExternalStorage to have the old behavior but it won't work anymore with Android R, so it's really a short term solution;
  2. Save your files using getExternalFilesDir() method. It's your private folder, other apps could access these files only if they have READ_EXTERNAL_STORAGE permission. In this case it would be good to use FileProvider to grant access to other apps to your files.
  3. Use the method getPrimaryStorageVolume().createOpenDocumentTreeIntent() of class StorageManager to ask for access to the extenal primary volume. In this case you need user consent and you won't be able to use File api directly anyway, but using DocumentFile class you have a very similar interface, so this is the solution closer to the old behavior. It works if you need to perform operations in foreground and background, i.e. without user interaction with the exception the first interaction to request the permission.

I link Flipper library for point 3, it helps to manage files like in older android versions.

Stichometry answered 5/9, 2019 at 20:18 Comment(6)
So if I save the file to my application's directory (context.getExternalFilesDir), I will be able to access that file by using File mFile = new File(PathToExternalFilesDir);? This will then return a file:// Uri when calling mFile.getAbsoluteFile() - Can my application then consume/use that file uri?Musset
File interface is allowed in your private directoryStichometry
@Musset You're right, apps can access to its specific directory even without any permission since Android-Q. Also, no other applications will have access to a specific app sandbox dir. See the scoped storage affects summarized table developer.android.com/training/data-storage/files/…Mandelbaum
Your point 2 is tricky. I've tried using a DownloadManager and setting the destination to getExternalFilesDir(). The download supposedly completes but once I check if the file exists, it does not.Secrete
Sorry bro! Can't understand how to check file exist or not and can't properly understand how to write code using Flipper. So, I down voting this answer! Because I really need help..Valerlan
This is my Previous ans. It will work 100% #62631605Ish
K
2

From Android 11 onwards you need to add MANAGE_EXTERNAL_STORAGE permission in the Manifest if you need access to external file directories.

An app can request All files access from the user by doing the following:

1. Declare the MANAGE_EXTERNAL_STORAGE permission in the manifest.

2. Use the ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION intent action to direct users to a system settings page where they can enable the following option for your app: Allow access to manage all files.

3. To determine whether your app has been granted the MANAGE_EXTERNAL_STORAGE permission, call Environment.isExternalStorageManager().

Read it here here

Kindred answered 10/1, 2021 at 12:45 Comment(2)
This isn't completely right. It'll work as long as you don't publish your app on the Play Store. However, if you do publish it, your app has to fall into a specific set of use cases in order to be granted the permission. developer.android.com/training/data-storage/…Guillaume
Android 10 solution and should be ok for other releases #69150934Tareyn
I
1

for above android Qenter image description here This is code is working fine to save Image on External Storage //don't forget to add Manifest permission //



        package com.myretro.saveimager;
        
        import androidx.appcompat.app.AppCompatActivity;
        
        import android.content.ContentResolver;
        import android.content.ContentValues;
        import android.graphics.Bitmap;
        import android.graphics.drawable.BitmapDrawable;
        import android.net.Uri;
        import android.os.Build;
        import android.os.Bundle;
        import android.os.Environment;
        import android.provider.MediaStore;
        import android.widget.Button;
        import android.widget.ImageView;
        import android.widget.Toast;
        
        import java.io.File;
        import java.io.FileNotFoundException;
        import java.io.OutputStream;
        
        public class MainActivity extends AppCompatActivity {
            private Button btnsave;
            private ImageView myImage;
        
            @Override
            protected void onCreate(Bundle savedInstanceState) {
                super.onCreate(savedInstanceState);
                setContentView(R.layout.activity_main);
                btnsave = findViewById(R.id.button);
                myImage = findViewById(R.id.imageView);
        
        
                btnsave.setOnClickListener(view -> {
                    BitmapDrawable bitmap1 = (BitmapDrawable) myImage.getDrawable();
                    Bitmap bitmap = bitmap1.getBitmap();
        
                    saveImmageIntoExternalStrogaeaboveQ(bitmap);
                });
            }
        
            private void saveImmageIntoExternalStrogaeaboveQ(Bitmap bitmap) {
                OutputStream outputStream;
        
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                    ContentResolver contentResolver = getContentResolver();
        
                    ContentValues mValue = new ContentValues();
        
                    mValue.put(MediaStore.MediaColumns.DISPLAY_NAME, "hacker" + ".jpg");
                    mValue.put(MediaStore.MediaColumns.MIME_TYPE, "image/jpg");
                    mValue.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_PICTURES + File.separator + "myimage");
                    Uri imageuri = contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, mValue);
        
                    try {
                        outputStream = contentResolver.openOutputStream(imageuri);
                        bitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream);
                        Toast.makeText(this, "Image Saved SuccessFull", Toast.LENGTH_SHORT).show();
                    } catch (FileNotFoundException e) {
                        Toast.makeText(this, "Something went wrong " + e.getMessage(), Toast.LENGTH_SHORT).show();
                        e.printStackTrace();
                    }
        
                } else {
                    //old method to write
                }
    
      }
    
    
Ierna answered 29/4, 2021 at 20:1 Comment(1)
<uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION"/>Ierna

© 2022 - 2024 — McMap. All rights reserved.