ifengouy 2 vuotta sitten
vanhempi
commit
cae360315f
36 muutettua tiedostoa jossa 2740 lisäystä ja 0 poistoa
  1. 34 0
      cameraui/build.gradle
  2. 26 0
      cameraui/src/androidTest/java/com/baidu/ai/cameraui/ExampleInstrumentedTest.java
  3. 11 0
      cameraui/src/main/AndroidManifest.xml
  4. 25 0
      cameraui/src/main/java/com/baidu/ai/cameraui/BankCardActivity.java
  5. 703 0
      cameraui/src/main/java/com/baidu/ai/cameraui/BaseActivity.java
  6. 25 0
      cameraui/src/main/java/com/baidu/ai/cameraui/GeneralActivity.java
  7. 37 0
      cameraui/src/main/java/com/baidu/ai/cameraui/IDcardCardActivity.java
  8. 20 0
      cameraui/src/main/java/com/baidu/ai/cameraui/camera/CameraListener.java
  9. 390 0
      cameraui/src/main/java/com/baidu/ai/cameraui/camera/CameraProxy1.java
  10. 53 0
      cameraui/src/main/java/com/baidu/ai/cameraui/camera/ICameraProxy.java
  11. 14 0
      cameraui/src/main/java/com/baidu/ai/cameraui/parameter/BankCardParameter.java
  12. 94 0
      cameraui/src/main/java/com/baidu/ai/cameraui/parameter/BaseParameter.java
  13. 15 0
      cameraui/src/main/java/com/baidu/ai/cameraui/parameter/GeneralParameter.java
  14. 15 0
      cameraui/src/main/java/com/baidu/ai/cameraui/parameter/IDcardParameter.java
  15. 89 0
      cameraui/src/main/java/com/baidu/ai/cameraui/util/AutoTrigger.java
  16. 57 0
      cameraui/src/main/java/com/baidu/ai/cameraui/util/CrashReporterHandler.java
  17. 176 0
      cameraui/src/main/java/com/baidu/ai/cameraui/util/ImageUtil.java
  18. 28 0
      cameraui/src/main/java/com/baidu/ai/cameraui/util/UiLog.java
  19. 390 0
      cameraui/src/main/java/com/baidu/ai/cameraui/view/CropView.java
  20. 259 0
      cameraui/src/main/java/com/baidu/ai/cameraui/view/MaskView.java
  21. 151 0
      cameraui/src/main/java/com/baidu/ai/cameraui/view/PreviewView.java
  22. BIN
      cameraui/src/main/res/drawable-xhdpi/album_btn.png
  23. BIN
      cameraui/src/main/res/drawable-xhdpi/cancel_btn.png
  24. BIN
      cameraui/src/main/res/drawable-xhdpi/confirm_btn.png
  25. BIN
      cameraui/src/main/res/drawable-xhdpi/flash_off_btn.png
  26. BIN
      cameraui/src/main/res/drawable-xhdpi/flash_on_btn.png
  27. BIN
      cameraui/src/main/res/drawable-xhdpi/id_card_locator_back.png
  28. BIN
      cameraui/src/main/res/drawable-xhdpi/id_card_locator_front.png
  29. BIN
      cameraui/src/main/res/drawable-xhdpi/rotate_picture_btn.png
  30. BIN
      cameraui/src/main/res/drawable-xhdpi/take_picture_btn.png
  31. 31 0
      cameraui/src/main/res/layout/action_bar_views.xml
  32. 40 0
      cameraui/src/main/res/layout/camera_view.xml
  33. 32 0
      cameraui/src/main/res/layout/preview_bar_views.xml
  34. 3 0
      cameraui/src/main/res/values/strings.xml
  35. 5 0
      cameraui/src/main/res/values/values.xml
  36. 17 0
      cameraui/src/test/java/com/baidu/ai/cameraui/ExampleUnitTest.java

+ 34 - 0
cameraui/build.gradle

@@ -0,0 +1,34 @@
+apply plugin: 'com.android.library'
+
+android {
+    compileSdkVersion 26
+
+
+
+    defaultConfig {
+        minSdkVersion 15
+        targetSdkVersion 26
+        versionCode 1
+        versionName "1.0"
+
+        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+
+    }
+
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+
+}
+
+dependencies {
+    implementation fileTree(dir: 'libs', include: ['*.jar'])
+
+    implementation 'com.android.support:appcompat-v7:26.1.0'
+    testImplementation 'junit:junit:4.12'
+    androidTestImplementation 'com.android.support.test:runner:1.0.2'
+    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
+}

+ 26 - 0
cameraui/src/androidTest/java/com/baidu/ai/cameraui/ExampleInstrumentedTest.java

@@ -0,0 +1,26 @@
+package com.baidu.ai.cameraui;
+
+import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
+ */
+@RunWith(AndroidJUnit4.class)
+public class ExampleInstrumentedTest {
+    @Test
+    public void useAppContext() throws Exception {
+        // Context of the app under test.
+        Context appContext = InstrumentationRegistry.getTargetContext();
+
+        assertEquals("com.baidu.ai.cameraui.test", appContext.getPackageName());
+    }
+}

+ 11 - 0
cameraui/src/main/AndroidManifest.xml

@@ -0,0 +1,11 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.baidu.ai.cameraui" >
+    <uses-permission android:name="android.permission.CAMERA"/>
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+    <uses-feature android:name="android.hardware.camera" />
+    <uses-feature android:name="android.hardware.camera.autofocus" />
+<!--    <application-->
+<!--        android:theme="@style/Theme.AppCompat.Light.NoActionBar">-->
+<!--    </application>-->
+
+</manifest>

+ 25 - 0
cameraui/src/main/java/com/baidu/ai/cameraui/BankCardActivity.java

@@ -0,0 +1,25 @@
+package com.baidu.ai.cameraui;
+
+
+
+import android.os.Bundle;
+
+import androidx.annotation.Nullable;
+
+import com.baidu.ai.cameraui.parameter.BankCardParameter;
+import com.baidu.ai.cameraui.parameter.BaseParameter;
+
+
+/**
+ * Created by ruanshimin on 2018/7/2.
+ */
+
+public abstract class BankCardActivity extends BaseActivity {
+
+    @Override
+    protected void onDispatchCreate(@Nullable Bundle savedInstanceState) {
+        BankCardParameter parameter = new BankCardParameter();
+        parameter.setDefaultTip("请将卡片放入框内");
+        setParameter(parameter);
+    }
+}

+ 703 - 0
cameraui/src/main/java/com/baidu/ai/cameraui/BaseActivity.java

