Create/Copy File in Android Q using MediaStore

I am trying to find method which can handle create and copy of any file except Media files (Picture/Video/Audio) to copy from one place to other in internal storage in Android Q. In this I have my file created in my app folder and I want those to move to Download Folder or some directory which I can create in Internal storage and then move their.

I searched and found modified below code but missing some thing to make it workable. Can some one help.

ContentResolver contentResolver = getContentResolver();

ContentValues contentValues = new ContentValues();
contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, "sam.txt");
contentValues.put(MediaStore.MediaColumns.MIME_TYPE, "text/plain");
contentValues.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS);

Uri uri = contentResolver.insert(MediaStore.Files.getContentUri("external"), contentValues);

try {
    InputStream inputStream = contentResolver.openInputStream(uri);
    OutputStream outputStream = new FileOutputStream(Environment.DIRECTORY_DOWNLOADS+"/");

    byte[] buffer = new byte[1024];

    int length;

    //copy the file content in bytes
    while ((length = inputStream.read(buffer)) > 0) {
            outputStream.write(buffer, 0, length);
        }
        inputStream.close();
        outputStream.close();
        } catch (Exception e) {
           e.printStackTrace();
        }

Is above complete code as I'm getting error 'Unknown URL'. What is missing? Please help.


1. Create and Write File

createAndWriteButton.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        try {
            ContentValues values = new ContentValues();

            values.put(MediaStore.MediaColumns.DISPLAY_NAME, "menuCategory");       //file name                     
            values.put(MediaStore.MediaColumns.MIME_TYPE, "text/plain");        //file extension, will automatically add to file
            values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DOCUMENTS + "/Kamen Rider Decade/");     //end "/" is not mandatory

            Uri uri = getContentResolver().insert(MediaStore.Files.getContentUri("external"), values);      //important!

            OutputStream outputStream = getContentResolver().openOutputStream(uri);

            outputStream.write("This is menu category data.".getBytes());

            outputStream.close();

            Toast.makeText(view.getContext(), "File created successfully", Toast.LENGTH_SHORT).show();
        } catch (IOException e) {
            Toast.makeText(view.getContext(), "Fail to create file", Toast.LENGTH_SHORT).show();
        }
    }
});

2. Find and Read File

findAndReadButton.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        Uri contentUri = MediaStore.Files.getContentUri("external");

        String selection = MediaStore.MediaColumns.RELATIVE_PATH + "=?";

        String[] selectionArgs = new String[]{Environment.DIRECTORY_DOCUMENTS + "/Kamen Rider Decade/"};

        Cursor cursor = getContentResolver().query(contentUri, null, selection, selectionArgs, null);

        Uri uri = null;

        if (cursor.getCount() == 0) {
            Toast.makeText(view.getContext(), "No file found in \"" + Environment.DIRECTORY_DOCUMENTS + "/Kamen Rider Decade/\"", Toast.LENGTH_LONG).show();
        } else {
            while (cursor.moveToNext()) {
                String fileName = cursor.getString(cursor.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME));

                if (fileName.equals("menuCategory.txt")) {
                    long id = cursor.getLong(cursor.getColumnIndex(MediaStore.MediaColumns._ID));

                    uri = ContentUris.withAppendedId(contentUri, id);

                    break;
                }
            }

            if (uri == null) {
                Toast.makeText(view.getContext(), "\"menuCategory.txt\" not found", Toast.LENGTH_SHORT).show();
            } else {
                try {
                    InputStream inputStream = getContentResolver().openInputStream(uri);

                    int size = inputStream.available();

                    byte[] bytes = new byte[size];

                    inputStream.read(bytes);

                    inputStream.close();

                    String jsonString = new String(bytes, StandardCharsets.UTF_8);

                    AlertDialog.Builder builder = new AlertDialog.Builder(view.getContext());

                    builder.setTitle("File Content");
                    builder.setMessage(jsonString);
                    builder.setPositiveButton("OK", null);

                    builder.create().show();
                } catch (IOException e) {
                    Toast.makeText(view.getContext(), "Fail to read file", Toast.LENGTH_SHORT).show();
                }
            }
        }
    }
});

3. Find and Overwrite File

