Control of the camera is abstracted into various components in the Android system: n Camera class—Accesses the camera hardware
n Camera.Parameters class—Specifies the camera parameters such as picture size,
picture quality, flash modes, and method to assign Global Positioning System (GPS) location
n Camera Preview methods—Sets the camera output display and toggles streaming video preview to the display
n SurfaceView class—Dedicates a drawing surface at the lowest level of the view
hierarchy as a placeholder to display the camera preview
Before describing how these are tied together, the layout structure is introduced.The main layout is shown in Listing 7.1 and includes a SurfaceView to hold the camera output.
Listing 7.1 res/layout/main.xml <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical"> <SurfaceView android:id="@+id/surface" android:layout_width="fill_parent" android:layout_height="fill_parent"> </SurfaceView> </LinearLayout>
A control interface can be added on top of this view by using a separate layout, as shown in Listing 7.2.This layout contains a button at the bottom, center of the screen to take a picture. Listing 7.2 res/layout/cameraoverlay.xml <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" android:gravity="bottom" android:layout_gravity="bottom"> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent"
ptg android:layout_height="wrap_content" android:orientation="horizontal" android:gravity="center_horizontal"> <Button android:id="@+id/button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="take picture" /> </LinearLayout> </LinearLayout>
The main activity involves multiple functionalities. First, the layout is set up as follows: 1. The window settings are changed to be translucent and full screen. (In this instance,
they hide the title and notification bar.)
2. The SurfaceView defined in the previous layout (R.id.surface) is then filled by
the camera preview. Each SurfaceView contains a SurfaceHolder for access and
control over the surface.The activity is added as the SurfaceHolder’s callback and
the type is set to SURFACE_TYPE_PUSH_BUFFERS, which means it creates a “push”
surface and the object does not own the buffer.This makes video streaming more efficient.
3. A LayoutInflater is declared to inflate another layout (cameraoverlay.xml) over
the original (main.xml) layout.
Next, the activity sets a trigger for taking a picture:
1. An OnClickListener is added on the button from the cameraoverlay layout, so
when clicked, it takes a picture (mCamera.takePicture()).
2. The takePicture() method needs three methods to be defined:.
n ShutterCallback() to define any effects needed after the picture is taken,
such as a sound to let the user know that picture has been captured.
n A PictureCallback() for raw picture data if hardware has enough memory
to support this feature. (Otherwise. the data might return as null.)
n A second PictureCallback() for the compressed picture data.This calls the
local method done() to save the picture.
Then, the activity saves any pictures that were taken:
1. The compressed picture byte array is saved to a local variable tempdata for manipu-
ptg
2. The media content provider is used to save the bitmap and return a URL. If this main activity were called by another activity, this URL would be the return infor- mation to the caller activity to retrieve the image.
3. After this process, finish() is called to kill the activity.
Finally, the activity sets up a response to a change in the surface view:
1. A SurfaceHolder.CallBack interface is implemented.This requires three methods
to be overridden:
n surfaceCreated()—Called when the surface is first created. Initialize
objects here.
n surfaceChanged()—Called after surface creation and when the surface
changes (for example, format or size).
n surfaceDestroyed()—Called between removing the surface from the view
of the user and destroying the surface.This is used for memory cleanup. 2. The parameters for the camera are changed when the surface is changed (such as
the PreviewSize based on the surface size).
These functionalities are in the complete activity shown in Listing 7.3.
Listing 7.3 src/com/cookbook/hardware/CameraApplication.java package com.cookbook.hardware; import android.app.Activity; import android.content.Intent; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.PixelFormat; import android.hardware.Camera; import android.hardware.Camera.PictureCallback; import android.hardware.Camera.ShutterCallback; import android.os.Bundle; import android.provider.MediaStore.Images; import android.util.Log; import android.view.LayoutInflater; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.View; import android.view.Window; import android.view.WindowManager; import android.view.View.OnClickListener; import android.view.ViewGroup.LayoutParams; import android.widget.Button; import android.widget.Toast;
ptg
public class CameraApplication extends Activity
implements SurfaceHolder.Callback { private static final String TAG = "cookbook.hardware"; private LayoutInflater mInflater = null;
Camera mCamera;
byte[] tempdata;
boolean mPreviewRunning = false;
private SurfaceHolder mSurfaceHolder; private SurfaceView mSurfaceView;
Button takepicture;
@Override
public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); getWindow().setFormat(PixelFormat.TRANSLUCENT); requestWindowFeature(Window.FEATURE_NO_TITLE); getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); setContentView(R.layout.main); mSurfaceView = (SurfaceView)findViewById(R.id.surface); mSurfaceHolder = mSurfaceView.getHolder(); mSurfaceHolder.addCallback(this); mSurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); mInflater = LayoutInflater.from(this);
View overView = mInflater.inflate(R.layout.cameraoverlay, null); this.addContentView(overView,
new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT)); takepicture = (Button) findViewById(R.id.button); takepicture.setOnClickListener(new OnClickListener(){
public void onClick(View view){
mCamera.takePicture(mShutterCallback, mPictureCallback, mjpeg); }
}); }
ShutterCallback mShutterCallback = new ShutterCallback(){ @Override
public void onShutter() {} };
ptg public void onPictureTaken(byte[] data, Camera c) {}
};
PictureCallback mjpeg = new PictureCallback() {
public void onPictureTaken(byte[] data, Camera c) { if(data !=null) { tempdata=data; done(); } } }; void done() { Bitmap bm = BitmapFactory.decodeByteArray(tempdata, 0, tempdata.length); String url = Images.Media.insertImage(getContentResolver(),
bm, null, null); bm.recycle();
Bundle bundle = new Bundle(); if(url!=null) {
bundle.putString("url", url);
Intent mIntent = new Intent(); mIntent.putExtras(bundle); setResult(RESULT_OK, mIntent); } else {
Toast.makeText(this, "Picture can not be saved", Toast.LENGTH_SHORT).show(); }
finish(); }
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) { Log.e(TAG, "surfaceChanged"); try { if (mPreviewRunning) { mCamera.stopPreview(); mPreviewRunning = false; } Camera.Parameters p = mCamera.getParameters(); p.setPreviewSize(w, h); mCamera.setParameters(p); mCamera.setPreviewDisplay(holder); mCamera.startPreview();
ptg mPreviewRunning = true; } catch(Exception e) { Log.d("",e.toString()); } } @Override
public void surfaceCreated(SurfaceHolder holder) { Log.e(TAG, "surfaceCreated");
mCamera = Camera.open();
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) { Log.e(TAG, "surfaceDestroyed"); mCamera.stopPreview(); mPreviewRunning = false; mCamera.release(); mCamera=null; } }
Note the camera preview from the camera hardware is not standardized, and some Android devices might show the preview sideways. In this case, simply add the following to the onCreate() method of the CameraPreview activity:
this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);