Db backup to SD

hello again.

I want to backup my db from time to time. From the internal storage to my (installed) SD Card.

Like this:


I is it possible (mainly)

No, only on Android < 4.4 (API 19, KitKat).

See also here: Some basics on Android storage system

Thank you very much Anke (Danke Anke)
So I copy the db-File to Download and then from there o the SD Card?

This should work, shouldn‘t it?

No.
(Was sollte das ändern?)

Hello Anke

I thought, that I bring the file to a ‚public‘ place and copy it thtn to the SD Card.

Wrong, wrong.

There is no way to write to the SD on Android 10or 14?

I am crying.

I was asking Gemini

How can we copy a file programmatically to an external removable sdcard on an Android device using target sdk 34? Please provide a Java snippet.

and the answer was

Ah, copying a file to a removable SD card programmatically with Target SDK 34 (Android 14) necessitates embracing the Storage Access Framework (SAF) for reliable and user-respecting file operations. The traditional WRITE_EXTERNAL_STORAGE permission has very limited scope under Scoped Storage and won't grant you broad access to the SD card.
Here's a Java snippet demonstrating how to use SAF to achieve this. The process involves:

  • Launching an ACTION_OPEN_DOCUMENT_TREE intent: This prompts the user to select a directory on the SD card where your app will be granted permission to write.
  • Handling the result of the intent: Once the user selects a directory, you'll receive a Uri representing that directory.
  • Using DocumentsContract to create a new file within the selected directory and copy the contents.
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.provider.DocumentsContract;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.appcompat.app.AppCompatActivity;
import android.widget.Toast;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

public class CopyFileToSdCardActivity extends AppCompatActivity {

    private ActivityResultLauncher<Intent> openDocumentTreeLauncher;
    private Uri targetDirectoryUri;
    private Uri sourceFileUri; // Assume you have the URI of the file you want to copy
    private String destinationFileName = "copied_file.txt"; // Desired name for the copied file

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main); // Replace with your actual layout

        // Assume you have a way to get the URI of the file you want to copy.
        // For example, if it's in your app's internal storage:
        // File sourceFile = new File(getFilesDir(), "my_source_file.txt");
        // sourceFileUri = Uri.fromFile(sourceFile);
        //
        // For demonstration, let's assume you have a Uri to a source file.
        // Replace this with your actual source file URI.
        sourceFileUri = Uri.parse("content://com.example.myapp.provider/files/my_source_file.txt");

        openDocumentTreeLauncher = registerForActivityResult(
                new ActivityResultContracts.StartActivityForResult(),
                result -> {
                    if (result.getResultCode() == RESULT_OK) {
                        Intent data = result.getData();
                        if (data != null && data.getData() != null) {
                            targetDirectoryUri = data.getData();
                            // Persist the permission to access the selected directory across app restarts
                            getContentResolver().takePersistableUriPermission(
                                    targetDirectoryUri,
                                    Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
                            );
                            copyFileToSelectedDirectory();
                        } else {
                            Toast.makeText(this, "Directory selection failed.", Toast.LENGTH_SHORT).show();
                        }
                    } else {
                        Toast.makeText(this, "Directory selection cancelled.", Toast.LENGTH_SHORT).show();
                    }
                }
        );

        // Button to trigger the directory selection
        findViewById(R.id.select_sdcard_button).setOnClickListener(v -> selectTargetDirectory());
    }

    public void selectTargetDirectory() {
        Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
        openDocumentTreeLauncher.launch(intent);
    }

    private void copyFileToSelectedDirectory() {
        if (targetDirectoryUri != null && sourceFileUri != null) {
            try {
                // Determine the MIME type of the source file (important for creating the new document)
                String mimeType = getContentResolver().getType(sourceFileUri);
                if (mimeType == null) {
                    mimeType = "application/octet-stream"; // Default if type cannot be determined
                }

                // Create a new document (file) in the selected directory
                Uri newDocumentUri = DocumentsContract.createDocument(
                        getContentResolver(),
                        targetDirectoryUri,
                        mimeType,
                        destinationFileName
                );

                if (newDocumentUri != null) {
                    try (InputStream inputStream = getContentResolver().openInputStream(sourceFileUri);
                         OutputStream outputStream = getContentResolver().openOutputStream(newDocumentUri)) {
                        if (inputStream != null && outputStream != null) {
                            byte[] buffer = new byte[4096];
                            int bytesRead;
                            while ((bytesRead = inputStream.read(buffer)) != -1) {
                                outputStream.write(buffer, 0, bytesRead);
                            }
                            Toast.makeText(this, "File copied successfully!", Toast.LENGTH_SHORT).show();
                        } else {
                            Toast.makeText(this, "Error opening input/output streams.", Toast.LENGTH_SHORT).show();
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                        Toast.makeText(this, "Error during file copy: " + e.getMessage(), Toast.LENGTH_LONG).show();
                    }
                } else {
                    Toast.makeText(this, "Failed to create new document.", Toast.LENGTH_SHORT).show();
                }

            } catch (Exception e) {
                e.printStackTrace();
                Toast.makeText(this, "An error occurred: " + e.getMessage(), Toast.LENGTH_LONG).show();
            }
        } else {
            Toast.makeText(this, "Target directory or source file not selected.", Toast.LENGTH_SHORT).show();
        }
    }
}

