Scoped Storage Stories: Durable Access

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 second post in a series where we will explore how to work with those alternatives, starting with the Storage Access Framework (SAF).


So, you used ACTION_OPEN_DOCUMENT or ACTION_CREATE_DOCUMENT to get a Uri that you can use to read and/or write some content. Great!

You got that Uri in the scope of some Activity. Perhaps you called startActivityForResult() directly on the Activity. Or, perhaps you called startActivityForResult() on some Fragment which, in turn, is managed by some Activity.

The good news is that your Activity should be able to work with that Uri, even across configuration changes.

The bad news is that your rights to that Uri content end there, by default.

Using the Uri in Other Components

You might try using that Uri from other components in your app. Perhaps you stick that Uri in some shared repository and have another Activity get that Uri and try using it. Or, perhaps you try passing the Uri to some Service, or try using it via WorkManager.

You will find out fairly quickly that your Uri rights vanish. You have access to the content identified by the Uri from the Activity that receives the Uri, and that’s it.

You can pass along those rights to other components of your app, or even components of other apps. To do that, though, you need to:

  • Pass the Uri in the “data” facet of an Intent

  • Add Intent.FLAG_GRANT_READ_URI_PERMISSION and/or Intent.FLAG_GRANT_WRITE_URI_PERMISSION to that Intent

  • Use that Intent to start the other component (e.g., startActivity(), startService())

val intent = Intent(this, TheOtherActivity::class.java)
  .setData(theUri)
  .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)

startActivity(intent)

The Intent flags indicate that you want to pass along the read or write rights that your component has to the component that you are starting.

Note that this works for any content Uri, not just those from the Storage Access Framework. ACTION_GET_CONTENT, for example, works the same as does ACTION_OPEN_DOCUMENT in terms of these component-level access rights.

Getting Long-Term Access

Using the Intent flags has clear limitations:

  • Not everything offers you a place to put those flags (e.g., WorkManager)

  • Eventually, your process gets terminated, at which point all of your rights lapse

For Storage Access Framework Uri values, you can request long-term access to the content via takePersistableUriPermission() on a ContentResolver. You just pass in the Uri along with Intent.FLAG_GRANT_READ_URI_PERMISSION and/or Intent.FLAG_GRANT_WRITE_URI_PERMISSION. This will give you the ability to work with that Uri not only between components, but between process invocations. You can save the String representation of the Uri somewhere (e.g., SharedPreferences) and use it again in the future.

However, bear in mind that there is no guarantee that the underlying content itself is durable. The user might use some other app to move or delete that content, at which point your persisted Uri becomes pointless. You will need to be able to deal with the FileNotFoundException and related forms of IOException that you will get when this sort of thing occurs.

In addition to takePersistableUriPermission(), there is:

  • releasePersistableUriPermission(), to say that you no longer need durable access; and

  • getPersistedUriPermissions(), to retrieve information about all outstanding persisted permissions that you took using takePersistableUriPermission() and have not released using releasePersistableUriPermission()

What Else Is There?

Previously, I covered the basics of using the Storage Access Framework.

Upcoming posts in this series will include:


Need Android app development training for your team? Mark Murphy has trained hundreds! Learn more!