Issues with File Transfer from Downloads to Internal Storage on Android 11 - API 30

I’m encountering a unique challenge when working with API 30, or Android 11, that I don’t experience on earlier versions. My app successfully exports both text and audio files from internal to external storage, specifically into the Downloads folder, on all APIs. However, while importing these files back into my app’s internal storage, the process functions correctly on other APIs. Yet, on API 30, it only creates folders for Text and Audio while failing to import text files. Interestingly, mp3 files are imported successfully. What could be causing this issue, and is there a fix available? Thanks for your assistance.

restore.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        String status = Environment.getExternalStorageState();
        if (Environment.MEDIA_MOUNTED.equals(status)) {
            if (SDK_INT >= 23) {
                if (isPermissionGranted()) {
                    try {
                        // create directory for Text
                        File externalTextDir = getBaseContext().getExternalFilesDir(null);
                        File textDirectory = new File(externalTextDir.getAbsolutePath() + "/TextFolder/");
                        textDirectory.mkdir();
                        FileOutputStream textStream = null;
                        try {
                            textStream = new FileOutputStream(textDirectory);
                            textStream.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }

                        // create directory for Audio
                        File externalAudioDir = getBaseContext().getExternalFilesDir(null);
                        File audioDirectory = new File(externalAudioDir.getAbsolutePath() + "/AudioFolder/");
                        audioDirectory.mkdir();
                        FileOutputStream audioStream = null;
                        try {
                            audioStream = new FileOutputStream(audioDirectory);
                            audioStream.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }

                        // import files from Downloads
                        File baseExternalDir = getBaseContext().getExternalFilesDir(null);
                        File baseDir = new File(baseExternalDir.getAbsolutePath());

                        transferFiles(new File(String.valueOf(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS))),
                                new File(baseDir.getAbsolutePath()));

                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                } else {
                    requestAccessPermission();
                }
            }
        }
    }
});

public void transferFiles(File sourceFolder, File destinationFolder) throws IOException {
    if (sourceFolder.isDirectory()) {
        if (!destinationFolder.exists()) {
            destinationFolder.mkdir();
        }

        String[] items = sourceFolder.list();
        for (String item : items) {
            transferFiles(new File(sourceFolder, item), new File(destinationFolder, item));
        }
    } else {
        InputStream input = new FileInputStream(sourceFolder);
        OutputStream output = new FileOutputStream(destinationFolder);

        // transfer the bytes from input stream to output stream
        byte[] buffer = new byte[1024];
        int bytesRead;
        while ((bytesRead = input.read(buffer)) > 0) {
            output.write(buffer, 0, bytesRead);
        }
        input.close();
        output.close();
    }
}

Hey Harry! Looks like you're bumping into the Scoped Storage restrictions in Android 11. These changes affect how apps interact with the external storage. Specifically, text file imports might fail due to lack of permissions.

Here's a quick fix: Use the Storage Access Framework (SAF) to access files in the Downloads folder for API 30.

Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
startActivityForResult(intent, REQUEST_CODE_OPEN_DIRECTORY);

// Handle the result in onActivityResult
@Override
protected void onActivityResult(int requestCode, int resultCode, 
                                Intent data) {
    if (requestCode == REQUEST_CODE_OPEN_DIRECTORY && resultCode == Activity.RESULT_OK) {
        Uri uri = data.getData();
        // Use uri to access files
    }
}

This will allow you to get user consent to read/write in the Downloads folder.

Harry, the workaround suggested by Alice45 is on point. The transition to Scoped Storage in Android 11 (API level 30) introduces stricter restrictions. Here, let's dig a bit deeper to clarify these details.

When dealing with file management on newer Android versions, especially post-Android 10, it's essential to leverage the Storage Access Framework (SAF) or use MediaStore API for accessing shared storage like the Downloads folder. This is mainly because direct access via file paths is limited.

The issue with text file import stems from permission models being more restrictive. Android restricts access unless explicit permission is granted by the user during runtime. This results in scenarios where some file types (e.g., audio) might work differently if they are handled via MediaStore.

To specifically address the import problem of text files, you should adjust the file handling by implementing SAF to acquire 'open document' permissions.

