content: package com.reactnativedocumentscannerandroid.views;
  
  import android.app.Activity;
  import android.content.Context;
  import android.content.Intent;
  import android.content.SharedPreferences;
  import android.content.pm.PackageManager;
  import android.graphics.Bitmap;
  import android.graphics.Color;
  import android.graphics.Rect;
  import android.hardware.Camera;
  import android.hardware.Camera.PictureCallback;
  import android.hardware.Camera.Size;
  import android.hardware.display.DisplayManager;
  import android.media.AudioManager;
  import android.media.ExifInterface;
  import android.media.MediaActionSound;
  import android.net.Uri;
  import android.os.Build;
  import android.os.Environment;
  import android.os.Handler;
  import android.os.HandlerThread;
  import android.os.Message;
  import android.preference.PreferenceManager;
  import android.provider.MediaStore;
  import android.util.AttributeSet;
  import android.util.Log;
  import android.view.Display;
  import android.view.LayoutInflater;
  import android.view.SurfaceHolder;
  import android.view.SurfaceView;
  import android.view.View;
  import android.view.ViewGroup;
  import android.view.WindowManager;
  import android.view.animation.AccelerateInterpolator;
  import android.view.animation.AlphaAnimation;
  import android.view.animation.Animation;
  import android.view.animation.AnimationUtils;
  import android.view.animation.AnimationSet;
  import android.view.animation.ScaleAnimation;
  import android.view.animation.TranslateAnimation;
  import android.widget.FrameLayout;
  import android.widget.ImageView;
  import android.widget.RelativeLayout;
  
  import com.reactnativedocumentscannerandroid.BuildConfig;
  import com.reactnativedocumentscannerandroid.ImageProcessor;
  import com.reactnativedocumentscannerandroid.R;
  import com.reactnativedocumentscannerandroid.helpers.CustomOpenCVLoader;
  import com.reactnativedocumentscannerandroid.helpers.OpenNoteMessage;
  import com.reactnativedocumentscannerandroid.helpers.PreviewFrame;
  import com.reactnativedocumentscannerandroid.helpers.ScannedDocument;
  import com.facebook.react.bridge.Promise;
  import com.facebook.react.bridge.WritableMap;
  import com.facebook.react.bridge.WritableNativeMap;
  
  import org.opencv.android.BaseLoaderCallback;
  import org.opencv.android.JavaCameraView;
  import org.opencv.android.LoaderCallbackInterface;
  import org.opencv.android.OpenCVLoader;
  import org.opencv.core.Core;
  import org.opencv.core.CvType;
  import org.opencv.core.Mat;
  import org.opencv.core.Point;
  import org.opencv.imgcodecs.Imgcodecs;
  import org.opencv.imgproc.Imgproc;
  
  import java.io.File;
  import java.io.FileInputStream;
  import java.io.FileNotFoundException;
  import java.io.FileOutputStream;
  import java.io.IOException;
  import java.io.InputStream;
  import java.io.OutputStream;
  import java.text.SimpleDateFormat;
  import java.util.ArrayList;
  import java.util.Date;
  import java.util.List;
  import java.util.logging.Logger;
  
  import static com.reactnativedocumentscannerandroid.helpers.Utils.addImageToGallery;
  import static com.reactnativedocumentscannerandroid.helpers.Utils.decodeSampledBitmapFromUri;
  
  public class OpenNoteCameraView extends JavaCameraView implements PictureCallback {
  
      private static final String TAG = "JavaCameraView";
      private String mPictureFileName;
      private Context mContext;
      SurfaceView mSurfaceView;
      private SurfaceHolder mSurfaceHolder;
      private HUDCanvasView mHud;
      private boolean mBugRotate=false;
      private ImageProcessor mImageProcessor;
      private boolean mFocused;
      private boolean safeToTakePicture;
      private Activity mActivity;
      private boolean mFlashMode = false;
      private SharedPreferences mSharedPref;
      private SimpleDateFormat mDateFormat = new SimpleDateFormat("yyyy:MM:dd HH:mm:ss");
      private boolean autoMode = true;
      private HandlerThread mImageThread;
      private View mWaitSpinner;
      private PictureCallback pCallback;
      private boolean scanClicked = false;
      private boolean mVisible;
  
      private boolean documentAnimation = false;
      private int numberOfRectangles = 15;
      private Boolean enableTorch = false;
      private String overlayColor = null;
      private View blinkView = null;
      private View mView = null;
      private boolean manualCapture = false;
  
      public static OpenNoteCameraView mThis;
  
      private OnScannerListener listener = null;
      private OnProcessingListener processingListener = null;
  
      public interface OnScannerListener{
          void onPictureTaken(WritableMap path);
      }
  
      public interface OnProcessingListener{
          void onProcessingChange(WritableMap path);
      }
  
      public void setOnScannerListener(OnScannerListener listener){
          this.listener = listener;
      }
  
      public void removeOnScannerListener(){
          this.listener = null;
      }
  
      public void setOnProcessingListener(OnProcessingListener processingListener){
          this.processingListener = processingListener;
      }
  
      public void removeOnProcessingListener(){
          this.processingListener = null;
      }
  
      public OpenNoteCameraView(Context context, AttributeSet attrs) {
          super(context, attrs);
      }
  
      public OpenNoteCameraView(Context context, Integer numCam, Activity activity, FrameLayout frameLayout) {
          super(context, numCam);
          this.mContext = context;
          this.mActivity = activity;
          mSharedPref = PreferenceManager.getDefaultSharedPreferences(context);
          pCallback = this;
          mView = frameLayout;
  
          LayoutInflater lf = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
  
          initOpenCv(context);
      }
  
      public static OpenNoteCameraView getInstance(){
          return mThis;
      }
  
      public void setDocumentAnimation(boolean animate){
          this.documentAnimation = animate;
      }
      public void setOverlayColor(String rgbaColor){
          this.overlayColor = rgbaColor;
      }
      public void setDetectionCountBeforeCapture(int numberOfRectangles){
          this.numberOfRectangles = numberOfRectangles;
      }
      public void setEnableTorch(boolean enableTorch){
          this.enableTorch = enableTorch;
  
          if(mCamera != null){
              Camera.Parameters p = mCamera.getParameters();
              p.setFlashMode(enableTorch ? Camera.Parameters.FLASH_MODE_TORCH : Camera.Parameters.FLASH_MODE_OFF);
              mCamera.setParameters(p);
          }
      }
  
      public void capture(){
          this.requestManualPicture();
      }
  
      public void focus(){
          this.requestManualFocus();
      }
  
      public void setManualOnly( boolean manualOnly){
          this.manualCapture = manualOnly;
      }
  
      public void setBrightness( double brightness ){
          if (mImageProcessor != null) {
              mImageProcessor.setBrightness(brightness);
          }
      }
  
      public void setContrast( double contrast){
          if (mImageProcessor != null) {
              mImageProcessor.setContrast(contrast);
          }
      }
  
      public void setRemoveGrayScale(boolean grayscale){
          mImageProcessor.setRemoveGrayScale(grayscale);
      }
  
      public void initOpenCv(Context context){
  
          mThis = this;
  
          mHud = (HUDCanvasView)  mView.findViewById(R.id.hud);
          mWaitSpinner = mView.findViewById(R.id.wait_spinner);
          blinkView = mView.findViewById(R.id.blink_view);
          blinkView.setBackgroundColor(Color.WHITE);
  
          mVisible = true;
  
          mActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
  
          Display display = mActivity.getWindowManager().getDefaultDisplay();
          android.graphics.Point size = new android.graphics.Point();
          display.getRealSize(size);
  
          BaseLoaderCallback mLoaderCallback = new BaseLoaderCallback(context) {
              @Override
              public void onManagerConnected(int status) {
                  switch (status) {
                      case LoaderCallbackInterface.SUCCESS: {
                          Log.d(TAG, "SUCCESS init openCV: "+status);
                          //System.loadLibrary("ndklibrarysample");
                          enableCameraView();
                      }
                      break;
                      default: {
                          Log.d(TAG, "ERROR init Opencv: "+status);
                          super.onManagerConnected(status);
                      }
                      break;
                  }
              }
          };
  
          if(!OpenCVLoader.initDebug()){
              CustomOpenCVLoader.initAsync(OpenCVLoader.OPENCV_VERSION_3_1_0, context, mLoaderCallback);
          }else{
              mLoaderCallback.onManagerConnected(LoaderCallbackInterface.SUCCESS);
          }
  
          if (mImageThread == null ) {
              mImageThread = new HandlerThread("Worker Thread");
              mImageThread.start();
          }
  
          if (mImageProcessor == null) {
              mImageProcessor = new ImageProcessor(mImageThread.getLooper(), new Handler(), this, mContext);
          }
          this.setImageProcessorBusy(false);
  
      }
  
      public HUDCanvasView getHUD() {
          return mHud;
      }
  
      private boolean imageProcessorBusy=true;
      private boolean attemptToFocus = false;
  
      public void setImageProcessorBusy(boolean imageProcessorBusy) {
          this.imageProcessorBusy = imageProcessorBusy;
      }
  
      public void setAttemptToFocus(boolean attemptToFocus) {
          this.attemptToFocus = attemptToFocus;
      }
  
      public boolean isFocused() { return this.mFocused; }
  
      public void turnCameraOn() {
          mSurfaceView = (SurfaceView) mView.findViewById(R.id.surfaceView);
          mSurfaceHolder = this.getHolder();
          mSurfaceHolder.addCallback(this);
          if(Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB){
            mSurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
          }
  
          mSurfaceView.setVisibility(SurfaceView.VISIBLE);
      }
  
      public void enableCameraView() {
          if (mSurfaceView == null) {
              turnCameraOn();
          }
      }
  
      private int findBestCamera() {
          int cameraId = -1;
          //Search for the back facing camera
          //get the number of cameras
          int numberOfCameras = Camera.getNumberOfCameras();
          //for every camera check
          for (int i = 0; i < numberOfCameras; i++) {
              Camera.CameraInfo info = new Camera.CameraInfo();
              Camera.getCameraInfo(i, info);
              if (info.facing == Camera.CameraInfo.CAMERA_FACING_BACK) {
                  cameraId = i;
                  break;
              }
              cameraId = i;
          }
          return cameraId;
      }
  
      public Size getMaxPreviewResolution() {
          int maxWidth=0;
          Size curRes=null;
  
          mCamera.lock();
  
          for ( Size r: getResolutionList() ) {
              if (r.width>maxWidth) {
                  Log.d(TAG,"supported preview resolution: "+r.width+"x"+r.height);
                  maxWidth=r.width;
                  curRes=r;
              }
          }
  
          return curRes;
      }
  
      public Size getMaxPictureResolution(float previewRatio) {
          int maxPixels=0;
          int ratioMaxPixels=0;
          Size currentMaxRes=null;
          Size ratioCurrentMaxRes=null;
          for ( Size r: getPictureResolutionList() ) {
              float pictureRatio = (float) r.width / r.height;
              Log.d(TAG,"supported picture resolution: "+r.width+"x"+r.height+" ratio: "+pictureRatio);
              int resolutionPixels = r.width * r.height;
  
              if (resolutionPixels>ratioMaxPixels && pictureRatio == previewRatio) {
                  ratioMaxPixels=resolutionPixels;
                  ratioCurrentMaxRes=r;
              }
  
              if (resolutionPixels>maxPixels) {
                  maxPixels=resolutionPixels;
                  currentMaxRes=r;
              }
          }
  
          //boolean matchAspect = mSharedPref.getBoolean("match_aspect", true);
  
          if (ratioCurrentMaxRes!=null && true) {
  
              Log.d(TAG,"Max supported picture resolution with preview aspect ratio: "
                      + ratioCurrentMaxRes.width+"x"+ratioCurrentMaxRes.height);
              return ratioCurrentMaxRes;
  
          }
  
          return currentMaxRes;
      }
  
      @Override
      public void surfaceCreated(SurfaceHolder holder) {
          try {
              int cameraId = findBestCamera();
              mCamera = Camera.open(cameraId);
          }
          catch (RuntimeException e) {
              System.err.println(e);
              return;
          }
  
          Camera.Parameters param;
          param = mCamera.getParameters();
  
          Size pSize = getMaxPreviewResolution();
          param.setPreviewSize(pSize.width, pSize.height);
          param.setWhiteBalance(Camera.Parameters.WHITE_BALANCE_AUTO);
          float previewRatio = (float) pSize.width / pSize.height;
  
          Display display = mActivity.getWindowManager().getDefaultDisplay();
          //Display display =
          android.graphics.Point size = new android.graphics.Point();
          display.getRealSize(size);
  
          int displayWidth = Math.min(size.y, size.x);
          int displayHeight = Math.max(size.y, size.x);
  
          float displayRatio =  (float) displayHeight / displayWidth;
  
          int previewHeight = displayHeight;
  
          if ( displayRatio > previewRatio ) {
              ViewGroup.LayoutParams surfaceParams = mSurfaceView.getLayoutParams();
              previewHeight = (int) ( (float) size.y/displayRatio*previewRatio);
              surfaceParams.height = previewHeight;
              mSurfaceView.setLayoutParams(surfaceParams);
  
              mHud.getLayoutParams().height = previewHeight;
          }
  
          int hotAreaWidth = displayWidth / 4;
          int hotAreaHeight = previewHeight / 2 - hotAreaWidth;
  
  
          Size maxRes = getMaxPictureResolution(previewRatio);
          if ( maxRes != null) {
              param.setPictureSize(maxRes.width, maxRes.height);
              Log.d(TAG,"max supported picture resolution: " + maxRes.width + "x" + maxRes.height);
          }
  
          PackageManager pm = mActivity.getPackageManager();
  
          if (pm.hasSystemFeature(PackageManager.FEATURE_CAMERA_AUTOFOCUS)) {
              param.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
          } else {
              mFocused = true;
          }
          if (pm.hasSystemFeature(PackageManager.FEATURE_CAMERA_FLASH)) {
              param.setFlashMode(enableTorch ? Camera.Parameters.FLASH_MODE_TORCH : Camera.Parameters.FLASH_MODE_OFF);
          }
  
          mCamera.setParameters(param);
  //        mBugRotate = mSharedPref.getBoolean("bug_rotate", false);
  
          if (mBugRotate) {
              mCamera.setDisplayOrientation(270);
          } else {
              mCamera.setDisplayOrientation(90);
          }
  
          if (mImageProcessor != null) {
              mImageProcessor.setBugRotate(mBugRotate);
              mImageProcessor.setNumOfRectangles(numberOfRectangles);
          }
  
          try {
              mCamera.setAutoFocusMoveCallback(new Camera.AutoFocusMoveCallback() {
                  @Override
                  public void onAutoFocusMoving(boolean start, Camera camera) {
                      mFocused = !start;
                      Log.d(TAG, "focusMoving: " + mFocused);
                  }
              });
          } catch (Exception e) {
              Log.d(TAG, "failed setting AutoFocusMoveCallback");
          }
  
          // some devices doesn't call the AutoFocusMoveCallback - fake the
          // focus to true at the start
          mFocused = true;
  
          safeToTakePicture = true;
  
      }
  
      @Override
      public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
          refreshCamera();
      }
  
      private void refreshCamera() {
          final boolean torchEnabled = this.enableTorch;
  
          try {
              mCamera.stopPreview();
          }
  
          catch (Exception e) {
          }
  
          try {
              mCamera.setPreviewDisplay(mSurfaceHolder);
              mThis.setEnableTorch(torchEnabled);
              mCamera.startPreview();
              mCamera.setPreviewCallback(this);
          }
          catch (Exception e) {
          }
      }
  
      @Override
      public void surfaceDestroyed(SurfaceHolder holder) {
          if (mCamera != null) {
              mCamera.stopPreview();
              mCamera.setPreviewCallback(null);
              mCamera.release();
              mCamera = null;
          }
      }
  
      @Override
      public void onPreviewFrame(byte[] data, Camera camera) {
  
          Size pictureSize = camera.getParameters().getPreviewSize();
  
          if ( mFocused && ! imageProcessorBusy ) {
              setImageProcessorBusy(true);
              Mat yuv = new Mat(new org.opencv.core.Size(pictureSize.width, pictureSize.height * 1.5), CvType.CV_8UC1);
              yuv.put(0, 0, data);
  
              Mat mat = new Mat(new org.opencv.core.Size(pictureSize.width, pictureSize.height), CvType.CV_8UC4);
              Imgproc.cvtColor(yuv, mat, Imgproc.COLOR_YUV2RGBA_NV21, 4);
  
              yuv.release();
  
              if(!manualCapture){
                  sendImageProcessorMessage("previewFrame", new PreviewFrame( mat, autoMode, !(autoMode) ));
              }
  
          }
  
      }
  
      public void invalidateHUD() {
          mActivity.runOnUiThread(new Runnable() {
              @Override
              public void run() {
                  mHud.invalidate();
              }
          });
      }
  
      public void sendImageProcessorMessage(String messageText , Object obj ) {
          Log.d(TAG,"sending message to ImageProcessor: "+messageText+" - "+obj.toString());
          Message msg = mImageProcessor.obtainMessage();
          msg.obj = new OpenNoteMessage(messageText, obj );
          mImageProcessor.sendMessage(msg);
      }
  
      public void blinkScreenAndShutterSound(){
          AudioManager audio = (AudioManager) mActivity.getSystemService(Context.AUDIO_SERVICE);
          switch( audio.getRingerMode() ){
              case AudioManager.RINGER_MODE_NORMAL:
                  MediaActionSound sound = new MediaActionSound();
                  sound.play(MediaActionSound.SHUTTER_CLICK);
                  break;
              case AudioManager.RINGER_MODE_SILENT:
                  break;
              case AudioManager.RINGER_MODE_VIBRATE:
                  break;
          }
      }
  
      public void waitSpinnerVisible() {
          mActivity.runOnUiThread(new Runnable() {
              @Override
              public void run() {
                  mWaitSpinner.setVisibility(View.VISIBLE);
                  WritableMap data = new WritableNativeMap();
                  data.putBoolean("processing", true);
                  mThis.processingListener.onProcessingChange(data);
              }
          });
      }
  
      public void waitSpinnerInvisible() {
          mActivity.runOnUiThread(new Runnable() {
              @Override
              public void run() {
  //                blinkView.setVisibility(View.INVISIBLE);
                  mWaitSpinner.setVisibility(View.INVISIBLE);
                  WritableMap data = new WritableNativeMap();
                  data.putBoolean("processing", false);
                  mThis.processingListener.onProcessingChange(data);
              }
          });
      }
  
      public void blinkScreen() {
          mActivity.runOnUiThread(new Runnable() {
              @Override
              public void run() {
                  blinkView.bringToFront();
                  Animation animation = AnimationUtils.loadAnimation(mContext, R.anim.blink);
                  blinkView.startAnimation(animation);
                  blinkView.setVisibility(View.INVISIBLE);
  
              }
          });
      }
  
      public boolean requestManualFocus(){
        PackageManager pm = mActivity.getPackageManager();
        if(safeToTakePicture){
          safeToTakePicture = false;
          if(pm.hasSystemFeature(PackageManager.FEATURE_CAMERA_AUTOFOCUS)) {
            mCamera.autoFocus(new Camera.AutoFocusCallback() {
              @Override
              public void onAutoFocus(boolean success, Camera camera) {
                Log.d(TAG,"Manual focus => " + success);
              }
            });
          }
          safeToTakePicture = true;
          return true;
        }
        return false;
      }
  
      public boolean requestManualPicture(){
          this.blinkScreenAndShutterSound();
          this.waitSpinnerVisible();
  
          if(safeToTakePicture){
  
              safeToTakePicture = false;
  
              try{
                  mCamera.takePicture(null,null, pCallback);
              }catch(Exception e){
                  this.waitSpinnerInvisible();
              }
              return true;
          }
          return false;
      }
  
      public boolean requestPicture() {
          PackageManager pm = mActivity.getPackageManager();
          if (safeToTakePicture) {
  
              safeToTakePicture = false;
  
              try {
                  if(pm.hasSystemFeature(PackageManager.FEATURE_CAMERA_AUTOFOCUS)){
                      mCamera.autoFocus(new Camera.AutoFocusCallback() {
                          @Override
                          public void onAutoFocus(boolean success, Camera camera) {
                              if (success) {
                                  mCamera.takePicture(null, null, pCallback);
                                  blinkScreen();
                                  blinkScreenAndShutterSound();
                              }
                              if (attemptToFocus) {
                                  return;
                              } else {
                                  attemptToFocus = true;
                              }
                          }
                      });
                  }else{
                      mCamera.takePicture(null,null,pCallback);
                      blinkScreen();
                      blinkScreenAndShutterSound();
                  }
              }catch(Exception e){
                  waitSpinnerInvisible();
              }finally {
                  waitSpinnerInvisible();
              }
              return true;
          }
          return false;
      }
  
  
      public void saveDocument(ScannedDocument scannedDocument) {
  
          Mat doc = (scannedDocument.processed != null) ? scannedDocument.processed : scannedDocument.original;
  
          Intent intent = mActivity.getIntent();
          String fileName;
          boolean isIntent = false;
          Uri fileUri = null;
  
          if (intent.getAction()!=null && intent.getAction().equals("android.media.action.IMAGE_CAPTURE")) {
              fileUri = ((Uri) intent.getParcelableExtra(MediaStore.EXTRA_OUTPUT));
              Log.d(TAG,"intent uri: " + fileUri.toString());
              try {
                  fileName = File.createTempFile("onsFile",".jpg", mContext.getCacheDir()).getPath();
              } catch (IOException e) {
                  e.printStackTrace();
                  return;
              }
              isIntent = true;
          } else {
              String folderName="documents";
              File folder = new File(Environment.getExternalStorageDirectory().toString()
                      + "/" + folderName );
              if (!folder.exists()) {
                  folder.mkdirs();
                  Log.d(TAG, "wrote: created folder "+folder.getPath());
              }
              fileName = Environment.getExternalStorageDirectory().toString()
                      + "/" + folderName + "/DOC-"
                      + new SimpleDateFormat("yyyyMMdd-HHmmss").format(new Date())
                      + ".jpg";
          }
          Mat endDoc = new Mat(Double.valueOf(doc.size().width).intValue(),
                  Double.valueOf(doc.size().height).intValue(), CvType.CV_8UC4);
  
          Core.flip(doc.t(), endDoc, 1);
  
          Imgcodecs.imwrite(fileName, endDoc);
  
  
          endDoc.release();
  
          WritableMap data = new WritableNativeMap();
          if(this.listener != null){
              data.putString("path",fileName);
              this.listener.onPictureTaken(data);
          }
  
          if (isIntent) {
              InputStream inputStream = null;
              OutputStream realOutputStream = null;
              try {
                  inputStream = new FileInputStream(fileName);
                  realOutputStream = mActivity.getContentResolver().openOutputStream(fileUri);
                  // Transfer bytes from in to out
                  byte [] buffer = new byte[1024];
                  int len;
                  while( (len = inputStream.read(buffer)) > 0 ) {
                      realOutputStream.write(buffer, 0, len);
                  }
              } catch (FileNotFoundException e) {
                  e.printStackTrace();
                  return;
              } catch (IOException e) {
                  e.printStackTrace();
                  return;
              } finally {
                  try {
                      inputStream.close();
                      realOutputStream.close();
                  } catch (IOException e) {
                      e.printStackTrace();
                  }
              }
  
          }
  
          Log.d(TAG, "wrote: " + fileName);
  
          if (isIntent) {
              new File(fileName).delete();
              mActivity.setResult(Activity.RESULT_OK, intent);
              mActivity.finish();
          } else {
              animateDocument(fileName,scannedDocument);
              addImageToGallery(fileName , mContext);
          }
  
          // Record goal "PictureTaken"
  //        ((OpenNoteScannerApplication) getApplication()).getTracker().trackGoal(1);
  
          refreshCamera();
  
      }
  
      private void animateDocument(String filename, ScannedDocument quadrilateral) {
  
          //if(!documentAnimation) return;
  
          AnimationRunnable runnable = new AnimationRunnable(filename,quadrilateral);
          mActivity.runOnUiThread(runnable);
          this.waitSpinnerInvisible();
  
      }
  
  
  
      class AnimationRunnable implements Runnable {
  
          private org.opencv.core.Size imageSize;
          private Point[] previewPoints =null;
          public org.opencv.core.Size previewSize = null;
          public String fileName = null;
          public int width;
          public int height;
          private Bitmap bitmap;
  
          public AnimationRunnable(String filename, ScannedDocument document) {
              this.fileName = filename;
              this.imageSize = document.processed.size();
  
              if (document.quadrilateral != null) {
                  this.previewPoints = document.previewPoints;
                  this.previewSize = document.previewSize;
              }
          }
  
          public double hipotenuse(Point a , Point b) {
              return Math.sqrt( Math.pow(a.x - b.x , 2 ) + Math.pow(a.y - b.y , 2 ));
          }
  
          @Override
          public void run() {
              final ImageView imageView = (ImageView) mView.findViewById(R.id.scannedAnimation);
  
              Display display = mActivity.getWindowManager().getDefaultDisplay();
              android.graphics.Point size = new android.graphics.Point();
              display.getRealSize(size);
  
              int width = Math.min(size.x, size.y);
              int height = Math.max(size.x, size.y);
  
              // ATENTION: captured images are always in landscape, values should be swapped
              double imageWidth = imageSize.height;
              double imageHeight = imageSize.width;
  
              RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) imageView.getLayoutParams();
  
              if (previewPoints != null) {
                  double documentLeftHeight = hipotenuse(previewPoints[0], previewPoints[1]);
                  double documentBottomWidth = hipotenuse(previewPoints[1], previewPoints[2]);
                  double documentRightHeight = hipotenuse(previewPoints[2], previewPoints[3]);
                  double documentTopWidth = hipotenuse(previewPoints[3], previewPoints[0]);
  
                  double documentWidth = Math.max(documentTopWidth, documentBottomWidth);
                  double documentHeight = Math.max(documentLeftHeight, documentRightHeight);
  
                  Log.d(TAG, "device: " + width + "x" + height + " image: " + imageWidth + "x" + imageHeight + " document: " + documentWidth + "x" + documentHeight);
  
  
                  Log.d(TAG, "previewPoints[0] x=" + previewPoints[0].x + " y=" + previewPoints[0].y);
                  Log.d(TAG, "previewPoints[1] x=" + previewPoints[1].x + " y=" + previewPoints[1].y);
                  Log.d(TAG, "previewPoints[2] x=" + previewPoints[2].x + " y=" + previewPoints[2].y);
                  Log.d(TAG, "previewPoints[3] x=" + previewPoints[3].x + " y=" + previewPoints[3].y);
  
                  // ATENTION: again, swap width and height
                  double xRatio = width / previewSize.height;
                  double yRatio = height / previewSize.width;
  
                  params.topMargin = (int) (previewPoints[3].x * yRatio);
                  params.leftMargin = (int) ( (previewSize.height - previewPoints[3].y ) * xRatio);
                  params.width = (int) (documentWidth * xRatio);
                  params.height = (int) (documentHeight * yRatio);
              } else {
                  params.topMargin = height/4;
                  params.leftMargin = width/4;
                  params.width = width/2;
                  params.height = height/2;
              }
  
              bitmap = decodeSampledBitmapFromUri(fileName, params.width, params.height);
  
              imageView.setImageBitmap(bitmap);
  
              imageView.setVisibility(View.VISIBLE);
  
              TranslateAnimation translateAnimation = new TranslateAnimation(
                      Animation.ABSOLUTE , 0 , Animation.ABSOLUTE , -params.leftMargin ,
                      Animation.ABSOLUTE , 0 , Animation.ABSOLUTE , height-params.topMargin
              );
  
              ScaleAnimation scaleAnimation = new ScaleAnimation(1, 0, 1, 0);
  
              AnimationSet animationSet = new AnimationSet(true);
  
              animationSet.addAnimation(scaleAnimation);
              animationSet.addAnimation(translateAnimation);
  
              animationSet.setDuration(600);
              animationSet.setInterpolator(new AccelerateInterpolator());
  
              animationSet.setAnimationListener(new Animation.AnimationListener() {
                  @Override
                  public void onAnimationStart(Animation animation) {
  
                  }
  
                  @Override
                  public void onAnimationEnd(Animation animation) {
                      imageView.setVisibility(View.INVISIBLE);
                      imageView.setImageBitmap(null);
                      AnimationRunnable.this.bitmap.recycle();
                  }
  
                  @Override
                  public void onAnimationRepeat(Animation animation) {
  
                  }
              });
  
  
              imageView.startAnimation(animationSet);
  
          }
      }
  
      public List<String> getEffectList() {
          return mCamera.getParameters().getSupportedColorEffects();
      }
  
      public boolean isEffectSupported() {
          return (mCamera.getParameters().getColorEffect() != null);
      }
  
      public boolean isEffectSupported(String effect) {
          List<String> effectList = getEffectList();
          for(String str: effectList) {
              if(str.trim().contains(effect))
                  return true;
          }
          return false;
      }
  
      public String getEffect() {
          return mCamera.getParameters().getColorEffect();
      }
  
      public void setEffect(String effect) {
          Camera.Parameters params = mCamera.getParameters();
          params.setColorEffect(effect);
          mCamera.setParameters(params);
      }
  
      public List<Size> getResolutionList() {
          return mCamera.getParameters().getSupportedPreviewSizes();
      }
  
      public List<Size> getPictureResolutionList() {
          return mCamera.getParameters().getSupportedPictureSizes();
      }
  
      public void setMaxPictureResolution() {
          int maxWidth=0;
          Size curRes=null;
          for ( Size r: getPictureResolutionList() ) {
              Log.d(TAG,"supported picture resolution: "+r.width+"x"+r.height);
              if (r.width>maxWidth) {
                  maxWidth=r.width;
                  curRes=r;
              }
          }
  
          if (curRes!=null) {
              Camera.Parameters parameters = mCamera.getParameters();
              parameters.setPictureSize(curRes.width, curRes.height);
              mCamera.setParameters(parameters);
              Log.d(TAG, "selected picture resolution: " + curRes.width + "x" + curRes.height);
          }
  
          return;
      }
  
  
  
      public void setMaxPreviewResolution() {
          int maxWidth=0;
          Size curRes=null;
  
          mCamera.lock();
  
          for ( Size r: getResolutionList() ) {
              if (r.width>maxWidth) {
                  Log.d(TAG,"supported preview resolution: "+r.width+"x"+r.height);
                  maxWidth=r.width;
                  curRes=r;
              }
          }
  
          if (curRes!=null) {
              setResolution(curRes);
              Log.d(TAG, "selected preview resolution: " + curRes.width + "x" + curRes.height);
          }
  
          return;
      }
  
      public void setResolution(Size resolution) {
          disconnectCamera();
          mMaxHeight = resolution.height;
          mMaxWidth = resolution.width;
          connectCamera(getWidth(), getHeight());
          Log.d(TAG,"resolution: "+resolution.width+" x "+resolution.height);
      }
  
      public Size getResolution() {
          return mCamera.getParameters().getPreviewSize();
      }
  
  
      public void setFlash(boolean stateFlash) {
          /* */
          Camera.Parameters par = mCamera.getParameters();
          par.setFlashMode(stateFlash ? Camera.Parameters.FLASH_MODE_TORCH : Camera.Parameters.FLASH_MODE_OFF);
          mCamera.setParameters(par);
          Log.d(TAG,"flash: " + (stateFlash?"on":"off"));
          // */
      }
  
      @Override
      public void onPictureTaken(byte[] data, Camera camera) {
  
          Size pictureSize = camera.getParameters().getPictureSize();
  
          Mat mat = new Mat(new org.opencv.core.Size(pictureSize.width, pictureSize.height), CvType.CV_8U);
          mat.put(0, 0, data);
  
          setImageProcessorBusy(true);
          sendImageProcessorMessage("pictureTaken", mat);
  
          safeToTakePicture = true;
  
      }
  }
  