The CommonsBlog


"Elements of Android Jetpack" Version 0.7 Released

Subscribers now have access to Version 0.7 of Elements of Android Jetpack, in PDF, EPUB, and MOBI/Kindle formats. Just log into your Warescription page to download it, or set up an account and subscribe!

This update adds five new chapters:

And, as usual, it fixes lots of bugs.

Jan 20, 2020


"Elements of Android Room" Version 0.1 Released

Subscribers now have access to Version 0.1 of Elements of Android Room, in PDF, EPUB, and MOBI/Kindle formats. Just log into your Warescription page to download it, or set up an account and subscribe!


I cover a fair amount of material in Android’s Architecture Components. However, that is a book on first-generation Android development techniques, using Java and the Android Support Library edition of the Architecture Components. And the biggest single topic in Android’s Architecture Components is on Room, Google’s reactive abstraction layer over SQLite.

Elements of Android Room is all about the updates:

  • Updating the material to cover second-generation Android, including Kotlin and the Jetpack/AndroidX version of the Architecture Components

  • Updating the material to reflect newer Room features, from coroutines/Flow to built-in full-text indexing support, probably doubling the total Room coverage from what I had in Android’s Architecture Components

  • Updating the references to external tools and libraries, notably for integrating Room with SQLCipher for Android without the need for my SafeRoom library

This is Version 0.1, so it is a work in progress. I have all of the basics in there:

  • Entity, DAO, and database setup

  • Coroutines, RxJava, and LiveData

  • Migrations

  • And so on

More advanced topics will be added in the coming months.

As always, with Version 0.1 of the book, there may be production problems. If you run into issues with your copy of the book, let me know!

Jan 13, 2020


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:

Jan 11, 2020


Scoped Storage Stories: Modifying the Content of Other Apps

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 tenth post in a series where we will explore how to work with those alternatives, now looking at MediaStore options.


Earlier, we saw that you can store stuff via MediaStore on Android 10 without WRITE_EXTERNAL_STORAGE as a permission. And, we saw that you can read your own MediaStore stuff without a permission, but you need READ_EXTERNAL_STORAGE to read other apps’ stuff in the MediaStore. The remaining piece is: how do we modify other apps’ stuff in the MediaStore?

You might think that WRITE_EXTERNAL_STORAGE is the solution. After all, if READ_EXTERNAL_STORAGE lets you read other apps’ stuff, WRITE_EXTERNAL_STORAGE should let you modify other apps’ stuff. Right?

(if you have not read much of my writing, when I use that sort of “leading argument” paragraph structure, it is almost always creative foreshadowing of a reversal)

(we return you now to your regularly-scheduled blog post, already in progress…)

In truth, WRITE_EXTERNAL_STORAGE does not help here. WRITE_EXTERNAL_STORAGE grants blanket access, and one of the objectives of Android 10’s scoped storage is to get rid of that sort of blanket access.

Instead, Android 10 introduces the concept of a RecoverableSecurityException. If we catch one of these, the exception allows us to request fine-grained access from the user, and we can use that to proceed.

To illustrate this, let’s look at Google’s storage-samples repo and the MediaStore sample inside of it.

This module has a MainActivityViewModel that, among other things, offers a performDeleteImage() function. Basically, given a Uri, it will attempt to delete the item from the MediaStore. However, frequently the item was not created by the app, but instead was found by querying the MediaStore. As a result, deletion often fails… but we can recover from it.

private suspend fun performDeleteImage(image: MediaStoreImage) {
    withContext(Dispatchers.IO) {
        try {
            /**
             * In [Build.VERSION_CODES.Q] and above, it isn't possible to modify
             * or delete items in MediaStore directly, and explicit permission
             * must usually be obtained to do this.
             *
             * The way it works is the OS will throw a [RecoverableSecurityException],
             * which we can catch here. Inside there's an [IntentSender] which the
             * activity can use to prompt the user to grant permission to the item
             * so it can be either updated or deleted.
             */
            getApplication<Application>().contentResolver.delete(
                image.contentUri,
                "${MediaStore.Images.Media._ID} = ?",
                arrayOf(image.id.toString())
            )
        } catch (securityException: SecurityException) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                val recoverableSecurityException =
                    securityException as? RecoverableSecurityException
                        ?: throw securityException

                // Signal to the Activity that it needs to request permission and
                // try the delete again if it succeeds.
                pendingDeleteImage = image
                _permissionNeededForDelete.postValue(
                    recoverableSecurityException.userAction.actionIntent.intentSender
                )
            } else {
                throw securityException
            }
        }
    }
}

This function tries to delete() the content using a ContentResolver. If delete() throws an exception, we can see if that exception is a RecoverableSecurityException. If it is, rather than treat the exception normally, we use userAction.actionIntent.intentSender to get an IntentSender associated with the exception. IntentSender is an uncommon class, but you get one from a PendingIntent via getIntentSender(). Like a PendingIntent, an IntentSender has the ability to send an Intent, where the holder of the IntentSender does not know what the action is (start an activity? send a broadcast? start a service?) or who the recipient is.