@@ -0,0 +1,703 @@
+package com.baidu.ai.cameraui;
+
+import android.Manifest;
+import android.app.Activity;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.graphics.drawable.BitmapDrawable;
+import android.media.ExifInterface;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.MediaStore;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.Toast;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.core.app.ActivityCompat;
+import androidx.core.content.ContextCompat;
+
+import com.baidu.ai.cameraui.camera.CameraListener;
+import com.baidu.ai.cameraui.parameter.BaseParameter;
+import com.baidu.ai.cameraui.util.AutoTrigger;
+import com.baidu.ai.cameraui.util.CrashReporterHandler;
+import com.baidu.ai.cameraui.util.ImageUtil;
+import com.baidu.ai.cameraui.util.UiLog;
+import com.baidu.ai.cameraui.view.CropView;
+import com.baidu.ai.cameraui.view.MaskView;
+import com.baidu.ai.cameraui.view.PreviewView;
+
+import java.io.File;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+import static com.baidu.ai.cameraui.parameter.BaseParameter.KEY_HAS_ALBUM_BUTTON;
+import static com.baidu.ai.cameraui.parameter.BaseParameter.KEY_HAS_FLASH_BUTTON;
+import static com.baidu.ai.cameraui.parameter.BaseParameter.KEY_MAIN;
+import static com.baidu.ai.cameraui.parameter.BaseParameter.KEY_MASK_TYPE;
+import static com.baidu.ai.cameraui.parameter.BaseParameter.KEY_OUTPUT_FILE_PATH;
+import static com.baidu.ai.cameraui.parameter.BaseParameter.KEY_TAKE_PICTURE_TYPE;
+import static com.baidu.ai.cameraui.parameter.BaseParameter.TYPE_MANUAL;
+
+/**
+ * Created by ruanshimin on 2018/6/13.
+ */
+
+abstract class BaseActivity<T extends BaseParameter> extends Activity {
+
+    static final int REQUEST_PERMISSION_CODE_CAMERA = 1;
+    static final int REQUEST_PERMISSION_CODE_STORAGE = 2;
+
+    static final int INTENT_CODE_PICK_IMAGE = 1;
+
+    private volatile boolean statusPreview = true;
+
+    private PreviewView mPreviewView;
+    private ImageView previewSnapshot;
+
+    private MaskView mMaskView;
+    private CropView mCropView;
+
+    private ImageView takePictureBtn;
+    private ImageView flashBtn;
+    private ImageView albumBtn;
+
+    private ImageView rotatePictureBtn;
+    private ImageView cancelBtn;
+    private ImageView confirmBtn;
+
+    private boolean isFlashOpen = false;
+
+    private boolean isAuto;
+
+    private boolean isToAlbumActivity = false;
+
+    protected T parameter;
+
+    protected abstract void onPictureProcess(Bitmap bitmap, @Nullable Bitmap origin, boolean isTriggerManual);
+
+    protected abstract void onDispatchCreate(@Nullable Bundle savedInstanceState);
+
+    protected void setParameter(T param) {
+        parameter = param;
+    }
+
+    protected T getParameter() {
+        return parameter;
+    }
+
+    /**
+     * 保存图片,供activity之间参数传递
+     * @param bitmap
+     */
+    protected void saveOutputFile(Bitmap bitmap) {
+        File file = new File(parameter.getResultImagePath());
+        try {
+            if (!file.exists()) {
+                file.createNewFile();
+            }
+            FileOutputStream fileOutputStream = new FileOutputStream(file);
+            boolean isSuccess = bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fileOutputStream);
+            if (!isSuccess) {
+                UiLog.error("bitmap save output fail");
+            }
+            fileOutputStream.flush();
+            fileOutputStream.close();
+        } catch (IOException e) {
+            UiLog.error("bitmap save output exception" , e);
+            e.printStackTrace();
+        }
+    }
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        CrashReporterHandler.init(this);
+        Bundle bundle = getIntent().getBundleExtra(KEY_MAIN);
+        onDispatchCreate(savedInstanceState);
+        if (parameter == null) {
+            parameter = (T) new BaseParameter();
+        }
+        if (bundle != null) {
+            parameter.setFlashShow(bundle.getBoolean(KEY_HAS_FLASH_BUTTON, true));
+            parameter.setAlbumShow(bundle.getBoolean(KEY_HAS_ALBUM_BUTTON, true));
+            parameter.setType(bundle.getInt(KEY_TAKE_PICTURE_TYPE, BaseParameter.TYPE_AUTO));
+            parameter.setMaskType(bundle.getInt(KEY_MASK_TYPE));
+            parameter.setResultImagePath(bundle.getString(KEY_OUTPUT_FILE_PATH, ""));
+        }
+        if (parameter.getType() == BaseParameter.TYPE_AUTO) {
+            isAuto = true;
+        }
+
+        requestPermissionCamera();
+    }
+
+    private void init() {
+        setContentView(R.layout.camera_view);
+
+        mPreviewView = (PreviewView) findViewById(R.id.preview_view);
+
+        // 预览按钮
+        takePictureBtn = findViewById(R.id.take_picture_btn);
+        initOptionBtns();
+
+        // 操作按钮
+        rotatePictureBtn = findViewById(R.id.rotate_picture_btn);
+        cancelBtn = findViewById(R.id.cancel_btn);
+        confirmBtn = findViewById(R.id.confirm_btn);
+        toggleActionBtns(false);
+
+        previewSnapshot = (ImageView) findViewById(R.id.preview_snapshot);
+        mMaskView = (MaskView) findViewById(R.id.maskview);
+        mCropView = (CropView) findViewById(R.id.cropview);
+        mCropView.setVisibility(View.GONE);
+
+        if (!isAuto) {
+            takePictureBtn.setOnClickListener(new View.OnClickListener() {
+                @Override
+                public void onClick(View v) {
+                    mPreviewView.takePicture(new CameraListener.TakePictureListener() {
+                        @Override
+                        public void onTakenPicture(Bitmap bitmap) {
+                            pictureTaken(bitmap);
+                        }
+                    });
+                }
+            });
+            takePictureBtn.setVisibility(View.VISIBLE);
+        } else {
+            takePictureBtn.setVisibility(View.GONE);
+        }
+
+
+        rotatePictureBtn.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                rotatePicture();
+            }
+        });
+
+        cancelBtn.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                returnToPreview();
+            }
+        });
+
+        confirmBtn.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                Bitmap origin =   ((BitmapDrawable) previewSnapshot.getDrawable()).getBitmap();
+                Bitmap bitmap = doCropBitmap(origin);
+                onPictureProcess(bitmap, origin, true);
+            }
+        });
+
+        switch (parameter.getMaskType()) {
+            case BaseParameter.MASK_TYPE_BANKCARD:
+                mMaskView.setMaskType(MaskView.MASK_TYPE_BANKCARD);
+                mMaskView.setTipString(parameter.getDefaultTip());
+                break;
+            case BaseParameter.MASK_TYPE_IDCARD_FRONT:
+                mMaskView.setMaskType(MaskView.MASK_TYPE_IDCARD_FRONT);
+                mMaskView.setTipString(parameter.getDefaultTip());
+                break;
+            case BaseParameter.MASK_TYPE_IDCARD_BACK:
+                mMaskView.setMaskType(MaskView.MASK_TYPE_IDCARD_BACK);
+                mMaskView.setTipString(parameter.getDefaultTip());
+                break;
+            default:
+                mMaskView.setMaskType(MaskView.MASK_NONE);
+        }
+
+        if (isAuto) {
+            createAutoTakeTimer();
+        }
+    }
+
+    public void setTipString(final String tip) {
+        runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mMaskView.setTipStringAndUpdate(tip);
+            }
+        });
+
+    }
+
+
+    /**
+     * 开启自动拍照
+     */
+    private void createAutoTakeTimer() {
+        if (getParameter().getType() == BaseParameter.TYPE_AUTO) {
+            AutoTrigger.createAutoTakeTimerTask(new Runnable() {
+                @Override
+                public void run() {
+                    synchronized (this) {
+                        if (statusPreview) {
+                            mPreviewView.takePicture(new CameraListener.TakePictureListener() {
+                                @Override
+                                public void onTakenPicture(Bitmap bitmap) {
+                                    pictureTakenAndCrop(bitmap);
+                                }
+                            });
+                        }
+                    }
+                }
+            });
+        }
+    }
+
+    /**
+     * 关闭自动拍照
+     */
+    private void cancelAutoTakeTimer() {
+        if (getParameter().getType() == BaseParameter.TYPE_AUTO) {
+            AutoTrigger.cancelAutoTakeTimer();
+        }
+    }
+
+    /**
+     * 根据参数选择性展示按钮
+     */
+    private void initOptionBtns() {
+
+        albumBtn = findViewById(R.id.album_btn);
+        flashBtn = findViewById(R.id.flash_btn);
+
+        if (parameter.isFlashShow()) {
+            flashBtn.setVisibility(View.VISIBLE);
+            flashBtn.setOnClickListener(new View.OnClickListener() {
+                @Override
+                public void onClick(View v) {
+                    isFlashOpen = !isFlashOpen;
+                    refreshFlash();
+                }
+            });
+        } else {
+            flashBtn.setVisibility(View.GONE);
+        }
+
+        if (parameter.isAlbumShow()) {
+            albumBtn.setVisibility(View.VISIBLE);
+            albumBtn.setOnClickListener(new View.OnClickListener() {
+                @Override
+                public void onClick(View v) {
+                    requestPermissionAlbum();
+                }
+            });
+        } else {
+            albumBtn.setVisibility(View.GONE);
+        }
+    }
+
+
+    /**
+     * 裁剪图片
+     * @param bitmap
+     * @return
+     */
+    private Bitmap doCropBitmap(Bitmap bitmap) {
+        Rect crop = mCropView.getCropRect();
+        Matrix matrix = previewSnapshot.getImageMatrix();
+        Matrix inverted = new Matrix();
+        matrix.invert(inverted);
+        float[] pts = {crop.left, crop.top, crop.right, crop.bottom};
+        inverted.mapPoints(pts);
+        float x = pts[0] < 0 ? 0 : pts[0];
+        float y = pts[1] < 0 ? 0 : pts[1];
+        float width = pts[2] - pts[0] > bitmap.getWidth() ? bitmap.getWidth() : pts[2] - pts[0];
+        float height = pts[3] - pts[1] > bitmap.getHeight() ? bitmap.getHeight() : pts[3] - pts[1];
+        Bitmap bt = ImageUtil.createCropBitmap(bitmap, x,
+                y , width, height, new Matrix());
+        return bt;
+    }
+
+    /**
+     * 裁剪图片
+     * 不通过ImageView
+     *
+     * @param bitmap
+     * @return
+     */
+    private Bitmap doCropBitmapAutoPicture(Bitmap bitmap) {
+        Rect crop = mCropView.getCropRect();
+        float ratio = (float) previewSnapshot.getWidth() / bitmap.getWidth();
+        Matrix matrix = new Matrix();
+        matrix.postScale(ratio, ratio);
+        matrix.invert(matrix);
+        float[] pts = {crop.left, crop.top, crop.right, crop.bottom};
+        matrix.mapPoints(pts);
+        Bitmap bt = ImageUtil.createCropBitmap(bitmap, pts[0],
+                pts[1] , pts[2] - pts[0], pts[3] - pts[1], matrix);
+        return bt;
+    }
+
+    /**
+     * 动态申请相册权限
+     * 成功后打开相册
+     */
+    private void requestPermissionAlbum() {
+        // 判断是否已经赋予权限
+        if (ContextCompat.checkSelfPermission(this,
+                Manifest.permission.WRITE_EXTERNAL_STORAGE)
+                != PackageManager.PERMISSION_GRANTED) {
+            // 如果应用之前请求过此权限但用户拒绝了请求,此方法将返回 true。
+            ActivityCompat.requestPermissions(this,
+                    new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_PERMISSION_CODE_STORAGE);
+        } else {
+            openAlum();
+        }
+    }
+
+    /**
+     * 打开相册选图
+     */
+    private void openAlum() {
+        Intent intent = new Intent(Intent.ACTION_PICK);
+        isToAlbumActivity = true;
+        intent.setType("image/*");
+        startActivityForResult(intent, INTENT_CODE_PICK_IMAGE);
+    }
+
+    private String getRealPathFromURI(Uri contentURI) {
+        String result;
+        Cursor cursor = null;
+        try {
+            cursor = getContentResolver().query(contentURI, null, null, null, null);
+        } catch (Throwable e) {
+            e.printStackTrace();
+        }
+        if (cursor == null) {
+            result = contentURI.getPath();
+        } else {
+            cursor.moveToFirst();
+            int idx = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA);
+            result = cursor.getString(idx);
+            cursor.close();
+        }
+        return result;
+    }
+
+    /**
+     * 从相册读取图片
+     * @param path
+     * @return
+     */
+    private Bitmap readFile(String path) {
+        BitmapFactory.Options option = new BitmapFactory.Options();
+
+        BitmapFactory.decodeFile(path, option);
+        option.inJustDecodeBounds = true;
+        if (option.outWidth > 1080 || option.outHeight > 1920) {
+            option.inSampleSize = 2;
+        }
+        option.inJustDecodeBounds = false;
+        Bitmap bitmap = BitmapFactory.decodeFile(path, option);
+        UiLog.info("pick image success");
+        return bitmap;
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        super.onActivityResult(requestCode, resultCode, data);
+        if (requestCode == INTENT_CODE_PICK_IMAGE) {
+            if (resultCode == Activity.RESULT_OK) {
+                Uri uri = data.getData();
+                String path = getRealPathFromURI(uri);
+                UiLog.info("pick image url: " + path);
+                Bitmap bitmap = readFile(path);
+                ExifInterface exif = null;
+                int exifRotation = 0;
+                try {
+                    exif = new ExifInterface(path);
+                    exifRotation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION,
+                            ExifInterface.ORIENTATION_NORMAL);
+                } catch (IOException e) {
+                    UiLog.error("album selection picture get exif info error", e);
+                }
+
+                int rotation = ImageUtil.exifToDegrees(exifRotation);
+                Bitmap rotateBitmap = ImageUtil.createRotateBitmap(bitmap, rotation);
+                pictureFromAlbum(rotateBitmap);
+            } else {
+                // 取消的时候退出到的是预览界面
+                isToAlbumActivity = false;
+            }
+        }
+    }
+
+    private void refreshFlash() {
+        if (mPreviewView.isCameraOpened()) {
+            mPreviewView.getCameraControl().toggleFlash(isFlashOpen);
+            if (isFlashOpen) {
+                flashBtn.setImageResource(R.drawable.flash_on_btn);
+            } else {
+                flashBtn.setImageResource(R.drawable.flash_off_btn);
+            }
+        }
+    }
+
+    private int previewRotation = 0;
+
+    /**
+     * 旋转预览
+     */
+    private void rotatePicture() {
+        previewRotation -= 90;
+        Bitmap rotateBitmap = ImageUtil.createRotateBitmap(snapShotBitmap, previewRotation);
+        previewSnapshot.setImageBitmap(rotateBitmap);
+        setCropBound();
+    }
+
+    /**
+     * 设置边界
+     */
+    private void setCropBound() {
+        Matrix matrix = previewSnapshot.getImageMatrix();
+        float[] pts;
+        if (previewRotation % 180 != 0) {
+            pts = new float[]{0, 0, snapShotBitmap.getHeight(), snapShotBitmap.getWidth()};
+            matrix.mapPoints(pts);
+
+        } else {
+            pts = new float[]{0, 0, snapShotBitmap.getWidth(), snapShotBitmap.getHeight()};
+            matrix.mapPoints(pts);
+        }
+        mCropView.setBound((int) pts[0], (int) pts[1], (int) pts[2], (int) pts[3]);
+    }
+
+    private Bitmap snapShotBitmap;
+
+    /**
+     * 切换预览界面的按钮展示与否
+     * @param isShow
+     */
+    private void togglePreviewBtns(boolean isShow) {
+        int flag = isShow ? View.VISIBLE : View.GONE;
+        takePictureBtn.setVisibility(flag);
+        if (parameter.isFlashShow()) {
+            flashBtn.setVisibility(flag);
+        } else {
+            flashBtn.setVisibility(View.GONE);
+        }
+        if (parameter.isAlbumShow()) {
+            albumBtn.setVisibility(flag);
+        } else {
+            albumBtn.setVisibility(View.GONE);
+        }
+    }
+
+    /**
+     * 切换操作界面的按钮展示与否
+     * @param isShow
+     */
+    private void toggleActionBtns(boolean isShow) {
+        int flag = isShow ? View.VISIBLE : View.GONE;
+        rotatePictureBtn.setVisibility(flag);
+        confirmBtn.setVisibility(flag);
+        cancelBtn.setVisibility(flag);
+        confirmBtn.setVisibility(View.VISIBLE);
+    }
+
+    /**
+     * 点击拍照按钮,或者选择相册图片
+     * @param bitmap
+     */
+    private void pictureTaken(final Bitmap bitmap) {
+        snapShotBitmap = bitmap;
+        UiLog.info("takePictureSuccess");
+        runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                previewSnapshot.setImageBitmap(bitmap);
+                changeToAction(false);
+                setCropBound();
+            }
+        });
+    }
+
+    private void pictureFromAlbum(final Bitmap bitmap) {
+        snapShotBitmap = bitmap;
+        UiLog.info("albumSelectPictureSuccess");
+        runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                previewSnapshot.setImageBitmap(bitmap);
+                changeToAction(true);
+                setCropBound();
+            }
+        });
+    }
+
+    /**
+     * 自动拍照后裁剪
+     * @param bitmap
+     */
+    private void pictureTakenAndCrop(final Bitmap bitmap) {
+        UiLog.info("autoTakePictureSuccess");
+        if (mCropView.getCropRect() == null) {
+            mCropView.setCropRect(mMaskView.getMaskFrame());
+        }
+        Bitmap cropBitmap = doCropBitmapAutoPicture(bitmap);
+        onPictureProcess(cropBitmap, bitmap, false);
+    }
+
+    /**
+     * 旋转图片
+     */
+    private void changeToAction(boolean isFromAlbum) {
+        togglePreviewBtns(false);
+        toggleActionBtns(true);
+        mCropView.setVisibility(View.VISIBLE);
+        mMaskView.setVisibility(View.GONE);
+        mCropView.setCropRect(mMaskView.getMaskFrame());
+        previewSnapshot.setVisibility(View.VISIBLE);
+
+        mPreviewView.setVisibility(View.GONE);
+
+        isFlashOpen = false;
+        if (!isFromAlbum) {
+            mPreviewView.stopPreview();
+            refreshFlash();
+        }
+
+        statusPreview = false;
+
+    }
+
+    /**
+     * 从裁剪图片页面返回预览页面
+     */
+    private void returnToPreview() {
+        previewRotation = 0;
+        if (getParameter().getType() == TYPE_MANUAL) {
+            togglePreviewBtns(true);
+        }
+        toggleActionBtns(false);
+        mCropView.setVisibility(View.GONE);
+        mMaskView.setVisibility(View.VISIBLE);
+        previewSnapshot.setVisibility(View.GONE);
+        mPreviewView.getCameraControl().startPreviewForce();
+        mPreviewView.setVisibility(View.VISIBLE);
+        statusPreview = true;
+        if (isAuto) {
+            createAutoTakeTimer();
+        }
+    }
+
+    /**
+     * 请求相机权限
+     */
+    private void requestPermissionCamera() {
+        // 判断是否已经赋予权限
+        if (ContextCompat.checkSelfPermission(this,
+                Manifest.permission.CAMERA)
+                != PackageManager.PERMISSION_GRANTED) {
+            // 如果应用之前请求过此权限但用户拒绝了请求,此方法将返回 true。
+            ActivityCompat.requestPermissions(this,
+                    new String[]{Manifest.permission.CAMERA}, REQUEST_PERMISSION_CODE_CAMERA);
+        } else {
+            init();
+        }
+    }
+
+    /**
+     * 监听Back键按下事件,方法1:
+     * 注意:
+     * super.onBackPressed()会自动调用finish()方法,关闭
+     * 当前Activity.
+     * 若要屏蔽Back键盘,注释该行代码即可
+     */
+    @Override
+    public void onBackPressed() {
+        if (statusPreview) {
+            super.onBackPressed();
+        } else {
+            returnToPreview();
+        }
+
+    }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+        statusPreview = false;
+    }
+
+    @Override
+    protected void onPause() {
+
+        // 如果首次弹出权限框会导致暂时退出再返回
+        // 还有一种情况是手机助手类实现动态权限错误,导致相机未打开
+        // 此时不是真正的退出,无需做出反应
+        if (mPreviewView != null && mPreviewView.isCameraOpened()) {
+            // 关闭闪光灯
+            isFlashOpen = false;
+            refreshFlash();
+
+            cancelAutoTakeTimer();
+
+            // 停止mPreviewView预览
+            mPreviewView.getCameraControl().closeCamera();
+
+        }
+
+        UiLog.info("activity pause");
+        super.onPause();
+    }
+
+    @Override
+    protected void onResume() {
+
+        // 如果不是第一次启动就需要重新打开预览
+        if (mPreviewView != null && mPreviewView.getCameraControl() != null) {
+            UiLog.info("activity resume");
+            if (isAuto && !isToAlbumActivity) {
+                createAutoTakeTimer();
+            }
+            isToAlbumActivity = false;
+
+            mPreviewView.getCameraControl().startPreview();
+            statusPreview = true;
+
+        }
+        super.onResume();
+    }
+
+    @Override
+    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
+                                           @NonNull int[] grantResults) {
+        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
+        switch (requestCode) {
+            case REQUEST_PERMISSION_CODE_CAMERA:
+                if (grantResults[0] == -1) {
+                    Toast toast = Toast.makeText(this, "请选择权限", Toast.LENGTH_SHORT);
+                    toast.show();
+                    finish();
+                } else {
+                    init();
+                }
+                break;
+            case REQUEST_PERMISSION_CODE_STORAGE:
+                if (grantResults[0] == -1) {
+                    Toast toast = Toast.makeText(this, "请选择权限", Toast.LENGTH_SHORT);
+                    toast.show();
+                } else {
+                    openAlum();
+                }
+                break;
+            default:
+                break;
+        }
+
+    }
+}

