ACTION_SEND, the Chooser, and ClipData

Roderick Gadellaa pointed out an interesting problem with the “share sheet”. In Android 10+, the share sheet can show a preview of your content. In particular, it can show a preview of an image that you are sharing. The catch is that the share sheet needs to know what image that is… and whether it does depends on how you are sharing it.

Sharing content involves ACTION_SEND, and if we use EXTRA_STREAM to supply a Uri, we need to add FLAG_GRANT_READ_URI_PERMISSION and/or FLAG_GRANT_WRITE_URI_PERMISSION on the Intent:

val intent = Intent(Intent.ACTION_SEND).apply {
    putExtra(Intent.EXTRA_STREAM, uri)
    type = "image/webp"
    addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}

Frequently, when using ACTION_SEND, we wrap it in a chooser Intent, such as via Intent.createChooser(). So, we pass our Intent to createChooser() and roll from there:

val intent = Intent(Intent.ACTION_SEND).apply {
  putExtra(Intent.EXTRA_STREAM, uri)
  type = "image/webp"
  addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}

startActivity(Intent.createChooser(intent, null))

And, if you try this, you will not get a preview in the share sheet.

This, however, works:

val intent = Intent(Intent.ACTION_SEND).apply {
  clipData = ClipData.newRawUri(null, uri)
  putExtra(Intent.EXTRA_STREAM, uri)
  type = "image/webp"
  addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}

startActivity(Intent.createChooser(intent, null))

The reason is that the framework copies the ClipData out of the ACTION_SEND and puts it in the ACTION_CHOOSER Intent created by createChooser(). It does not copy any EXTRA_STREAM value, though.

There is also the possibility that you are using ShareCompat.IntentBuilder:

ShareCompat.IntentBuilder.from(this)
  .setType("image/webp")
  .addStream(uri)
  .startChooser()

This too fails… for now. cketti filed an issue for that one, and while it is fixed, I do not believe that fix has shipped yet.

UPDATE 2021-01-13: The fix is available in androidx.core:core:1.5.0-beta01!

Ideally, you use setClipData() on an ACTION_SEND Intent or that repaired version of ShareCompat.IntentBuilder, so your shared images are able to be previewed properly.


Interested in learning Kotlin? Check out the Klassbook for Kotlin language lessons that you can run right in your browser!