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.