Skip to content

Instantly share code, notes, and snippets.

@tolmachevroman
Last active September 23, 2019 02:33
Show Gist options
  • Save tolmachevroman/c267024f49109955d135 to your computer and use it in GitHub Desktop.
Save tolmachevroman/c267024f49109955d135 to your computer and use it in GitHub Desktop.
Camera Preview to render camera capture in real time + take photo + resize it + upload it to the server
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="@color/orders_grey_background">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:elevation="16dp">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar_actionbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:layout_scrollFlags="scroll|enterAlways"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />
</android.support.design.widget.AppBarLayout>
<!-- Camera -->
<FrameLayout
android:id="@+id/camera_preview"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_marginBottom="64dp"
android:layout_marginTop="@dimen/abc_action_bar_default_height_material" />
<!-- Receipt photo -->
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="top|center_horizontal"
android:layout_marginTop="80dp"
android:background="@drawable/receipt_photo_background"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="@string/receipt_photo"
android:textColor="@color/c_white"
android:textSize="15sp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="@string/refine_focus"
android:textColor="@color/c_white"
android:textSize="14sp" />
</LinearLayout>
<!-- Take photo button -->
<FrameLayout
android:id="@+id/take_photo_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/take_photo_background"
android:layout_gravity="bottom|center_horizontal"
android:layout_marginBottom="90dp">
<ImageView
android:layout_width="40dp"
android:layout_height="40dp"
android:src="@drawable/ic_camera_alt_black_48dp"
android:layout_gravity="center"/>
</FrameLayout>
<!-- Retake photo -->
<TextView
android:id="@+id/retake_photo_button"
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_gravity="bottom|center_horizontal"
android:layout_marginBottom="64dp"
android:background="@drawable/retake_photo_background"
android:gravity="center"
android:text="@string/retake_photo"
android:textAllCaps="true"
android:textColor="@color/c_white"
android:textSize="16sp"
android:textStyle="bold"
android:visibility="gone"/>
<!-- Step 1 of 4 Continue -->
<RelativeLayout
android:id="@+id/step_one_of_four_layout"
android:layout_width="match_parent"
android:layout_height="64dp"
android:layout_gravity="bottom"
android:background="@color/footer_white_background"
android:orientation="vertical">
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_alignParentTop="true"
android:background="@color/orders_grey_line" />
<ImageView
android:id="@+id/next_arrow"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:scaleType="fitXY"
android:src="@drawable/ic_keyboard_arrow_right_white_48dp"
android:tint="@color/accepted_right_arrow" />
<TextView
android:id="@+id/next_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toLeftOf="@+id/next_arrow"
android:fontFamily="sans-serif"
android:text="@string/next"
android:textAllCaps="true"
android:textColor="@color/accepted_right_arrow"
android:textSize="16sp"
android:textStyle="bold" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toLeftOf="@+id/next_text"
android:fontFamily="sans-serif"
android:paddingLeft="@dimen/general_view_margin"
android:text="@string/step_1_of_4"
android:textColor="@color/step_x_of_y"
android:textSize="18sp"
android:textStyle="normal" />
</RelativeLayout>
</android.support.design.widget.CoordinatorLayout>
import android.content.Context;
import android.hardware.Camera;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import java.io.IOException;
import java.util.List;
public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {
private final String TAG = "CameraPreview";
private SurfaceHolder holder;
private Camera camera;
private List<Camera.Size> supportedPreviewSizes;
private Camera.Size previewSize;
public CameraPreview(Context context, Camera camera) {
super(context);
this.camera = camera;
// supported preview sizes
supportedPreviewSizes = camera.getParameters().getSupportedPreviewSizes();
// Install a SurfaceHolder.Callback so we get notified when the
// underlying surface is created and destroyed.
holder = getHolder();
holder.addCallback(this);
// deprecated setting, but required on Android versions prior to 3.0
holder.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 {
Camera.Parameters params = camera.getParameters();
if (params.getSupportedFocusModes().contains(
Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO)) {
params.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
}
camera.setParameters(params);
camera.setPreviewDisplay(holder);
camera.stopPreview();
camera.setDisplayOrientation(90);
camera.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 (this.holder.getSurface() == null){
// preview surface does not exist
return;
}
// stop preview before making changes
try {
camera.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 {
Camera.Parameters parameters = camera.getParameters();
parameters.setPreviewSize(previewSize.width, previewSize.height);
camera.setParameters(parameters);
camera.setDisplayOrientation(90);
camera.setPreviewDisplay(holder);
camera.startPreview();
} catch (Exception e){
Log.d(TAG, "Error starting camera preview: " + e.getMessage());
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final int width = resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec);
final int height = resolveSize(getSuggestedMinimumHeight(), heightMeasureSpec);
if (supportedPreviewSizes != null) {
previewSize = getOptimalPreviewSize(supportedPreviewSizes, width, height);
}
float ratio;
if(previewSize.height >= previewSize.width)
ratio = (float) previewSize.height / (float) previewSize.width;
else
ratio = (float) previewSize.width / (float) previewSize.height;
setMeasuredDimension(width, (int) (width * ratio));
}
private Camera.Size getOptimalPreviewSize(List<Camera.Size> sizes, int w, int h) {
final double ASPECT_TOLERANCE = 0.1;
double targetRatio = (double) h / w;
if (sizes == null)
return null;
Camera.Size optimalSize = null;
double minDiff = Double.MAX_VALUE;
int targetHeight = h;
for (Camera.Size size : sizes) {
double ratio = (double) size.height / size.width;
if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE)
continue;
if (Math.abs(size.height - targetHeight) < minDiff) {
optimalSize = size;
minDiff = Math.abs(size.height - targetHeight);
}
}
if (optimalSize == null) {
minDiff = Double.MAX_VALUE;
for (Camera.Size size : sizes) {
if (Math.abs(size.height - targetHeight) < minDiff) {
optimalSize = size;
minDiff = Math.abs(size.height - targetHeight);
}
}
}
return optimalSize;
}
}
package com.cornershopapp.shopper.android.ui.main;
import android.app.LoaderManager;
import android.content.ContentUris;
import android.content.CursorLoader;
import android.content.Intent;
import android.content.Loader;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.hardware.Camera;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.view.MenuItem;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
import com.cornershopapp.shopper.android.R;
import com.cornershopapp.shopper.android.entity.responses.SimpleResponse;
import com.cornershopapp.shopper.android.enums.BarcodeTypes;
import com.cornershopapp.shopper.android.enums.PendingRequestTypes;
import com.cornershopapp.shopper.android.network.RestApi;
import com.cornershopapp.shopper.android.services.InsertPendingRequestService;
import com.cornershopapp.shopper.android.sql.Order;
import com.cornershopapp.shopper.android.ui.BaseActivity;
import com.cornershopapp.shopper.android.utils.CameraPreview;
import com.cornershopapp.shopper.android.utils.Utils;
import org.parceler.Parcels;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import butterknife.ButterKnife;
import butterknife.InjectView;
import retrofit.Callback;
import retrofit.RetrofitError;
import retrofit.client.Response;
/**
* Created by romantolmachev on 11/11/15.
*/
public class TakePhotoActivity extends BaseActivity implements View.OnClickListener,
LoaderManager.LoaderCallbacks<Cursor> {
@InjectView(R.id.step_one_of_four_layout)
RelativeLayout stepOneOfFourLayout;
@InjectView(R.id.camera_preview)
FrameLayout cameraPreviewLayout;
@InjectView(R.id.take_photo_button)
FrameLayout takePhotoButton;
@InjectView(R.id.retake_photo_button)
TextView retakePhotoButton;
Camera camera;
CameraPreview cameraPreview;
File photoFile;
boolean photoTaken;
Camera.PictureCallback picture = new Camera.PictureCallback() {
@Override
public void onPictureTaken(final byte[] originalImageData, Camera camera) {
photoTaken = true;
new Thread(new Runnable() {
@Override
public void run() {
byte[] resizedImageData = resizeImage(originalImageData);
photoFile = Utils.createPublicImageFile();
if (photoFile == null) {
System.out.println("Error creating media file, check storage permissions: ");
return;
}
try {
FileOutputStream fos = new FileOutputStream(photoFile);
fos.write(resizedImageData);
fos.close();
//TODO switch to Retrofit 2.0 and cancel uploading previous screenshot
uploadPhotoRequest();
} catch (FileNotFoundException e) {
System.out.println("File not found: " + e.getMessage());
} catch (IOException e) {
System.out.println("Error accessing file: " + e.getMessage());
}
}
}).start();
}
};
final int PHOTO_WIDTH = 1200;
final int PHOTO_HEIGHT = 1200;
BarcodeTypes barcodeType;
float total;
public static final int CHECKOUT = 102;
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == RESULT_OK && requestCode == CHECKOUT) {
setResult(RESULT_OK);
finish();
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_take_photo);
setUpToolbarAndTitleAndHome(getString(R.string.finish_picking), R.menu.menu_accept_order);
ButterKnife.inject(this);
stepOneOfFourLayout.setOnClickListener(this);
takePhotoButton.setOnClickListener(this);
retakePhotoButton.setOnClickListener(this);
getLoaderManager().initLoader(1, null, this);
}
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.step_one_of_four_layout:
if(photoTaken) {
Bundle args = new Bundle();
args.putParcelable(getString(R.string.key_barcode), Parcels.wrap(barcodeType));
args.putFloat(getString(R.string.key_order_total), total);
startActivityForResult(new Intent(this, ScanReceiptBarcodeActivity.class).putExtras(args), CHECKOUT);
}
break;
case R.id.take_photo_button:
camera.takePicture(null, null, picture);
takePhotoButton.setVisibility(View.GONE);
retakePhotoButton.setVisibility(View.VISIBLE);
break;
case R.id.retake_photo_button:
retakePhotoButton.setVisibility(View.GONE);
takePhotoButton.setVisibility(View.VISIBLE);
camera.startPreview();
break;
}
}
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_call:
showWhomToCallDialog();
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
protected void onResume() {
super.onResume();
camera = getCameraInstance();
if(camera != null) {
setupCamera();
} else {
/**
* Delay is necessary for cases when you go back from the ScanReceiptBarcodeActivity
* and need to wait a bit before Camera is released
*/
cameraPreviewLayout.postDelayed(new Runnable() {
@Override
public void run() {
camera = getCameraInstance();
if(camera != null) {
setupCamera();
}
}
}, 300);
}
}
@Override
protected void onPause() {
super.onPause();
if (camera != null) {
camera.setPreviewCallback(null);
cameraPreview.getHolder().removeCallback(cameraPreview);
camera.release();
camera = null;
}
}
/**
* 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
}
byte[] resizeImage(byte[] input) {
Bitmap original = BitmapFactory.decodeByteArray(input, 0, input.length);
Bitmap resized = Bitmap.createScaledBitmap(original, PHOTO_WIDTH, PHOTO_HEIGHT, true);
ByteArrayOutputStream blob = new ByteArrayOutputStream();
resized.compress(Bitmap.CompressFormat.JPEG, 100, blob);
return blob.toByteArray();
}
void setupCamera() {
cameraPreview = new CameraPreview(TakePhotoActivity.this, camera);
cameraPreviewLayout.removeAllViews();
cameraPreviewLayout.addView(cameraPreview);
retakePhotoButton.setVisibility(View.GONE);
takePhotoButton.setVisibility(View.VISIBLE);
}
@Override
public Loader<Cursor> onCreateLoader(int i, Bundle bundle) {
String[] projection = {Order._ID, Order.TOTAL, Order.SCAN_TYPE};
return new CursorLoader(this, ContentUris.withAppendedId(Order.CONTENT_URI, orderId), projection, null, null, null);
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
if (cursor != null && cursor.moveToFirst()) {
//if scan receipt is enabled
if (cursor.getString(cursor.getColumnIndex(Order.SCAN_TYPE)) != null) {
barcodeType = BarcodeTypes.valueOf(cursor.getString(cursor.getColumnIndex(Order.SCAN_TYPE)));
total = cursor.getFloat(cursor.getColumnIndex(Order.TOTAL));
}
}
}
@Override
public void onLoaderReset(Loader<Cursor> loader) {
}
/**
* ================================= API REQUESTS ==============================================
*/
private void uploadPhotoRequest() {
RestApi.uploadReceipt(orderId, photoFile, new Callback<SimpleResponse>() {
@Override
public void success(SimpleResponse simpleResponse, Response response) {
}
@Override
public void failure(RetrofitError error) {
handleErrors(error);
}
});
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment