Custom Camera

Using straight Android we can build a custom camera Activity.

Here are the basics:

package com.mobvcasting.snapshot;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Iterator;
import java.util.List;

import android.app.Activity;
import android.content.ContentValues;
import android.content.res.Configuration;
import android.hardware.Camera;
import android.net.Uri;
import android.os.Bundle;
import android.provider.MediaStore.Images.Media;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Toast;

public class SnapShot extends Activity implements OnClickListener,
		SurfaceHolder.Callback, Camera.PictureCallback {

	SurfaceView cameraView;
	SurfaceHolder surfaceHolder;
	Camera camera;

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);

		setContentView(R.layout.main);

		cameraView = (SurfaceView) this.findViewById(R.id.CameraView);
		surfaceHolder = cameraView.getHolder();
		surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
		surfaceHolder.addCallback(this);

		cameraView.setFocusable(true);
		cameraView.setFocusableInTouchMode(true);
		cameraView.setClickable(true);

		cameraView.setOnClickListener(this);
	}

	public void onClick(View v) {
		camera.takePicture(null, null, this);
	}

	public void onPictureTaken(byte[] data, Camera camera) {
// data is a byte array already in JPEG form
		Uri imageFileUri = getContentResolver().insert(
				Media.EXTERNAL_CONTENT_URI, new ContentValues());
		try {
			OutputStream imageFileOS = getContentResolver().openOutputStream(
					imageFileUri);
			imageFileOS.write(data);
			imageFileOS.flush();
			imageFileOS.close();
		} catch (FileNotFoundException e) {
			Toast t = Toast.makeText(this, e.getMessage(), Toast.LENGTH_SHORT);
			t.show();
		} catch (IOException e) {
			Toast t = Toast.makeText(this, e.getMessage(), Toast.LENGTH_SHORT);
			t.show();
		}
		camera.startPreview();
	}

	public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
		camera.startPreview();
	}

	public void surfaceCreated(SurfaceHolder holder) {
		camera = Camera.open();
		try {
			camera.setPreviewDisplay(holder);
			Camera.Parameters parameters = camera.getParameters();
			if (this.getResources().getConfiguration().orientation != Configuration.ORIENTATION_LANDSCAPE) {
				parameters.set("orientation", "portrait");

				// For Android Version 2.2 and above
				camera.setDisplayOrientation(90);

				// For Android Version 2.0 and above
				parameters.setRotation(90);
			}

			

			camera.setParameters(parameters);
		} catch (IOException exception) {
			camera.release();
		}
	}

	public void surfaceDestroyed(SurfaceHolder holder) {
		camera.stopPreview();
		camera.release();
	}
} // End the Activity

Here is the corresponding main.xml file from res/layout:

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

Don't forget to add CAMERA permissions into your AndroidManifest.xml file.

Displaying Images

To display images in straight Android we have to go about things in a slightly different way than we did with Processing.

In this case, we have to use a Bitmap object which can be displayed on an ImageView.

Here is an example which allows the user to pick an image from the gallery, resizes the image to the screen resolution and then displays it in an ImageView.


package com.mobvcasting.simpledisplayimage;

import android.app.Activity;
import android.os.Bundle;
import java.io.FileNotFoundException;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.util.Log;
import android.view.Display;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ImageView;

public class SimpleDisplayImageActivity extends Activity implements OnClickListener {

	ImageView chosenImageView;
	Button choosePicture;

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

		chosenImageView = (ImageView) this.findViewById(R.id.ChosenImageView);
		choosePicture = (Button) this.findViewById(R.id.ChoosePictureButton);

