Thursday, October 18, 2012

Android app building | Android Camera

There have been a lot of posts on this blog relating to android but none of them say anything about android application development. This is the first one to be talking about android application development. Yes that's right ".apk". Though its customary for you to build a "Hello, World!" program whenever you start to learn some new language, I took a small detour to teach you guys how easy it will be to utilize real hardware on your phone. This post will outline an app to take pictures from your phone camera("Yes!, it does the same work as your default camera app").

If you don't have Android SDK downloaded and installed along with Eclipse. And ADT plugin configured with it. Do it fast.

Now fire up your IDE and create a new project with appropriate name. Now that we want to create a camera app, let's see what google has to offer. Android has a page to describe building a camera app, nice!!.

The first snippet it gives is to detect the camera availability. It runs like this.


/** Check if this device has a camera */
private boolean checkCameraHardware(Context context) {
    if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA)){
        // this device has a camera
        return true;
    } else {
        // no camera on this device
        return false;
    }
}

This is a really useful piece of code I decided to put out of my program, becoz I know my phone has a camera(lol). Anyways if you want to check out run it in your program. The next code is to get an instance of the camera. You'll obviously need it in your program.


/** A safe way to get an instance of the Camera object. */
public static Camera getCameraInstance(){
    Camera c = null;
    try {
        c = Camera.open(); // attempt to get a Camera instance
    }
    catch (Exception e){
        // Camera is not available (in use or does not exist)
    }
    return c; // returns null if camera is unavailable
}

Now here is something that you must know. Android handles camera as an OBJECT. So this code creates a camera object and calls the associated function camera.open(). This creates an exception if the camera doesn't exist or the resource isn't available (Some other program utilizing it). Now your layout will typically look like this over eclipse


We need to change this (obviously!!) to display our camera. We'll add a frame to the display to make a viewfinder out of it.


<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >
    
     <FrameLayout
         android:id="@+id/camera_preview"
         android:layout_width="fill_parent"
         android:layout_height="fill_parent"
         android:layout_alignParentLeft="true"
         android:layout_alignParentTop="true"
         />
     
      <Button
             android:id="@+id/button_capture"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:gravity="center|left"
             android:text="capture" />

   
</RelativeLayout>

Now that you have a button that says capture and a frame to display the camera view. It must pretty much look like this.

We'll come back to refining this later. Now let us add the code to the main program. Eclipse has created initial code for me, that runs like this

package com.regnartstranger.cam;

import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;

public class Cam extends Activity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_cam);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.activity_cam, menu);
        return true;
    }
}


Adding up our program to get the camera resource. Now the file is


Observe that we haven't declared the variable camera that is giving us an error. So create an object to hold our camera. We'll call it camera itself(yes, because we have already started using the name).


package com.regnartstranger.cam;

import android.hardware.Camera;
import android.os.Bundle;
import android.app.Activity;
import android.util.Log;
import android.view.Menu;

public class Cam extends Activity {

private String TAG = "Cam";
private Camera camera;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_cam);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.activity_cam, menu);
        return true;
    }
    
    public void getCameraResource(){
    try{
        camera = Camera.open();
        }catch(Exception ioe)
        {
        Log.d(TAG,ioe.getMessage());
        }
    }
}



Notice that I've also used a TAG and updated the log on error(Like a good programmer should). Also note the extra importing of the android.hardware.camera that the eclipse has done for me(Thank you!). Now to create a preview, the developer website list's it to be a class called preview class


/** A basic Camera preview class */
public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {
    private SurfaceHolder mHolder;
    private Camera mCamera;

    public CameraPreview(Context context, Camera camera) {
        super(context);
        mCamera = camera;

        // Install a SurfaceHolder.Callback so we get notified when the
        // underlying surface is created and destroyed.
        mHolder = getHolder();
        mHolder.addCallback(this);
        // deprecated setting, but required on Android versions prior to 3.0
        mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
    }

    public void surfaceCreated(SurfaceHolder holder) {
        // The Surface has been created, now tell the camera where to draw the preview.
        try {
            mCamera.setPreviewDisplay(holder);
            mCamera.startPreview();
        } catch (IOException e) {
            Log.d(TAG, "Error setting camera preview: " + e.getMessage());
        }
    }

    public void surfaceDestroyed(SurfaceHolder holder) {
        // empty. Take care of releasing the Camera preview in your activity.
    }

    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
        // If your preview can change or rotate, take care of those events here.
        // Make sure to stop the preview before resizing or reformatting it.

        if (mHolder.getSurface() == null){
          // preview surface does not exist
          return;
        }

        // stop preview before making changes
        try {
            mCamera.stopPreview();
        } catch (Exception e){
          // ignore: tried to stop a non-existent preview
        }

        // set preview size and make any resize, rotate or
        // reformatting changes here

        // start preview with new settings
        try {
            mCamera.setPreviewDisplay(mHolder);
            mCamera.startPreview();

        } catch (Exception e){
            Log.d(TAG, "Error starting camera preview: " + e.getMessage());
        }
    }
}