// Step 1: Launch the document picker
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
intent.setType("*/*"); // Set the correct MIME type for text if needed
intent.addCategory(Intent.CATEGORY_OPENABLE);
startActivityForResult(intent, REQUEST_CODE_IMPORT_FILE);

// Step 2: Get the URI in onActivityResult
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {  
    if (requestCode == REQUEST_CODE_IMPORT_FILE && resultCode == Activity.RESULT_OK) {  
        Uri uri = data.getData();  
        // Use the URI to open the document
        try {
            InputStream inputStream = getContentResolver().openInputStream(uri);
            // Use inputStream to read file
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
     }  
}

By following this approach, you ensure that your app has the user's consent to interact with files in the Downloads directory, thereby bypassing the limitations set by Scoped Storage for file types you wish to manage.

Consider reviewing the Android documentation on Scoped Storage for a comprehensive understanding: Android Scoped Storage.

Hello Harry,

From what you've described, it sounds like your issue is related to the scoped storage restrictions introduced in Android 11. These changes restrict how apps can interact with external storage, which could explain why text files are not importing correctly while mp3 files are.

To resolve this, you should consider using the Storage Access Framework (SAF), which is better suited for accessing files in the Downloads folder on devices running API 30. Here's an actionable approach:

// Step 1: Launch the document picker to access external files
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
intent.setType("*/*");
intent.addCategory(Intent.CATEGORY_OPENABLE);
startActivityForResult(intent, REQUEST_CODE_IMPORT_FILE);

// Step 2: Handle the result in onActivityResult
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (requestCode == REQUEST_CODE_IMPORT_FILE && resultCode == Activity.RESULT_OK) {
        Uri uri = data.getData();
        try {
            InputStream inputStream = getContentResolver().openInputStream(uri);
            // Use inputStream to read and process the file
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }
}

This method gives your app the necessary permissions to interact with files within the Downloads folder, thus overcoming the constraints set by scoped storage.

For detailed guidance, it would be beneficial to review Google's documentation on scoped storage.

Best of luck with your implementation!

Hey Harry!

It seems like you're running into the scoped storage changes in Android 11, which restrict direct file access. The good news is that using the Storage Access Framework (SAF) sorts this out. Here's how:

// Request access to the Downloads folder
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
intent.setType("*/*"); // Adjust MIME type for text files
intent.addCategory(Intent.CATEGORY_OPENABLE);
startActivityForResult(intent, REQUEST_CODE_IMPORT_FILE);

// Handle the user's selection
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (requestCode == REQUEST_CODE_IMPORT_FILE && resultCode == Activity.RESULT_OK) {
        Uri uri = data.getData();
        try {
            InputStream inputStream = getContentResolver().openInputStream(uri);
            // Work with the input stream
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }
}

This gives your app permission and sidesteps the storage limits on API 30.

More on this in the Android docs.

Harry, your issue largely stems from the Scoped Storage restrictions enforced in Android 11, designed to bolster privacy by limiting direct file path access. While you've been using getExternalFilesDir(), which grants your app private storage under the external storage without extra permissions, accessing files from public directories like Downloads requires explicit permissions through the Storage Access Framework (SAF).

Audio files might be importing successfully due to their handling via MediaStore, which offers more relaxed access leading to seamless operations. For text files, you'll need to adapt your approach. Here's a concise way to leverage SAF for accessing the Downloads folder and securing necessary permissions:

// Step 1: Initiate file selection via SAF
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
intent.setType("text/*"); // Specify MIME type if known
intent.addCategory(Intent.CATEGORY_OPENABLE);
startActivityForResult(intent, REQUEST_CODE_SELECT_FILE);

// Step 2: Handle the result, accessing files through URI
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (requestCode == REQUEST_CODE_SELECT_FILE && resultCode == Activity.RESULT_OK) {
        Uri uri = data.getData();  
        try {
            InputStream inputStream = getContentResolver().openInputStream(uri);
            // Process input stream for file content
        } catch (FileNotFoundException e) {
             e.printStackTrace();
        }
    }
}

This implementation ensures that the user consents to file access, thus adhering to Android's new privacy protocols. By adopting SAF, you'll secure required permissions dynamically, streamlining the import of text files back into your internal storage.

For comprehensive guidelines, consult the official Android Scoped Storage documentation. This information will be invaluable in refining your app's storage access methodology.