		choosePicture.setOnClickListener(this);
	}

	public void onClick(View v) {
		Intent choosePictureIntent = new Intent(Intent.ACTION_PICK,
				android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
		startActivityForResult(choosePictureIntent, 0);
	}

	protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
		super.onActivityResult(requestCode, resultCode, intent);

		if (resultCode == RESULT_OK) {
			Uri imageFileUri = intent.getData();

			Display currentDisplay = getWindowManager().getDefaultDisplay();
			int dw = currentDisplay.getWidth();
			int dh = currentDisplay.getHeight();

			try {
				// Load up the image's dimensions not the image itself
				BitmapFactory.Options bmpFactoryOptions = new BitmapFactory.Options();
				bmpFactoryOptions.inJustDecodeBounds = true;
				Bitmap bmp = BitmapFactory
						.decodeStream(getContentResolver().openInputStream(
								imageFileUri), null, bmpFactoryOptions);

				int heightRatio = (int) Math.ceil(bmpFactoryOptions.outHeight
						/ (float) dh);
				int widthRatio = (int) Math.ceil(bmpFactoryOptions.outWidth
						/ (float) dw);

				if (heightRatio > 1 && widthRatio > 1) {
					if (heightRatio > widthRatio) {
						bmpFactoryOptions.inSampleSize = heightRatio;
					} else {
						bmpFactoryOptions.inSampleSize = widthRatio;
					}
				}

				bmpFactoryOptions.inJustDecodeBounds = false;
				bmp = BitmapFactory
						.decodeStream(getContentResolver().openInputStream(
								imageFileUri), null, bmpFactoryOptions);

				chosenImageView.setImageBitmap(bmp);

			} catch (FileNotFoundException e) {
				Log.v("ERROR", e.toString());
			}
		}
	}
}

It requires the following main.xml file to be in the res/layout folder:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
<Button
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:text="Choose Picture" android:id="@+id/ChoosePictureButton"/>
<ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/ChosenImageView">

Of course, very similar code to this could be written to display an image from the built-in camera application which is triggered through an Intent.

Rebuilding SoundPlayer Example in Straight Android

A lot of the code that we used for various media related programs with Processing for Android are able to be used with very little modification in regular Android.

For instance, in our Audio Playback using MediaPlayer example we have the following class:

class SoundPlayer extends Object  implements android.media.MediaPlayer.OnCompletionListener {

  android.media.MediaPlayer mediaPlayer;
  android.content.res.AssetFileDescriptor assetFileDescriptor;
  processing.core.PApplet theSketch;

  boolean ready = false;

  public SoundPlayer(PApplet _theSketch, String assetAudioFile) {
    theSketch = _theSketch;

    try {
      assetFileDescriptor = theSketch.getAssets().openFd(assetAudioFile);

      mediaPlayer = new android.media.MediaPlayer();
      mediaPlayer.setDataSource(assetFileDescriptor.getFileDescriptor(),assetFileDescriptor.getStartOffset(), assetFileDescriptor.getLength());
      mediaPlayer.setOnCompletionListener(this);
      mediaPlayer.prepare();
      ready = true;
    } catch (Exception e) {
      theSketch.println(e.toString());
    }
  }

  public SoundPlayer(PApplet _theSketch, java.io.File _audioFile) {
    this(_theSketch, android.net.Uri.fromFile(_audioFile));
  }

  public SoundPlayer(PApplet _theSketch, android.net.Uri _audioFileUri) {
    theSketch = _theSketch;
    theSketch.println("The Uri: " + _audioFileUri.toString());
    try {
      mediaPlayer = android.media.MediaPlayer.create(_theSketch, _audioFileUri);
      mediaPlayer.setOnCompletionListener(this);
      ready = true;
    } catch (Exception e) {
      theSketch.println(e.toString());
      e.printStackTrace();

    }
  }

  public void play() {
    if (ready) {
      try {
        theSketch.println("playSound");
        mediaPlayer.start();
        ready = false;
      } catch (Exception e) {
        theSketch.println(e.toString());
        e.printStackTrace();
      }
    }
  }

  public void onCompletion(android.media.MediaPlayer mp) {
    try {
      theSketch.println("Trying to prepare");
      mediaPlayer.stop();
      mediaPlayer.prepare();
      ready = true;
    }
    catch (Exception e) {
      theSketch.println(e.toString());
      e.printStackTrace();
    }
  }
}

Using this in Eclipse requires us to create a new Java Class with the above contents. We can even make it a bit nicer by using import statements. Also, we’ll have to remove references to the Processing constructs such as the PApplet (theSketch object). In the below code, the Processing println methods have been changed to Log.v

package com.mobvcasting.simpleaudioplayer;

import android.media.MediaPlayer;
import android.util.Log;
import android.app.Activity;
import android.content.res.AssetFileDescriptor;

class SoundPlayer extends Object  implements android.media.MediaPlayer.OnCompletionListener {

