Retrieving image in scoped storage Android 11 - java

I'm working on scanner app. My problem is while retrieve saved image from scoped storage in Android 10 and 11. Image is saved successfully but I am not able to retrieve it. When I get path of save image bitmap is null.
Here is the code to save the image in Android 11:
File dirDest = new File(Environment.DIRECTORY_PICTURES, context.getString(R.string.app_name));
Long date = System.currentTimeMillis();
String extension = ".jpg";
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
ContentResolver resolver = context.getContentResolver();
ContentValues contentValues = new ContentValues();
contentValues.put(MediaStore.Images.Media.DISPLAY_NAME, date + extension);
contentValues.put(MediaStore.Images.Media.MIME_TYPE, "image/" + extension);
contentValues.put(MediaStore.Images.Media.DATE_ADDED, date);
contentValues.put(MediaStore.Images.Media.DATE_MODIFIED, date);
contentValues.put(MediaStore.Images.Media.SIZE, bitmap.getByteCount());
contentValues.put(MediaStore.Images.Media.WIDTH, bitmap.getWidth());
contentValues.put(MediaStore.Images.Media.HEIGHT, bitmap.getHeight());
contentValues.put(MediaStore.Images.Media.RELATIVE_PATH, dirDest + File.separator);
Uri newImageUri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues);
FileOutputStream fos = (FileOutputStream) resolver.openOutputStream(Objects.requireNonNull(newImageUri));
callback.write(fos);
fos.flush();
fos.close();
return String.valueOf(dirDest) + date + extension;
Here is the code to retrieve the save image from shared storage in scoped storage in Android 11. I am getting issue in this part of code, bitmap is null. I am not getting the main point of the problem. In the end the problem is FileNotFoundException.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
String[] file = {MediaStore.Images.Media._ID,
MediaStore.Images.Media.RELATIVE_PATH,
MediaStore.Images.Media.DISPLAY_NAME,
MediaStore.Images.Media.SIZE,
MediaStore.Images.Media.MIME_TYPE,
MediaStore.Images.Media.WIDTH,
MediaStore.Images.Media.HEIGHT,
MediaStore.Images.Media.DATE_MODIFIED};
if (uri != null) {
cursor = context.getContentResolver().query(uri, file, null, null, null);
if (cursor != null) {
try {
cursor.moveToFirst();
int columnIndex = cursor.getColumnIndex(file[0]);
File path = new File(uri.getPath());
bitmap = BitmapFactory.decodeFile(path.getAbsolutePath());
cursor.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}

You can query them all with the help of SimpleStorage:
val images: List<MediaFile> = MediaStoreCompat.fromMediaType(applicationContext, MediaType.IMAGE)
images.forEach {
println(it.absolutePath)
}

Related

How to save a bitmap on storage in Android Q and later?

In my application, I have to store a bitmap as a PNG file in shared memory, to make it visible for Gallery app. First, I tried to store the image in /Android/data/package.name/files/Pictures. I got this path from context.getExternalFilesDir(Environment.DIRECTORY_PICTURES). Images stored in this directory are not detected by Gallery. Then I read a few articles and SO posts about MediaStore and I tried to save my image with it.
This is a function that I use to store bitmap. It does not throw any exception, returns true, bitmap.compress() also returns true but I can't find any PNG image in device's memory. I tried to look for it using Gallery and file manager. I also tried to change it to save JPEG instead of PNG but it also does not work.
Could you help me to figure out why this function does not save image to device's store?
I tested it on Samsung A52s 5G, Android 12, OneUI 4.0.
private boolean saveImageToStorageAndroidQ(Bitmap bitmap, String filename) {
filename = filename + ".png";
ContentValues values = new ContentValues();
values.put(MediaStore.Images.Media.DISPLAY_NAME, filename);
values.put(MediaStore.Images.Media.MIME_TYPE, "image/png");
values.put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_PICTURES);
final ContentResolver resolver = getActivity().getContentResolver();
final Uri contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
Uri uri = resolver.insert(contentUri, values);
try {
OutputStream imageOutStream = resolver.openOutputStream(uri);
bitmap.compress(Bitmap.CompressFormat.PNG, 95, imageOutStream);
imageOutStream.flush();
imageOutStream.close();
return true;
} catch (Exception e) {
return false;
} finally {
if (uri != null)
resolver.delete(uri, null, null);
}
}
You can also use this method. Thought it's long, it's better as the other one has been deprecated. Use this code:
String filename = "name.png";
File sd = Environment.getExternalStorageDirectory();
File dest = new File(sd, filename);
try {
FileOutputStream out = new FileOutputStream(dest);
bitmap.compress(Bitmap.CompressFormat.PNG, 90, out);
out.flush();
out.close();
} catch (Exception e) {
e.printStackTrace();
}
You can use a very easy method:
MediaStore.Images.Media.insertImage(getContentResolver(), bitmap, filename, "Description");
This is deprecated. Try this answer

How can I rename a video gotten from android mediastore?

How can I rename a video gotten from mediastore without deleting or corrupting it?
I tried using this code gotten from a similar question but it deletes or corrupts the video whose name I changed.
String title = editTextTitle.getText().toString().trim();
video.setTitle(title);
File parentFile = videoFile.getParentFile();
if (parentFile != null) {
String path = parentFile.getAbsolutePath();
String extension = videoFile.getAbsolutePath(); extension = extension.substring(extension.lastIndexOf("."));
String newPath = path + "/" + title + extension;
File newFile = new File(newPath);
boolean rename = videoFile.renameTo(newFile); if (rename) {
ContentResolver resolver = context.getContentResolver(); resolver.delete(MediaStore.Files.getContentUri("external"), MediaStore.MediaColumns.DATA + "=?", new String[]{videoFile.getAbsolutePath()});
Toast.makeText(context, "renamed", Toast.LENGTH_SHORT). show();
}
}
Finally solved it
textViewRename.setOnClickListener(v1 -> { String title = editTextTitle.getText().toString().trim(); video.setTitle(title);
String extension = videoFile.getAbsolutePath(); extension = extension.substring(extension.lastIndexOf("."));
ContentValues values = new ContentValues(2); values.put(MediaStore.Video.Media.TITLE, title);
values.put(MediaStore.Video.Media.DISPLAY_NAME, title + extension );
context.getContentResolver().update(MediaStore.Video.Media. EXTERNAL_CONTENT_URI, values,
MediaStore.MediaColumns.DATA + "=?", new String[]{videoFile. getAbsolutePath()});
if (videoOptionListener != null) { videoOptionListener.onEdit();
} dialogRenameVideo.dismiss(); });

Access photos from external storage in Android Q

I recently upgraded the app's target version to API 29. Due to the scoped storage in Android 10, i used MediaStore API to store and retrieve images from app external storage. Earlier, i used getExternalStoragePublicDirectory to store images taken through camera, now i use MediaStore.Images.Media.EXTERNAL_CONTENT_URI to write file to an external storage location.
Issue i am facing now is,
When i open my app and take pictures, it stores under a folder name that i gave 'myapp' and i can retrieve my images through Mediastore cursor and show them in a custom gallery. And when i uninstall my app 'myapp' folder still exists. And when i install my app again and try to read the images from the gallery, cursor is not returning any image. But if i take picture again, then i could load them to my custom gallery. Custom gallery view is just a row of images at the bottom of the screen, so that user doesn't have to browse through the photos folder to load the image to the app.
This is how i store my images in the MediaStore
Content values:
String RELATIVE_PATH = Environment.DIRECTORY_PICTURES + File.separator + "myApp";
final ContentValues contentValues = new ContentValues();
contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, generateImageName(new Date()));
contentValues.put(MediaStore.MediaColumns.MIME_TYPE, "image/jpg");
contentValues.put(MediaStore.MediaColumns.RELATIVE_PATH, RELATIVE_PATH);
Generate Name method:
int sameSecondCount;
protected String generateName(Date now)
{
String result = formatter.format(now);
long nowMillis = now.getTime();
if (nowMillis / 1000 == lastMillis / 1000)
{
sameSecondCount++;
result += "_" + sameSecondCount;
}
else
sameSecondCount = 0;
lastMillis = nowMillis;
return result + PICTURE_EXTENSION_JPG;
}
#WorkerThread
private Uri writePictureToFile(ContentValues contentValues, byte[] bitmapBytes) throws IOException
{
final ContentResolver resolver = getApplication().getContentResolver();
Uri uri = null;
final Uri contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
try
{
uri = resolver.insert(contentUri, contentValues);
if (uri == null)
throw new IOException("Failed to create new MediaStore record.");
OutputStream stream = resolver.openOutputStream(uri);
if (stream == null)
{
throw new IOException("Failed to get output stream.");
}
stream.write(bitmapBytes);
}
catch (IOException e)
{
// Delete the content from the media store
if (uri != null)
resolver.delete(uri, null, null);
throw e;
}
return uri;
}
Reading images
{
String selectionMimeType = MediaStore.Files.FileColumns.MIME_TYPE + " in (?,?,?)";
String[] args = new String[]{
MimeTypeMap.getSingleton().getMimeTypeFromExtension("jpg"),
MimeTypeMap.getSingleton().getMimeTypeFromExtension("png")};
Cursor cursor = context.getContentResolver()
.query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, columns, selectionMimeType, selectionArgs,
orderBy + " DESC");
if (cursor != null)
{
int idColumnIndex = imageCursor.getColumnIndex(MediaStore.Images.Media._ID);
imageCursor.moveToFirst();
int imageCount = imageCursor.getCount();
for (int i = 0; i < imageCount && i < totalCount; i++)
{
final long imageId = imageCursor.getLong(idColumnIndex);
Uri uriImage = Uri.withAppendedPath(uriExternal, "" + imageId);
GalleryData galleryImageData = new GalleryImageData(imageId, uriImage); // Custom class with id and Uri
galleryViewModelList.add(galleryImageData);
imageCursor.moveToNext();
}
imageCursor.close();
}
Why the images that i stored in the folder in Mediastore is not being returned by the above code when i reinstall my App. Is it by design or am i missing something?
These are the columns i am retrieving,
final String[] columns = { MediaStore.Images.Media.DATA, MediaStore.Images.Media._ID, MediaStore.Images.Media.MIME_TYPE };
final String orderBy = MediaStore.Images.Media.DATE_TAKEN; ```
For unclear reasons, Android 10 considers two app installations separated by time to be separate apps, no different than if those were two completely different apps.
As a result, once your app is uninstalled, it loses access to all files that it created... even if the same app is later reinstalled.
So, you need to treat files from a previous installation of your app the same way as you would treat files from completely unrelated apps: request READ_EXTERNAL_STORAGE to be able to query for them and read their contents. The "read their contents" part requires android:requestLegacyExternalStorage="true" on the <application> in the manifest.

Wrong file extension and file name after creating image file with Android's MediaStore (ContentResolver/scoped storage)

I am using the MediaStore and ContentResolver API to save image files (jpg/png/gif) using MediaStore.Images.
It works, but the problem is that all the files are saved with the .jpg extension and the file name is always "the current time in millis.jpg".
Here is an example of what I put in the ContentValues:
date_added=1575480356 _display_name=2067623.gif date_modified=1575480356 mime_type=image/gif
But after inserting it to the ContentResolver and writing the file, the display name changes to 2067623.jpg and the file name to 1575480356.jpg.
How do I get it to keep the file name and extension?
Code:
ContentResolver resolver = activity.getContentResolver();
Uri mediaCollection;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
mediaCollection = MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY);
} else {
mediaCollection = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
}
ContentValues mediaDetails = new ContentValues();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
final String relativeLocation = Environment.DIRECTORY_PICTURES + File.separator + "my_folder";
mediaDetails.put(MediaStore.Images.Media.DATE_TAKEN, System.currentTimeMillis());
mediaDetails.put(MediaStore.Images.Media.IS_PENDING, 1);
mediaDetails.put(MediaStore.MediaColumns.RELATIVE_PATH, relativeLocation);
}
String mimeType = "image" + "/" + getExtension(p); // jpg/png/gif
mediaDetails.put(MediaStore.Images.Media.DISPLAY_NAME, fileName);
mediaDetails.put(MediaStore.Images.Media.MIME_TYPE, mimeType);
mediaDetails.put(MediaStore.Images.Media.DATE_ADDED, System.currentTimeMillis() / 1000);
mediaDetails.put(MediaStore.Images.Media.DATE_MODIFIED, System.currentTimeMillis() / 1000);
Uri contentUri = resolver.insert(mediaCollection, mediaDetails);
write(contentUri, resolver, p, fileName); // Write the file content using resolver.openOutputStream(contentUri)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
mediaDetails.clear();
mediaDetails.put(MediaStore.Images.Media.IS_PENDING, 0);
resolver.update(contentUri, mediaDetails, null, null);
}
Did you find a soution? I did not, so I end up doing this:
private void addImageToGalleryLegacy(#NonNull final String imagePath, #NonNull final String fileName) throws IOException {
File fIn = new File(imagePath);
String outPath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).toString();
File fOut = new File(outPath, fileName);
FileInputStream in = new FileInputStream(fIn);
FileOutputStream out = new FileOutputStream(fOut);
copy(in, out);
}
Basically (as we are in pre Q) we can copy the image straight to the gallery.
"imagePath" is "Android/data/[package name]/files/Pictures" in my case.

file.exists() returns false but the image uri exists

I've spent hours trying to figure out my problems but I just can't seem to figure out the solution.
I'm trying to delete an image that was saved when I was trying to get it's uri. The image was put in the internal storage.
My code is:
Uri imageUri = getImageUri(this, selectedImage);
File file = new File(imageUri.getPath());
if (!file.exists()) {
Log.i(TAG, "nope");
else{
file.delete();
}
}
public Uri getImageUri(Context Context, Bitmap image) {
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
image.compress(Bitmap.CompressFormat.JPEG, 100, bytes);
String path = MediaStore.Images.Media.insertImage(Context.getContentResolver(), image, "Title", null);
Log.i(TAG, path);
return Uri.parse(path);
}
However, file.exists() returns false despite the fact that I have just made the image uri. Why is this so?
Update
Thanks to Shinil M S I have found the solution to my problem. However I have one more issue in that whenever I delete the photo, the image is replaced with this other empty image. I have attached the picture as I'm not sure exactly what it is. How do I get rid of it completely so that the image doesn't appear at all? Image mentioned above
Update 2
I have gotten everything to work as intended.
My code is:
public static void deleteUri(Context context, Uri uri){
long mediaId = ContentUris.parseId(uri);
Uri contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
Uri itemUri = ContentUris.withAppendedId(contentUri, mediaId);
context.getContentResolver().delete(itemUri, null, null);
}
I used this person's solution: Deleting files via a 'ContentResolver' as opposed to deleting them via 'file.delete()'
try this,
Uri imageUri = getImageUri(this, selectedImage);
String[] filePathColumn = {MediaStore.Images.Media.DATA};
Cursor cursor = context.getContentResolver().query(imageUri, filePathColumn, null, null, null);
cursor.moveToFirst();
int columnIndex = cursor.getColumnIndex(filePathColumn[0]);
String filePath = cursor.getString(columnIndex);
cursor.close();
File file = new File(filePath);
if (!file.exists())
Log.i(TAG, "nope");
else
file.delete();

Categories

Resources