CameraTest [Android]
In trying to develop my first Android app SudoTape I encountered an issue when using the built in Camera intent for Android. Sure I could have done what a lot of other examples suggest in creating a preview and simple camera activity but that seemed like a lot of work when there already is an Camera intent to use that launches the full option camera taking software that comes bundled with all the phones these days. Why write some half-baked camera capture when I should be able to reuse what is already on everyone’s device and simply intercept the photo that was taken and use the image for my own software.
Sadly this proved a lot more difficult than I expected, and it was my first foray into what a lot of Android detractors point out as a major flaw with Android and one that I never took seriously until now… ‘fragmentation‘.
The idea of reusing the built-in camera intent is sound the problem is that most phones out these days tweak the built-in camera intent to do different things. Even worse, none of them function the same as the emulator. Worse still, the emulator doesn’t work well at all for simulating the camera image capture workflow and the built-in intent seems broken.
This article discusses all the other articles I read online and the problems I encountered in trying to implement the articles, why they didn’t work for me and what the final solution I ended up implementing was.
I posted my findings in a small project on github to try and expose the issues so people could play with the sample app, improve on it and perhaps get some other feedback on the whole issue. In my opinion the camera intent stuff in Android isn’t really finished, it may never be consistent due to the interference the phone manufacturers impose on their products.
My first attempts at using the built-in camera intent was centered around Andrey Chorniy’s article titled “Howto launch android camera using intents”. You can read his article here:
http://achorniy.wordpress.com/2010/04/26/howto-launch-android-camera-using-intents/
The code is easy to follow and makes sense, sadly though it doesn’t work, not for all devices. I was lucky enough to have my friend to test the sample app on his HTC Evo and the code fails because the file specified in the output Uri is never output on a HTC device. This was particularly frustrating because the code works in the emulator and on my phone (Samsung Galaxy S). I spent a lot of time sending test APKs for him to sideload to try and troubleshoot what was going wrong and on his device the returned data from the Camera intent was significantly different than my phone or the emulator.
For example, the code returned from the intent looks like:
if (resultCode == RESULT_OK) {
/* what makes the result confusing is that on some devices data is null (which goes
against what most articles suggest should happen), we can try and use the cached imageUri
too but again, on some devices this is also not valid (HTC sense interfering)
*/
if (data == null) {
File file = convertImageUriToFile(imageUri, this);
showAlert(this, "Picture Captured(Uri not provided)", "Path: " + file.getPath());
} else {
Uri dataUri = data.getData();
File file = convertImageUriToFile(dataUri, this);
showAlert(this, "Picture Captured(Uri provided with Data)", "Path: " + file.getPath());
}
}
The code isn’t that easy to follow, this is because for a lot of devices the returned data from the intent is simply null, so you would imagine that you would have to cache the Uri from when you launched the intent. That isn’t the case though, on HTC devices SenseUI actually returns data, sadly the data isn’t the Uri but a pointer to the bitmap data from the captured image. This code took a LONG time to figure out what was going on because it was so difficult to replicate. Very few places (google code, etc) discuss the fact that the returned data for SenseUI enabled devices actually returned 1) data and 2) something other than a pointer to the Uri of the requested image path.
My research lead me to another post by Jon Simon. Jon discusses the differences he discovered when capturing images via the built-in camera intent on devices with HTC Sense. You can read Jon’s article here:
http://www.jondev.net/articles/Capturing,_Saving,_and_Displaying_an_Image_in_Android_%281.5,_1.6,_2.0,_2.1,_2.2,_Sense_UI_-_Hero%29
The problem with Jon’s article is that the data returned is a bitmap of the data. As this point in my research I assumed that I would just need two branches in the code, one for devices that returned bitmap data and one for situations where the data returned was null or a Uri. Sadly, after doing some experimentation I found that the bitmap returned is actually just a thumbnail image, which is pretty useless unless all your app does is capture images and you’re not concerned with the returned image other than to perhaps ‘preview’ the results. For SudoTape I wanted to get access to the best quality image, perhaps the original captured image if possible. Jon didn’t have a solution, his article didn’t discuss that the returned image was only a thumbnail, perhaps at the time he published his article and the device he used or the version of SenseUI actually returned a bitmap to the full data. Either way I wasn’t happy with the results so I started again looking for a solution to capture an image from the built-in camera intent.
Next I was sniffing through some code on github when I discovered Fredrik Leijon’s tumblr application. You can read about Fredrik’s project here:
http://blog.tacticalnuclearstrike.com/tttumblr/
I saw he handles capturing images and saw some code in the intent return that was unfamiliar:
File f = new File(selUri.getPath());
selUri = Uri.parse(android.provider.MediaStore.Images.Media.insertImage(getContentResolver(), f.getAbsolutePath(), null, null));
if(!f.delete())// delete the thumbnail image
showAlert(this,"Delete error","could not delete the thumbnail:"+f.getPath());
File result = convertImageUriToFile(selUri, this);
This code was unfamiliar but worked a lot better than any of my previous attempts, what the code does is force the media store to update causing the image to appear. This would provide us with a way to create an image file a the Uri we specified when we launched the intent (selUri). At this point I was worried that the result wouldn’t work on HTC type devices but after getting my friend to prototype the code on his HTC device the results were really good, I was happy that I could plug this directly into SudoTape and get the results I wanted without having two branches of code to go through.
But (there is always a but), the result bothered me in that multiple images were saved.
It irked me that I had to pollute the user’s directory with so many copies of the same file. I also wanted to offer the ability for the user to delete the image they captured if they decided that they didn’t want to use it in SudoTape so this would be impossible since the real camera intent image was not returned to me in any form so at least 1 image was left over from using the built-in camera intent.
I had a usable solution but not the best solution, there had to be yet another way to get the camera intent to return me ONE file.
I had
- One image from the camera intent that was NOT returned in any form. This image was typically saved in the DCIM directory.
- One image that we specified in launching the intent (selUri), this was the full non-thumbnail image, the one I planned on using for SudoTape
- One image that was also returned from the intent, which was saved in the DCIM directory but the actual thumbnail image.
Back to sniffing code out on github/google code.
Lastly I came upon a project by Jan Peter Hooiveld that sounded promising. Basically he wrote a background task that would sit an sniff for new camera images and automatically upload them to Picasa. You can read about Jan’s project here:
http://code.google.com/p/picasaphotouploader/
I didn’t care about uploading but I liked the idea of writing a listener that would just notify me when an image was captured. This way I wouldn’t have to care how each phone’s camera intent functioned, only be notified when a new image appeared. I figured I should be able to modify his code to listen and ignore new image events whenever I wanted. I should be able to modify the code to listen for images just before I started the built-in camera intent and stop listening once I was notified that a new image appeared in the image/media table for the device.
This all worked in theory, until I hit a snag. Jan’s code had some odd behavior in that on my phone the code would crash with some cryptic message about the ‘cursor’ not being closed. The stack trace did not give any indication WHERE the cursor was not being closed (no entry point into my code). The code also didn’t work in the emulator, mostly because the emulator did not capture images when no EXTRA_OUTPUT was included as part of launching the intent. I figured it was worth trying to fix the code. It turned out that all of the listener code needed to be modified and call cursor.close() whenever we were finished with reading the information from the media table. Once I made the necessary bug fixes the listener started working in my sample app. It also worked perfect on the HTC device as well. This to me was the best solution. The listener only returned one path, the path to the actual image that was saved in the DCIM directory, no extra thumbnails, no need to specify a secondary Uri that I didn’t really want.
After all this work I finally had the solution I wanted. If I look at the solution objectively it seems like a LOT of work to simply find out the image file but the result works, and lets the user use the camera intent with all the functionality that the phone’s manufacturer included in the camera activity such as lighting, ISO settings, focus settings, etc. For my first project it makes me a little scared of some of the other behaviors I’ll encounter that will be different from device to device. It makes it difficult to plan on releasing software to the Android market if you don’t have friends with a variety of devices because you might not know what sort of results you’ll get. Putting out an app that works for you but crashes for 30+% of the other android users is a death knell for negative ratings for your software. Making sure that it will be even more difficult to attract new users to your work.
I hope that some of my comments are of help to other Android developers. You can find the sample test app on github under my account there. Feel free to download it and play with it. Let me know the results you get from it and your comments/improvements to it. If you know of a less convoluted way to get images captured from the built-in camera let me know. I’ve sent enough time on this so I’m curious now on what other solutions may be, where I may have made bad assumptions in my code and if perhaps Google will improve the capture intent code in further iterations of Android.
Thanks to my friend Jesse Virgil for all his help sideloading my test app on his Evo to help debug all my work on a HTC Sense device. Jesse helped me with the design of SudoTape so hit his portfolio online if you’re interested in some of his other work.
January 23rd, 2011 at 10:35 pm
[…] CameraTest [Android] /* This is a basic set of rules designed to work well with the Twenty Ten theme provided as part of WordPress 3.0. */ #main .widget-area ul.hl_recent_tweets { clear: both; list-style: none; margin: 0; padding: 6px 0 0; } .hl_recent_tweets li { margin-bottom: 6px; } .hl_recent_tweets p { margin-bottom: 0; } .hl_recent_tweets span { display: block; font-size: 10px; } .hl_recent_tweets_none { margin-bottom: 0; } .hl_recent_tweets_meta { font-size: 10px; color: #999; font-style: italic; } […]
February 23rd, 2011 at 2:06 am
Very useful work :). I’m having a slight problem in that the “final solution” doesn’t work on my Cyanogenmod V6 HTC Desire – after pressing the OK button it sits there displaying the “saving camera captured image” dialog (from verifyImageEvent I’m assuming) and requires phone reboot!
Any thoughts?
February 23rd, 2011 at 2:10 am
Would you believe it! – I went to pick up my phone this time and the dialog box closed (that was after about 4 mins of sitting there!)
Simon
February 23rd, 2011 at 8:23 am
I believe there is still an issue but I believe it has to do with timing.
I don’t know what the CameraUI looks like on Cyanogen (they’re still trying to get it to work on the Galaxy S otherwise I’d probably run it too) but on my Samsung after you take the image you’re shown the image with a Save and Cancel button. If I’m quick and press the Save button as soon as the image appears I will see the same progress “Saving camera captured image”.
I suspect that the image still has not been saved the sdcard and closing the camera intent quickly causes the code to get out of sync. If I wait a few seconds before pressing the Save button the code almost always launches with the correct filename.
The worst part is that I can’t debug it, as soon as I plug in the device to USB debug it always works. Perhaps it’s related to the media scanner running at the same time as ‘listening’ code slowing everything down.
The other solutions will work but save multiple images but I still think the last solution is the best.
If I get some time I may put a boatload of logging into the github source and see if it’s more obvious that there is a timing issue.
Thanks for your comments, if you discover anything in your experimentation let me know.
March 1st, 2011 at 8:50 am
I’ve spent some time simplifying the CameraTest app, you can update the source from Github. It seems to work better on my device. The image is always captured now.
March 13th, 2011 at 9:36 pm
I’m happy You mention about this issue. Totally agree with You.
March 15th, 2011 at 6:29 pm
I love your content. I’ll definitely visit this site later.
March 21st, 2011 at 6:04 am
Very well written article indeed, thank you so much for sharing such information with us.
April 17th, 2011 at 11:44 am
Thanks for sharing, this is a fantastic article.
September 19th, 2011 at 3:03 am
Regards for helping out, good info .
November 21st, 2011 at 5:36 am
It works for me on a Sony Ericsson Xperia X10 mini. However, it’s not working on the emulator (API 7). The newImageAvailable() callback isn’t being called.
November 21st, 2011 at 8:00 am
As I probably mentioned in the article the whole camera API seems pretty ‘up in the air’ as part of Android 2.1 and earlier. I probably need to re-evaluate the code again for something newer like 2.3, 3.0 or perhaps 4.0.
I noticed currently on my SGS (Gingerbread 2.3.5) that the whole camera content (CameraTest and SudoTape) does not work at all anymore so I should probably revisit the process.
I haven’t looked at ICS (Android 4.0) but most previews read that 4.0 has a lot better camera functionality so with luck they’ve addressed some of the shortcomings of the current camera code.
If you discover anything please let me know.
Thanks for the input/comments. 🙂
January 18th, 2012 at 2:30 am
This was very informative. Keep up with good posts.
January 18th, 2012 at 4:04 pm
Very nice article, exactly what I wanted to find.