content: /*
   * Copyright 2014 David Lázaro Esparcia.
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  package com.alipay.hulu.ui;
  
  import android.content.Context;
  import android.content.pm.PackageManager;
  import android.graphics.Point;
  import android.graphics.PointF;
  import android.graphics.Rect;
  import android.graphics.RectF;
  import android.hardware.Camera;
  import android.os.AsyncTask;
  import android.util.AttributeSet;
  import android.util.Log;
  import android.view.MotionEvent;
  import android.view.Surface;
  import android.view.SurfaceHolder;
  import android.view.SurfaceView;
  import android.view.WindowManager;
  
  import com.alipay.hulu.ui.scan.Orientation;
  import com.alipay.hulu.ui.scan.QRToViewPointTransformer;
  import com.alipay.hulu.ui.scan.camera.CameraManager;
  import com.google.zxing.BarcodeFormat;
  import com.google.zxing.BinaryBitmap;
  import com.google.zxing.DecodeHintType;
  import com.google.zxing.MultiFormatReader;
  import com.google.zxing.NotFoundException;
  import com.google.zxing.PlanarYUVLuminanceSource;
  import com.google.zxing.Result;
  import com.google.zxing.ResultPoint;
  import com.google.zxing.common.HybridBinarizer;
  
  import java.io.IOException;
  import java.lang.ref.WeakReference;
  import java.util.ArrayList;
  import java.util.List;
  import java.util.Map;
  
  import static android.hardware.Camera.getCameraInfo;
  
  /**
   * AnyCodeReaderView Class which uses ZXING lib and let you easily integrate a QR decoder view.
   * Take some classes and made some modifications in the original ZXING - Barcode Scanner project.
   *
   * @author David Lázaro
   */
  public class AnyCodeReaderView extends SurfaceView
          implements SurfaceHolder.Callback, Camera.PreviewCallback {
  
      public interface OnCodeReadListener {
  
          void onCodeRead(BarcodeFormat format, String text, PointF[] points);
      }
  
      private OnCodeReadListener mOnCodeReadListener;
  
      private static final String TAG = AnyCodeReaderView.class.getName();
  
      private MultiFormatReader mCodeReader;
      private int mPreviewWidth;
      private int mPreviewHeight;
      private CameraManager mCameraManager;
      private boolean mQrDecodingEnabled = true;
      private DecodeFrameTask decodeFrameTask;
      private Map<DecodeHintType, Object> decodeHints;
  
      public AnyCodeReaderView(Context context) {
          this(context, null);
      }
  
      public AnyCodeReaderView(Context context, AttributeSet attrs) {
          super(context, attrs);
  
          if (isInEditMode()) {
              return;
          }
  
          if (checkCameraHardware()) {
              mCameraManager = new CameraManager(getContext());
              mCameraManager.setPreviewCallback(this);
              getHolder().addCallback(this);
              setBackCamera();
          } else {
              throw new RuntimeException("Error: Camera not found");
          }
      }
  
      /**
       * Set the callback to return decoding result
       *
       * @param onCodeReadListener the listener
       */
      public void setOnCodeReadListener(OnCodeReadListener onCodeReadListener) {
          mOnCodeReadListener = onCodeReadListener;
      }
  
      /**
       * Set QR decoding enabled/disabled.
       * default value is true
       *
       * @param qrDecodingEnabled decoding enabled/disabled.
       */
      public void setQRDecodingEnabled(boolean qrDecodingEnabled) {
          this.mQrDecodingEnabled = qrDecodingEnabled;
      }
  
      /**
       * Set QR hints required for decoding
       *
       * @param decodeHints hints for decoding qrcode
       */
      public void setDecodeHints(Map<DecodeHintType, Object> decodeHints) {
          this.decodeHints = decodeHints;
      }
  
      /**
       * Starts camera preview and decoding
       */
      public void startCamera() {
          mCameraManager.startPreview();
      }
  
      /**
       * Stop camera preview and decoding
       */
      public void stopCamera() {
          mCameraManager.stopPreview();
      }
  
      /**
       * Set Camera autofocus interval value
       * default value is 5000 ms.
       *
       * @param autofocusIntervalInMs autofocus interval value
       */
      public void setAutofocusInterval(long autofocusIntervalInMs) {
          if (mCameraManager != null) {
              mCameraManager.setAutofocusInterval(autofocusIntervalInMs);
          }
      }
  
      /**
       * Trigger an auto focus
       */
      public void forceAutoFocus() {
          if (mCameraManager != null) {
              mCameraManager.forceAutoFocus();
          }
      }
  
      /**
       * Set Torch enabled/disabled.
       * default value is false
       *
       * @param enabled torch enabled/disabled.
       */
      public void setTorchEnabled(boolean enabled) {
          if (mCameraManager != null) {
              mCameraManager.setTorchEnabled(enabled);
          }
      }
  
      /**
       * Allows user to specify the camera ID, rather than determine
       * it automatically based on available cameras and their orientation.
       *
       * @param cameraId camera ID of the camera to use. A negative value means "no preference".
       */
      public void setPreviewCameraId(int cameraId) {
          mCameraManager.setPreviewCameraId(cameraId);
      }
  
      /**
       * Camera preview from device back camera
       */
      public void setBackCamera() {
          setPreviewCameraId(Camera.CameraInfo.CAMERA_FACING_BACK);
      }
  
      /**
       * Camera preview from device front camera
       */
      public void setFrontCamera() {
          setPreviewCameraId(Camera.CameraInfo.CAMERA_FACING_FRONT);
      }
  
      private float oldDist = 1f;
  
      @Override
      public boolean onTouchEvent(MotionEvent event) {
          Camera camera = mCameraManager.getOpenCamera().getCamera();
          if (event.getPointerCount() == 1) {
              handleFocusMetering(event, camera);
          } else {
              switch (event.getAction() & MotionEvent.ACTION_MASK) {
                  case MotionEvent.ACTION_POINTER_DOWN:
                      oldDist = getFingerSpacing(event);
                      break;
                  case MotionEvent.ACTION_MOVE:
                      float newDist = getFingerSpacing(event);
                      if (newDist > oldDist) {
                          Log.e("Camera","进入放大手势");
                          handleZoom(true, camera);
                      } else if (newDist < oldDist) {
                          Log.e("Camera","进入缩小手势");
                          handleZoom(false, camera);
                      }
                      oldDist = newDist;
                      break;
              }
          }
          return true;
      }
  
      private void handleZoom(boolean isZoomIn, Camera camera) {
          Log.e("Camera","进入缩小放大方法");
          Camera.Parameters params = camera.getParameters();
          if (params.isZoomSupported()) {
              int maxZoom = params.getMaxZoom();
              int zoom = params.getZoom();
              if (isZoomIn && zoom < maxZoom) {
                  Log.e("Camera","进入放大方法zoom="+zoom);
                  zoom++;
              } else if (zoom > 0) {
                  Log.e("Camera","进入缩小方法zoom="+zoom);
                  zoom--;
              }
              params.setZoom(zoom);
              camera.setParameters(params);
          } else {
              Log.i(TAG, "zoom not supported");
          }
      }
  
      private static void handleFocusMetering(MotionEvent event, Camera camera) {
          Log.e("Camera","进入handleFocusMetering");
          Camera.Parameters params = camera.getParameters();
  
          Camera.Size previewSize = params.getPreviewSize();
          Rect focusRect = calculateTapArea(event.getX(), event.getY(), 1f, previewSize);
          Rect meteringRect = calculateTapArea(event.getX(), event.getY(), 1.5f, previewSize);
  
          camera.cancelAutoFocus();
  
          if (params.getMaxNumFocusAreas() > 0) {
              List<Camera.Area> focusAreas = new ArrayList<>();
              focusAreas.add(new Camera.Area(focusRect, 800));
              params.setFocusAreas(focusAreas);
          } else {
              Log.i(TAG, "focus areas not supported");
          }
          if (params.getMaxNumMeteringAreas() > 0) {
              List<Camera.Area> meteringAreas = new ArrayList<>();
              meteringAreas.add(new Camera.Area(meteringRect, 800));
              params.setMeteringAreas(meteringAreas);
          } else {
              Log.i(TAG, "metering areas not supported");
          }
          final String currentFocusMode = params.getFocusMode();
          params.setFocusMode(Camera.Parameters.FOCUS_MODE_MACRO);
          camera.setParameters(params);
  
          camera.autoFocus(new Camera.AutoFocusCallback() {
              @Override
              public void onAutoFocus(boolean success, Camera camera) {
                  Camera.Parameters params = camera.getParameters();
                  params.setFocusMode(currentFocusMode);
                  camera.setParameters(params);
              }
          });
      }
  
      private static float getFingerSpacing(MotionEvent event) {
          float x = event.getX(0) - event.getX(1);
          float y = event.getY(0) - event.getY(1);
          Log.e("Camera","getFingerSpacing ，计算距离 = " + (float) Math.sqrt(x * x + y * y));
          return (float) Math.sqrt(x * x + y * y);
      }
  
      private static Rect calculateTapArea(float x, float y, float coefficient, Camera.Size previewSize) {
          float focusAreaSize = 300;
          int areaSize = Float.valueOf(focusAreaSize * coefficient).intValue();
          int centerX = (int) (x / previewSize.width - 1000);
          int centerY = (int) (y / previewSize.height - 1000);
  
          int left = clamp(centerX - areaSize / 2, -1000, 1000);
          int top = clamp(centerY - areaSize / 2, -1000, 1000);
  
          RectF rectF = new RectF(left, top, left + areaSize, top + areaSize);
  
          return new Rect(Math.round(rectF.left), Math.round(rectF.top), Math.round(rectF.right), Math.round(rectF.bottom));
      }
  
      private static int clamp(int x, int min, int max) {
          if (x > max) {
              return max;
          }
          if (x < min) {
              return min;
          }
          return x;
      }
  
      @Override public void onDetachedFromWindow() {
          super.onDetachedFromWindow();
  
          if (decodeFrameTask != null) {
              decodeFrameTask.cancel(true);
              decodeFrameTask = null;
          }
      }
  
      /****************************************************
       * SurfaceHolder.Callback,Camera.PreviewCallback
       ****************************************************/
  
      @Override public void surfaceCreated(SurfaceHolder holder) {
          Log.d(TAG, "surfaceCreated");
  
          try {
              // Indicate camera, our View dimensions
              mCameraManager.openDriver(holder, this.getWidth(), this.getHeight());
          } catch (IOException | RuntimeException e) {
              Log.w(TAG, "Can not openDriver: " + e.getMessage());
              mCameraManager.closeDriver();
          }
  
          try {
              mCodeReader = new MultiFormatReader();
              mCameraManager.startPreview();
          } catch (Exception e) {
              Log.e(TAG, "Exception: " + e.getMessage());
              mCameraManager.closeDriver();
          }
      }
  
      @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
          Log.d(TAG, "surfaceChanged");
  
          if (holder.getSurface() == null) {
              Log.e(TAG, "Error: preview surface does not exist");
              return;
          }
  
          if (mCameraManager.getPreviewSize() == null) {
              Log.e(TAG, "Error: preview size does not exist");
              return;
          }
  
          mPreviewWidth = mCameraManager.getPreviewSize().x;
          mPreviewHeight = mCameraManager.getPreviewSize().y;
  
          mCameraManager.stopPreview();
  
          // Fix the camera sensor rotation
          mCameraManager.setPreviewCallback(this);
          mCameraManager.setDisplayOrientation(getCameraDisplayOrientation());
  
          mCameraManager.startPreview();
      }
  
      @Override public void surfaceDestroyed(SurfaceHolder holder) {
          Log.d(TAG, "surfaceDestroyed");
  
          mCameraManager.setPreviewCallback(null);
          mCameraManager.stopPreview();
          mCameraManager.closeDriver();
      }
  
      // Called when camera take a frame
      @Override public void onPreviewFrame(byte[] data, Camera camera) {
          if (!mQrDecodingEnabled || decodeFrameTask != null
                  && (decodeFrameTask.getStatus() == AsyncTask.Status.RUNNING
                  || decodeFrameTask.getStatus() == AsyncTask.Status.PENDING)) {
              return;
          }
  
          decodeFrameTask = new DecodeFrameTask(this, decodeHints);
          decodeFrameTask.execute(data);
      }
  
      /** Check if this device has a camera */
      private boolean checkCameraHardware() {
          if (getContext().getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA)) {
              // this device has a camera
              return true;
          } else if (getContext().getPackageManager()
                  .hasSystemFeature(PackageManager.FEATURE_CAMERA_FRONT)) {
              // this device has a front camera
              return true;
          } else {
              // this device has any camera
              return getContext().getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY);
          }
      }
  
      /**
       * Fix for the camera Sensor on some devices (ex.: Nexus 5x)
       */
      @SuppressWarnings("deprecation") private int getCameraDisplayOrientation() {
          if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.GINGERBREAD) {
              return 90;
          }
  
          Camera.CameraInfo info = new Camera.CameraInfo();
          getCameraInfo(mCameraManager.getPreviewCameraId(), info);
          WindowManager windowManager =
                  (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
          int rotation = windowManager.getDefaultDisplay().getRotation();
          int degrees = 0;
          switch (rotation) {
              case Surface.ROTATION_0:
                  degrees = 0;
                  break;
              case Surface.ROTATION_90:
                  degrees = 90;
                  break;
              case Surface.ROTATION_180:
                  degrees = 180;
                  break;
              case Surface.ROTATION_270:
                  degrees = 270;
                  break;
              default:
                  break;
          }
  
          int result;
          if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
              result = (info.orientation + degrees) % 360;
              result = (360 - result) % 360;  // compensate the mirror
          } else {  // back-facing
              result = (info.orientation - degrees + 360) % 360;
          }
          return result;
      }
  
      private static class DecodeFrameTask extends AsyncTask<byte[], Void, Result> {
  
          private final WeakReference<AnyCodeReaderView> viewRef;
          private final WeakReference<Map<DecodeHintType, Object>> hintsRef;
          private final QRToViewPointTransformer qrToViewPointTransformer =
                  new QRToViewPointTransformer();
  
          public DecodeFrameTask(AnyCodeReaderView view, Map<DecodeHintType, Object> hints) {
              viewRef = new WeakReference<>(view);
              hintsRef = new WeakReference<>(hints);
          }
  
          @Override protected Result doInBackground(byte[]... params) {
              final AnyCodeReaderView view = viewRef.get();
              if (view == null) {
                  return null;
              }
  
              final PlanarYUVLuminanceSource source =
                      view.mCameraManager.buildLuminanceSource(params[0], view.mPreviewWidth,
                              view.mPreviewHeight);
  
              final HybridBinarizer hybBin = new HybridBinarizer(source);
              final BinaryBitmap bitmap = new BinaryBitmap(hybBin);
  
              try {
                  return view.mCodeReader.decode(bitmap, hintsRef.get());
              } catch (NotFoundException e) {
                  Log.d(TAG, "No QR Code found");
              } finally {
                  view.mCodeReader.reset();
              }
  
              return null;
          }
  
          @Override protected void onPostExecute(Result result) {
              super.onPostExecute(result);
  
              final AnyCodeReaderView view = viewRef.get();
  
              // Notify we found a QRCode
              if (view != null && result != null && view.mOnCodeReadListener != null) {
                  // Transform resultPoints to View coordinates
                  final PointF[] transformedPoints =
                          transformToViewCoordinates(view, result.getResultPoints());
                  view.mOnCodeReadListener.onCodeRead(result.getBarcodeFormat(), result.getText(), transformedPoints);
              }
          }
  
          /**
           * Transform result to surfaceView coordinates
           *
           * This method is needed because coordinates are given in landscape camera coordinates when
           * device is in portrait mode and different coordinates otherwise.
           *
           * @return a new PointF array with transformed points
           */
          private PointF[] transformToViewCoordinates(AnyCodeReaderView view, ResultPoint[] resultPoints) {
              int orientationDegrees = view.getCameraDisplayOrientation();
              Orientation orientation =
                      orientationDegrees == 90 || orientationDegrees == 270 ? Orientation.PORTRAIT
                              : Orientation.LANDSCAPE;
              Point viewSize = new Point(view.getWidth(), view.getHeight());
              Point cameraPreviewSize = view.mCameraManager.getPreviewSize();
              boolean isMirrorCamera =
                      view.mCameraManager.getPreviewCameraId() == Camera.CameraInfo.CAMERA_FACING_FRONT;
  
              return qrToViewPointTransformer.transform(resultPoints, isMirrorCamera, orientation, viewSize,
                      cameraPreviewSize);
          }
      }
  }
  