// ABBYY ® Mobile Imaging SDK II © 2018 ABBYY Production LLC.
// ABBYY is either a registered trademark or a trademark of ABBYY Software Ltd.

package com.abbyy.mobile.sample;

import android.Manifest;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.graphics.ImageFormat;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.SurfaceTexture;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraManager;
import android.hardware.camera2.CameraMetadata;
import android.hardware.camera2.CaptureFailure;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.TotalCaptureResult;
import android.hardware.camera2.params.MeteringRectangle;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.media.Image;
import android.media.ImageReader;
import android.media.MediaActionSound;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.util.Log;
import android.util.Size;
import android.view.MotionEvent;
import android.view.Surface;
import android.view.TextureView;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.RelativeLayout;
import android.widget.Spinner;
import android.widget.TextView;

import com.abbyy.mobile.rtr.Engine;
import com.abbyy.mobile.rtr.IImageCaptureService;
import com.abbyy.mobile.rtr.IImagingCoreAPI;

import java.util.Arrays;

// Camera2

public class MainActivity extends Activity {

	// Licensing
	private static final String licenseFileName = "license";

	///////////////////////////////////////////////////////////////////////////////
	// The default behavior in this sample is to start capture when application is started or
	// resumed. You can turn off this behavior or remove it completely to simplify the application
	private static final boolean startCaptureOnAppStart = true;
	// The default behavior is to crop the captured image. You might want to show the captured image
	// with the document boundary instead
	private static final boolean cropCapturedImage = true;

	// A set of available sample document sizes
	private enum SampleDocumentSize {
		// Unknown size but require boundaries
		DocumentWithBoundaries( "Unknown size / Require boundaries", Float.MAX_VALUE, Float.MAX_VALUE ),
		// A4 paper size for office documents (ISO)
		A4( "210×297 mm (ISO A4)", 210f, 297f ),
		// Letter paper size for office documents (US Letter)
		Letter( "215.9×279.4 mm (US Letter)", 215.9f, 279.4f ),
		// International Business Card
		BusinessCard( "53.98×85.6 mm (International)", 53.98f, 85.6f ),
		// Unknown size / Unknown boundaries
		Auto( "Unknown size / Unknown boundaries", 0, 0 );

		SampleDocumentSize( String usage, float width, float height )
		{
			Usage = usage;
			Width = width;
			Height = height;
		}

		public String Usage;
		public float Width;
		public float Height;
	}

	// By default the document size is unknown
	private SampleDocumentSize documentSize = SampleDocumentSize.DocumentWithBoundaries;

	///////////////////////////////////////////////////////////////////////////////

	// Camera permission request code for Android 6.0 and higher
	private static final int CAMERA_PERMISSION_REQUEST_CODE = 42;

	// The 'ABBYY MI SDK Engine' and 'Image Capture Service' to be used in this sample application
	private Engine engine;
	private IImageCaptureService imageCaptureService;

	// The camera and the preview
	private CameraManager cameraManager;
	private CameraDevice cameraDevice;
	private CameraCaptureSession cameraCaptureSession;
	private CameraCharacteristics cameraCharacteristics;
	private CaptureRequest.Builder builder;
	private String cameraId;
	private Size previewSize;
	private TextureView imageView;
	private ImageReader imageReader;
	private OnImageAvailableListener imageAvailableListener = new OnImageAvailableListener();
	private OnTouchListener onTouchListener = new OnTouchListener();
	private Size imageSize;
	private int orientation;
	private boolean isFocusing = false;

	// Auxiliary variables
	private boolean startCaptureWhenReady; // Start capture next time when ready (and reset this flag)
	private Handler handler = new Handler(); // Posting some delayed actions;
	private boolean imageCaptured = false;

	// UI components
	private Button startButton; // The start button
	private DrawView drawView; // View for painting results
	private TextView warningTextView; // Show warnings from the service
	private TextView errorTextView; // Show errors from the service
	private Spinner documentSizeSpinner; // Document size selection
	private Dialog errorDialog;

