In The Eye Of Chaos: Taming The Android Camera

Building and maintaining an Android camera app is a thing of nightmares.

For the past few months, a large percentage of our Android development resources has been spent receiving, investigating and patching bugs related to the camera API.

Many of you have been in contact with us about how your specific device refuses to behave, each time presenting a new colourful chapter in the quest to tame the camera, more crash reports, more lines of code and most importantly, someone who can't use our app.

Here's a brief musing on what we've been up against.

Sharing is caring...

Ah! We couldn't access your camera. Close any apps that might be using it right now and try again.

If we got a dollar for the number of times we've had to show this message to a user, we'd have $443 this month.

This message displays when the camera resource has been locked within another app. When an app on your device calls camera.open(), it locks out every other app from the camera resource. If the developer does not call camera.release() when their Activity becomes paused, then there is absolutely no way for us to access the camera. Furthermore than that, we don't know who the culprit is!. This is why we prompt you to close any and all camera apps you have open.

Unfortunately, this issue will be sticking around for the foreseeable future...

Roll up your sleeves, it's camera opening time...

Opening the camera is a marathon effort. To give you an idea of the horrific process, I'll outline the steps involved. One of the biggest issues we had was that a large number of methods both take callbacks and throw exceptions. If that's not enough to shake you up, it seems that you can receive a random RuntimeException at any point along the way. Anyway, here goes:

A warning to non-technical readers, this is about to get jargony...

Opening

  1. Get the CameraManager, which may or may not be null for some strange reason...
  2. Get the list of cameras and pick one.
  3. Get the CameraCharacteristics from your chosen camera and your StreamConfigurationMap from your CameraCharacteristics.
  4. Grab your chosen output streams:
    • Preview (what's displayed on the screen)
    • Capture (single photo capture)
  5. For each of your chosen output streams, choose an output format (JPEG, YUV, etc.)
  6. Determine if the Capture output is rotated by 90º or not, rotate accordingly.
  7. Select a Preview size which will best suit the size and aspect ratio of the device (with absolutely zero assistance).
  8. Determine if the Preview output is rotated by 90º or not, rotate accordingly.
  9. Take a sanity break, consider causing damage to some objects around you...
  10. It's now time to actually open the camera! Pass in your onDisconnected and onError callbacks, but prepare for a CameraAccessException and a SecurityException.
  11. Set up a capture request for each output stream. If you're using single-shot capture, set up an ImageReader along with its callbacks.
  12. Create a capture session, passing in all your output streams. Be careful, the order matters! This will need an onConfigureFailed callback and throws a CameraAccessException.
  13. Start the preview request as a repeating request. Don't mind all those callbacks you probably don't need.

We don't like to play favourites here at OzGuild, but the number of steps listed here here is more than the lines of code needed to open the camera in iOS and Windows combined!

Capturing

I thought I'd finish by acknoweledging the same issues which crop up during the capture process:

  1. When a single-shot capture is needed, trigger the capture method. It takes an onCaptureFailed callback and throws a CameraAccessException.
  2. Wait for the onImageAvailable callback from your ImageReader.
  3. Also, keep an eye on any layout changes on the preview and be prepared to tear everything down and reopen the camera if the size changes.

Marshmallow Permissions

With Android Marshmallow and API 23, Google has decided to shift from install-time permissions to runtime permissions. Instead of users granting permissions before installing from Google Play, developers must prompt for permissions at the point of needing access.

This is a definitely a good move. It allows users more fine grained control over how apps use their hardware and data. However, this change was made quite recklessly, with hardly any warning from the Android developers. Furthermore, once you had deployed an app targeting Marshmallow, any attempt to submit an update targeting a lower API level was denied.

This left us chasing our tails to write in the logic and UI for presenting permissions requests, while our users on newer devices were staring at camera error messages until we could write in the code to ask for permissions!

If you're interested in bumping up to API 23, consider this article.

Moving forward

We are so grateful for the patience and understanding of our users during this painful journey, and are excited to see the end of the tunnel (maybe) as OzGuild Android beats back the chaotic beast that is the Android Camera.

Our v2.4.1 release saw the introduction of a completely refactored camera implementation which (so far) has been without any major crashes.

Considering the lack of open-sourced Camera libraries currently floating on the internet, maybe we'll throw this solution up on our GitHub in the coming months. Tweet at us or post on our Facebook page to register interest!