+ 25 - 0
cameraui/src/main/java/com/baidu/ai/cameraui/GeneralActivity.java

@@ -0,0 +1,25 @@
+package com.baidu.ai.cameraui;
+
+
+
+import android.os.Bundle;
+
+import androidx.annotation.Nullable;
+
+import com.baidu.ai.cameraui.parameter.BankCardParameter;
+import com.baidu.ai.cameraui.parameter.GeneralParameter;
+
+
+/**
+ * Created by ruanshimin on 2018/7/2.
+ */
+
+public abstract class GeneralActivity extends BaseActivity {
+
+    @Override
+    protected void onDispatchCreate(@Nullable Bundle savedInstanceState) {
+        GeneralParameter parameter = new GeneralParameter();
+        parameter.setDefaultTip("请对正文字部分");
+        setParameter(parameter);
+    }
+}

+ 37 - 0
cameraui/src/main/java/com/baidu/ai/cameraui/IDcardCardActivity.java

@@ -0,0 +1,37 @@
+package com.baidu.ai.cameraui;
+
+
+
+import android.os.Bundle;
+
+import androidx.annotation.Nullable;
+
+import com.baidu.ai.cameraui.parameter.IDcardParameter;
+
+import static com.baidu.ai.cameraui.parameter.BaseParameter.KEY_MAIN;
+import static com.baidu.ai.cameraui.parameter.BaseParameter.KEY_MASK_TYPE;
+import static com.baidu.ai.cameraui.parameter.BaseParameter.MASK_TYPE_IDCARD_BACK;
+import static com.baidu.ai.cameraui.parameter.BaseParameter.MASK_TYPE_IDCARD_FRONT;
+
+
+/**
+ * Created by ruanshimin on 2018/7/2.
+ */
+
+public abstract class IDcardCardActivity extends BaseActivity {
+
+    @Override
+    protected void onDispatchCreate(@Nullable Bundle savedInstanceState) {
+        IDcardParameter parameter = new IDcardParameter();
+        Bundle bundle = getIntent().getBundleExtra(KEY_MAIN);
+        if (bundle != null) {
+            if (bundle.getInt(KEY_MASK_TYPE) == MASK_TYPE_IDCARD_FRONT) {
+                parameter.setDefaultTip("请对正身份证头像页");
+            }
+            if (bundle.getInt(KEY_MASK_TYPE) == MASK_TYPE_IDCARD_BACK) {
+                parameter.setDefaultTip("请对正身份证国徽页");
+            }
+        }
+        setParameter(parameter);
+    }
+}

+ 20 - 0
cameraui/src/main/java/com/baidu/ai/cameraui/camera/CameraListener.java

@@ -0,0 +1,20 @@
+package com.baidu.ai.cameraui.camera;
+
+import android.graphics.Bitmap;
+
+/**
+ * Created by ruanshimin on 2017/11/30.
+ */
+
+public class CameraListener {
+    public interface CommonListener {
+        void onSwitchCamera();
+    }
+
+    /**
+     * 获取照片,实际是获取预览缓存的照片,变通的进行取景
+     */
+    public interface TakePictureListener {
+        void onTakenPicture(Bitmap bitmap);
+    }
+}

+ 390 - 0
cameraui/src/main/java/com/baidu/ai/cameraui/camera/CameraProxy1.java