	// Text displayed on start button
	private static final String BUTTON_TEXT_START = "Start";
	private static final String BUTTON_TEXT_STOP = "Stop";
	private static final String BUTTON_TEXT_STARTING = "Starting...";

	// To communicate with the Image Capture Service we will need this callback:
	private IImageCaptureService.Callback imageCaptureCallback = new IImageCaptureService.Callback() {

		@Override
		public void onRequestLatestFrame( byte[] buffer )
		{
			// Nothing to do for camera2
		}

		@Override
		public void onFrameProcessed( IImageCaptureService.Status status, IImageCaptureService.Result result )
		{
			if( result != null && checkResult( result ) ) {
				playSoundEffect();
				stopCapture();

				try( IImagingCoreAPI coreAPI = engine.createImagingCoreAPI() ) {
					try( IImagingCoreAPI.Image image = coreAPI.loadImage( result.ImageBuffer, result.ImageWidth, result.ImageHeight, orientation ) ) {
						if( cropCapturedImage ) {
							if( result.DocumentBoundary != null ) {
								IImagingCoreAPI.CropOperation crop = coreAPI.createCropOperation();
								crop.DocumentBoundary = result.DocumentBoundary;
								if( result.DocumentWidth != Float.MAX_VALUE && result.DocumentHeight != Float.MAX_VALUE ) {
									// Document with known size
									crop.DocumentWidth = result.DocumentWidth;
									crop.DocumentHeight = result.DocumentHeight;
								}
								crop.apply( image );
							}
							// The cropped image will be shown with a boundary drawn around the image
							drawView.setDocumentBoundary( null );
						} else {
							drawView.setDocumentBoundary( result.DocumentBoundary );
						}
						drawView.setBitmap( image.toBitmap() );
					}
				}
				imageCaptured = true;
			} else if( !imageCaptured ) {
				checkStatus( status );
				drawView.setDocumentBoundary( status.DocumentBoundary );
				if( status.QualityAssessmentForOcrBlocks != null ) {
					drawView.setQualityAssessmentBlocks( status.QualityAssessmentForOcrBlocks );
				}
			}
		}

		@Override
		public void onError( Exception e )
		{
			// An error occurred while processing. Log it. Processing will continue
			Log.e( getString( R.string.app_name ), "Error: " + e.getMessage() );
			if( BuildConfig.DEBUG ) {
				// Make the error easily visible to the developer
				String message = e.getMessage();
				if( message == null ) {
					message = "Unspecified error while creating the service. See logcat for details.";
				}
				errorTextView.setText( message );
			}
		}

		private MediaActionSound sound;

		protected void playSoundEffect()
		{
			startButton.playSoundEffect( android.view.SoundEffectConstants.CLICK );
			if( sound == null ) {
				sound = new MediaActionSound();
			}
			sound.play( MediaActionSound.SHUTTER_CLICK );
		}

		protected boolean checkResult( IImageCaptureService.Result result )
		{
			if( documentSize.Width > 0f && documentSize.Height > 0f ) {
				// Require document boundary
				if( result.DocumentBoundary == null ) {
					warningTextView.setText( "NO DOCUMENT" );
					return false;
				}
			}
			warningTextView.setText( "" );
			return true;
		}

		protected void checkStatus( IImageCaptureService.Status status )
		{
			if( documentSize.Width > 0f && documentSize.Height > 0f ) {
				// Require document boundary
				warningTextView.setText( status.DocumentBoundary == null ? "NO DOCUMENT" : "" );
			}
		}
	};

	// CameraDevice.StateCallback is called when the currently active CameraDevice changes its state.
	private CameraDevice.StateCallback cameraCallback = new CameraDevice.StateCallback() {
		@Override
		public void onOpened( @NonNull CameraDevice camera )
		{
			cameraDevice = camera;
			createCameraPreview();
		}

		@Override
		public void onDisconnected( @NonNull CameraDevice camera )
		{
			camera.close();
			cameraDevice = null;
		}

		@Override
		public void onError( @NonNull CameraDevice camera, int error )
		{
			camera.close();
			cameraDevice = null;
			Log.e( getString( R.string.app_name ), "CameraDevice error!:" + error );
		}
	};