MainActivityViewModel then posts that IntentSender to a LiveData, which is observed by MainActivity:

viewModel.permissionNeededForDelete.observe(this, Observer { intentSender ->
    intentSender?.let {
        // On Android 10+, if the app doesn't have permission to modify
        // or delete an item, it returns an `IntentSender` that we can
        // use here to prompt the user to grant permission to delete (or modify)
        // the image.
        startIntentSenderForResult(
            intentSender,
            DELETE_PERMISSION_REQUEST,
            null,
            0,
            0,
            0,
            null
        )
    }
})

Here, we use startIntentSenderForResult() on Activity to invoke the IntentSender. If the IntentSender wraps an activity Intent, startIntentSenderForResult() will call startActivityForResult() on that underlying Intent. In the case of the IntentSender from the RecoverableSecurityException, this will bring up a UI to allow the user to grant your app rights to modify the content affected by the failed delete() request. And, if onActivityResult() gets RESULT_OK for the startIntentSenderForResult() call, this means the user granted you permission, and you can retry your delete(), which should now succeed.

Many thanks to Nicole Borelli of Google for creating and posting this sample code!


This series of posts includes:

Jan 05, 2020


Scoped Storage Stories: Reading via MediaStore

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 ninth post in a series where we will explore how to work with those alternatives, now looking at MediaStore options.


In our last episode, we looked at saving content to a Uri obtained by insert() into the MediaStore. If we put data into the MediaStore, it may also prove useful to get data back out of it.

The basic recipe for this has not changed from past Android versions. You can use a ContentResolver to query() a MediaStore collection of relevance.

For example, this sample project has its own edition of VideoRepository that will query() the MediaStore for all videos on external storage:

suspend fun listTitles(): List<String>? =
  withContext(Dispatchers.IO) {
    val resolver = context.contentResolver

    resolver.query(collection, PROJECTION, null, null, SORT_ORDER)
      ?.use { cursor ->
        cursor.mapToList { it.getString(0) }
      }
  }

In this function, inside of a coroutine, we:

  • Obtain a ContentResolver from a suitable Context

  • Call query() on that ContentResolver to obtain a Cursor with interesting stuff

  • Get the 0th column out of each Cursor row and return that in a List

PROJECTION and SORT_ORDER are just for the title:

private val PROJECTION = arrayOf(MediaStore.Video.Media.TITLE)
private const val SORT_ORDER = MediaStore.Video.Media.TITLE

…and mapToList() just iterates over the Cursor rows and applies the lambda expression to each row:

private fun <T : Any> Cursor.mapToList(predicate: (Cursor) -> T): List<T> =
  generateSequence { if (moveToNext()) predicate(this) else null }
    .toList()

collection, though, varies a bit by OS version, as on Android 10+ we can get dedicated Uri values for media by storage volume. Here, we get the value for external storage:

private val collection =
  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
    MediaStore.Video.Media.getContentUri(
      MediaStore.VOLUME_EXTERNAL
    )
  } else {
    MediaStore.Video.Media.EXTERNAL_CONTENT_URI
  }

Where things get even more interesting is how this bit of code relates to permissions.

On Android 9 and older devices (back to Android 4.4 IIRC), if you run that code without READ_EXTERNAL_STORAGE (or perhaps WRITE_EXTERNAL_STORAGE), you crash with a SecurityException. However, once you have that permission, your query will return a list of the titles of all videos available in that collection.

On Android 10, you can run that code with no permissions. However, you will only get back those pieces of content that your app inserted into the MediaStore collection. Only if you hold READ_EXTERNAL_STORAGE will you be able to get the list of all titles in that collection, including those placed there by other apps or by the user (e.g., via USB cable).

You can see this in action if you run that sample app on an Android 10 device. The UI is mostly a RecyclerView showing the list of titles, and that will be empty the first time you run the app. If you click the “add to library” toolbar button, the app will copy a small MP4 file from assets/ into the MediaStore, using the techniques from the previous blog post. You will then see “test” show up in the RecyclerView, as the media’s filename is test.mp4. If you click the “all inclusive” action bar item, you will be prompted to grant read access to external storage. After that, the list will show all videos in external storage, because now the app has READ_EXTERNAL_STORAGE rights and can access all the content.

In contrast, if you run that app on Android 9 or older devices, you will be prompted to grant READ_EXTERNAL_STORAGE right away, as otherwise we cannot query() the MediaStore


This sample app just needs the titles. If you wanted to actually do something with the content, such as play it back using ExoPlayer, you can get a Uri on the content by:

  • Including MediaStore.Video.Media._ID in your projection

  • Obtaining the _ID column value for the piece of content of interest

  • Pass that plus the collection Uri that you queried to ContentUris.withAppendedId() to get the Uri for that particular piece of content

If you were able to conduct the query, you will be able to read in the content. If you want to use a third-party app to do something with the content – such as passing the Uri to a video player via ACTION_VIEW – be sure to add the Intent.FLAG_GRANT_READ_URI_PERMISSION flag to the Intent, to pass along read access rights. Otherwise, the video player app may not have permission to work with that Uri.


This series of posts includes:

Dec 29, 2019


Older Posts