@@ -0,0 +1,390 @@
+package com.baidu.ai.cameraui.camera;
+
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.ImageFormat;
+import android.graphics.Rect;
+import android.graphics.SurfaceTexture;
+import android.graphics.YuvImage;
+import android.hardware.Camera;
+
+import com.baidu.ai.cameraui.util.ImageUtil;
+import com.baidu.ai.cameraui.util.UiLog;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+import static android.hardware.Camera.getCameraInfo;
+import static android.hardware.Camera.getNumberOfCameras;
+import static com.baidu.ai.cameraui.util.AutoTrigger.cancelAutoFocusTimer;
+import static com.baidu.ai.cameraui.util.AutoTrigger.createAutoFocusTimerTask;
+
+/**
+ * Created by ruanshimin on 2018/4/16.
+ */
+
+public class CameraProxy1 implements ICameraProxy {
+
+    public static final int STATUS_INIT = 0;
+
+    public static final int STATUS_OPENED = 1;
+
+    public static final int STATUS_PREVIEWING = 2;
+
+    public static final int TEXTURE_STATUS_INITED = 0;
+
+    private static final int TEXTURE_STATUS_READY = 1;
+
+    private SurfaceTexture mSurfaceTexture;
+
+    private boolean isBack = true;
+
+    private Camera mCamera;
+
+    private int status;
+    private int textureStatus;
+
+    private int layoutWidth;
+    private int layoutHeight;
+
+    private int[] previewSize = new int[2];
+
+    private CameraListener.CommonListener mCameraListener;
+
+    private int minPreviewCandidateRatio = 1;
+
+    private int previewRotation = 90;
+    private int displayRotation = 0;
+
+    private int previewCaptureRotation = 0;
+
+    private Camera.Parameters cameraParameters;
+
+    private byte[] currentFrameData;
+
+    /**
+     * 初始化方法
+     * @param width 预期预览窗口宽度
+     * @param height 预期预览窗口高度
+     */
+    public CameraProxy1(int width, int height) {
+        layoutWidth = width;
+        layoutHeight = height;
+        status = STATUS_INIT;
+        textureStatus = TEXTURE_STATUS_INITED;
+    }
+
+    @Override
+    public int getStatus() {
+        return status;
+    }
+
+    /**
+     * 获取预览长宽
+     * @return
+     */
+    public int[] getPreviewSize() {
+        return previewSize;
+    }
+
+    /**
+     * 挑选一个长宽比合适的
+     * 根据界面特点,这里需要宽度占满后高度大于界面高度
+     *
+     * @param list
+     * @return
+     */
+    private Camera.Size getPreviewSize(List<Camera.Size> list) {
+        ArrayList validSizeList = new ArrayList<Camera.Size>();
+        float ratio = (float) layoutHeight / layoutWidth;
+
+        // minPreviewCandidateRatio是符合预览界面宽度的最小比值
+        for (Camera.Size size : list) {
+            if ((float) size.width / size.height >= ratio
+                    && size.width > (layoutWidth * minPreviewCandidateRatio)) {
+                validSizeList.add(size);
+            }
+        }
+
+        // 没有符合条件的,直接返回第一个,有风险
+        if (validSizeList.size() == 0) {
+            UiLog.warn("found no valid preview size");
+            return list.get(0);
+        }
+
+        // 选出符合候选中最小的一个预览尺寸
+        Camera.Size size = (Camera.Size) Collections.min(validSizeList, new Comparator<Camera.Size>() {
+            @Override
+            public int compare(Camera.Size s1, Camera.Size s2) {
+                return s1.width - s2.width;
+            }
+        });
+
+        return size;
+    }
+
+    /**
+     * 打开关闭闪光灯
+     *
+     * @param isOpen 打开或关闭
+     */
+    public void toggleFlash(boolean isOpen) {
+        if (isOpen) {
+            if (cameraParameters.getSupportedFlashModes().contains(Camera.Parameters.FLASH_MODE_TORCH)) {
+                cameraParameters.setFlashMode(Camera.Parameters.FLASH_MODE_TORCH);
+            } else {
+                UiLog.warn("flash open fail, FLASH_MODE_TORCH not supported");
+            }
+
+        } else {
+            cameraParameters.setFlashMode(Camera.Parameters.FLASH_MODE_OFF);
+        }
+        mCamera.setParameters(cameraParameters);
+    }
+
+    /**
+     * 调用open打开摄像头
+     *
+     * @param isOpenBack 是否是后置摄像头
+     * @return
+     */
+    private Camera open(boolean isOpenBack) {
+        int numberOfCameras = getNumberOfCameras();
+        Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
+        for (int i = 0; i < numberOfCameras; i++) {
+            getCameraInfo(i, cameraInfo);
+            if (isOpenBack) {
+                if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_BACK) {
+                    caculatePreviewRotation(cameraInfo);
+                    return Camera.open(i);
+                }
+            } else {
+                if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
+                    caculatePreviewRotation(cameraInfo);
+                    return Camera.open(i);
+                }
+            }
+
+        }
+        return null;
+    }
+
+    public void setDisplayRotation(int degree) {
+        displayRotation = degree;
+    }
+
+    /**
+     * 计算旋转
+     * @param info
+     */
+    private void caculatePreviewRotation(Camera.CameraInfo info) {
+        int degree;
+        if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
+            degree = (info.orientation + displayRotation) % 360;
+            degree = (360 - degree) % 360;
+            previewCaptureRotation = degree;
+        } else {
+            // back-facing
+            degree = (info.orientation - displayRotation + 360) % 360;
+            previewCaptureRotation = (360 + degree) % 360;
+        }
+        previewRotation = degree;
+    }
+
+    /**
+     * 直接开始预览
+     */
+    public void startPreviewForce() {
+        if (status != STATUS_OPENED) {
+            return;
+        }
+        mCamera.startPreview();
+        mCamera.setPreviewCallback(mPreviewCallback);
+        createAutoFocusTimerTask(new Runnable() {
+            @Override
+            public void run() {
+                synchronized (CameraProxy1.this) {
+                    try {
+                        mCamera.autoFocus(new Camera.AutoFocusCallback() {
+                            @Override
+                            public void onAutoFocus(boolean success, Camera camera) {
+                            }
+                        });
+                    } catch (Throwable e) {
+                        // startPreview是异步实现,可能在某些机器上前几次调用会autofocus failß
+                    }
+                }
+            }
+        });
+    }
+
+    @Override
+    public void startPreview() {
+        // MIUI上会在activity stop时destory关闭相机
+        // 触发textureView的destory方法,此时会设置mSurfaceTexture为null
+        // 这里就需要重新打开摄像头后再预览
+        if (mCamera == null) {
+            try {
+                openCamera();
+                status = STATUS_OPENED;
+            } catch (RuntimeException e) {
+                return;
+            }
+            if (mSurfaceTexture != null ) {
+                try {
+                    mCamera.setPreviewTexture(mSurfaceTexture);
+                } catch (IOException e) {
+                    UiLog.error("camera setPreviewTexture throw exception", e);
+                }
+            }
+            startPreviewForce();
+        } else  {
+            // 如果已经TextureAvailable,设置texture并且预览,(必须先调用了openCamera)
+            try {
+                mCamera.setPreviewTexture(mSurfaceTexture);
+            } catch (IOException e) {
+                UiLog.error("camera setPreviewTexture throw exception", e);
+            }
+
+            startPreviewForce();
+            mCamera.setPreviewCallback(mPreviewCallback);
+            status = STATUS_PREVIEWING;
+            return;
+        }
+    }
+
+    /**
+     * 预览图片过滤
+     */
+    private Camera.PreviewCallback mPreviewCallback = new Camera.PreviewCallback() {
+        @Override
+        public void onPreviewFrame(byte[] data, Camera camera) {
+            // 在某些机型和某项项目中,某些帧的data的数据不符合nv21的格式,需要过滤,否则后续处理会导致crash
+            if (data.length != cameraParameters.getPreviewSize().width
+                    * cameraParameters.getPreviewSize().height * 1.5) {
+                return;
+            }
+            currentFrameData = data;
+        }
+    };
+
+    /**
+     * 打开相机,设置一些参数
+     */
+    public void openCamera() {
+        try {
+             mCamera = open(isBack);
+        } catch (RuntimeException e) {
+            // 某些机子会在权限检查通过后跳出权限选择界面
+            UiLog.error("unexpected fail to open camera", e);
+            throw e;
+        }
+
+        cameraParameters = mCamera.getParameters();
+
+        List<Camera.Size> supportedPreviewSizes =  mCamera.getParameters().getSupportedPreviewSizes();
+
+        // 挑选合适的预览尺寸,并存起来供视图获取
+        Camera.Size size = getPreviewSize(supportedPreviewSizes);
+
+        // 如果是90或者270度这对调长宽比
+        if (previewRotation == 90 || previewRotation == 270) {
+            this.previewSize[0] = size.height;
+            this.previewSize[1] = size.width;
+        } else {
+            this.previewSize[0] = size.width;
+            this.previewSize[1] = size.height;
+        }
+
+
+        if (isBack) {
+            cameraParameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
+        }
+        cameraParameters.setPreviewSize(size.width, size.height);
+        mCamera.setDisplayOrientation(previewRotation);
+        mCamera.setParameters(cameraParameters);
+        status = STATUS_OPENED;
+        if (mCameraListener != null) {
+            mCameraListener.onSwitchCamera();
+        }
+    }
+
+    @Override
+    public void setSurfaceTexture(SurfaceTexture surfaceTexture) {
+        mSurfaceTexture = surfaceTexture;
+    }
+
+    /**
+     * 关闭相机
+     */
+    public void closeCamera() {
+        stopPreview();
+        mCamera.setPreviewCallback(null);
+        mCamera.release();
+        mCamera = null;
+        status = STATUS_INIT;
+        UiLog.info("close camera");
+    }
+
+    @Override
+    public void stopPreview() {
+        cancelAutoFocusTimer();
+        mCamera.stopPreview();
+        status = STATUS_OPENED;
+        UiLog.info("stop preview");
+    }
+
+    @Override
+    public void switchSide() {
+        isBack = !isBack;
+        stopPreview();
+        closeCamera();
+        openCamera();
+        startPreview();
+    }
+
+    /**
+     * 把原始图片数据
+     *
+     * @param data
+     * @return
+     */
+    private Bitmap convertPreviewDataToBitmap(byte[] data) {
+        Camera.Size size = cameraParameters.getPreviewSize();
+        YuvImage img = new YuvImage(data, ImageFormat.NV21, size.width, size.height, null);
+        ByteArrayOutputStream os = new ByteArrayOutputStream(data.length);
+        img.compressToJpeg(new Rect(0, 0, size.width, size.height), 80, os);
+        byte[] jpeg = os.toByteArray();
+        Bitmap bitmap = BitmapFactory.decodeByteArray(jpeg, 0, jpeg.length);
+        Bitmap bitmapRotate;
+        if (isBack) {
+            bitmapRotate = ImageUtil.createRotateBitmap(bitmap, previewCaptureRotation);
+        } else {
+            bitmapRotate = ImageUtil.createRotateBitmap(bitmap, previewCaptureRotation);
+        }
+        try {
+            os.close();
+        } catch (IOException e) {
+            UiLog.error("convertPreviewDataToBitmap close io exception", e);
+        }
+        return bitmapRotate;
+    }
+
+    @Override
+    public void takePicture(CameraListener.TakePictureListener listener) {
+        if (currentFrameData != null) {
+            Bitmap bitmap = convertPreviewDataToBitmap(currentFrameData);
+            listener.onTakenPicture(bitmap);
+            UiLog.info("convert bitmap success");
+        }
+    }
+
+    @Override
+    public void setEventListener(CameraListener.CommonListener listener) {
+        mCameraListener = listener;
+    }
+}

+ 53 - 0
cameraui/src/main/java/com/baidu/ai/cameraui/camera/ICameraProxy.java