	// Handles several lifecycle events of a TextureView
	private TextureView.SurfaceTextureListener textureListener = new TextureView.SurfaceTextureListener() {
		@Override
		public void onSurfaceTextureAvailable( SurfaceTexture surface, int width, int height )
		{
			openCamera( width, height );
		}

		@Override
		public void onSurfaceTextureSizeChanged( SurfaceTexture surface, int width, int height )
		{
		}

		@Override
		public boolean onSurfaceTextureDestroyed( SurfaceTexture surface )
		{
			return false;
		}

		@Override
		public void onSurfaceTextureUpdated( SurfaceTexture surface )
		{
		}
	};

	// This is a callback class for the ImageReader. "onImageAvailable" will be called when a
	// YUV420_888 image is ready to be submited to image capture service
	private class OnImageAvailableListener implements ImageReader.OnImageAvailableListener {

		@Override
		public void onImageAvailable( ImageReader reader )
		{
			// The Images produced by the ImageReader need to be closed, otherwise the application freezes
			try( Image image = reader.acquireNextImage() ) {
				imageCaptureService.submitFrame( image );
			}
		}
	}

	// Handle "onTouch" event to focus manually
	private class OnTouchListener implements View.OnTouchListener {
		@Override
		public boolean onTouch( View view, MotionEvent motionEvent )
		{
			if( cameraCaptureSession == null ) {
				return false;
			}

			final int actionMasked = motionEvent.getActionMasked();
			if( actionMasked != MotionEvent.ACTION_DOWN ) {
				return false;
			}

			if( isFocusing ) {
				return true;
			}

			final Rect sensorArraySize = cameraCharacteristics.get( CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE );
			Matrix matrix = getTransform( view.getWidth(), view.getHeight(),
				sensorArraySize.width(), sensorArraySize.height(), orientation );

			float[] coords = { motionEvent.getX(), motionEvent.getY() };
			matrix.mapPoints( coords );

			final int touchRadius = 120;

			MeteringRectangle focusAreaTouch = new MeteringRectangle(
				Math.max( (int) coords[0] - touchRadius, 0 ),
				Math.max( (int) coords[1] - touchRadius, 0 ),
				touchRadius * 2,
				touchRadius * 2,
				MeteringRectangle.METERING_WEIGHT_MAX - 1 );

			CameraCaptureSession.CaptureCallback captureCallbackHandler = new CameraCaptureSession.CaptureCallback() {
				@Override
				public void onCaptureCompleted( @NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result )
				{
					super.onCaptureCompleted( session, request, result );
					isFocusing = false;
					builder.set( CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_IDLE );
					try {
						cameraCaptureSession.setRepeatingRequest( builder.build(), null, null );
					} catch( CameraAccessException e ) {
						e.printStackTrace();
					}
				}

				@Override
				public void onCaptureFailed( @NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull CaptureFailure failure )
				{
					super.onCaptureFailed( session, request, failure );
					Log.e( getString( R.string.app_name ), "Manual AutoFocus failure: " + failure );
					isFocusing = false;
				}
			};

			try {
				cameraCaptureSession.stopRepeating();
				final MeteringRectangle[] meteringRectangles = new MeteringRectangle[] { focusAreaTouch };
				builder.set( CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_AUTO );
				builder.set( CaptureRequest.CONTROL_AF_REGIONS, meteringRectangles );
				builder.set( CaptureRequest.CONTROL_AE_REGIONS, meteringRectangles );
				builder.set( CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_START );
				cameraCaptureSession.capture( builder.build(), captureCallbackHandler, null );
				isFocusing = true;
			} catch( CameraAccessException e ) {
				e.printStackTrace();
			}
			view.performClick();
			return true;
		}
	}

