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.

The entire series of “Scoped Storage Stories” posts includes posts on:

The AndroidX Tech site contains source code, transitive dependency details, and much more for Google’s androidx artifacts!

— Jan 11, 2020