findAndWriteButton.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        Uri contentUri = MediaStore.Files.getContentUri("external");

        String selection = MediaStore.MediaColumns.RELATIVE_PATH + "=?";

        String[] selectionArgs = new String[]{Environment.DIRECTORY_DOCUMENTS + "/Kamen Rider Decade/"};    //must include "/" in front and end

        Cursor cursor = getContentResolver().query(contentUri, null, selection, selectionArgs, null);

        Uri uri = null;

        if (cursor.getCount() == 0) {
            Toast.makeText(view.getContext(), "No file found in \"" + Environment.DIRECTORY_DOCUMENTS + "/Kamen Rider Decade/\"", Toast.LENGTH_LONG).show();
        } else {
            while (cursor.moveToNext()) {
                String fileName = cursor.getString(cursor.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME));

                if (fileName.equals("menuCategory.txt")) {                          //must include extension
                    long id = cursor.getLong(cursor.getColumnIndex(MediaStore.MediaColumns._ID));

                    uri = ContentUris.withAppendedId(contentUri, id);

                    break;
                }
            }

            if (uri == null) {
                Toast.makeText(view.getContext(), "\"menuCategory.txt\" not found", Toast.LENGTH_SHORT).show();
            } else {
                try {
                    OutputStream outputStream = getContentResolver().openOutputStream(uri, "rwt");      //overwrite mode, see below

                    outputStream.write("This is overwritten data。\n你就不要想起我。".getBytes());

                    outputStream.close();

                    Toast.makeText(view.getContext(), "File written successfully", Toast.LENGTH_SHORT).show();
                } catch (IOException e) {
                    Toast.makeText(view.getContext(), "Fail to write file", Toast.LENGTH_SHORT).show();
                }
            }
        }
    }
});
  • Read/Write mode: https://developer.android.com/training/data-storage/shared/media#open-file-descriptor

Demo: https://www.youtube.com/watch?v=idsUMiWjfnM

Hope this may help you.


As you mentioned Environment.getExternalStoragePublicDirectory is marked deprecated. So there is no regular way to get the path to Downloads directory to save your file there. Alternatively you can use ACTION_CREATE_DOCUMENT to show path picker and then use returned uri to write file to selected location.

This is how to show picker:

// Request code for creating a document.

const val CREATE_FILE = 1

private fun createFile(pickerInitialUri: Uri) {
    val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
        addCategory(Intent.CATEGORY_OPENABLE)
        type = "text/plain"
        putExtra(Intent.EXTRA_TITLE, "sam.txt")

        // Optionally, specify a URI for the directory that should be opened in
        // the system file picker before your app creates the document.
        putExtra(DocumentsContract.EXTRA_INITIAL_URI, pickerInitialUri)
    }
    startActivityForResult(intent, CREATE_FILE)
}

And this is how to get selected uri and write file:

override fun onActivityResult(requestCode: Int, resultCode: Int, resultData: Intent?) {
    if (requestCode == CREATE_FILE && resultCode == Activity.RESULT_OK) {
        // The result data contains a URI for the document or directory that
        // the user selected.
        resultData?.data?.also { outputUri ->
            // Perform operations on the document using its URI.
            FileInputStream(inputFile).use { inputStream ->
                context.contentResolver.openFileDescriptor(outputUri, "w")?.use {
                    FileOutputStream(it.fileDescriptor).use { outputStream ->
                        FileUtils.copy(inputStream, outputStream)
                    }
                }
            }
        }
    }
}

More information can be found here.

EDIT:

To pick a directory to persist files ACTION_OPEN_DOCUMENT_TREE can be used. Then use takePersistableUriPermission method to take granted persistable permission to be able to use it after device restart. And then use DocumentFile to execute file operations.

Open directory request:

private static final int OPEN_DIRECTORY_REQUEST_CODE = 1;

void openDirectory() {
    Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
    intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
    startActivityForResult(intent, OPEN_DIRECTORY_REQUEST_CODE);
}

Receive picked directory and take persistable permission:

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (requestCode == OPEN_DIRECTORY_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
        Uri directoryUri = data.getData();
        if (directoryUri == null)
            return;
        requireContext()
                .getContentResolver()
                .takePersistableUriPermission(directoryUri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
        // persist picked uri to be able to reuse it later
    } else
        super.onActivityResult(requestCode, resultCode, data);
}

And at last persist the file:

private void persistFile(@NonNull Uri directoryUri,
                         @NonNull File fileToPersist,
                         @NonNull String mimeType,
                         @NonNull String displayName) {
    DocumentFile dirFile = DocumentFile.fromSingleUri(requireContext(), directoryUri);
    if (dirFile != null) {
        DocumentFile file = dirFile.createFile(mimeType, displayName);
        if (file != null) {
            Uri outputUri = file.getUri();
            try (ParcelFileDescriptor fd = requireContext().getContentResolver().openFileDescriptor(outputUri, "w")) {
                if (fd != null) {
                    try (FileInputStream inputStream = new FileInputStream(fileToPersist)) {
                        try (FileOutputStream outputStream = new FileOutputStream(fd.getFileDescriptor())) {
                            FileUtils.copy(inputStream, outputStream);
                        }
                    }
                }
            } catch (Throwable th) {
                th.printStackTrace();
            }
        }
    }
}

Review this repo for an example of ACTION_CREATE_DOCUMENT usage.