Create/Copy File in Android Q Using Mediastore

How to copy Images/Videos from DocumentFile Object to Gallery Folder using MediaStore Api

Thanks to @blackapps!

All I had to do was to request a writable URI from the MediaStore using the insert() method and then open a InputStream for source URI and OutputStream for destination URI.

Code for above action :-

public void saveFile(Uri sourceUri, String fileName, String mimeType) throws IOException{

ContentValues values = new ContentValues();
Uri destinationUri;

if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P){
values.put(MediaStore.MediaColumns.DISPLAY_NAME, fileName);
values.put(MediaStore.MediaColumns.MIME_TYPE, mimeType);

if (fileName.endsWith(".mp4")){
values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_MOVIES + "/MyFolder");
destinationUri = context.getContentResolver().insert(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, values);
} else {
values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_PICTURES + "/MyFolder");
destinationUri = context.getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
}

InputStream inputStream = context.getContentResolver().openInputStream(sourceUri);
OutputStream outputStream = context.getContentResolver().openOutputStream(destinationUri);

IOUtils.copy(inputStream, outputStream);
Toast.makeText(context, "The File has been saved!", Toast.LENGTH_SHORT).show();

}

Note :- You have to add the Commons-io dependency in your build.gradle file to access IOUtils.copy() function

If you find any better way to do this, Do share it!

How to move/copy any type of file from asset file to scoped storage ANDROID Q in JAVA?

I finally find a solution, I pretty sure it's not the best way but it work.
I give me access to all files acccess by this way :

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R){
try {
Intent intentFiles = new Intent();
intentFiles.setAction(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION);
Uri uriFiles = Uri.fromParts("package", myContext.getPackageName(), null);
intentFiles.setData(uriFiles);
myContext.startActivity(intentFiles);
} catch (Exception e)
{
Intent intentFiles = new Intent();
intentFiles.setAction(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION);
myContext.startActivity(intentFiles);
}

add this line to your manifest:

    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />

after that, this code below work :

AssetManager assetManager = Objects.requireNonNull(requireContext()).getAssets();
Context myContext = requireContext();
//Essential for creating the external storage directory for the first launch
myContext.getExternalFilesDir(null);
File databasesFolder = new File(myContext.getExternalFilesDir(null).getParent(), "com.mydb.orca/databases");
databasesFolder.mkdirs();

if (files!= null) {
for (String filename : files) {
InputStream in;
OutputStream out;
try {
in = assetManager.open("database/test/" + filename);
File outFile = new File(databasesFolder, filename);
out = new FileOutputStream(outFile);
copyFile(in, out);
in.close();
out.flush();
out.close();
} catch (IOException e) {
Log.e("tag", "Failed to copy asset file: " + filename, e);
}
}
} else {
Log.e("Error NPE", "files is null");
}

private void copyFile(InputStream in, OutputStream out) throws IOException {
byte[] buffer = new byte[1024];
int read;
while ((read = in.read(buffer)) != -1) {
out.write(buffer, 0, read);
}

}

if someone have a best solution it should be nice

I tested on android 11 and it work

How to copy file from app-specific folder (file:// scheme) to MediaStore Images collection (content:// scheme) in Android Q?

length = inputStream.read(buffer) 

Remove that statement.

You are reading a lot of bytes but not writing them to the new file.

So the new file misses the 'header'.

The new file is shorter. But you did not compare file sizes. –

How to save an image in Android Q using MediaStore?

Try the next method. Android Q (and above) already takes care of creating the folders if they don’t exist. The example is hard-coded to output into the DCIM folder. If you need a sub-folder then append the sub-folder name as next:

final String relativeLocation = Environment.DIRECTORY_DCIM + File.separator + “YourSubforderName”;

Consider that the compress format should be related to the mime-type parameter. For example, with a JPEG compress format the mime-type would be "image/jpeg", and so on. Probably you may also want to pass the compress quality as a parameter, in this example is hardcoded to 95.

Java:

@NonNull
public Uri saveBitmap(@NonNull final Context context, @NonNull final Bitmap bitmap,
@NonNull final Bitmap.CompressFormat format,
@NonNull final String mimeType,
@NonNull final String displayName) throws IOException {

final ContentValues values = new ContentValues();
values.put(MediaStore.MediaColumns.DISPLAY_NAME, displayName);
values.put(MediaStore.MediaColumns.MIME_TYPE, mimeType);
values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DCIM);

final ContentResolver resolver = context.getContentResolver();
Uri uri = null;

try {
final Uri contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
uri = resolver.insert(contentUri, values);

if (uri == null)
throw new IOException("Failed to create new MediaStore record.");

try (final OutputStream stream = resolver.openOutputStream(uri)) {
if (stream == null)
throw new IOException("Failed to open output stream.");

if (!bitmap.compress(format, 95, stream))
throw new IOException("Failed to save bitmap.");
}

return uri;
}
catch (IOException e) {

if (uri != null) {
// Don't leave an orphan entry in the MediaStore
resolver.delete(uri, null, null);
}

throw e;
}
}

Kotlin:

@Throws(IOException::class)
fun saveBitmap(
context: Context, bitmap: Bitmap, format: Bitmap.CompressFormat,
mimeType: String, displayName: String
): Uri {

val values = ContentValues().apply {
put(MediaStore.MediaColumns.DISPLAY_NAME, displayName)
put(MediaStore.MediaColumns.MIME_TYPE, mimeType)
put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DCIM)
}

val resolver = context.contentResolver
var uri: Uri? = null

try {
uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)
?: throw IOException("Failed to create new MediaStore record.")

resolver.openOutputStream(uri)?.use {
if (!bitmap.compress(format, 95, it))
throw IOException("Failed to save bitmap.")
} ?: throw IOException("Failed to open output stream.")

return uri

} catch (e: IOException) {

uri?.let { orphanUri ->
// Don't leave an orphan entry in the MediaStore
resolver.delete(orphanUri, null, null)
}

throw e
}
}

Kotlin variant, with a more functional style:

@Throws(IOException::class)
fun saveBitmap(
context: Context, bitmap: Bitmap, format: Bitmap.CompressFormat,
mimeType: String, displayName: String
): Uri {

val values = ContentValues().apply {
put(MediaStore.MediaColumns.DISPLAY_NAME, displayName)
put(MediaStore.MediaColumns.MIME_TYPE, mimeType)
put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DCIM)
}

var uri: Uri? = null

return runCatching {
with(context.contentResolver) {
insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)?.also {
uri = it // Keep uri reference so it can be removed on failure

openOutputStream(it)?.use { stream ->
if (!bitmap.compress(format, 95, stream))
throw IOException("Failed to save bitmap.")
} ?: throw IOException("Failed to open output stream.")

} ?: throw IOException("Failed to create new MediaStore record.")
}
}.getOrElse {
uri?.let { orphanUri ->
// Don't leave an orphan entry in the MediaStore
context.contentResolver.delete(orphanUri, null, null)
}

throw it
}
}


Related Topics



Leave a reply



Submit