Access photos from external storage in Android Q
Asked Answered
Y

1

10

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; ```

Yoong answered 7/4, 2020 at 5:1 Comment(12)
If you have problems or questions please tell and ask.Gimble
You should show wich content values you use.Gimble
Sorry, formatting issue. This is my question, "Why the images that i stored in the folder in Mediastore is not being returned by the above code when i reinstall my App?"Yoong
You have not told the content values. Questions should be asked in your post. Not in a comment.Gimble
hmmm, question is there in the post as well. Just posted in the comment as a reply to you.Yoong
RELATIVE_PATH); ? What is RELATIVE_PATH ? Should we guess? And why dont you put that at the start of your post? Write a decent post.Gimble
Thanks for your suggestions. I wasn't sure how much information is necessary. Understood now, thanks for looking.Yoong
The .DATA column does not exist under Q. Please elaborate.Gimble
"Why the images that i stored in the folder in Mediastore is not being returned by the above code when i reinstall my App?" -- because from Android 10's standpoint, your reinstalled app is unrelated to your original app installation. From your newly-installed app's perspective, the old installation's file are equivalent to files contributed by other apps or the user. Those are inaccessible without READ_EXTERNAL_STORAGE.Idel
@Gimble That's a mistake. I didn't use that at all but failed to remove that line.Yoong
Thanks @CommonsWare, that makes sense. Let me try the approach you suggested.Yoong
You are right @CommonsWare. I removed the external storage permission assuming that it is not needed. Thank you very much :)Yoong
I
8

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.

Idel answered 8/4, 2020 at 20:6 Comment(6)
I'm able to query the content of the files even after reinstalling it, like I can query for it's id and display name. But when I tried to convert it into bitmap or even when using glide, I'm getting file not found exception, and I've given the READ_EXTERNAL_STORAGE permission. To confirm here it is happening only for the files which was created by my app before reinstalling. Do you know why is this happening?Tiffanitiffanie
@ParagPawar: I clarified the answer -- see if that addresses your concerns.Idel
But I should not use requestLegacyExternalStorage for Android 10 and above right? I'll explain my problem more clearly. I'm capturing images using cameraX API and I'm storing those into app-specific storage, but after storing I'm making entry of it into mediastore using MediaScannerConnection.scanFile, so that galley app can access it. But now actual issue is, when I uninstall the app it gets deleted, but upon reinstalling I'm still getting it's mediaStore entry, but it does exist in the storage where I stored it. But I'm able to see it in the gallery app(Photos). So this should not happen?Tiffanitiffanie
When I query the content resolver for media entries, I'm getting those entries for those images which should be deleted upon uninstalling my app. But when I'm trying to load these entries using glide, it gives file not found exception. Even I'm not able to share these images from photos app to Skype. That means even skype is unable to find these images, but how can they exist in the photos app?Tiffanitiffanie
@ParagPawar: "But I should not use requestLegacyExternalStorage for Android 10 and above right?" -- the advice has changed there. On Android 10, this is safe to use, as they are allowing it to be used even after your targetSdkVersion reaches 29. On Android 11, READ_EXTERNAL_STORAGE works (mostly) like it did before. "but it does exist in the storage where I stored it" -- possibly a backup was restored.Idel
@Idel After reinstall in app target SDK 30, with permission READ_EXTERNAL_STORAGE , not able to read and write the files created earlier under document directory. As requestLegacyExternalStorage has no use if we target SDK is 30. val contentUri = MediaStore.Files.getContentUri("external") This is the query i am using to read the files. Please help me to point to right direction. if this approach is correct. I want to build the backup directory which should be persistent.Emcee

© 2022 - 2024 — McMap. All rights reserved.