	  public static final String LOGTAG = "SoundPlayer";
	
	  MediaPlayer mediaPlayer;
	  AssetFileDescriptor assetFileDescriptor;
	  Activity theActivity;

	  boolean ready = false;

	  public SoundPlayer(Activity _theActivity, String assetAudioFile) {
	    theActivity = _theActivity;

	    try {
	      assetFileDescriptor = theActivity.getAssets().openFd(assetAudioFile);

	      mediaPlayer = new android.media.MediaPlayer();
	      mediaPlayer.setDataSource(assetFileDescriptor.getFileDescriptor(),assetFileDescriptor.getStartOffset(), assetFileDescriptor.getLength());
	      mediaPlayer.setOnCompletionListener(this);
	      mediaPlayer.prepare();
	      ready = true;
	    } catch (Exception e) {
	      Log.v(LOGTAG,"e.toString()");
	    }
	  }

	  public SoundPlayer(Activity _theActivity, java.io.File _audioFile) {
	    this(_theActivity, android.net.Uri.fromFile(_audioFile));
	  }

	  public SoundPlayer(Activity _theActivity, android.net.Uri _audioFileUri) {
	    theActivity = _theActivity;
	    Log.v(LOGTAG,"The Uri: " + _audioFileUri.toString());
	    try {
	      mediaPlayer = android.media.MediaPlayer.create(_theActivity, _audioFileUri);
	      mediaPlayer.setOnCompletionListener(this);
	      ready = true;
	    } catch (Exception e) {
	      Log.v(LOGTAG,e.toString());
	      e.printStackTrace();

	    }
	  }

	  public void play() {
	    if (ready) {
	      try {
	        Log.v(LOGTAG,"playSound");
	        mediaPlayer.start();
	        ready = false;
	      } catch (Exception e) {
	        Log.v(LOGTAG,e.toString());
	        e.printStackTrace();
	      }
	    }
	  }

	  public void onCompletion(android.media.MediaPlayer mp) {
	    try {
	      Log.v(LOGTAG,"Trying to prepare");
	      mediaPlayer.stop();
	      mediaPlayer.prepare();
	      ready = true;
	    }
	    catch (Exception e) {
	      Log.v(LOGTAG,e.toString());
	      e.printStackTrace();
	    }
	  }
	}

Here is a full example

Launching Built-In Apps with Intents

Just like in Processing for Android, we can launch built-in applications using an Intent with straight Android.

Intents are extremely useful in leveraging the built-in capabilities on Android. Using an Intent, we can allow the user to send an SMS message, compose an email, launch a browser window, select a picture, take a picture and more.

Here is a code snippet for sending an SMS message via an Intent:

Intent smsIntent = new Intent(Intent.ACTION_VIEW);
smsIntent.setType("vnd.android-dir/mms-sms");
smsIntent.putExtra("address", "12125551212");
smsIntent.putExtra("sms_body","Body of Message");
startActivity(smsIntent);

For launching a web page in the browser:

Intent intent = new Intent(Intent.ACTION_VIEW); 
Uri uri = Uri.parse("http://www.mobvcasting.com/"); 
intent.setData(uri); 
startActivity(intent);

For taking a picture:

Intent i = new Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE);
startActivityForResult(i, CAMERA_RESULT);

In this case, we called it with startActivityForResult so that we can get access to the picture once it is captured.

Here is the full example:

package com.apress.proandroidmedia.ch1.cameraintent;

import android.app.Activity;
import android.content.Intent;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.widget.ImageView;

public class CameraIntent extends Activity {

     final static int CAMERA_RESULT = 0;

     ImageView imv;

     @Override
     public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);
	
		Intent i = new Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE);
		startActivityForResult(i, CAMERA_RESULT);
	}

	protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
		 super.onActivityResult(requestCode, resultCode, intent);
	
		if (resultCode == RESULT_OK) {
			Bundle extras = intent.getExtras();
			Bitmap bmp = (Bitmap) extras.get("data");
			imv = (ImageView) findViewById(R.id.ReturnedImageView);
			imv.setImageBitmap(bmp);
		}
	}
}

A whole slew of applications allow specific actions to be triggered via an intent. http://www.openintents.org offers a good list.