@@ -0,0 +1,53 @@
+package com.baidu.ai.cameraui.camera;
+
+import android.graphics.SurfaceTexture;
+
+
+/**
+ * Created by ruanshimin on 2017/3/29.
+ */
+public interface ICameraProxy {
+    /**
+     * 打开摄像机
+     */
+    void openCamera();
+
+    /**
+     * 打开或者关闭闪光灯
+     */
+    void toggleFlash(boolean isOpen);
+
+    int getStatus();
+
+    void setSurfaceTexture(SurfaceTexture surfaceTexture);
+
+    void setDisplayRotation(int degree);
+
+    /**
+     * 开启预览
+     */
+    void startPreview();
+
+    /**
+     * 直接开启预览
+     */
+    void startPreviewForce();
+
+    /**
+     * 停止预览
+     */
+    void stopPreview();
+
+    /**
+     * 关闭摄像头
+     */
+    void closeCamera();
+
+    void switchSide();
+
+    int[] getPreviewSize();
+
+    void takePicture(CameraListener.TakePictureListener listener);
+
+    void setEventListener(CameraListener.CommonListener commonListener);
+}

+ 14 - 0
cameraui/src/main/java/com/baidu/ai/cameraui/parameter/BankCardParameter.java

@@ -0,0 +1,14 @@
+package com.baidu.ai.cameraui.parameter;
+
+/**
+ * Created by ruanshimin on 2018/7/2.
+ */
+
+public class BankCardParameter extends BaseParameter {
+
+    public BankCardParameter() {
+        super();
+        maskType = MASK_TYPE_BANKCARD;
+    }
+
+}

+ 94 - 0
cameraui/src/main/java/com/baidu/ai/cameraui/parameter/BaseParameter.java

@@ -0,0 +1,94 @@
+package com.baidu.ai.cameraui.parameter;
+
+/**
+ * Created by ruanshimin on 2018/7/2.
+ */
+
+public class BaseParameter {
+
+    public static final String KEY_HAS_FLASH_BUTTON = "has_flash_btn";
+    public static final String KEY_HAS_ALBUM_BUTTON = "has_album_btn";
+    public static final String KEY_OUTPUT_FILE_PATH = "temp_file_path";
+    public static final String KEY_TAKE_PICTURE_TYPE = "take_picture_type";
+    public static final String KEY_MASK_TYPE = "mask_type";
+    public static final String KEY_DEFAULT_TIP = "default_tip";
+    public static final String KEY_MAIN = "extra";
+
+    public static final int TYPE_AUTO = 0;
+    public static final int TYPE_MANUAL = 1;
+
+    public static final int MASK_TYPE_NONE = -1;
+    public static final int MASK_TYPE_BANKCARD = 0;
+    public static final int MASK_TYPE_IDCARD_FRONT = 1;
+    public static final int MASK_TYPE_IDCARD_BACK = 2;
+
+    private int type;
+    private boolean isFlashShow;
+    private boolean isAlbumShow;
+
+    public String getDefaultTip() {
+        return defaultTip;
+    }
+
+    public void setDefaultTip(String defaultTip) {
+        this.defaultTip = defaultTip;
+    }
+
+    protected String defaultTip;
+
+    protected int maskType;
+
+    private String resultImagePath;
+
+    public int getMaskType() {
+        return maskType;
+    }
+
+    public void setMaskType(int maskType) {
+        this.maskType = maskType;
+    }
+
+    public String getResultImagePath() {
+        return resultImagePath;
+    }
+
+    public void setResultImagePath(String resultImagePath) {
+        this.resultImagePath = resultImagePath;
+    }
+
+    public int getType() {
+        return type;
+    }
+
+    public void setType(int type) {
+        this.type = type;
+    }
+
+    public BaseParameter() {
+        this.type = TYPE_MANUAL;
+        this.isFlashShow = true;
+        this.isAlbumShow = true;
+    }
+
+    public BaseParameter(int type, boolean isFlashShow, boolean isAlbumShow) {
+        this.type = type;
+        this.isFlashShow = isFlashShow;
+        this.isAlbumShow = isAlbumShow;
+    }
+
+    public boolean isFlashShow() {
+        return isFlashShow;
+    }
+
+    public void setFlashShow(boolean flashShow) {
+        isFlashShow = flashShow;
+    }
+
+    public boolean isAlbumShow() {
+        return isAlbumShow;
+    }
+
+    public void setAlbumShow(boolean albumShow) {
+        isAlbumShow = albumShow;
+    }
+}

+ 15 - 0
cameraui/src/main/java/com/baidu/ai/cameraui/parameter/GeneralParameter.java

@@ -0,0 +1,15 @@
+package com.baidu.ai.cameraui.parameter;
+
+/**
+ * Created by ruanshimin on 2018/7/16.
+ */
+
+public class GeneralParameter extends BaseParameter {
+
+    public GeneralParameter() {
+        super();
+        maskType = MASK_TYPE_NONE;
+    }
+
+
+}

+ 15 - 0
cameraui/src/main/java/com/baidu/ai/cameraui/parameter/IDcardParameter.java

@@ -0,0 +1,15 @@
+package com.baidu.ai.cameraui.parameter;
+
+/**
+ * Created by ruanshimin on 2018/7/16.
+ */
+
+public class IDcardParameter extends BaseParameter {
+
+    public IDcardParameter() {
+        super();
+        maskType = MASK_TYPE_IDCARD_FRONT;
+    }
+
+
+}

+ 89 - 0
cameraui/src/main/java/com/baidu/ai/cameraui/util/AutoTrigger.java

@@ -0,0 +1,89 @@
+package com.baidu.ai.cameraui.util;
+
+import java.util.Timer;
+import java.util.TimerTask;
+
+/**
+ * Created by ruanshimin on 2018/7/2.
+ */
+
+public class AutoTrigger {
+
+    static Timer timerFocus = null;
+
+    static Timer timerAutoTake = null;
+
+    /**
+     * 对焦频率
+     */
+    static final long cameraFocusInterval = 2000;
+
+    /**
+     * 自动拍照频率
+     */
+    static final long cameraAutoTakeInterval = 50;
+
+    /**
+     * 创建一个定时对焦的timer任务
+     * @param runnable 对焦代码
+     * @return Timer Timer对象,用来终止自动对焦
+     */
+    public static Timer createAutoFocusTimerTask(final Runnable runnable) {
+        if (timerFocus != null) {
+            return timerFocus;
+        }
+        timerFocus = new Timer();
+        TimerTask task = new TimerTask() {
+            @Override
+            public void run() {
+                runnable.run();
+            }
+        };
+        timerFocus.scheduleAtFixedRate(task, 0, cameraFocusInterval);
+        return timerFocus;
+    }
+
+    /**
+     * 创建一个定时相机获取任务
+     * @param runnable 对焦代码
+     * @return Timer Timer对象,用来终止自动对焦
+     */
+    public static Timer createAutoTakeTimerTask(final Runnable runnable) {
+        if (timerAutoTake != null) {
+            return timerAutoTake;
+        }
+        timerAutoTake = new Timer();
+        TimerTask task = new TimerTask() {
+            @Override
+            public void run() {
+                runnable.run();
+            }
+        };
+        timerAutoTake.scheduleAtFixedRate(task, 0, cameraAutoTakeInterval);
+        return timerAutoTake;
+    }
+
+    /**
+     * 终止自动对焦任务,实际调用了cancel方法并且清空对象
+     * 但是无法终止执行中的任务,需额外处理
+     *
+     */
+    public static void cancelAutoFocusTimer() {
+        if (timerFocus != null) {
+            timerFocus.cancel();
+            timerFocus = null;
+        }
+    }
+
+    /**
+     * 终止自动拍照,实际调用了cancel方法并且清空对象
+     * 但是无法终止执行中的任务,需额外处理
+     *
+     */
+    public static void cancelAutoTakeTimer() {
+        if (timerAutoTake != null) {
+            timerAutoTake.cancel();
+            timerAutoTake = null;
+        }
+    }
+}

+ 57 - 0
cameraui/src/main/java/com/baidu/ai/cameraui/util/CrashReporterHandler.java

@@ -0,0 +1,57 @@
+package com.baidu.ai.cameraui.util;
+
+import android.content.Context;
+
+
+
+
+/**
+ * Created by ruanshimin on 2017/2/21.
+ */
+public class CrashReporterHandler implements Thread.UncaughtExceptionHandler {
+
+
+
+    private static CrashReporterHandler instance;
+
+    private static Thread.UncaughtExceptionHandler defaultHandler = null;
+
+    private Context ctx;
+
+    public CrashReporterHandler(Context context) {
+        ctx = context;
+    }
+
+    /**
+     * Factory method, return a singleton CrashReporterHandler object
+     *
+     * @param ctx application context
+     */
+    public static CrashReporterHandler init(Context ctx) {
+        if (instance == null) {
+            instance = new CrashReporterHandler(ctx);
+        }
+        if (defaultHandler == null) {
+            defaultHandler = Thread.getDefaultUncaughtExceptionHandler();
+        }
+
+        Thread.setDefaultUncaughtExceptionHandler(instance);
+        return instance;
+    }
+
+    public void release() {
+        ctx = null;
+    }
+
+
+
+    @Override
+    public void uncaughtException(Thread t, Throwable e) {
+        try {
+            UiLog.error("uncaughtException", e);
+        } catch (Throwable throwable) {
+            // do nothing
+        }
+        defaultHandler.uncaughtException(t, e);
+    }
+}

+ 176 - 0
cameraui/src/main/java/com/baidu/ai/cameraui/util/ImageUtil.java

