Scoped Storage Stories: The Diabolical Details of Downloads
Android 10 is greatly restricting access to external storage
via filesystem APIs. Instead, we need to use other APIs to work with content.
This is the eleventh and final(?) post in a series where we will explore how to work with those
alternatives, now looking at MediaStore options.
Another wrinkle introduced by Android 10 was MediaStore.Downloads. This is advertised
as being another MediaStore collection that we can use, akin to the ones for
video, pictures, etc.
The basic usage of MediaStore.Downloads indeed does match what we use for
other MediaStore collections. The Download Wrangler sample app
is a bit of a mash-up of some of the previous samples, with a DownloadRepository class
that offers:
A download() function to download a URL to a requested filename in Downloads, and
A listTitles() function to query Downloads and return the names of
all the items found in there
These have both ���Q��� and ���legacy��� variants, where the ���Q��� edition uses
MediaStore.Downloads and older devices use
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).
Those functions are tied to three toolbar buttons in the UI:
A ���refresh��� button that calls listTitles() and updates a RecyclerView with the titles
A ���download��� button that downloads a PDF of an appendix from Elements of Kotlin Coroutines
that I used in an Android Summit workshop
A ���all��� button that appears on Android 10 devices and allows you to ���upgrade���
your permissions with READ_EXTERNAL_STORAGE (on Android 9 and older, that
permission is requested when you first run the app)
The behavior on legacy devices is what you would expect:
The list shows the contents of the Downloads/ directory
You need READ_EXTERNAL_STORAGE to read the contents of that directory and
WRITE_EXTERNAL_STORAGE to save content to that directory
On Android 10, what we would expect is:
Without any permissions, the list contains titles of content that you downloaded by means of MediaStore.Downloads
With READ_EXTERNAL_STORAGE, the list contains all content in MediaStore.Downloads, including
those from other apps
Without any permissions, you can save content to MediaStore.Downloads
The first and the last items in that list do occur. The second does not.
You cannot access other apps��� downloads via MediaStore.Downloads, even with READ_EXTERNAL_STORAGE permission.
For that matter, WRITE_EXTERNAL_STORAGE does not help.
You can see this by running the sample app. It has two product flavors, cunningly
named one and two. Run one and download the PDF, then run two and use the ���all���
button to request READ_EXTERNAL_STORAGE. You will not see the PDF downloaded by
one or anything else that might reside in Downloads/.
This is covered in the documentation:
In particular, if your app wants to access a file within the MediaStore.Downloads collection that your app didn���t create, you must use the Storage Access Framework.
For apps that are merely downloading content to MediaStore.Downloads, or accessing
their own downloaded content, this is not a problem. It���s just something to bear
in mind: Android 10���s MediaStore enhancements are not the same across all
collections.
That should wrap up this blog post series, though I may write more on this subject
in the future if interesting edge cases come to my attention.
The entire series includes:
The basics of using the Storage Access Framework
Getting durable access to the selected content
Working with DocumentFile for individual documents
Working with document trees
Working with DocumentsContract
Problems with the SAF API
A specific problem with listFiles() on DocumentFile
Storing content using MediaStore
Reading content from the MediaStore
Modifying MediaStore content from other apps
Limitations of MediaStore.Downloads


