content: package org.opencv.android;
  
  import java.util.List;
  
  import org.opencv.R;
  import org.opencv.core.Mat;
  import org.opencv.core.Size;
  
  import android.app.Activity;
  import android.app.AlertDialog;
  import android.content.Context;
  import android.content.DialogInterface;
  import android.content.res.TypedArray;
  import android.graphics.Bitmap;
  import android.graphics.Canvas;
  import android.graphics.Rect;
  import android.util.AttributeSet;
  import android.util.Log;
  import android.view.SurfaceHolder;
  import android.view.SurfaceView;
  
  /**
   * This is a basic class, implementing the interaction with Camera and OpenCV library.
   * The main responsibility of it - is to control when camera can be enabled, process the frame,
   * call external listener to make any adjustments to the frame and then draw the resulting
   * frame to the screen.
   * The clients shall implement CvCameraViewListener.
   */
  public abstract class CameraBridgeViewBase extends SurfaceView implements SurfaceHolder.Callback {
  
      private static final String TAG = "CameraBridge";
      private static final int MAX_UNSPECIFIED = -1;
      private static final int STOPPED = 0;
      private static final int STARTED = 1;
  
      private int mState = STOPPED;
      private Bitmap mCacheBitmap;
      private CvCameraViewListener2 mListener;
      private boolean mSurfaceExist;
      private Object mSyncObject = new Object();
  
      protected int mFrameWidth;
      protected int mFrameHeight;
      protected int mMaxHeight;
      protected int mMaxWidth;
      protected float mScale = 0;
      protected int mPreviewFormat = RGBA;
      protected int mCameraIndex = CAMERA_ID_ANY;
      protected boolean mEnabled;
      protected FpsMeter mFpsMeter = null;
  
      public static final int CAMERA_ID_ANY   = -1;
      public static final int CAMERA_ID_BACK  = 99;
      public static final int CAMERA_ID_FRONT = 98;
      public static final int RGBA = 1;
      public static final int GRAY = 2;
  
      public CameraBridgeViewBase(Context context, int cameraId) {
          super(context);
          mCameraIndex = cameraId;
          getHolder().addCallback(this);
          mMaxWidth = MAX_UNSPECIFIED;
          mMaxHeight = MAX_UNSPECIFIED;
      }
  
      public CameraBridgeViewBase(Context context, AttributeSet attrs) {
          super(context, attrs);
  
          int count = attrs.getAttributeCount();
          Log.d(TAG, "Attr count: " + Integer.valueOf(count));
  
          TypedArray styledAttrs = getContext().obtainStyledAttributes(attrs, R.styleable.CameraBridgeViewBase);
          if (styledAttrs.getBoolean(R.styleable.CameraBridgeViewBase_show_fps, false))
              enableFpsMeter();
  
          mCameraIndex = styledAttrs.getInt(R.styleable.CameraBridgeViewBase_camera_id, -1);
  
          getHolder().addCallback(this);
          mMaxWidth = MAX_UNSPECIFIED;
          mMaxHeight = MAX_UNSPECIFIED;
          styledAttrs.recycle();
      }
  
      /**
       * Sets the camera index
       * @param cameraIndex new camera index
       */
      public void setCameraIndex(int cameraIndex) {
          this.mCameraIndex = cameraIndex;
      }
  
      public interface CvCameraViewListener {
          /**
           * This method is invoked when camera preview has started. After this method is invoked
           * the frames will start to be delivered to client via the onCameraFrame() callback.
           * @param width -  the width of the frames that will be delivered
           * @param height - the height of the frames that will be delivered
           */
          public void onCameraViewStarted(int width, int height);
  
          /**
           * This method is invoked when camera preview has been stopped for some reason.
           * No frames will be delivered via onCameraFrame() callback after this method is called.
           */
          public void onCameraViewStopped();
  
          /**
           * This method is invoked when delivery of the frame needs to be done.
           * The returned values - is a modified frame which needs to be displayed on the screen.
           * TODO: pass the parameters specifying the format of the frame (BPP, YUV or RGB and etc)
           */
          public Mat onCameraFrame(Mat inputFrame);
      }
  
      public interface CvCameraViewListener2 {
          /**
           * This method is invoked when camera preview has started. After this method is invoked
           * the frames will start to be delivered to client via the onCameraFrame() callback.
           * @param width -  the width of the frames that will be delivered
           * @param height - the height of the frames that will be delivered
           */
          public void onCameraViewStarted(int width, int height);
  
          /**
           * This method is invoked when camera preview has been stopped for some reason.
           * No frames will be delivered via onCameraFrame() callback after this method is called.
           */
          public void onCameraViewStopped();
  
          /**
           * This method is invoked when delivery of the frame needs to be done.
           * The returned values - is a modified frame which needs to be displayed on the screen.
           * TODO: pass the parameters specifying the format of the frame (BPP, YUV or RGB and etc)
           */
          public Mat onCameraFrame(CvCameraViewFrame inputFrame);
      };
  
      protected class CvCameraViewListenerAdapter implements CvCameraViewListener2  {
          public CvCameraViewListenerAdapter(CvCameraViewListener oldStypeListener) {
              mOldStyleListener = oldStypeListener;
          }
  
          public void onCameraViewStarted(int width, int height) {
              mOldStyleListener.onCameraViewStarted(width, height);
          }
  
          public void onCameraViewStopped() {
              mOldStyleListener.onCameraViewStopped();
          }
  
          public Mat onCameraFrame(CvCameraViewFrame inputFrame) {
               Mat result = null;
               switch (mPreviewFormat) {
                  case RGBA:
                      result = mOldStyleListener.onCameraFrame(inputFrame.rgba());
                      break;
                  case GRAY:
                      result = mOldStyleListener.onCameraFrame(inputFrame.gray());
                      break;
                  default:
                      Log.e(TAG, "Invalid frame format! Only RGBA and Gray Scale are supported!");
              };
  
              return result;
          }
  
          public void setFrameFormat(int format) {
              mPreviewFormat = format;
          }
  
          private int mPreviewFormat = RGBA;
          private CvCameraViewListener mOldStyleListener;
      };
  
      /**
       * This class interface is abstract representation of single frame from camera for onCameraFrame callback
       * Attention: Do not use objects, that represents this interface out of onCameraFrame callback!
       */
      public interface CvCameraViewFrame {
  
          /**
           * This method returns RGBA Mat with frame
           */
          public Mat rgba();
  
          /**
           * This method returns single channel gray scale Mat with frame
           */
          public Mat gray();
      };
  
      public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) {
          Log.d(TAG, "call surfaceChanged event");
          synchronized(mSyncObject) {
              if (!mSurfaceExist) {
                  mSurfaceExist = true;
                  checkCurrentState();
              } else {
                  /** Surface changed. We need to stop camera and restart with new parameters */
                  /* Pretend that old surface has been destroyed */
                  mSurfaceExist = false;
                  checkCurrentState();
                  /* Now use new surface. Say we have it now */
                  mSurfaceExist = true;
                  checkCurrentState();
              }
          }
      }
  
      public void surfaceCreated(SurfaceHolder holder) {
          /* Do nothing. Wait until surfaceChanged delivered */
      }
  
      public void surfaceDestroyed(SurfaceHolder holder) {
          synchronized(mSyncObject) {
              mSurfaceExist = false;
              checkCurrentState();
          }
      }
  
      /**
       * This method is provided for clients, so they can enable the camera connection.
       * The actual onCameraViewStarted callback will be delivered only after both this method is called and surface is available
       */
      public void enableView() {
          synchronized(mSyncObject) {
              mEnabled = true;
              checkCurrentState();
          }
      }
  
      /**
       * This method is provided for clients, so they can disable camera connection and stop
       * the delivery of frames even though the surface view itself is not destroyed and still stays on the scren
       */
      public void disableView() {
          synchronized(mSyncObject) {
              mEnabled = false;
              checkCurrentState();
          }
      }
  
      /**
       * This method enables label with fps value on the screen
       */
      public void enableFpsMeter() {
          if (mFpsMeter == null) {
              mFpsMeter = new FpsMeter();
              mFpsMeter.setResolution(mFrameWidth, mFrameHeight);
          }
      }
  
      public void disableFpsMeter() {
              mFpsMeter = null;
      }
  
      /**
       *
       * @param listener
       */
  
      public void setCvCameraViewListener(CvCameraViewListener2 listener) {
          mListener = listener;
      }
  
      public void setCvCameraViewListener(CvCameraViewListener listener) {
          CvCameraViewListenerAdapter adapter = new CvCameraViewListenerAdapter(listener);
          adapter.setFrameFormat(mPreviewFormat);
          mListener = adapter;
      }
  
      /**
       * This method sets the maximum size that camera frame is allowed to be. When selecting
       * size - the biggest size which less or equal the size set will be selected.
       * As an example - we set setMaxFrameSize(200,200) and we have 176x152 and 320x240 sizes. The
       * preview frame will be selected with 176x152 size.
       * This method is useful when need to restrict the size of preview frame for some reason (for example for video recording)
       * @param maxWidth - the maximum width allowed for camera frame.
       * @param maxHeight - the maximum height allowed for camera frame
       */
      public void setMaxFrameSize(int maxWidth, int maxHeight) {
          mMaxWidth = maxWidth;
          mMaxHeight = maxHeight;
      }
  
      public void SetCaptureFormat(int format)
      {
          mPreviewFormat = format;
          if (mListener instanceof CvCameraViewListenerAdapter) {
              CvCameraViewListenerAdapter adapter = (CvCameraViewListenerAdapter) mListener;
              adapter.setFrameFormat(mPreviewFormat);
          }
      }
  
      /**
       * Called when mSyncObject lock is held
       */
      private void checkCurrentState() {
          Log.d(TAG, "call checkCurrentState");
          int targetState;
  
          if (mEnabled && mSurfaceExist && getVisibility() == VISIBLE) {
              targetState = STARTED;
          } else {
              targetState = STOPPED;
          }
  
          if (targetState != mState) {
              /* The state change detected. Need to exit the current state and enter target state */
              processExitState(mState);
              mState = targetState;
              processEnterState(mState);
          }
      }
  
      private void processEnterState(int state) {
          Log.d(TAG, "call processEnterState: " + state);
          switch(state) {
          case STARTED:
              onEnterStartedState();
              if (mListener != null) {
                  mListener.onCameraViewStarted(mFrameWidth, mFrameHeight);
              }
              break;
          case STOPPED:
              onEnterStoppedState();
              if (mListener != null) {
                  mListener.onCameraViewStopped();
              }
              break;
          };
      }
  
      private void processExitState(int state) {
          Log.d(TAG, "call processExitState: " + state);
          switch(state) {
          case STARTED:
              onExitStartedState();
              break;
          case STOPPED:
              onExitStoppedState();
              break;
          };
      }
  
      private void onEnterStoppedState() {
          /* nothing to do */
      }
  
      private void onExitStoppedState() {
          /* nothing to do */
      }
  
      // NOTE: The order of bitmap constructor and camera connection is important for android 4.1.x
      // Bitmap must be constructed before surface
      private void onEnterStartedState() {
          Log.d(TAG, "call onEnterStartedState");
          /* Connect camera */
          if (!connectCamera(getWidth(), getHeight())) {
              AlertDialog ad = new AlertDialog.Builder(getContext()).create();
              ad.setCancelable(false); // This blocks the 'BACK' button
              ad.setMessage("It seems that you device does not support camera (or it is locked). Application will be closed.");
              ad.setButton(DialogInterface.BUTTON_NEUTRAL,  "OK", new DialogInterface.OnClickListener() {
                  public void onClick(DialogInterface dialog, int which) {
                      dialog.dismiss();
                      ((Activity) getContext()).finish();
                  }
              });
              ad.show();
  
          }
      }
  
      private void onExitStartedState() {
          disconnectCamera();
          if (mCacheBitmap != null) {
              mCacheBitmap.recycle();
          }
      }
  
      /**
       * This method shall be called by the subclasses when they have valid
       * object and want it to be delivered to external client (via callback) and
       * then displayed on the screen.
       * @param frame - the current frame to be delivered
       */
      protected void deliverAndDrawFrame(CvCameraViewFrame frame) {
          Mat modified;
  
          if (mListener != null) {
              modified = mListener.onCameraFrame(frame);
          } else {
              modified = frame.rgba();
          }
  
          boolean bmpValid = true;
          if (modified != null) {
              try {
                  Utils.matToBitmap(modified, mCacheBitmap);
              } catch(Exception e) {
                  Log.e(TAG, "Mat type: " + modified);
                  Log.e(TAG, "Bitmap type: " + mCacheBitmap.getWidth() + "*" + mCacheBitmap.getHeight());
                  Log.e(TAG, "Utils.matToBitmap() throws an exception: " + e.getMessage());
                  bmpValid = false;
              }
          }
  
          if (bmpValid && mCacheBitmap != null) {
              Canvas canvas = getHolder().lockCanvas();
              if (canvas != null) {
                  canvas.drawColor(0, android.graphics.PorterDuff.Mode.CLEAR);
                  Log.d(TAG, "mStretch value: " + mScale);
  
                  if (mScale != 0) {
                      canvas.drawBitmap(mCacheBitmap, new Rect(0,0,mCacheBitmap.getWidth(), mCacheBitmap.getHeight()),
                           new Rect((int)((canvas.getWidth() - mScale*mCacheBitmap.getWidth()) / 2),
                           (int)((canvas.getHeight() - mScale*mCacheBitmap.getHeight()) / 2),
                           (int)((canvas.getWidth() - mScale*mCacheBitmap.getWidth()) / 2 + mScale*mCacheBitmap.getWidth()),
                           (int)((canvas.getHeight() - mScale*mCacheBitmap.getHeight()) / 2 + mScale*mCacheBitmap.getHeight())), null);
                  } else {
                       canvas.drawBitmap(mCacheBitmap, new Rect(0,0,mCacheBitmap.getWidth(), mCacheBitmap.getHeight()),
                           new Rect((canvas.getWidth() - mCacheBitmap.getWidth()) / 2,
                           (canvas.getHeight() - mCacheBitmap.getHeight()) / 2,
                           (canvas.getWidth() - mCacheBitmap.getWidth()) / 2 + mCacheBitmap.getWidth(),
                           (canvas.getHeight() - mCacheBitmap.getHeight()) / 2 + mCacheBitmap.getHeight()), null);
                  }
  
                  if (mFpsMeter != null) {
                      mFpsMeter.measure();
                      mFpsMeter.draw(canvas, 20, 30);
                  }
                  getHolder().unlockCanvasAndPost(canvas);
              }
          }
      }
  
      /**
       * This method is invoked shall perform concrete operation to initialize the camera.
       * CONTRACT: as a result of this method variables mFrameWidth and mFrameHeight MUST be
       * initialized with the size of the Camera frames that will be delivered to external processor.
       * @param width - the width of this SurfaceView
       * @param height - the height of this SurfaceView
       */
      protected abstract boolean connectCamera(int width, int height);
  
      /**
       * Disconnects and release the particular camera object being connected to this surface view.
       * Called when syncObject lock is held
       */
      protected abstract void disconnectCamera();
  
      // NOTE: On Android 4.1.x the function must be called before SurfaceTexture constructor!
      protected void AllocateCache()
      {
          mCacheBitmap = Bitmap.createBitmap(mFrameWidth, mFrameHeight, Bitmap.Config.ARGB_8888);
      }
  
      public interface ListItemAccessor {
          public int getWidth(Object obj);
          public int getHeight(Object obj);
      };
  
      /**
       * This helper method can be called by subclasses to select camera preview size.
       * It goes over the list of the supported preview sizes and selects the maximum one which
       * fits both values set via setMaxFrameSize() and surface frame allocated for this view
       * @param supportedSizes
       * @param surfaceWidth
       * @param surfaceHeight
       * @return optimal frame size
       */
      protected Size calculateCameraFrameSize(List<?> supportedSizes, ListItemAccessor accessor, int surfaceWidth, int surfaceHeight) {
          int calcWidth = 0;
          int calcHeight = 0;
  
          int maxAllowedWidth = (mMaxWidth != MAX_UNSPECIFIED && mMaxWidth < surfaceWidth)? mMaxWidth : surfaceWidth;
          int maxAllowedHeight = (mMaxHeight != MAX_UNSPECIFIED && mMaxHeight < surfaceHeight)? mMaxHeight : surfaceHeight;
  
          for (Object size : supportedSizes) {
              int width = accessor.getWidth(size);
              int height = accessor.getHeight(size);
  
              if (width <= maxAllowedWidth && height <= maxAllowedHeight) {
                  if (width >= calcWidth && height >= calcHeight) {
                      calcWidth = (int) width;
                      calcHeight = (int) height;
                  }
              }
          }
  
          return new Size(calcWidth, calcHeight);
      }
  }
  