@@ -0,0 +1,176 @@
+package com.baidu.ai.cameraui.util;
+
+import android.graphics.Bitmap;
+import android.graphics.Matrix;
+import android.media.ExifInterface;
+import android.util.Log;
+
+/**
+ * Created by ruanshimin on 2018/5/8.
+ */
+
+public class ImageUtil {
+    private static final String TAG = "CameraExif";
+
+    /**
+     * 旋转一张bitmap
+     * @param bitmap
+     * @param rotation
+     * @return
+     */
+    public static Bitmap createRotateBitmap(Bitmap bitmap, int rotation) {
+        Matrix matrix = new Matrix();
+        matrix.postRotate(rotation);
+        return Bitmap.createBitmap(
+                bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, false);
+    }
+
+    public static Bitmap createMirrorRotateBitmap(Bitmap bitmap, int rotation) {
+        Matrix matrix = new Matrix();
+        matrix.postRotate(rotation);
+        matrix.postScale(-1, 1);
+        return Bitmap.createBitmap(
+                bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, false);
+    }
+
+    /**
+     * 重新定位一张bitmap
+     * @param bitmap
+     * @param x
+     * @param y
+     * @param width
+     * @param height
+     * @param matrix
+     * @return
+     */
+    public static Bitmap createCropBitmap(Bitmap bitmap, float x, float y, float width, float height, Matrix matrix) {
+        return Bitmap.createBitmap(
+                bitmap, (int) x, (int) y, (int) width, (int) height, matrix, false);
+    }
+
+    public static int exifToDegrees(int exifOrientation) {
+        if (exifOrientation == ExifInterface.ORIENTATION_ROTATE_90) {
+            return 90;
+        } else if (exifOrientation == ExifInterface.ORIENTATION_ROTATE_180) {
+            return 180;
+        } else if (exifOrientation == ExifInterface.ORIENTATION_ROTATE_270) {
+            return 270;
+        }
+        return 0;
+    }
+
+    // Returns the degrees in clockwise. Values are 0, 90, 180, or 270.
+    public static int getOrientation(byte[] jpeg) {
+        if (jpeg == null) {
+            return 0;
+        }
+
+        int offset = 0;
+        int length = 0;
+
+        // ISO/IEC 10918-1:1993(E)
+        while (offset + 3 < jpeg.length && (jpeg[offset++] & 0xFF) == 0xFF) {
+            int marker = jpeg[offset] & 0xFF;
+
+            // Check if the marker is a padding.
+            if (marker == 0xFF) {
+                continue;
+            }
+            offset++;
+
+            // Check if the marker is SOI or TEM.
+            if (marker == 0xD8 || marker == 0x01) {
+                continue;
+            }
+            // Check if the marker is EOI or SOS.
+            if (marker == 0xD9 || marker == 0xDA) {
+                break;
+            }
+
+            // Get the length and check if it is reasonable.
+            length = pack(jpeg, offset, 2, false);
+            if (length < 2 || offset + length > jpeg.length) {
+                Log.e(TAG, "Invalid length");
+                return 0;
+            }
+
+            // Break if the marker is EXIF in APP1.
+            if (marker == 0xE1 && length >= 8
+                    && pack(jpeg, offset + 2, 4, false) == 0x45786966
+                    && pack(jpeg, offset + 6, 2, false) == 0) {
+                offset += 8;
+                length -= 8;
+                break;
+            }
+
+            // Skip other markers.
+            offset += length;
+            length = 0;
+        }
+
+        // JEITA CP-3451 Exif Version 2.2
+        if (length > 8) {
+            // Identify the byte order.
+            int tag = pack(jpeg, offset, 4, false);
+            if (tag != 0x49492A00 && tag != 0x4D4D002A) {
+                Log.e(TAG, "Invalid byte order");
+                return 0;
+            }
+            boolean littleEndian = (tag == 0x49492A00);
+
+            // Get the offset and check if it is reasonable.
+            int count = pack(jpeg, offset + 4, 4, littleEndian) + 2;
+            if (count < 10 || count > length) {
+                Log.e(TAG, "Invalid offset");
+                return 0;
+            }
+            offset += count;
+            length -= count;
+
+            // Get the count and go through all the elements.
+            count = pack(jpeg, offset - 2, 2, littleEndian);
+            while (count-- > 0 && length >= 12) {
+                // Get the tag and check if it is orientation.
+                tag = pack(jpeg, offset, 2, littleEndian);
+                if (tag == 0x0112) {
+                    // We do not really care about type and count, do we?
+                    int orientation = pack(jpeg, offset + 8, 2, littleEndian);
+                    switch (orientation) {
+                        case 1:
+                            return 0;
+                        case 3:
+                            return 180;
+                        case 6:
+                            return 90;
+                        case 8:
+                            return 270;
+                        default:
+                            return 0;
+                    }
+                }
+                offset += 12;
+                length -= 12;
+            }
+        }
+
+        Log.i(TAG, "Orientation not found");
+        return 0;
+    }
+
+    private static int pack(byte[] bytes, int offset, int length,
+                            boolean littleEndian) {
+        int step = 1;
+        if (littleEndian) {
+            offset += length - 1;
+            step = -1;
+        }
+
+        int value = 0;
+        while (length-- > 0) {
+            value = (value << 8) | (bytes[offset] & 0xFF);
+            offset += step;
+        }
+        return value;
+    }
+
+}

+ 28 - 0
cameraui/src/main/java/com/baidu/ai/cameraui/util/UiLog.java

@@ -0,0 +1,28 @@
+package com.baidu.ai.cameraui.util;
+
+import android.util.Log;
+
+/**
+ * Created by ruanshimin on 2018/5/4.
+ */
+
+public class UiLog {
+
+    private static final String TAG = "bd_aipe_ocr_offline_cameraui_log";
+
+    public static void info(String log) {
+        Log.i(TAG, log);
+    }
+
+    public static void warn(String log) {
+        Log.w(TAG, log);
+    }
+
+    public static void error(String log) {
+        Log.e(TAG, log);
+    }
+
+    public static void error(String log, Throwable e) {
+        Log.e(TAG, log, e);
+    }
+}

+ 390 - 0
cameraui/src/main/java/com/baidu/ai/cameraui/view/CropView.java