	private void showStartupError( String message )
	{
		errorDialog = new AlertDialog.Builder( this )
			.setTitle( "ABBYY MI SDK" )
			.setMessage( message )
			.setIcon( android.R.drawable.ic_dialog_alert )
			.setCancelable( false )
			.setPositiveButton( android.R.string.ok, new DialogInterface.OnClickListener() {
				@Override
				public void onClick( DialogInterface dialogInterface, int i )
				{
					finish();
				}
			} )
			.show();
	}

	// Load ABBYY MI SDK engine and configure the image capture service
	private boolean createImageCaptureService()
	{
		// Initialize the engine and image capture service
		try {
			engine = Engine.load( this, licenseFileName );
			imageCaptureService = engine.createImageCaptureService( imageCaptureCallback );
			imageCaptureService.setDocumentSize( documentSize.Width, documentSize.Height );
			return true;
		} catch( java.io.IOException e ) {
			// Troubleshooting for the developer
			Log.e( getString( R.string.app_name ), "Error loading ABBYY MI SDK:", e );
			showStartupError( "Could not load some required resource files. Make sure to configure " +
				"'assets' directory in your application and specify correct 'license file name'. See logcat for details." );
		} catch( Engine.LicenseException e ) {
			// Troubleshooting for the developer
			Log.e( getString( R.string.app_name ), "Error loading ABBYY MI SDK:", e );
			showStartupError( "License not valid. Make sure you have a valid license file in the " +
				"'assets' directory and specify correct 'license file name' and 'application id'. See logcat for details." );
		} catch( Throwable e ) {
			// Troubleshooting for the developer
			Log.e( getString( R.string.app_name ), "Error loading ABBYY MI SDK:", e );
			showStartupError( "Unspecified error while loading the engine. See logcat for details." );
		}
		return false;
	}

	// Start capturing
	private void startCapture()
	{
		assert cameraDevice != null;

		clearCaptureResults();

		builder.addTarget( imageReader.getSurface() );
		updatePreview();

		// Get area of interest (in coordinates of image frames)
		int width = 0;
		int height = 0;
		switch( orientation ) {
			case 0:
			case 180:
				width = imageSize.getWidth();
				height = imageSize.getHeight();
				break;
			case 90:
			case 270:
				width = imageSize.getHeight();
				height = imageSize.getWidth();
				break;
		}

		Rect areaOfInterest = new Rect( 0, 0, width, height );

		// Clear error message
		warningTextView.setText( "" );
		errorTextView.setText( "" );

		// Start the service
		imageCaptureService.start( imageSize.getWidth(), imageSize.getHeight(), orientation, areaOfInterest );

		// Change the text on the start button to 'Stop'
		startButton.setText( BUTTON_TEXT_STOP );
		startButton.setEnabled( true );
	}

	// Stop capture
	void stopCapture()
	{
		startButton.setEnabled( false );
		if( imageReader != null && builder != null ) {
			builder.removeTarget( imageReader.getSurface() );
		}

		new AsyncTask<Void, Void, Void>() {
			protected Void doInBackground( Void... params )
			{
				imageCaptureService.stop();
				return null;
			}

			protected void onPostExecute( Void result )
			{
				startButton.setText( BUTTON_TEXT_START );
				startButton.setEnabled( true );
			}
		}.execute();
	}

	// Clear capture results
	private void clearCaptureResults()
	{
		drawView.setDocumentBoundary( null );
		drawView.setBitmap( null );
		drawView.setQualityAssessmentBlocks( null );
		warningTextView.setText( "" );
		imageCaptured = false;
	}

	// Returns orientation of camera
	private int getCameraOrientation()
	{
		assert cameraCharacteristics != null;
		int rotation = getWindowManager().getDefaultDisplay().getRotation();
		int deviceOrientation = rotation * 90;
		int sensorOrientation = cameraCharacteristics.get( CameraCharacteristics.SENSOR_ORIENTATION );
		return ( sensorOrientation - deviceOrientation + 360 ) % 360;
	}