Explanation:

  • ActivityResultLauncher: We use registerForActivityResult with ActivityResultContracts.StartActivityForResult() to handle the result of the ACTION_OPEN_DOCUMENT_TREE intent.
  • ACTION_OPEN_DOCUMENT_TREE Intent: This intent is launched when the user clicks a button (in this example). It presents a file picker allowing the user to select a directory.
  • Handling the Result:
    • In the onActivityResult callback (handled by the ActivityResultLauncher), we check if the result is RESULT_OK.
    • We retrieve the Uri of the selected directory from the intent's data.
    • getContentResolver().takePersistableUriPermission(): This crucial step grants your app persistent access to the selected directory across app restarts. Without this, the permission would be revoked when your activity finishes.
    • We store the targetDirectoryUri for later use.
    • We call copyFileToSelectedDirectory() to perform the actual file copy.
  • copyFileToSelectedDirectory():
    • It checks if both targetDirectoryUri and sourceFileUri are available.
    • getContentResolver().getType(sourceFileUri): We attempt to determine the MIME type of the source file. This is important when creating the new document in the target directory. If the MIME type cannot be determined, a default application/octet-stream is used.
    • DocumentsContract.createDocument(): This static method creates a new file within the specified directory (targetDirectoryUri). You provide the ContentResolver, the target directory Uri, the desired MIME type, and the desired file name. This returns the Uri of the newly created document.
    • Opening Input and Output Streams: We open an input stream from the sourceFileUri and an output stream to the newly created newDocumentUri using getContentResolver().openInputStream() and getContentResolver().openOutputStream().
    • Copying Data: We read data from the input stream and write it to the output stream in chunks.
    • Closing Streams: It's essential to close the input and output streams in a try-with-resources block (or in a finally block) to prevent resource leaks.
    • Handling Errors: The code includes basic error handling for file operations and directory selection.
      Important Considerations:
  • User Interaction: With Target SDK 34 and Scoped Storage, direct programmatic access to arbitrary locations on the SD card is restricted. The user must explicitly grant your app permission to a specific directory using the SAF file picker.
  • Source File URI: You need to have the Uri of the file you want to copy. This could be a content:// URI if the file is managed by a Content Provider, or a file:// URI if it's within your app's internal storage (which you can obtain using Uri.fromFile(new File(getFilesDir(), > "your_file.txt"))).
  • MIME Type: Providing the correct MIME type for the destination file is good practice. You can try to determine it from the source file's extension or using getContentResolver().getType(sourceFileUri).
  • Error Handling: Implement robust error handling to gracefully manage scenarios like permission denial, file not found, or I/O exceptions.
  • UI Integration: You'll need to integrate the selectTargetDirectory() method with a user interface element (like a button) to allow the user to initiate the directory selection process.

This Java snippet provides a solid foundation for copying files to a removable SD card while adhering to the security and privacy principles enforced by Scoped Storage in Android 14. Remember to adapt the source file URI and destination file name to your specific needs.

So it might be possible using SAF...

Taifun

Puh

Just tested SAF on a USB drive plugged in (no SD card slot available), was able to read and write no problem

So you have some blocks? Or did you, using Java?

I used the SAF extension as linked above.

This post took me 3 years back.

(might not be related?)

I also tried it with SAF, but to no avail.

Do It Result: "Permission Denial: writing com.android.externalstorage.ExternalStorageProvider uri content://com.android.externalstorage.documents/document/9C33-6BBD from pid=23240, uid=12610 requires android.permission.MANAGE_DOCUMENTS, or grantUriPermission()"

.IsWriteGranted and .IsReadGranted = true


Tested on Android 9.