@@ -0,0 +1,390 @@
+package com.baidu.ai.cameraui.view;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.Point;
+import android.graphics.PorterDuff;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+
+import androidx.annotation.Nullable;
+
+/**
+ * Created by ruanshimin on 2018/6/20.
+ * 裁剪框类
+ */
+
+public class CropView extends View {
+    private Rect regionRect;
+    private Rect regionRectDefault = new Rect();
+
+    static final int ANCHOR_LEFT_TOP = 1;
+    static final int ANCHOR_RIGHT_TOP = 3;
+    static final int ANCHOR_LEFT_BOTTOM = 7;
+    static final int ANCHOR_RIGHT_BOTTOM = 9;
+
+    static final int ANCHOR_CENTER = 10;
+    static final int ANCHOR_RELEASE = -1;
+
+    private int ox;
+    private int oy;
+
+    private int lmtTop;
+    private int lmtBottom;
+    private int lmtLeft;
+    private int lmtRight;
+
+    private int minW = 300;
+    private int minH = 300;
+
+    private int rectAnchorFlag = ANCHOR_RELEASE;
+
+    public CropView(Context context) {
+        super(context);
+    }
+
+    public CropView(Context context, @Nullable AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public CropView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    /**
+     * 获取参见的矩形范围
+     * @return
+     */
+    public Rect getCropRect() {
+        return regionRect;
+    }
+
+    /**
+     * 初始设置裁剪框(保存为默认的裁剪框)
+     * @return
+     */
+    public void setCropRect(Rect region) {
+        regionRect = region;
+        regionRectDefault.set(region);
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        setLayerType(View.LAYER_TYPE_SOFTWARE, null);
+
+    }
+
+    /**
+     * 设置裁剪框约束范围
+     * 弱约束与初始裁剪框冲突,则限制裁剪框
+     *
+     * @param left
+     * @param right
+     * @param top
+     * @param bottom
+     */
+    public void setBound(int left, int top, int right, int bottom) {
+        lmtTop = top;
+        lmtBottom = bottom;
+        lmtLeft = left;
+        lmtRight = right;
+        if (regionRect != null && (regionRect.top < lmtTop || regionRect.bottom > lmtBottom
+                || regionRect.left < lmtLeft || regionRect.right > lmtRight)) {
+            regionRect.set(regionRectDefault);
+
+            // 判断是否款选区域大于图片区域
+            regionRect.set(regionRectDefault.left < left ? left : regionRect.left,
+                    regionRectDefault.top < top ? top : regionRect.top,
+                    regionRectDefault.right > right ? right : regionRect.right,
+                    regionRectDefault.bottom > bottom ? bottom : regionRect.bottom);
+            invalidate();
+        }
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        int offsetX;
+        int offsetY;
+        int x;
+        int y;
+        switch (event.getAction()) {
+            case MotionEvent.ACTION_DOWN:
+                ox = (int) event.getX();
+                oy = (int) event.getY();
+                rectAnchorFlag = getAnchor(ox, oy);
+                if (rectAnchorFlag != ANCHOR_RELEASE) {
+                    return true;
+                }
+                break;
+            case MotionEvent.ACTION_MOVE:
+                switch (rectAnchorFlag)  {
+                    case ANCHOR_CENTER:
+                        x = (int) event.getX();
+                        y = (int) event.getY();
+                        offsetX = x - ox;
+                        offsetY = y - oy;
+                        ox = x;
+                        oy = y;
+                        regionRect.set(tileInBound(offsetX, offsetY));
+                        invalidate();
+                        break;
+                    case ANCHOR_LEFT_TOP:
+                        x = (int) event.getX();
+                        y = (int) event.getY();
+                        regionRect.set(tileInBound(x, y, ANCHOR_LEFT_TOP));
+                        invalidate();
+                        break;
+                    case ANCHOR_RIGHT_TOP:
+                        x = (int) event.getX();
+                        y = (int) event.getY();
+                        oy = y;
+                        regionRect.set(tileInBound(x, y, ANCHOR_RIGHT_TOP));
+                        invalidate();
+                        break;
+                    case ANCHOR_LEFT_BOTTOM:
+                        x = (int) event.getX();
+                        y = (int) event.getY();
+                        regionRect.set(tileInBound(x, y, ANCHOR_LEFT_BOTTOM));
+                        invalidate();
+                        break;
+                    case ANCHOR_RIGHT_BOTTOM:
+                        x = (int) event.getX();
+                        y = (int) event.getY();
+                        regionRect.set(tileInBound(x, y, ANCHOR_RIGHT_BOTTOM));
+                        invalidate();
+                        break;
+                    default:
+                        break;
+                }
+                break;
+            case MotionEvent.ACTION_UP:
+                rectAnchorFlag = ANCHOR_RELEASE;
+                break;
+            default:
+                break;
+        }
+
+        return false;
+    }
+
+    /**
+     * 拖动四个角,限制最终区域
+     *
+     * @param x x坐标
+     * @param y y坐标
+     * @param type 类型,左上,右上,左下,右下
+     * @return
+     */
+    private Rect tileInBound(int x, int y, int type) {
+        int leftT = regionRect.left;
+        int topT = regionRect.top;
+        int rightT = regionRect.right;
+        int bottomT = regionRect.bottom;
+        int w = getWidth();
+        int h = getHeight();
+        switch (type) {
+            case ANCHOR_LEFT_TOP:
+                leftT = x < lmtLeft ? lmtLeft : x;
+                topT = y < lmtTop ? lmtTop : y;
+                if (rightT - leftT < minW) {
+                    leftT = rightT - minW;
+                }
+                if (bottomT - topT < minH) {
+                    topT = bottomT - minH;
+                }
+                break;
+            case ANCHOR_RIGHT_TOP:
+                rightT = x > lmtRight ? lmtRight : x;
+                topT = y < lmtTop ? lmtTop : y;
+                if (rightT - leftT < minW) {
+                    rightT = leftT + minW;
+                }
+                if (bottomT - topT < minH) {
+                    topT = bottomT - minH;
+                }
+                break;
+            case ANCHOR_LEFT_BOTTOM:
+                leftT = x < lmtLeft ? lmtLeft : x;
+                bottomT = y > lmtBottom ? lmtBottom : y;
+                if (rightT - leftT < minW) {
+                    leftT = rightT - minW;
+                }
+                if (bottomT - topT < minH) {
+                    bottomT =  topT + minH;
+                }
+                break;
+            case ANCHOR_RIGHT_BOTTOM:
+                rightT = x > lmtRight ? lmtRight : x;
+                bottomT = y > lmtBottom ? lmtBottom : y;
+                if (rightT - leftT < minW) {
+                    rightT = leftT + minW;
+                }
+                if (bottomT - topT < minH) {
+                    bottomT =  topT + minH;
+                }
+                break;
+            default:
+                break;
+
+        }
+        return new Rect(leftT, topT, rightT, bottomT);
+    }
+
+    /**
+     * 拖动移动时判断是否超出边界
+     *
+     * @return
+     */
+    private Rect tileInBound(int offsetX, int offsetY) {
+        int left;
+        int top;
+        int right;
+        int bottom;
+        int w = regionRect.right - regionRect.left;
+        int h = regionRect.bottom - regionRect.top;
+        top = regionRect.top + offsetY;
+        bottom = regionRect.bottom + offsetY;
+        left = regionRect.left + offsetX;
+        right = regionRect.right + offsetX;
+        if (regionRect.left + offsetX < lmtLeft) {
+            left = lmtLeft;
+            right = lmtLeft + w;
+        }
+        if (regionRect.right + offsetX > lmtRight) {
+            right = lmtRight;
+            left = lmtRight - w;
+        }
+        if (regionRect.top + offsetY < lmtTop) {
+            top = lmtTop;
+            bottom = lmtTop + h;
+        }
+        if (regionRect.bottom + offsetY > lmtBottom) {
+            bottom = lmtBottom;
+            top = bottom - h;
+        }
+        return new Rect(left, top, right, bottom);
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        drawRectFrame(canvas);
+        super.onDraw(canvas);
+    }
+
+    /**
+     * 绘制裁剪框框
+     * @param canvas
+     */
+    private void drawRectFrame(Canvas canvas) {
+        Paint filled = new Paint();
+        filled.setStyle(Paint.Style.FILL);
+        filled.setColor(Color.BLACK);
+        filled.setAlpha(70);
+        Paint paint = new Paint();
+        int sw = 2;
+        int offset = 5;
+        int tw = 50;
+
+        paint.setStyle(Paint.Style.STROKE);
+        paint.setColor(Color.WHITE);
+        paint.setStrokeWidth(sw);
+
+        // 绘制可显示区域
+        canvas.drawRect(new Rect(0, 0,
+                getWidth(), getHeight()), filled);
+
+        // 去掉除可选区域
+        canvas.clipRect(new Rect(regionRect.left, regionRect.top,
+                regionRect.right, regionRect.bottom));
+
+        canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
+
+        // 绘制框框
+        canvas.drawRect(new Rect(regionRect.left + offset, regionRect.top + offset,
+                regionRect.right - offset, regionRect.bottom - offset), paint);
+
+        Point p1 = new Point(regionRect.left + offset, regionRect.top + offset);
+        Point p2 = new Point(regionRect.right - offset, regionRect.top + offset);
+        Point p3 = new Point(regionRect.left + offset, regionRect.bottom - offset);
+        Point p4 = new Point(regionRect.right - offset, regionRect.bottom - offset);
+
+        // 绘制四个角
+        Paint paintCorner = new Paint();
+        paintCorner.setStyle(Paint.Style.STROKE);
+        paintCorner.setColor(Color.WHITE);
+        paintCorner.setStrokeWidth(10);
+
+        Path path = new Path();
+
+        path.moveTo(p1.x, p1.y  + tw);
+        path.lineTo(p1.x, p1.y);
+        path.lineTo(p1.x + tw, p1.y);
+
+        path.moveTo(p2.x - tw, p2.y);
+        path.lineTo(p2.x, p2.y);
+        path.lineTo(p2.x, p2.y + tw);
+
+        path.moveTo(p3.x, p3.y  - tw);
+        path.lineTo(p3.x, p3.y);
+        path.lineTo(p3.x  + tw, p3.y);
+
+        path.moveTo(p4.x, p4.y - tw);
+        path.lineTo(p4.x, p4.y);
+        path.lineTo(p4.x - tw, p4.y );
+        canvas.drawPath(path, paintCorner);
+    }
+
+    /**
+     * 1   3
+     *
+     * 7   9
+     * 判断拖动的锚点
+     * @param x
+     * @param y
+     * @return
+     */
+    private int getAnchor(int x, int y) {
+        Rect rectTemp = new Rect();
+        int of = 100;
+
+        // 左上锚点
+        rectTemp.set(regionRect.left - of, regionRect.top - of,
+                regionRect.left + of, regionRect.top + of);
+        if (rectTemp.contains(x, y)) {
+            return ANCHOR_LEFT_TOP;
+        }
+
+        // 右上锚点
+        rectTemp.set(regionRect.right - of, regionRect.top - of,
+                regionRect.right + of, regionRect.top + of);
+        if (rectTemp.contains(x, y)) {
+            return ANCHOR_RIGHT_TOP;
+        }
+
+        // 左下锚点
+        rectTemp.set(regionRect.left - of, regionRect.bottom - of,
+                regionRect.left + of, regionRect.bottom + of);
+        if (rectTemp.contains(x, y)) {
+            return ANCHOR_LEFT_BOTTOM;
+        }
+
+        // 右下锚点
+        rectTemp.set(regionRect.right - of, regionRect.bottom - of,
+                regionRect.right + of, regionRect.bottom + of);
+        if (rectTemp.contains(x, y)) {
+            return ANCHOR_RIGHT_BOTTOM;
+        }
+
+        if (regionRect.contains(x, y)) {
+            return ANCHOR_CENTER;
+        }
+
+        return ANCHOR_RELEASE;
+    }
+}

+ 259 - 0
cameraui/src/main/java/com/baidu/ai/cameraui/view/MaskView.java

@@ -0,0 +1,259 @@
+package com.baidu.ai.cameraui.view;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewTreeObserver;
+
+import androidx.annotation.Nullable;
+
+import com.baidu.ai.cameraui.R;
+
+
+/**
+ * Created by ruanshimin on 2018/6/20.
+ * 展示不同样式的模板遮罩层
+ */
+
+public class MaskView extends View {
+
+    public MaskView(Context context) {
+        super(context);
+    }
+
+    public MaskView(Context context, @Nullable AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public MaskView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    public static final int MASK_NONE = 0;
+
+    public static final int MASK_TYPE_BANKCARD = 1;
+
+    public static final int MASK_TYPE_IDCARD_FRONT = 2;
+
+    public static final int MASK_TYPE_IDCARD_BACK = 3;
+
+    public void setTipString(String tipString) {
+        this.tipString = tipString;
+    }
+
+    public void setTipStringAndUpdate(String tipString) {
+        this.tipString = tipString;
+        invalidate();
+    }
+
+    private String tipString;
+
+    private int maskType;
+
+    public int getMaskType() {
+        return maskType;
+    }
+
+    private Paint eraser;
+
+    private Rect maskFrame;
+
+    private int width;
+    private int height;
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        setLayerType(View.LAYER_TYPE_SOFTWARE, null);
+        eraser = new Paint();
+        eraser.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
+        getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
+            @Override
+            public boolean onPreDraw() {
+                width = getWidth();
+                height = getHeight();
+                int hc = height / 2;
+                int wPad = 50;
+                float whRatio = 0.64f;
+                int rw = width - wPad * 2;
+                int rh = (int) (whRatio * rw);
+                maskFrame = new Rect(wPad,
+                        (int) (hc - (0.5 * rh)),  width - wPad, (int) (hc + (0.5 * rh)));
+                return true;
+            }
+        });
+    }
+
+    public Rect getMaskFrame() {
+        return maskFrame;
+    }
+
+    public void setMaskType(int maskType) {
+        this.maskType = maskType;
+    }
+
+    private Path fillRectRound(float left, float top, float right, float bottom, float rx, float ry, boolean
+            conformToOriginalPost) {
+
+        Path path = new Path();
+        if (rx < 0) {
+            rx = 0;
+        }
+        if (ry < 0) {
+            ry = 0;
+        }
+        float width = right - left;
+        float height = bottom - top;
+        if (rx > width / 2) {
+            rx = width / 2;
+        }
+        if (ry > height / 2) {
+            ry = height / 2;
+        }
+        float widthMinusCorners = (width - (2 * rx));
+        float heightMinusCorners = (height - (2 * ry));
+
+        path.moveTo(right, top + ry);
+        path.rQuadTo(0, -ry, -rx, -ry);
+        path.rLineTo(-widthMinusCorners, 0);
+        path.rQuadTo(-rx, 0, -rx, ry);
+        path.rLineTo(0, heightMinusCorners);
+        path.rQuadTo(0, ry, rx, ry);
+        path.rLineTo(widthMinusCorners, 0);
+        path.rQuadTo(rx, 0, rx, -ry);
+
+        path.rLineTo(0, -heightMinusCorners);
+        path.close();
+        return path;
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+
+        switch (maskType) {
+            case MASK_TYPE_BANKCARD:
+                drawMaskBankcard(canvas);
+                drawTip(canvas);
+                break;
+            case MASK_TYPE_IDCARD_FRONT:
+                drawTip(canvas);
+                drawMaskIDcard(canvas, true);
+                break;
+            case MASK_TYPE_IDCARD_BACK:
+                drawTip(canvas);
+                drawMaskIDcard(canvas, false);
+                break;
+            default:
+                drawMaskBackground(canvas);
+        }
+
+        super.onDraw(canvas);
+    }
+
+    /**
+     * 绘制背景
+     *
+     */
+    private void drawMaskBackground(Canvas canvas) {
+        int w = getWidth();
+        int h = getHeight();
+        Paint filled = new Paint();
+        filled.setStyle(Paint.Style.FILL);
+        filled.setColor(Color.BLACK);
+        filled.setAlpha(50);
+        canvas.drawRect(new Rect(0, 0, w, h), filled);
+    }
+
+    /**
+     * 绘制提示信息
+     *
+     * @param canvas
+     */
+    private void drawTip(Canvas canvas) {
+        if (tipString == null) {
+            return;
+        }
+        Paint pt = new Paint();
+        float w = getWidth();
+        float bt = maskFrame.bottom;
+        pt.setColor(Color.WHITE);
+        pt.setTextSize(35);
+        pt.setTextAlign(Paint.Align.CENTER);
+        canvas.drawText(tipString,  w / 2 , bt + 100, pt);
+    }
+
+    /**
+     * 绘制银行卡蒙版
+     *
+     * @param canvas
+     */
+    private void drawMaskBankcard(Canvas canvas) {
+
+        Path path = fillRectRound(maskFrame.left, maskFrame.top, maskFrame.right,
+                maskFrame.bottom, 25, 25, false);
+
+        Paint stroke = new Paint();
+
+        stroke.setStyle(Paint.Style.STROKE);
+        stroke.setColor(Color.WHITE);
+        stroke.setStrokeWidth(2);
+
+        canvas.drawPath(path, stroke);
+        canvas.drawPath(path, eraser);
+    }
+
+    /**
+     * 绘制身份证模板
+     * @param canvas
+     * @param isFront 是否是正面
+     */
+    private void drawMaskIDcard(Canvas canvas, boolean isFront) {
+
+        Path path = fillRectRound(maskFrame.left, maskFrame.top, maskFrame.right,
+                maskFrame.bottom, 25, 25, false);
+
+        int offsetAvatarLeft = (int) (maskFrame.left + maskFrame.width() * 0.58f);
+        int offsetAvatarTop = (int) (maskFrame.top + maskFrame.height() / 7.0f);
+        int offsetAvatarRight = (int) (maskFrame.left + maskFrame.width() * 0.94f);
+        int offsetAvatarBottom = (int) (maskFrame.top + maskFrame.height() * 0.74f);
+
+        int offsetEmblemLeft = (int) (maskFrame.left + maskFrame.width() * 0.05f);
+        int offsetEmblemTop = (int) (maskFrame.top + maskFrame.height() * 0.07f);
+        int offsetEmblemRight = (int) (maskFrame.left + maskFrame.width() * 0.25f);
+        int offsetEmblemBottom = (int) (maskFrame.top + maskFrame.height() * 0.39f);
+
+        Paint stroke = new Paint();
+
+        stroke.setStyle(Paint.Style.STROKE);
+        stroke.setColor(Color.WHITE);
+        stroke.setStrokeWidth(2);
+
+        Paint bitmapPaint = new Paint();
+
+        canvas.drawPath(path, stroke);
+        canvas.drawPath(path, eraser);
+        if (isFront) {
+            Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.id_card_locator_front);
+            canvas.drawBitmap(bitmap,
+                    null,
+                    new Rect(offsetAvatarLeft, offsetAvatarTop, offsetAvatarRight, offsetAvatarBottom),
+                    bitmapPaint);
+        } else {
+            Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.id_card_locator_back);
+            canvas.drawBitmap(bitmap,
+                    null,
+                    new Rect(offsetEmblemLeft, offsetEmblemTop, offsetEmblemRight, offsetEmblemBottom),
+                    bitmapPaint);
+        }
+
+    }
+}

