Android: Null data returned from Camera Intent

Anyone who has tried calling the image or video capture intent using the default Camera activity probably has been met with much frustration. There are many approaches and workarounds for various phones and API levels because of the insane fragmentation of Android. Most of us just want a simple way to call the default Camera activity, have the video or image stored in the Gallery, and retrieve a the Camera intent results for further processing.

The Android documentation provides what appear to be a very straight forward way to capture images and video and either save them in the default location or a folder of your choosing. The resources available for doing so can be found here:

Image capture intent
Saving Media Files

However, I am not sure Google actually tests their Android examples on real phones (developer phones). Testing the method for capturing images on a Galaxy Nexus or Nexus One both return a null value for the data when receiving the camera intent result. However, capturing video seems to work as expected. Though both the image and video files are written to the specified folders on the device.

So naturally, as a developer you are stuck with a borked example that must be modified for a real world implementation. Luckily for my needs I didn’t need to store images or video in an external folder, I wanted the camera application to store the requested media in the default location with a default name. This is usually the Camera folder.

Below is my modification to the onActivityResult() method of the Activity for retrieving the Uri of the captured image. I have left the rest of the code from the Android Camera example for storing video and images in the activity in case you are feeling particularly brave and want to store your images in a different location.

You might ask why I couldn’t insert the video into the MediaStore the same way I am inserting the captured image (by passing the Uri and ContentValues). Doing this for video actually created two files for me, one in the Media Store, and an 0kb file in the external video folder on the SD card. This only happened on the Nexus One (Android 2.3.4) and not the Galaxy Nexus (4.0.2). So you end up with two different methods for making sure media appears in the MediaStore.

AndroidCameraTestsActivity

package com.thanksmister.mobile;

import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;

import android.app.Activity;
import android.content.ContentValues;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.provider.MediaStore;
import android.util.Log;
import android.view.View;

public class AndroidCameraTestsActivity extends Activity 
{
	private static final String TAG = AndroidCameraTestsActivity.class.getSimpleName(); 
	
	private static final int CAPTURE_IMAGE_ACTIVITY_REQUEST_CODE = 100;
	private static final int CAPTURE_VIDEO_ACTIVITY_REQUEST_CODE = 200;
	public static final int MEDIA_TYPE_IMAGE = 1;
    public static final int MEDIA_TYPE_VIDEO = 2;

