As we all know, Android 11 uses proprietary directories and makes them mandatory. As for the introduction of the exclusive directory, I will not go into details here, because the official document has been very clear, and it may be written later. Here are some of the potholes I encountered in the external storage root.

Exclusive directory is the exclusive space opened up by Android11 for applications. APP will save files to the exclusive directory, no longer need to request storage permission, can be directly saved. In addition, other applications cannot access the files in the exclusive directory, ensuring the privacy of users.

Instead of saving files in a private directory, or in a media directory, I want to create a new folder, save my CSV files, and share the CSV.

In fact, FOR Android10, I had already adopted the method of FileProvider, but a year later, the same code error, and Android10 is ok, Android11 has a problem. I had two main problems,

  1. CSV file does not exist in Android 10:Failed to find configured root ....
  2. Android 11 save as CSV, prompt:EPERM (Operation not permitted)

Here’s my solution from start to finish:

1. Manifest.xml

(1) under the application to add: android: requestLegacyExternalStorage = “true”

② Define FileProvider: Add an element to your application manifest.

Name use fixed androidx. Core. Content. FileProvider,

Authorities + fileProvider,

Exported to false,

GrantUriPermissions is true and grants temporary access to the file for sharing.

<provider
    android:name="androidx.core.content.FileProvider"
    android:authorities="com.adsale.registersite.fileprovider"
    android:exported="false"
    android:grantUriPermissions="true">

    <! Metadata -->
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/file_paths" />
</provider>
Copy the code

(3) inresCreating a DirectoryxmlFolder, create file namefile_pathsAdd the following code to it.


      
<resources xmlns:android="http://schemas.android.com/apk/res/android">
    <paths>
        <external-path name="com.adsale.ConcurrentEvent" path="." />
    </paths>

</resources>
Copy the code

Path attributes:

External – code path is equal to the Environment. External.getexternalstoragedirectory (), so if you use this path code, use external – path.

Name is you’re ExternalStorageDirectory () established under the folder’s name, such as my file name is here: com. Adsale. ConcurrentEvent

Path uses., or /.

Other path codes:

The < files – path / > — Context. GetFilesDir ()

< cache – the path / > — Context. GetCacheDir ()

< external path / > — Environment. External.getexternalstoragedirectory ()

< external files – path / > — Context. GetExternalFilesDir (String)

< external cache – the path / > — Context. GetExternalCacheDir ()

< external – media – path / > — Context. GetExternalMediaDirs ()

2. Code creates an external storage directory

Request external read and write permissions first, needless to say. If the folder does not exist, create it

public String setRootDir(Activity activity) {
        String rootPath = "";
        if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {/ / a SD card
            rootPath = Environment.getExternalStorageDirectory() + "/";
        } else {
            rootPath = Environment.getDataDirectory() + "/";
        }
        rootPath = rootPath + "com.adsale.ConcurrentEvent/";
        if (RequestPermissionUtil.requestPermissionWriteStorage(activity)) { // If you have the storage permission, create a folder
            createRootDir(rootPath);
        }
        return rootPath;   // Return the absolute path of the folder
        // /storage/emulated/0/com.adsale.ConcurrentEvent/
    }

	/** * create folder **/
    public boolean createRootDir(String rootPath) {
        File dirRoot = new File(rootPath);
        if(! dirRoot.exists() || ! dirRoot.isDirectory()) {boolean isCreateRoot = dirRoot.mkdirs();
            return isCreateRoot;
        }
        App.rootDir = rootPath ; 
        return true;
    }
Copy the code

So if we get the absolute path, we can save our files in this directory. The file writing process is omitted.

3. Share files

private void sendCSVByEmail(a) {
/ / App. RootDir is first step to get the absolute path: / storage/emulated / 0 / com. Adsale. ConcurrentEvent /
// csvName is the file name: check-in query -2021-09-28 15.42.26.csv

        String csvPath = App.rootDir + csvName;
        File file = new File(csvPath);
        if(! file.exists()) { Toast.makeText(getApplicationContext(),"CSV does not exist", Toast.LENGTH_SHORT).show();
            return;
        }
        
        try {
            Intent intent = new Intent(Intent.ACTION_SEND);
            intent.putExtra("subject", csvName); 
            intent.putExtra("body".""); / / body
            Uri uri = FileProvider.getUriForFile(getApplicationContext(), "com.adsale.registersite.fileprovider", file);
            intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
            intent.setFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
            intent.putExtra(Intent.EXTRA_STREAM, uri); // Add an attachment. The attachment is the file object
            if (csvName.endsWith(".csv")) {
                intent.setType("application/octet-stream"); // All others use the stream as binary data to be sent
            }
            startActivity(intent); // Call the system's mail client for sending
        } catch (ActivityNotFoundException e) {
            Toast.makeText(getApplicationContext(), "System has no mail client!", Toast.LENGTH_SHORT).show(); }}Copy the code

Key codes:

Uri uri = FileProvider.getUriForFile(getApplicationContext(), "com.adsale.registersite.fileprovider", file);
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.setFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
Copy the code

Uri uri = FileProvider.getUriForFile(getApplicationContext(), “com.adsale.registersite.fileprovider”, file);

Com. Adsale. Registersite fileprovider is defined in the XML of authorities, the file is a CSV file, through fileprovider access to content Uri. Grant temporary read and write permissions to the returned content URI by setFlags.

This fulfills the need to store directories externally.

QAQ

On Android11, because I saved the file name as the current time, it was generated using the time format YYYY-MM-DD HH: MM :ss, but I kept encountering the EPERM (Operation not permitted) problem. I thought there was a problem with the file directory permissions on Android11. Search just know, the original file name is saved when the problem…… This problem will not be solved if the time format is changed to. Vomiting blood…

Reference:

Creating a CSV file in Android 11 Return Error “java.io.FileNotFoundException: EPERM (Operation not permitted)”

Developer documentation FileProvider