ACTION_IMAGE_CAPTURE and Android R

Quoting the documentation:

Starting in Android 11, only pre-installed system camera apps can respond to the following intent actions:

  • android.media.action.VIDEO_CAPTURE
  • android.media.action.IMAGE_CAPTURE
  • android.media.action.IMAGE_CAPTURE_SECURE

This is from the page of effects with targetSdkVersion of 30.

They are very serious about this… to the point of throwing ActivityNotFoundException if the user disabled the pre-installed camera app, even if they have other capable camera app(s) installed. So, if the user wants to use, say, Open Camera and feels no need to have the pre-installed camera app cluttering up their launcher, they’ll have problems with other apps that use ACTION_IMAGE_CAPTURE.

This is not tied to targetSdkVersion – an app will get the ActivityNotFoundException with a target of 29, for example.

From a UX standpoint, this sucks.

Similarly, even with the appropriate <queries> element in the manifest, you cannot use queryIntentActivities() to find all activities that support your Intent action. Google’s selected filtering mechanism works at a lower level than this.

The docs, though, do hint at a workaround:

If you want your app to use a specific third-party camera app to capture images or videos on its behalf, you can make these intents explicit by setting a package name or component for the intent.

This also works for EXTRA_INITIAL_INTENTS:

private val CAMERA_CANDIDATES = listOf(
  "net.sourceforge.opencamera"
)

fun enhanceCameraIntent(context: Context, baseIntent: Intent, title: String): Intent {
  val pm = context.packageManager

  val cameraIntents =
    CAMERA_CANDIDATES.map { Intent(baseIntent).setPackage(it) }
      .filter { pm.queryIntentActivities(it, 0).isNotEmpty() }
      .toTypedArray()

  return Intent.createChooser(baseIntent, title)
    .putExtra(Intent.EXTRA_INITIAL_INTENTS, cameraIntents)
}

(a slightly-improved version of this code appears in this sample project, profiled in this book)

Here, enhanceCameraIntent() will return an Intent that will display a chooser if 2+ camera apps are available, taking into account both pre-installed camera apps and Open Camera (if it happens to be installed). If only Open Camera is available, or if only a pre-installed camera app is available, the user is taken directly to that app. And, in principle, you could whitelist other third-party camera apps by adding their application IDs to CAMERA_CANDIDATES.

You still need to use try/catch to deal with ActivityNotFoundException. After all, the user might be trying to use a different camera app than those in your whitelist. Also, the user may be operating in a restricted profile or otherwise lack access to camera apps in general.

You could switch to taking pictures directly in your app, using the still-in-alpha CameraX or other existing libraries. ACTION_IMAGE_CAPTURE has sucked for years. However, taking a picture in your own app means that you need to request the CAMERA permission, and your code will lack many of the features of modern camera apps.

I do not know why Google elected to add this restriction. In particular, I do not know why Google set it up that app developers will take the blame when the user cannot use their preferred camera app to take a picture. Using code like I show above, we can at least attempt to improve upon a bad situation.