	// Open back-facing camera if found
	void openCamera( int width, int height )
	{
		if( cameraDevice == null ) {
			try {
				setBackCameraInfo();
				StreamConfigurationMap map = cameraCharacteristics.get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP );
				Size[] sizes = map.getOutputSizes( SurfaceTexture.class );
				previewSize = chooseOptimalPreviewSize( sizes );
				transformView( width, height );
				orientation = getCameraOrientation();

				Size[] imageSizes = map.getOutputSizes( ImageFormat.YUV_420_888 );
				assert imageSizes != null && imageSizes.length > 0;
				// Image size and preview size should be the same
				imageSize = previewSize;
				Matrix matrix = getTransform( imageSize.getWidth(), imageSize.getHeight(),
					drawView.getWidth(), drawView.getHeight(), orientation );
				drawView.setImageToViewTransform( matrix );

				imageReader = ImageReader.newInstance( imageSize.getWidth(), imageSize.getHeight(), ImageFormat.YUV_420_888, 1 );
				imageReader.setOnImageAvailableListener( imageAvailableListener, null );

				if( ActivityCompat.checkSelfPermission( this, Manifest.permission.CAMERA ) != PackageManager.PERMISSION_GRANTED ) {
					ActivityCompat.requestPermissions( this, new String[] { Manifest.permission.CAMERA }, CAMERA_PERMISSION_REQUEST_CODE );
					return;
				}
				cameraManager.openCamera( cameraId, cameraCallback, null );
			} catch( CameraAccessException e ) {
				e.printStackTrace();
			}
		}
	}

	void closeCamera()
	{
		if( cameraCaptureSession != null ) {
			cameraCaptureSession.close();
			cameraCaptureSession = null;
		}
		if( cameraDevice != null ) {
			cameraDevice.close();
			cameraDevice = null;
		}
		if( imageReader != null ) {
			imageReader.close();
			imageReader = null;
		}
	}

	private void createCameraPreview()
	{
		SurfaceTexture texture = imageView.getSurfaceTexture();
		assert texture != null;
		texture.setDefaultBufferSize( previewSize.getWidth(), previewSize.getHeight() );
		Surface surface = new Surface( texture );
		try {
			builder = cameraDevice.createCaptureRequest( CameraDevice.TEMPLATE_PREVIEW );
			builder.addTarget( surface );
			cameraDevice.createCaptureSession( Arrays.asList( surface, imageReader.getSurface() ), new CameraCaptureSession.StateCallback() {
				@Override
				public void onConfigured( @NonNull CameraCaptureSession session )
				{
					if( cameraDevice == null ) {
						return;
					}
					cameraCaptureSession = session;
					updatePreview();
				}

				@Override
				public void onConfigureFailed( @NonNull CameraCaptureSession cameraCaptureSession )
				{

				}
			}, null );
		} catch( CameraAccessException e ) {
			e.printStackTrace();
		}
	}

	private void updatePreview()
	{
		assert cameraDevice != null;
		builder.set( CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_AF_MODE_CONTINUOUS_VIDEO );
		try {
			cameraCaptureSession.setRepeatingRequest( builder.build(), null, null );
			if( startCaptureWhenReady ) {
				handler.post( new Runnable() {
					@Override
					public void run()
					{
						startCapture();
					}
				} );
				startCaptureWhenReady = false;
			}
		} catch( CameraAccessException | IllegalStateException e ) {
			e.printStackTrace();
		}
	}

	// Set id and characteristic of back camera.
	private void setBackCameraInfo() throws CameraAccessException
	{
		if( cameraId != null ) { return; }

		String[] cameraIds = cameraManager.getCameraIdList();
		for( String id : cameraIds ) {
			cameraCharacteristics = cameraManager.getCameraCharacteristics( id );
			Integer facing = cameraCharacteristics.get( CameraCharacteristics.LENS_FACING );
			if( facing == CameraMetadata.LENS_FACING_BACK ) {
				cameraId = id;
				return;
			}
		}
		assert cameraId != null;
	}

	// The 'Start' and 'Stop' button
	public void onStartButtonClick( View view )
	{
		if( startButton.getText().equals( BUTTON_TEXT_STOP ) ) {
			stopCapture();
		} else {
			startButton.setEnabled( false );
			startButton.setText( BUTTON_TEXT_STARTING );
			startCapture();
		}
	}

	// Camera permission request handler for Android 6.0 and higher
	@Override
	public void onRequestPermissionsResult( int requestCode, String[] permissions, int[] grantResults )
	{
		switch( requestCode ) {
			case CAMERA_PERMISSION_REQUEST_CODE:
				if( grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED ) {
				} else {
					showStartupError( "Camera is essential for this application." );
				}
				break;
			default:
				throw new IllegalArgumentException();
		}
	}

	// Initialize documentSize spinner in the UI with available languages
	private void initializeDocumentSizeSpinner()
	{
		final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences( this );

		// Make the collapsed spinner the size of the selected item
		ArrayAdapter<String> adapter = new ArrayAdapter<String>( MainActivity.this, R.layout.spinner_item, R.id.spinner_item_text ) {
			@Override
			public View getView( int position, View convertView, ViewGroup parent )
			{
				View view = super.getView( position, convertView, parent );
				RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(
					RelativeLayout.LayoutParams.WRAP_CONTENT,
					RelativeLayout.LayoutParams.WRAP_CONTENT );
				view.setLayoutParams( params );
				return setUsage( view, position );
			}

			@Override
			public View getDropDownView( int position, View convertView, ViewGroup parent )
			{
				return setUsage( super.getDropDownView( position, convertView, parent ), position );
			}

			View setUsage( View view, int position )
			{
				SampleDocumentSize documentSize = SampleDocumentSize.values()[position];
				TextView usage = (TextView) view.findViewById( R.id.spinner_item_usage );
				usage.setText( documentSize.Usage );
				return view;
			}
		};

		// Stored preference
		final String sampeDocumentSizeKey = "SampleDocumentSize";
		String selectedSample = preferences.getString( sampeDocumentSizeKey, SampleDocumentSize.values()[0].toString() );

		// Fill the spinner with available document sizes selecting the previously chosen size
		int selectedIndex = -1;
		int defaultIndex = -1;
		for( int i = 0; i < SampleDocumentSize.values().length; i++ ) {
			SampleDocumentSize scenario = SampleDocumentSize.values()[i];
			String name = scenario.toString();
			adapter.add( name );
			if( name.equalsIgnoreCase( selectedSample ) ) {
				selectedIndex = i;
			}
			if( name.equalsIgnoreCase( documentSize.toString() ) ) {
				defaultIndex = i;
			}
		}
		if( selectedIndex == -1 ) {
			selectedIndex = defaultIndex;
		} else {
			documentSize = SampleDocumentSize.valueOf( selectedSample );
		}

		documentSizeSpinner.setAdapter( adapter );

		if( selectedIndex != -1 ) {
			documentSizeSpinner.setSelection( selectedIndex );
		}

		// The callback to be called when a size is selected
		documentSizeSpinner.setOnItemSelectedListener( new AdapterView.OnItemSelectedListener() {
			@Override
			public void onItemSelected( AdapterView<?> parent, View view, int position, long id )
			{
				String selectedSize = (String) parent.getItemAtPosition( position );
				if( !preferences.getString( sampeDocumentSizeKey, "" ).equalsIgnoreCase( selectedSize ) ) {
					documentSize = SampleDocumentSize.valueOf( selectedSize );
					if( imageCaptureService != null ) {
						imageCaptureService.setDocumentSize( documentSize.Width, documentSize.Height );
					}
					// Store the selection in preferences
					SharedPreferences.Editor editor = preferences.edit();
					editor.putString( sampeDocumentSizeKey, selectedSize );
					editor.commit();
				}
			}

			@Override
			public void onNothingSelected( AdapterView<?> parent )
			{
			}
		} );
	}

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

		// Retrieve some ui components
		warningTextView = (TextView) findViewById( R.id.warningText );
		errorTextView = (TextView) findViewById( R.id.errorText );
		startButton = (Button) findViewById( R.id.startButton );
		documentSizeSpinner = (Spinner) findViewById( R.id.documentSizeSpinner );

		// Initialize the documentSizes spinner
		initializeDocumentSizeSpinner();

		imageView = (TextureView) findViewById( R.id.image_view );
		drawView = (DrawView) findViewById( R.id.draw_view );

		// Create capture service
		if( createImageCaptureService() ) {
			imageView.setSurfaceTextureListener( textureListener );
			imageView.setOnTouchListener( onTouchListener );
			cameraManager = (CameraManager) getSystemService( Context.CAMERA_SERVICE );
		}
	}

	@Override
	public void onResume()
	{
		super.onResume();
		startCaptureWhenReady = startCaptureOnAppStart;
		if( cameraManager != null ) {
			if( imageView.isAvailable() ) {
				openCamera( imageView.getWidth(), imageView.getHeight() );
			} else {
				imageView.setSurfaceTextureListener( textureListener );
			}
		}
	}

	@Override
	public void onPause()
	{
		// Clear all pending actions
		handler.removeCallbacksAndMessages( null );
		// Stop the capture capture service
		if( imageCaptureService != null ) {
			imageCaptureService.stop();
		}
		startButton.setText( BUTTON_TEXT_START );
		// Clear capture results
		clearCaptureResults();
		closeCamera();
		super.onPause();
	}

	@Override
	protected void onDestroy()
	{
		if( errorDialog != null ) {
			errorDialog.dismiss();
			errorDialog = null;
		}
		super.onDestroy();
	}

	private void transformView( int viewWidth, int viewHeight )
	{
		if( previewSize == null ) {
			return;
		}
		int rotation = getWindowManager().getDefaultDisplay().getRotation();
		Matrix matrix = new Matrix();
		RectF viewRect = new RectF( 0, 0, viewWidth, viewHeight );
		RectF bufferRect = new RectF( 0, 0, previewSize.getHeight(), previewSize.getWidth() );
		float centerX = viewRect.centerX();
		float centerY = viewRect.centerY();
		if( Surface.ROTATION_90 == rotation || Surface.ROTATION_270 == rotation ) {
			bufferRect.offset( centerX - bufferRect.centerX(), centerY - bufferRect.centerY() );
			matrix.setRectToRect( viewRect, bufferRect, Matrix.ScaleToFit.FILL );
			float scale = Math.max(
				(float) viewHeight / previewSize.getHeight(),
				(float) viewWidth / previewSize.getWidth() );
			matrix.postScale( scale, scale, centerX, centerY );
			matrix.postRotate( 90 * ( rotation - 2 ), centerX, centerY );
		}
		imageView.setTransform( matrix );
	}

	// Choose preview size. Find 1920x1080,
	// if it doesn't exist choose first resolution in available sizes for YUV420_888 output format.
	// Big resolution (such as 4k) provides slow image processing.
	private Size chooseOptimalPreviewSize( Size[] sizes )
	{
		for( Size size : sizes ) {
			if( size.getWidth() == 1920 && size.getHeight() == 1080 ) {
				return size;
			}
		}
		return sizes[0];
	}

	static private Matrix getTransform( int srcWidth, int srcHeight, int dstWidth, int dstHeight, int orientation )
	{
		Matrix matrix = new Matrix();
		switch( orientation ) {
			case 90:
				matrix.setTranslate( 0, -srcHeight );
				matrix.postRotate( 90f );
				matrix.postScale( (float) dstWidth / srcHeight,
					(float) dstHeight / srcWidth );
				break;
			case 0:
				matrix.setScale( (float) dstWidth / srcWidth,
					(float) dstHeight / srcHeight );
				break;
			case 180:
				matrix.setTranslate( -srcWidth, -srcHeight );
				matrix.postRotate( 180f );
				matrix.postScale( (float) dstWidth / srcWidth,
					(float) dstHeight / srcHeight );
				break;
			default:
				assert false;
		}
		return matrix;
	}
}