    private Uri fileUri;
    
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) 
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    }

    /** 
     * https://developer.android.com/guide/topics/media/camera.html 
     * **/
    public void onCaptureImage(View v) 
    {
        // give the image a name so we can store it in the phone's default location
    	String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
    	
        ContentValues values = new ContentValues();
		values.put(MediaStore.Images.Media.TITLE, "IMG_" + timeStamp + ".jpg");

        Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        
        //fileUri = getOutputMediaFileUri(MEDIA_TYPE_IMAGE); // create a file to save the image (this doesn't work at all for images)
        fileUri = getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values); // store content values
		intent.putExtra( MediaStore.EXTRA_OUTPUT,  fileUri);
       
        // start the image capture Intent
        startActivityForResult(intent, CAPTURE_IMAGE_ACTIVITY_REQUEST_CODE);
    }
    
    /** 
     * https://developer.android.com/guide/topics/media/camera.html 
     * **/
    public void onCaptureVideo(View v) 
    {
    	 //create new Intent
        Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);

        //fileUri = getOutputMediaFileUri(MEDIA_TYPE_VIDEO);  // create a file to save the video in specific folder (this works for video only)
        //intent.putExtra(MediaStore.EXTRA_OUTPUT, fileUri);  // set the image file name
      
        intent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 1); // set the video image quality to high

        // start the Video Capture Intent
        startActivityForResult(intent, CAPTURE_VIDEO_ACTIVITY_REQUEST_CODE);
    }
    
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) 
    {
    	super.onActivityResult(requestCode, resultCode, data);
    	
        if (requestCode == CAPTURE_IMAGE_ACTIVITY_REQUEST_CODE) {
            if (resultCode == RESULT_OK) {
            	
            	// Originally I was going to iterate through the list of images and grab last added to the MediaStore.
            	// But this is not necessary if we store the Uri in the image
            	/*
            	String[] projection = {MediaStore.Images.ImageColumns._ID};
            	String sort = MediaStore.Images.ImageColumns._ID + " DESC";

            	Cursor cursor = this.managedQuery(MediaStore.Images.Thumbnails.EXTERNAL_CONTENT_URI, projection, null, null, sort);

            	try{
            		cursor.moveToFirst();
            		Long id = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Images.ImageColumns._ID));
            		fileUri = Uri.withAppendedPath(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, String.valueOf(id));
            	} finally{
            		cursor.close();
            	}
            	*/
                
				if(fileUri != null) {
					Log.d(TAG, "Image saved to:\n" + fileUri);
					Log.d(TAG, "Image path:\n" + fileUri.getPath());
					Log.d(TAG, "Image name:\n" + getName(fileUri)); // use uri.getLastPathSegment() if store in folder
				}
                
            } else if (resultCode == RESULT_CANCELED) {
                // User cancelled the image capture
            } else {
                // Image capture failed, advise user
            }
        }

        if (requestCode == CAPTURE_VIDEO_ACTIVITY_REQUEST_CODE) {
            if (resultCode == RESULT_OK) {
            	
                // Video captured and saved to fileUri specified in the Intent
            	fileUri = (Uri) data.getData();
				
				if(fileUri != null) {
					Log.d(TAG, "Video saved to:\n" + fileUri);
					Log.d(TAG, "Video path:\n" + fileUri.getPath());
					Log.d(TAG, "Video name:\n" + getName(fileUri)); // use uri.getLastPathSegment() if store in folder
				}
				
            } else if (resultCode == RESULT_CANCELED) {
                // User cancelled the video capture
            } else {
                // Video capture failed, advise user
            }
        }
    }
    
    /** Create a file Uri for saving an image or video to specific folder
     * https://developer.android.com/guide/topics/media/camera.html#saving-media
     * */
    private static Uri getOutputMediaFileUri(int type)
    {
          return Uri.fromFile(getOutputMediaFile(type));
    }

    /** Create a File for saving an image or video */
    private static File getOutputMediaFile(int type)
    {
        // To be safe, you should check that the SDCard is mounted
        
    	if(Environment.getExternalStorageState() != null) {
    		// this works for Android 2.2 and above
    		File mediaStorageDir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), "AndroidCameraTestsFolder");
            
            // This location works best if you want the created images to be shared
            // between applications and persist after your app has been uninstalled.

            // Create the storage directory if it does not exist
            if (! mediaStorageDir.exists()) {
                if (! mediaStorageDir.mkdirs()) {
                    Log.d(TAG, "failed to create directory");
                    return null;
                }
            }

            // Create a media file name
            String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
            File mediaFile;
            if (type == MEDIA_TYPE_IMAGE){
                mediaFile = new File(mediaStorageDir.getPath() + File.separator +
                "IMG_"+ timeStamp + ".jpg");
            } else if(type == MEDIA_TYPE_VIDEO) {
                mediaFile = new File(mediaStorageDir.getPath() + File.separator +
                "VID_"+ timeStamp + ".mp4");
            } else {
                return null;
            }

            return mediaFile;
    	}
        
    	return null;
    }

    // grab the name of the media from the Uri
    protected String getName(Uri uri) 
	{
		String filename = null;

		try {
			String[] projection = { MediaStore.Images.Media.DISPLAY_NAME };
			Cursor cursor = managedQuery(uri, projection, null, null, null);

			if(cursor != null && cursor.moveToFirst()){
				int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DISPLAY_NAME);
				filename = cursor.getString(column_index);
			} else {
				filename = null;
			}
		} catch (Exception e) {
			Log.e(TAG, "Error getting file name: " + e.getMessage());
		}

		return filename;
	}
}

Main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <Button android:text="Capture Image" android:onClick="onCaptureImage" 
        android:layout_width="wrap_content"
    android:layout_height="wrap_content"/>
    
	<Button android:text="Capture Video" android:onClick="onCaptureVideo"
	    android:layout_width="wrap_content"
    android:layout_height="wrap_content"/>
	
</LinearLayout>

ApplicationManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.thanksmister.mobile"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk android:targetSdkVersion="15" android:minSdkVersion="8"/>
    
    <uses-permission android:name="android.permission.CAMERA"/>
 	<uses-feature android:name="android.hardware.camera" />
 	<uses-feature android:name="android.hardware.camera.autofocus"/>
  	<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name" >
        <activity
            android:name="AndroidCameraTestsActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

Additional Resources:

Android Camera Test Project
Null Intent passed back On Samsung Galaxy Tab…

-Mister