+ 151 - 0
cameraui/src/main/java/com/baidu/ai/cameraui/view/PreviewView.java

@@ -0,0 +1,151 @@
+package com.baidu.ai.cameraui.view;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.SurfaceTexture;
+import android.util.AttributeSet;
+import android.view.TextureView;
+
+import com.baidu.ai.cameraui.camera.CameraListener;
+import com.baidu.ai.cameraui.camera.CameraProxy1;
+import com.baidu.ai.cameraui.camera.ICameraProxy;
+import com.baidu.ai.cameraui.util.UiLog;
+
+/**
+ * Created by ruanshimin on 2018/6/13.
+ * 处理预览与相机的交互
+ */
+
+public class PreviewView extends TextureView implements TextureView.SurfaceTextureListener {
+    private CameraProxy1 mCameraProxy;
+
+    private int layoutWidth;
+    private int layoutHeight;
+    private int actualHeight;
+    private int cropHeight;
+
+    private boolean isFirstLayout = true;
+    private boolean hasSurfaceDestory = false;
+
+    public PreviewView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        setSurfaceTextureListener(this);
+    }
+
+    public PreviewView(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        setSurfaceTextureListener(this);
+    }
+
+    /**
+     *
+     * @param listener
+     */
+    public void takePicture(final CameraListener.TakePictureListener listener) {
+        if (mCameraProxy == null) {
+            return;
+        }
+        mCameraProxy.takePicture(new CameraListener.TakePictureListener() {
+            @Override
+            public void onTakenPicture(Bitmap bitmap) {
+                Bitmap cropBitmap = Bitmap.createBitmap(bitmap, 0, 0,
+                        bitmap.getWidth(), cropHeight);
+                listener.onTakenPicture(cropBitmap);
+            }
+        });
+    }
+
+    /**
+     * 停止摄像机预览
+     */
+    public void stopPreview() {
+        mCameraProxy.stopPreview();
+    }
+
+    /**
+     * 范湖相机操作代理
+     * @return
+     */
+    public ICameraProxy getCameraControl() {
+        return mCameraProxy;
+    }
+
+    /**
+     *  根据首次绘制获得的预览框实际大小,对预览框重新调整比例(高度拉伸)
+     *  从camera.getPreviewSize方法获取的size满足长宽比小于实际预览窗口长宽比
+     *  仅对5.0以下camera有意义
+     */
+    private void setStretchHeight() {
+        int[] size = mCameraProxy.getPreviewSize();
+        actualHeight =  (int) (((float)  size[1] / size[0]) * layoutWidth);
+        cropHeight = (int) (((float)  layoutHeight / layoutWidth) * size[0]);
+    }
+
+
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        // 第一次正常measure,第二次之后视为需要根据camera预览实际长宽重新拉伸
+        if (!isFirstLayout) {
+            setMeasuredDimension(getMeasuredWidth(), actualHeight);
+        } else {
+            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        }
+    }
+
+    public boolean isCameraOpened() {
+        return mCameraProxy.getStatus() > 0;
+    }
+
+    @Override
+    public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
+        UiLog.info("onSurfaceTextureAvailable" + width + ":" + height);
+        if (isFirstLayout) {
+            layoutWidth = width;
+            layoutHeight = height;
+            mCameraProxy = new CameraProxy1(width, height);
+            // 打开相机后可获取相机参数
+            try {
+                mCameraProxy.openCamera();
+            } catch (RuntimeException e) {
+                // 主要是某些手机权限返回granted但实际还未获取到权限
+                return;
+            }
+            setStretchHeight();
+            requestLayout();
+            isFirstLayout = false;
+        }
+
+        // 某些机器stop后会销毁SurfaceTexture,需要重新打开摄像机
+        if (hasSurfaceDestory) {
+            try {
+                mCameraProxy.openCamera();
+            } catch (RuntimeException e) {
+                // 主要是某些手机权限返回granted但实际还未获取到权限
+                return;
+            }
+            hasSurfaceDestory = false;
+        }
+
+        mCameraProxy.setSurfaceTexture(surface);
+        mCameraProxy.startPreview();
+    }
+
+    @Override
+    public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
+        // MIUI activity stop后会销毁SurfaceTexture,需要通知camera
+        UiLog.info("onSurfaceTextureDestroyed");
+        mCameraProxy.setSurfaceTexture(null);
+        hasSurfaceDestory = true;
+        return false;
+    }
+
+    @Override
+    public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
+    }
+
+    @Override
+    public void onSurfaceTextureUpdated(SurfaceTexture surface) {
+
+    }
+}

BIN
cameraui/src/main/res/drawable-xhdpi/album_btn.png


BIN
cameraui/src/main/res/drawable-xhdpi/cancel_btn.png


BIN
cameraui/src/main/res/drawable-xhdpi/confirm_btn.png


BIN
cameraui/src/main/res/drawable-xhdpi/flash_off_btn.png


BIN
cameraui/src/main/res/drawable-xhdpi/flash_on_btn.png


BIN
cameraui/src/main/res/drawable-xhdpi/id_card_locator_back.png


BIN
cameraui/src/main/res/drawable-xhdpi/id_card_locator_front.png


BIN
cameraui/src/main/res/drawable-xhdpi/rotate_picture_btn.png


BIN
cameraui/src/main/res/drawable-xhdpi/take_picture_btn.png


+ 31 - 0
cameraui/src/main/res/layout/action_bar_views.xml

@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+    <ImageView
+        android:cropToPadding="true"
+        android:id="@+id/rotate_picture_btn"
+        android:layout_width="30dp"
+        android:layout_height="30dp"
+        android:layout_centerHorizontal="true"
+        android:layout_centerVertical="true"
+        android:src="@drawable/rotate_picture_btn"
+        />
+    <ImageView
+        android:id="@+id/confirm_btn"
+        android:layout_width="@dimen/sides_btn_size"
+        android:layout_height="@dimen/sides_btn_size"
+        android:layout_centerVertical="true"
+        android:layout_marginRight="@dimen/sides_btn_margin"
+        android:layout_alignParentRight="true"
+        android:src="@drawable/confirm_btn"
+        />
+    <ImageView
+        android:cropToPadding="true"
+        android:id="@+id/cancel_btn"
+        android:layout_width="40dp"
+        android:layout_height="40dp"
+        android:padding="8dp"
+        android:layout_centerVertical="true"
+        android:layout_marginLeft="@dimen/sides_btn_margin"
+        android:src="@drawable/cancel_btn"
+        />
+</merge>

+ 40 - 0
cameraui/src/main/res/layout/camera_view.xml

@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:background="#CCCCCC"
+    android:orientation="vertical"
+    android:layout_height="match_parent"
+    android:layout_width="match_parent">
+    <FrameLayout
+        android:id="@+id/preview_wrapper"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_weight="1"
+        >
+        <ImageView
+            android:id="@+id/preview_snapshot"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent" />
+        <com.baidu.ai.cameraui.view.PreviewView
+            android:id="@+id/preview_view"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            />
+        <com.baidu.ai.cameraui.view.MaskView
+            android:id="@+id/maskview"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent" />
+        <com.baidu.ai.cameraui.view.CropView
+            android:id="@+id/cropview"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent" />
+    </FrameLayout>
+    <RelativeLayout
+        android:background="#232323"
+        android:layout_width="match_parent"
+        android:layout_height="100px"
+        >
+        <include layout="@layout/preview_bar_views"></include>
+        <include layout="@layout/action_bar_views"></include>
+    </RelativeLayout>
+</LinearLayout>

+ 32 - 0
cameraui/src/main/res/layout/preview_bar_views.xml

@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+    <ImageView
+        android:id="@+id/take_picture_btn"
+        android:layout_width="65dp"
+        android:layout_height="65dp"
+        android:layout_centerHorizontal="true"
+        android:layout_centerVertical="true"
+        android:src="@drawable/take_picture_btn"
+        />
+    <ImageView
+        android:id="@+id/flash_btn"
+        android:cropToPadding="true"
+        android:layout_width="50dp"
+        android:layout_height="50dp"
+        android:padding="8dp"
+        android:layout_centerVertical="true"
+        android:layout_marginRight="@dimen/sides_btn_margin"
+        android:layout_alignParentRight="true"
+        android:src="@drawable/flash_off_btn"
+        />
+    <ImageView
+        android:id="@+id/album_btn"
+        android:cropToPadding="true"
+        android:layout_width="40dp"
+        android:layout_height="@dimen/sides_btn_size"
+        android:paddingLeft="10dp"
+        android:layout_centerVertical="true"
+        android:layout_marginLeft="@dimen/sides_btn_margin"
+        android:src="@drawable/album_btn"
+        />
+</merge>

+ 3 - 0
cameraui/src/main/res/values/strings.xml

@@ -0,0 +1,3 @@
+<resources>
+    <string name="app_name">CameraUI</string>
+</resources>

+ 5 - 0
cameraui/src/main/res/values/values.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <dimen name="sides_btn_margin">30dp</dimen>
+    <dimen name="sides_btn_size">30dp</dimen>
+</resources>

+ 17 - 0
cameraui/src/test/java/com/baidu/ai/cameraui/ExampleUnitTest.java

@@ -0,0 +1,17 @@
+package com.baidu.ai.cameraui;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
+ */
+public class ExampleUnitTest {
+    @Test
+    public void addition_isCorrect() throws Exception {
+        assertEquals(4, 2 + 2);
+    }
+}