package com.droidmapper.view;
import android.content.Context;
import android.graphics.ImageFormat;
import android.hardware.Camera;
import android.location.Location;
import android.util.AttributeSet;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.widget.Toast;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
/**
* A view that invokes the camera, renders its preview in the best possible resolution and provides
* a way for clients to capture JPG encoded pictures.
*/
public class CameraView extends SurfaceView implements SurfaceHolder.Callback {
private static final String TAG = CameraView.class.getName();
private boolean failedToConnectToCameraService;
private SurfaceHolder previewHolder;
private Camera camera;
/**
* Simple constructor to use when creating a CameraView from code.
*
* @param context The Context the view is running in, through which it can
* access the current theme, resources, etc.
*/
public CameraView(Context context) {
super(context);
initialize();
}
/**
* Constructor that is called when inflating a CameraView from XML. This is
* called when a view is being constructed from an XML file, supplying
* attributes that were specified in the XML file. This version uses a
* default style of 0, so the only attribute values applied are those in the
* Context's Theme and the given AttributeSet.
*
* @param context The Context the view is running in, through which it can
* access the current theme, resources, etc.
* @param attrs The attributes of the XML tag that is inflating the view.
*/
public CameraView(Context context, AttributeSet attrs) {
super(context, attrs);
initialize();
}
/**
* Perform inflation from XML and apply a class-specific base style. This
* constructor of CameraView allows subclasses to use their own base style
* when they are inflating.
*
* @param context The Context the view is running in, through which it can
* access the current theme, resources, etc.
* @param attrs the attributes of the XML tag that is inflating the view.
* @param defStyle the default style to apply to this view. If 0, no style will
* be applied (beyond what is included in the theme). This may
* either be an attribute resource, whose value will be retrieved
* from the current theme, or an explicit style resource.
*/
public CameraView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initialize();
}
/**
* This method is called immediately after this view's surface is first created. It turns on the
* camera and sets its preview display.
*
* @param holder The SurfaceHolder whose surface is being created.
*/
public void surfaceCreated(SurfaceHolder holder) {
if (!failedToConnectToCameraService) {
try {
camera.setPreviewDisplay(previewHolder);
camera.startPreview();
} catch (IOException e) {
e.printStackTrace();
Toast.makeText(getContext(), e.getMessage(), Toast.LENGTH_LONG).show();
}
}
}
/**
* This is called immediately after any structural changes (format or size)
* have been made to this view's surface. It sets camera parameters. It is
* always called at least once, after surfaceCreated() method.
*
* @param holder The SurfaceHolder whose surface has changed.
* @param format The new PixelFormat of the surface.
* @param width The new width of the surface.
* @param height The new height of the surface.
*/
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
Log.d(TAG, "surfaceChanged() :: View size is " + width + "x" + height);
// Start rendering the camera preview on the screen.
if (camera != null) {
Camera.Parameters parameters = camera.getParameters();
// Camera supports a fixed set of preview sizes(resolution of the video stream sent to
// the screen to be viewed by the user), figure out the optimal for the local device:
List<Camera.Size> prevSizes = new ArrayList<>(parameters.getSupportedPreviewSizes());
// Print all resolutions:
for (int i = 0; i < prevSizes.size(); i++) {
Camera.Size sz = prevSizes.get(i);
Log.d(TAG, "surfaceChanged() :: Preview size [" + i + "] = " + sz.width + "x" + sz.height);
}
// Remove all portrait sizes:
for (int i = 0; i < prevSizes.size(); i++) {
Camera.Size sz = prevSizes.get(i);
if (sz.width < sz.height) {
Log.d(TAG, "surfaceChanged() :: Removing #1 " + sz.width + "x" + sz.height);
prevSizes.remove(i);
i--;
}
}
// // Remove all sizes which are less than 1/2 of the view width:
// int vwhlf = width / 2;
// for (int i = 0; i < prevSizes.size(); i++) {
// Camera.Size sz = prevSizes.get(i);
// if (sz.width <= vwhlf) {
// Log.d(TAG, "surfaceChanged() :: Removing #2 " + sz.width + "x" + sz.height);
// prevSizes.remove(i);
// i--;
// }
// }
// Sort the sizes according to their width ASC:
Collections.sort(prevSizes, new Comparator<Camera.Size>() {
@Override
public int compare(Camera.Size lhs, Camera.Size rhs) {
if (lhs.width < rhs.width) {
return -1;
} else if (lhs.width > rhs.width) {
return 1;
}
return 0;
}
});
// Print all resolutions after sorting:
for (int i = 0; i < prevSizes.size(); i++) {
Camera.Size sz = prevSizes.get(i);
Log.d(TAG, "surfaceChanged() :: After sorting preview size [" + i + "] = " + sz.width + "x" + sz.height);
}
// // Pick the best resolution:
// Camera.Size previewSize = null;
// float targetRatio = ((float) width) / ((float) height);
// for (Camera.Size size : prevSizes) {
// if (previewSize == null) {
// previewSize = size;
// } else {
// float sizeRatio = Math.abs(((float) size.width) / ((float) size.height));
// if (Math.abs(targetRatio - sizeRatio) <= 0.1f) {
// previewSize = size;
// }
// }
// }
// Pick the smallest resolution:
Camera.Size previewSize = prevSizes.get(0);
Log.d(TAG, "surfaceChanged() :: Selected preview size is " + previewSize.width + "x" + previewSize.height);
// Camera also supports a fixed set of sizes of pictures that can be taken with the
// camera, figure out the maximal:
Camera.Size pictureSize = null;
for (Camera.Size size : parameters.getSupportedPictureSizes()) {
if (pictureSize == null) {
pictureSize = size;
} else {
if (size.width > pictureSize.width) {
pictureSize = size;
}
}
}
if (previewSize != null && pictureSize != null) {
// Pick the best FPS range(mix and max number of preview frames sent to the screen
// each second:
int[] previewBestFpsRange = parameters.getSupportedPreviewFpsRange().get(0);
// Set the parameters:
parameters.setPreviewSize(previewSize.width, previewSize.height);
parameters.setPictureSize(pictureSize.width, pictureSize.height);
parameters.setPreviewFormat(ImageFormat.NV21);
parameters.setPictureFormat(ImageFormat.JPEG);
parameters.setFlashMode(Camera.Parameters.FLASH_MODE_OFF);
parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_INFINITY);
parameters.setAntibanding(Camera.Parameters.ANTIBANDING_OFF);
parameters.setWhiteBalance(Camera.Parameters.WHITE_BALANCE_DAYLIGHT);
parameters.setSceneMode(Camera.Parameters.SCENE_MODE_AUTO);
parameters.setColorEffect(Camera.Parameters.EFFECT_NONE);
parameters.setPreviewFpsRange(previewBestFpsRange[Camera.Parameters.PREVIEW_FPS_MIN_INDEX], previewBestFpsRange[Camera.Parameters.PREVIEW_FPS_MAX_INDEX]);
if (parameters.isZoomSupported()) {
parameters.setZoom(0);
}
camera.setParameters(parameters);
}
camera.startPreview();
}
}
/**
* This is called immediately before this view's surface is being destroyed
* to stop the camera preview and release it.
*
* @param holder The SurfaceHolder whose surface is being destroyed.
*/
public void surfaceDestroyed(SurfaceHolder holder) {
// Stop rendering the camera preview on the screen.
camera.cancelAutoFocus();
camera.stopPreview();
camera.setPreviewCallback(null);
if (camera != null) {
camera.release();
}
camera = null;
}
/**
* Use this method to take a picture from camera. A JPEG encoded picture will delivered in a
* onPictureTaken() callback of the PictureCallback instance supplied as parameter.
*
* @param pictureCallback The callback for JPEG image data.
*/
public void takePicture(Camera.PictureCallback pictureCallback) {
if (pictureCallback == null) {
throw new NullPointerException("Picture callback parameter can't be null.");
}
camera.takePicture(null, null, null, pictureCallback);
}
/**
* Restarts the camera preview after a photo is taken.
*/
public void restartPreview() {
if (!failedToConnectToCameraService) {
camera.startPreview();
}
}
public Camera.Parameters getCameraParams() {
return camera.getParameters();
}
/**
* Sets the location the camera uses to geo tag images.
*
* @param deviceLocation The new location for geo tagging.
*/
public void setGeoTaggingLocation(Location deviceLocation) {
if (deviceLocation != null && camera != null && !failedToConnectToCameraService) {
Camera.Parameters parameters = camera.getParameters();
parameters.setGpsAltitude(deviceLocation.getAltitude());
parameters.setGpsLatitude(deviceLocation.getLatitude());
parameters.setGpsLongitude(deviceLocation.getLongitude());
parameters.setGpsProcessingMethod(deviceLocation.getProvider());
parameters.setGpsTimestamp(deviceLocation.getTime());
camera.setParameters(parameters);
}
}
/**
* Helper method used by class constructors to initialize the preview holder and open the camera.
*/
private void initialize() {
if (!isInEditMode()) {
// Initialize preview holder:
previewHolder = getHolder();
previewHolder.addCallback(this);
// Open the camera:
try {
failedToConnectToCameraService = false;
camera = Camera.open(Camera.CameraInfo.CAMERA_FACING_BACK);
// camera.setDisplayOrientation(90);
} catch (RuntimeException e) {
e.printStackTrace();
failedToConnectToCameraService = true;
Toast.makeText(getContext(), e.getMessage(), Toast.LENGTH_LONG).show();
}
}
}
}