To display the picture on the screen we use SurfaceView. SurfaceView is a class in android that can be used to paint a canvas to the screen. To use it we create a new class that inherits SurfaceView and implements its call back. So the functions surfacecreated, surfacedestoryed, surfacechanged implement these call backs. We initialize the class by creating a camera object, a holder for the Surface and adding callback to that holder.
Now using the same code into our app. We get


package com.regnartstranger.cam;

import java.io.IOException;

import android.hardware.Camera;
import android.os.Bundle;
import android.app.Activity;
import android.content.Context;
import android.util.Log;
import android.view.Menu;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

public class Cam extends Activity {

private String TAG = "Cam";
private Camera camera;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_cam);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.activity_cam, menu);
        return true;
    }
    /** A basic Camera preview class */
    public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {
        private SurfaceHolder mHolder;
        private Camera mCamera;

        public CameraPreview(Context context, Camera camera) {
            super(context);
            mCamera = camera;
            mHolder = getHolder();
            mHolder.addCallback(this);
            mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
        }

        public void surfaceCreated(SurfaceHolder holder) {
            try {
                mCamera.setPreviewDisplay(holder);
                mCamera.startPreview();
            } catch (IOException e) {
                Log.d(TAG, "Error setting camera preview: " + e.getMessage());
            }
        }

        public void surfaceDestroyed(SurfaceHolder holder) {
        }

        public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {

            if (mHolder.getSurface() == null){
              return;
            }

            try {
                mCamera.stopPreview();
            } catch (Exception e){
            }


            try {
                mCamera.setPreviewDisplay(mHolder);
                mCamera.startPreview();

            } catch (Exception e){
                Log.d(TAG, "Error starting camera preview: " + e.getMessage());
            }
        }
    }
    
    public void getCameraResource(){
    try{
        camera = Camera.open();
        }catch(Exception ioe)
        {
        Log.d(TAG,ioe.getMessage());
        }
   
    }
}



Now to create a preview in the screen, we need to create an object that handles it. It is called camerapreview. So adding these three lines of code sets up the camera view activity.


 mPreview = new CameraPreview(this, camera);
 FrameLayout preview = (FrameLayout) findViewById(R.id.camera_preview);
 preview.addView(mPreview);

The whole code looks like this now


package com.regnartstranger.cam;

import java.io.IOException;
import android.hardware.Camera;
import android.os.Bundle;
import android.app.Activity;
import android.content.Context;
import android.util.Log;
import android.view.Menu;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.widget.FrameLayout;

public class Cam extends Activity {

private String TAG = "Cam";
private Camera camera;
public CameraPreview mPreview;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_cam);
        getCameraResource();
        mPreview = new CameraPreview(this, camera);
        FrameLayout preview = (FrameLayout) findViewById(R.id.camera_preview);
        preview.addView(mPreview);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.activity_cam, menu);
        return true;
    }
    /** A basic Camera preview class */
    public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {
        private SurfaceHolder mHolder;
        private Camera mCamera;

        public CameraPreview(Context context, Camera camera) {
            super(context);
            mCamera = camera;
            mHolder = getHolder();
            mHolder.addCallback(this);
            mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
        }

        public void surfaceCreated(SurfaceHolder holder) {
            try {
                mCamera.setPreviewDisplay(holder);
                mCamera.startPreview();
            } catch (IOException e) {
                Log.d(TAG, "Error setting camera preview: " + e.getMessage());
            }
        }

        public void surfaceDestroyed(SurfaceHolder holder) {
        }

        public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {

            if (mHolder.getSurface() == null){
              return;
            }

            try {
                mCamera.stopPreview();
            } catch (Exception e){
            }


            try {
                mCamera.setPreviewDisplay(mHolder);
                mCamera.startPreview();

            } catch (Exception e){
                Log.d(TAG, "Error starting camera preview: " + e.getMessage());
            }
        }
    }
    
    public void getCameraResource(){
    try{
        camera = Camera.open();
        }catch(Exception ioe)
        {
        Log.d(TAG,ioe.getMessage());
        }
   
    }
}

At this point if you just add android.permission.CAMERA to the manifest file you'll be able to run the program.


