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 inDownloads
, and -
A
listTitles()
function to queryDownloads
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 aRecyclerView
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 andWRITE_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 inMediaStore.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.
The entire series of “Scoped Storage Stories” posts includes posts on:
- 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()
onDocumentFile
- Storing content using
MediaStore
- Reading content from the
MediaStore
- Modifying
MediaStore
content from other apps - Limitations of
MediaStore.Downloads
- The undocumented
Documents
option - More on
RecoverableSecurityException
- How to modify more metadata in
MediaStore