The camera now looks like this. If you want to capture a photograph all you need to do is to implement a callback method to save the photograph to a file in memory. The callback is described in the developer website as

private PictureCallback mPicture = new PictureCallback() {

    @Override
    public void onPictureTaken(byte[] data, Camera camera) {

        File pictureFile = getOutputMediaFile(MEDIA_TYPE_IMAGE);
        if (pictureFile == null){
            Log.d(TAG, "Error creating media file, check storage permissions: " +
                e.getMessage());
            return;
        }

        try {
            FileOutputStream fos = new FileOutputStream(pictureFile);
            fos.write(data);
            fos.close();
        } catch (FileNotFoundException e) {
            Log.d(TAG, "File not found: " + e.getMessage());
        } catch (IOException e) {
            Log.d(TAG, "Error accessing file: " + e.getMessage());
        }
    }
};

Add this to your code.It would finally look like this


package com.regnartstranger.cam;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import android.hardware.Camera;
import android.hardware.Camera.PictureCallback;
import android.os.Bundle;
import android.os.Environment;
import android.app.Activity;
import android.content.Context;
import android.util.Log;
import android.view.Menu;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.widget.Button;
import android.widget.FrameLayout;

public class Cam extends Activity {

private String TAG = "Cam";
private Camera camera;
public CameraPreview mPreview;
public Button captureButton;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_cam);
        getCameraResource();
        captureButton = (Button) findViewById(R.id.button_capture);
        mPreview = new CameraPreview(this, camera);
        FrameLayout preview = (FrameLayout) findViewById(R.id.camera_preview);
        preview.addView(mPreview);
        captureButton.setOnClickListener(
           new View.OnClickListener() {
               public void onClick(View v) {
                   // get an image from the camera
                   camera.takePicture(null, null, mPicture);
               }
           }
        );
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.activity_cam, menu);
        return true;
    }
    private PictureCallback mPicture = new PictureCallback() {

        public void onPictureTaken(byte[] data, Camera camera) {
        File mediaStorageDir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), "MyCameraApp");
        String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
        File mediaFile;
            mediaFile = new File(mediaStorageDir.getPath() + File.separator + "IMG_"+ timeStamp + ".jpg");

            if (mediaFile == null){
                Log.d(TAG, "Error creating media file, check storage permissions\n");
                return;
            }

            try {
                FileOutputStream fos = new FileOutputStream(mediaFile);
                fos.write(data);
                fos.close();
            } catch (FileNotFoundException e) {
                Log.d(TAG, "File not found: " + e.getMessage());
            } catch (IOException e) {
                Log.d(TAG, "Error accessing file: " + e.getMessage());
            }
        }
    };
    /** A basic Camera preview class */
    public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {
        private SurfaceHolder mHolder;
        private Camera mCamera;

        public CameraPreview(Context context, Camera camera) {
            super(context);
            mCamera = camera;
            mHolder = getHolder();
            mHolder.addCallback(this);
            mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
        }

        public void surfaceCreated(SurfaceHolder holder) {
            try {
                mCamera.setPreviewDisplay(holder);
                mCamera.startPreview();
            } catch (IOException e) {
                Log.d(TAG, "Error setting camera preview: " + e.getMessage());
            }
        }

        public void surfaceDestroyed(SurfaceHolder holder) {
        }

        public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {

            if (mHolder.getSurface() == null){
              return;
            }

            try {
                mCamera.stopPreview();
            } catch (Exception e){
            }


            try {
                mCamera.setPreviewDisplay(mHolder);
                mCamera.startPreview();

            } catch (Exception e){
                Log.d(TAG, "Error starting camera preview: " + e.getMessage());
            }
        }
    }
    
    public void getCameraResource(){
    try{
        camera = Camera.open();
        }catch(Exception ioe)
        {
        Log.d(TAG,ioe.getMessage());
        }
   
    }
}


Add the permission to write to sdcard [android.permission.WRITE_EXTERNAL_STORAGE]. Now you are good to go. Run the app once, you must be able to click pictures.

To create a more formal looking app. Add android:theme="@android:style/Theme.NoTitleBar.Fullscreen" under application in the manifest file. And android:screenOrientation="landscape" under activity. It must finally look like this.


<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.regnartstranger.cam"
    android:versionCode="1"
    android:versionName="1.0" >

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

    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@android:style/Theme.NoTitleBar.Fullscreen" >
        <activity
            android:name=".Cam"
            android:label="@string/title_activity_cam"
            android:screenOrientation="landscape" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

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

</manifest>


This gives it a decent camera look. Run the program to get your basic Camera App

Have a nice day!!

No comments:

Post a Comment