yangyang 1 год назад
Родитель
Сommit
292579278c
40 измененных файлов с 2944 добавлено и 271 удалено
  1. 2 2
      app/build.gradle
  2. 6 4
      app/src/main/AndroidManifest.xml
  3. 5 0
      app/src/main/java/com/goldze/mvvmhabit/data/source/model/MobileDatumResponseBean.java
  4. 7 0
      app/src/main/java/com/goldze/mvvmhabit/data/source/model/NextEvent.java
  5. 49 1
      app/src/main/java/com/goldze/mvvmhabit/ui/main/adapter/MonitoringAdapter.java
  6. 176 0
      app/src/main/java/com/goldze/mvvmhabit/ui/main/adapter/NewMonitoringAdapter.java
  7. 26 3
      app/src/main/java/com/goldze/mvvmhabit/ui/main/fragment/MonitoringFragment.java
  8. 74 0
      app/src/main/java/com/goldze/mvvmhabit/ui/monitor/MultiplePreviewActivity.java
  9. 275 0
      app/src/main/java/com/goldze/mvvmhabit/ui/order/FaceActivity.java
  10. 36 240
      app/src/main/java/com/goldze/mvvmhabit/ui/order/OrderInfoActivity.java
  11. 18 13
      app/src/main/java/com/goldze/mvvmhabit/ui/order/OrderInfoViewModel.java
  12. 18 4
      app/src/main/java/com/goldze/mvvmhabit/ui/view/TemplateTitleBar.java
  13. BIN
      app/src/main/res/drawable-xxhdpi/choose.png
  14. BIN
      app/src/main/res/drawable-xxhdpi/onchoose.png
  15. BIN
      app/src/main/res/drawable-xxhdpi/zhidao_face_scan_bg.png
  16. 53 0
      app/src/main/res/layout/activity_face.xml
  17. 33 0
      app/src/main/res/layout/activity_multiple_preview.xml
  18. 1 0
      app/src/main/res/layout/fragment_monitoring.xml
  19. 9 0
      app/src/main/res/layout/item_monitoring.xml
  20. 18 0
      app/src/main/res/layout/item_new_monitoring.xml
  21. 17 0
      app/src/main/res/layout/widget_title.xml
  22. 3 3
      app/src/main/res/values/strings.xml
  23. 2 0
      app/src/main/res/values/styles.xml
  24. 5 1
      build.gradle
  25. 194 0
      mvvmhabit/src/main/java/me/goldze/mvvmhabit/face/AspectRatio.java
  26. 516 0
      mvvmhabit/src/main/java/me/goldze/mvvmhabit/face/Camera1.java
  27. 570 0
      mvvmhabit/src/main/java/me/goldze/mvvmhabit/face/CameraView.java
  28. 84 0
      mvvmhabit/src/main/java/me/goldze/mvvmhabit/face/CameraViewImpl.java
  29. 35 0
      mvvmhabit/src/main/java/me/goldze/mvvmhabit/face/Constants.java
  30. 96 0
      mvvmhabit/src/main/java/me/goldze/mvvmhabit/face/DisplayOrientationDetector.java
  31. 81 0
      mvvmhabit/src/main/java/me/goldze/mvvmhabit/face/PreviewImpl.java
  32. 80 0
      mvvmhabit/src/main/java/me/goldze/mvvmhabit/face/Size.java
  33. 82 0
      mvvmhabit/src/main/java/me/goldze/mvvmhabit/face/SizeMap.java
  34. 89 0
      mvvmhabit/src/main/java/me/goldze/mvvmhabit/face/SurfaceViewPreview.java
  35. 146 0
      mvvmhabit/src/main/java/me/goldze/mvvmhabit/face/TextureViewPreview.java
  36. 40 0
      mvvmhabit/src/main/java/me/goldze/mvvmhabit/utils/ImageUtils.java
  37. 23 0
      mvvmhabit/src/main/res/layout/surface_view.xml
  38. 23 0
      mvvmhabit/src/main/res/layout/texture_view.xml
  39. 44 0
      mvvmhabit/src/main/res/values/attrs.xml
  40. 8 0
      mvvmhabit/src/main/res/values/styles.xml

+ 2 - 2
app/build.gradle

@@ -78,9 +78,9 @@ dependencies {
     //support
     implementation rootProject.ext.support["design"]
     //下拉刷新,上拉加载
-    implementation 'com.lcodecorex:tkrefreshlayout:1.0.7'
+    implementation 'com.lcodecorex:tkrefreshlayout:1.0.6'
     //底部tabBar
-    implementation('me.majiajie:pager-bottom-tab-strip:2.2.5') {
+    implementation('me.majiajie:pager-bottom-tab-strip:2.2.2') {
         exclude group: 'com.android.support'
     }
     //MVVMHabit

+ 6 - 4
app/src/main/AndroidManifest.xml

@@ -13,7 +13,9 @@
         android:supportsRtl="true"
         android:theme="@style/AppNoTitle"
         tools:replace="android:theme,android:allowBackup,android:icon">
-        <activity android:name=".ui.monitor.FileBrowserActivity"/>
+        <activity android:name=".ui.monitor.MultiplePreviewActivity"></activity>
+        <activity android:name=".ui.order.FaceActivity" />
+        <activity android:name=".ui.monitor.FileBrowserActivity" />
         <activity android:name=".ui.monitor.DownLoadRecordFileActivity" />
         <activity android:name=".ui.monitor.PlayBackActivity" />
         <activity android:name=".ui.monitor.LivePreviewActivity" />
@@ -48,14 +50,14 @@
             android:exported="true"
             android:foregroundServiceType="mediaProjection" />
     </application>
-    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
-    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
     <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
     <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
     <uses-permission android:name="android.permission.INTERNET" />
-
     <uses-permission android:name="android.permission.CAMERA" />
+    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
+
+    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
 
 </manifest>

+ 5 - 0
app/src/main/java/com/goldze/mvvmhabit/data/source/model/MobileDatumResponseBean.java

@@ -11,6 +11,11 @@ public class MobileDatumResponseBean implements Serializable {
     private boolean isSelect;
 
 
+    public MobileDatumResponseBean(String address, Integer channel) {
+        this.address = address;
+        this.channel = channel;
+    }
+
     public boolean isSelect() {
         return isSelect;
     }

+ 7 - 0
app/src/main/java/com/goldze/mvvmhabit/data/source/model/NextEvent.java

@@ -0,0 +1,7 @@
+package com.goldze.mvvmhabit.data.source.model;
+
+import java.io.Serializable;
+
+public class NextEvent implements Serializable {
+
+}

+ 49 - 1
app/src/main/java/com/goldze/mvvmhabit/ui/main/adapter/MonitoringAdapter.java

@@ -1,18 +1,25 @@
 package com.goldze.mvvmhabit.ui.main.adapter;
 
 import android.content.Context;
+import android.util.SparseBooleanArray;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
+import android.widget.CompoundButton;
 
 import com.goldze.mvvmhabit.R;
+import com.goldze.mvvmhabit.data.source.model.MobileDatumResponseBean;
 import com.goldze.mvvmhabit.databinding.ItemMonitoringBinding;
 
+import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 import androidx.annotation.NonNull;
 import androidx.databinding.DataBindingUtil;
 import androidx.recyclerview.widget.RecyclerView;
+import me.goldze.mvvmhabit.utils.ToastUtils;
 
 public class MonitoringAdapter extends RecyclerView.Adapter {
 
@@ -20,14 +27,22 @@ public class MonitoringAdapter extends RecyclerView.Adapter {
 
     private List<String> list;
 
+    private Map<Integer, MobileDatumResponseBean> select;
+
+    private SparseBooleanArray mSelectedItems;
+
+    private boolean isShow;
+
     public MonitoringAdapter(Context context, List<String> list) {
         this.context = context;
         this.list = list;
+        mSelectedItems = new SparseBooleanArray();
+        select = new HashMap<>();
     }
 
     @NonNull
     @Override
-    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull  ViewGroup parent, int viewType) {
+    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
         return new MyRecycleViewHolder(DataBindingUtil.inflate(LayoutInflater.from(context), R.layout.item_monitoring,
                 parent, false));
     }
@@ -36,6 +51,27 @@ public class MonitoringAdapter extends RecyclerView.Adapter {
     public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position) {
         MyRecycleViewHolder holder = (MyRecycleViewHolder) viewHolder;
         holder.binding.setItem(list.get(position));
+        holder.binding.check.setChecked(mSelectedItems.get(position));
+        holder.binding.check.setVisibility(isShow ? View.VISIBLE : View.INVISIBLE);
+        holder.binding.ivReturn.setVisibility(isShow ? View.GONE : View.VISIBLE);
+        holder.binding.check.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+            @Override
+            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+                if (isChecked) {
+                    if (select.size() == 6) {
+                        ToastUtils.showShort("最多选择六个");
+                        mSelectedItems.put(position, false);
+                        notifyItemChanged(position);
+                        return;
+                    }
+                    select.put(position,new MobileDatumResponseBean(list.get(position), position));
+                    mSelectedItems.put(position, true);
+                } else {
+                    select.remove(position);
+                    mSelectedItems.delete(position);
+                }
+            }
+        });
         holder.binding.rl.setOnClickListener(new View.OnClickListener() {
             @Override
             public void onClick(View v) {
@@ -64,7 +100,19 @@ public class MonitoringAdapter extends RecyclerView.Adapter {
     public void setOnItemClickListener(OnItemClickListener l) {
         this.mItemClickListener = l;
     }
+
     public interface OnItemClickListener {
         void onItemClick(int position);
     }
+
+    public List<MobileDatumResponseBean> getSelect() {
+        List<MobileDatumResponseBean> list = new ArrayList<>(select.values());
+        return list;
+    }
+
+    public void setShow(boolean b) {
+        select.clear();
+        mSelectedItems.clear();
+        isShow = b;
+    }
 }

+ 176 - 0
app/src/main/java/com/goldze/mvvmhabit/ui/main/adapter/NewMonitoringAdapter.java

@@ -0,0 +1,176 @@
+package com.goldze.mvvmhabit.ui.main.adapter;
+
+import android.content.Context;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.ViewGroup;
+
+import com.company.NetSDK.CB_fRealDataCallBackEx;
+import com.company.NetSDK.INetSDK;
+import com.company.NetSDK.SDK_RealPlayType;
+import com.company.PlaySDK.IPlaySDK;
+import com.goldze.mvvmhabit.R;
+import com.goldze.mvvmhabit.data.source.model.MobileDatumResponseBean;
+import com.goldze.mvvmhabit.databinding.ItemNewMonitoringBinding;
+import com.goldze.mvvmhabit.utils.SPUtil;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+import androidx.annotation.NonNull;
+import androidx.databinding.DataBindingUtil;
+import androidx.recyclerview.widget.RecyclerView;
+
+public class NewMonitoringAdapter extends RecyclerView.Adapter {
+
+    private Context context;
+
+    private List<MobileDatumResponseBean> list;
+
+    private final int STREAM_BUFFER_SIZE = 1024 * 1024 * 2;
+
+    private List<Long> playHandleList = new ArrayList<>();
+
+    HashMap<Long, Integer> handlersMapPorts = new HashMap<Long, Integer>();
+
+    public NewMonitoringAdapter(Context context, List<MobileDatumResponseBean> list) {
+        this.context = context;
+        this.list = list;
+    }
+
+    @NonNull
+    @Override
+    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+        return new MyRecycleViewHolder(DataBindingUtil.inflate(LayoutInflater.from(context), R.layout.item_new_monitoring,
+                parent, false));
+    }
+
+    @Override
+    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position) {
+        // 获取屏幕宽度
+        int screenWidth = viewHolder.itemView.getResources().getDisplayMetrics().heightPixels;
+        // 设置item高度为屏幕高度的一半
+        int itemHeight = screenWidth / 4;
+
+        // 为item的布局参数设置新的高度
+        viewHolder.itemView.getLayoutParams().height = itemHeight;
+        viewHolder.itemView.requestLayout();
+        MyRecycleViewHolder holder = (MyRecycleViewHolder) viewHolder;
+        holder.binding.setItem(list.get(position));
+//        holder.binding.rl.setOnClickListener(new View.OnClickListener() {
+//            @Override
+//            public void onClick(View v) {
+//                mItemClickListener.onItemClick(position);
+//            }
+//        });
+        boolean b = multiPlay_channel1(list.get(position).getChannel(), holder.binding.surfaceView);
+        Log.e("multiPlay_channel1", "onBindViewHolder: " + b);
+
+    }
+
+    @Override
+    public int getItemCount() {
+        return list == null ? 0 : list.size();
+    }
+
+
+    class MyRecycleViewHolder extends RecyclerView.ViewHolder {
+        private ItemNewMonitoringBinding binding;
+
+        MyRecycleViewHolder(@NonNull ItemNewMonitoringBinding itemView) {
+            super(itemView.getRoot());
+            this.binding = itemView;
+        }
+    }
+
+    private OnItemClickListener mItemClickListener;
+
+    public void setOnItemClickListener(OnItemClickListener l) {
+        this.mItemClickListener = l;
+    }
+
+    public interface OnItemClickListener {
+        void onItemClick(int position);
+    }
+
+    ///初始化视频窗口
+    public void initSurfaceView(int port, final SurfaceView sv) {
+        if (sv == null)
+            return;
+        sv.getHolder().addCallback(new SurfaceHolder.Callback() {
+            @Override
+            public void surfaceCreated(SurfaceHolder holder) {
+//                IPlaySDK.InitSurface(port,view);
+                IPlaySDK.InitSurface(port, sv);
+            }
+
+            @Override
+            public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+
+            }
+
+            @Override
+            public void surfaceDestroyed(SurfaceHolder holder) {
+
+            }
+        });
+
+    }
+
+    public boolean multiPlay_channel1(int chn, final SurfaceView view) {
+        final int port = IPlaySDK.PLAYGetFreePort();
+        initSurfaceView(port, view);
+        if (!openStream(view, port)) {
+            return false;
+        }
+
+        if (IPlaySDK.PLAYSetDelayTime(port, 500/*ms*/, 1000/*ms*/) == 0) {
+            Log.d("multiPlay_channel1", "SetDelayTime Failed");
+        }
+
+        long playHandle_1 = INetSDK.RealPlayEx(SPUtil.newInstance().getLongData(SPUtil.LoginHandle), chn, SDK_RealPlayType.SDK_RType_Realplay_0);
+        if (0 == playHandle_1) {
+            return false;
+        }
+        playHandleList.add(playHandle_1);
+        handlersMapPorts.put(playHandle_1, port);
+        CB_fRealDataCallBackEx mRealDataCallBackChannelOne = new CB_fRealDataCallBackEx() {
+            @Override
+            public void invoke(long lRealHandle, int dwDataType, byte[] pBuffer, int dwBufSize, int param) {
+                if (0 == dwDataType) {
+                    IPlaySDK.PLAYInputData(port, pBuffer, pBuffer.length);
+                }
+            }
+        };
+        INetSDK.SetRealDataCallBackEx(playHandle_1, mRealDataCallBackChannelOne, 1);
+        return true;
+    }
+
+    private boolean openStream(final SurfaceView view, final int port) {
+        if (IPlaySDK.PLAYOpenStream(port, null, 0, STREAM_BUFFER_SIZE) == 0) {
+            return false;
+        }
+        boolean result = IPlaySDK.PLAYPlay(port, view) == 0 ? false : true;
+        if (!result) {
+            IPlaySDK.PLAYCloseStream(port);
+            return false;
+        }
+        return true;
+    }
+
+    public void stopMultiPlay() {
+        for (int i = 0; i < playHandleList.size(); i++) {
+            INetSDK.StopRealPlayEx(playHandleList.get(i));
+            if (handlersMapPorts.containsKey(playHandleList.get(i))) {
+                int port1 = handlersMapPorts.get(playHandleList.get(i));
+                IPlaySDK.PLAYStop(port1);
+                IPlaySDK.PLAYCloseStream(port1);
+            }
+            handlersMapPorts.clear();
+            playHandleList.clear();
+        }
+    }
+}

+ 26 - 3
app/src/main/java/com/goldze/mvvmhabit/ui/main/fragment/MonitoringFragment.java

@@ -13,10 +13,12 @@ import com.goldze.mvvmhabit.ui.main.activity.MainActivity;
 import com.goldze.mvvmhabit.ui.main.adapter.MonitoringAdapter;
 import com.goldze.mvvmhabit.ui.monitor.FileBrowserActivity;
 import com.goldze.mvvmhabit.ui.monitor.LivePreviewActivity;
+import com.goldze.mvvmhabit.ui.monitor.MultiplePreviewActivity;
 import com.scwang.smart.refresh.layout.api.RefreshLayout;
 import com.scwang.smart.refresh.layout.listener.OnLoadMoreListener;
 import com.scwang.smart.refresh.layout.listener.OnRefreshListener;
 
+import java.io.Serializable;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -29,7 +31,9 @@ public class MonitoringFragment extends BaseFragment<FragmentMonitoringBinding,
 
     private MonitoringAdapter adapter;
 
-    private List<String> channelList=new ArrayList<>();
+    private List<String> channelList = new ArrayList<>();
+
+    private Integer type = 0;
 
     @Override
     public int initContentView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
@@ -46,7 +50,6 @@ public class MonitoringFragment extends BaseFragment<FragmentMonitoringBinding,
     public void initViewObservable() {
 
 
-
     }
 
     @Override
@@ -72,6 +75,26 @@ public class MonitoringFragment extends BaseFragment<FragmentMonitoringBinding,
                 startActivity(FileBrowserActivity.class);
             }
         });
+        binding.tt.setLeftText(R.string.choice);
+        binding.tt.setLeftTextOnClick(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                if (type == 0) {
+                    type = 1;
+                    adapter.setShow(true);
+                    adapter.notifyDataSetChanged();
+                    binding.tt.setLeftText(R.string.complete);
+                } else {
+                    type = 0;
+                    Bundle mBundle = new Bundle();
+                    mBundle.putSerializable("channel", (Serializable) adapter.getSelect());
+                    startActivity(MultiplePreviewActivity.class, mBundle);
+                    adapter.setShow(false);
+                    adapter.notifyDataSetChanged();
+                    binding.tt.setLeftText(R.string.choice);
+                }
+            }
+        });
 
 //        FullyGridLayoutManager managerOther = new FullyGridLayoutManager(getContext(),
 //                2, GridLayoutManager.VERTICAL, false);
@@ -104,7 +127,7 @@ public class MonitoringFragment extends BaseFragment<FragmentMonitoringBinding,
 //        MonitoringResponse.ResultBean resultBean = viewModel.orderList.get(position);
         Bundle mBundle = new Bundle();
         mBundle.putString("title", channelList.get(position));
-        mBundle.putInt("channel",position);
+        mBundle.putInt("channel", position);
         startActivity(LivePreviewActivity.class, mBundle);
     }
 }

+ 74 - 0
app/src/main/java/com/goldze/mvvmhabit/ui/monitor/MultiplePreviewActivity.java

@@ -0,0 +1,74 @@
+package com.goldze.mvvmhabit.ui.monitor;
+
+import android.os.Bundle;
+
+import com.goldze.mvvmhabit.BR;
+import com.goldze.mvvmhabit.R;
+import com.goldze.mvvmhabit.data.source.model.MobileDatumResponseBean;
+import com.goldze.mvvmhabit.databinding.ActivityMultiplePreviewBinding;
+import com.goldze.mvvmhabit.ui.main.adapter.NewMonitoringAdapter;
+import com.luck.picture.lib.decoration.GridSpacingItemDecoration;
+import com.luck.picture.lib.utils.DensityUtil;
+import com.luck.picture.lib.utils.FullyGridLayoutManager;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import androidx.recyclerview.widget.GridLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.recyclerview.widget.SimpleItemAnimator;
+import me.goldze.mvvmhabit.base.BaseActivity;
+import me.goldze.mvvmhabit.base.BaseViewModel;
+
+import static me.goldze.mvvmhabit.utils.Utils.getContext;
+
+public class MultiplePreviewActivity extends BaseActivity<ActivityMultiplePreviewBinding, BaseViewModel> {
+
+    private NewMonitoringAdapter adapter;
+
+    private List<MobileDatumResponseBean> channelList=new ArrayList<>();
+
+    @Override
+    public int initContentView(Bundle savedInstanceState) {
+        return R.layout.activity_multiple_preview;
+    }
+
+    @Override
+    public int initVariableId() {
+        return BR.viewModel;
+    }
+
+    @Override
+    public void initParam() {
+        Bundle mBundle = getIntent().getExtras();
+        if (mBundle != null) {
+            channelList = (List<MobileDatumResponseBean>) mBundle.getSerializable("channel");
+        }
+    }
+
+    @Override
+    public void initData() {
+        _init();
+    }
+
+    private void _init() {
+
+        FullyGridLayoutManager managerOther = new FullyGridLayoutManager(getContext(),
+                2, GridLayoutManager.VERTICAL, false);
+        RecyclerView.ItemAnimator itemAnimatorOther = binding.recycler.getItemAnimator();
+        if (itemAnimatorOther != null) {
+            ((SimpleItemAnimator) itemAnimatorOther).setSupportsChangeAnimations(false);
+        }
+        binding.recycler.setLayoutManager(managerOther);
+        binding.recycler.addItemDecoration(new GridSpacingItemDecoration(2,
+                DensityUtil.dip2px(getContext(), 5), false));
+        adapter = new NewMonitoringAdapter(this, channelList);
+        binding.recycler.setAdapter(adapter);
+    }
+
+    @Override
+    protected void onDestroy() {
+        adapter.stopMultiPlay();
+        super.onDestroy();
+    }
+}

+ 275 - 0
app/src/main/java/com/goldze/mvvmhabit/ui/order/FaceActivity.java

@@ -0,0 +1,275 @@
+package com.goldze.mvvmhabit.ui.order;
+
+import android.Manifest;
+import android.content.pm.PackageManager;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Matrix;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.YuvImage;
+import android.hardware.Camera;
+import android.media.FaceDetector;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.util.Log;
+
+import com.goldze.mvvmhabit.BR;
+import com.goldze.mvvmhabit.R;
+import com.goldze.mvvmhabit.app.AppViewModelFactory;
+import com.goldze.mvvmhabit.data.source.model.OrderResponseBean;
+import com.goldze.mvvmhabit.databinding.ActivityFaceBinding;
+import com.goldze.mvvmhabit.utils.ImageUtil;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+import androidx.core.app.ActivityCompat;
+import androidx.core.content.ContextCompat;
+import androidx.lifecycle.ViewModelProviders;
+import me.goldze.mvvmhabit.base.BaseActivity;
+import me.goldze.mvvmhabit.face.CameraView;
+import me.goldze.mvvmhabit.utils.ToastUtils;
+
+public class FaceActivity extends BaseActivity<ActivityFaceBinding, OrderInfoViewModel> {
+
+    private static final int REQUEST_CAMERA_PERMISSION = 1;
+    private Handler mBackgroundHandler;
+    long lastModirTime;
+    boolean isStart = false;
+    private int num;
+    Handler mHandler = new Handler();
+    private OrderResponseBean.ResultBean entity;
+
+    private CameraView.Callback mCallback = new CameraView.Callback() {
+
+        @Override
+        public void onCameraOpened(CameraView cameraView) {
+            Log.d(TAG, "onCameraOpened");
+        }
+
+        @Override
+        public void onCameraClosed(CameraView cameraView) {
+            Log.d(TAG, "onCameraClosed");
+        }
+
+        @Override
+        public void onPictureTaken(CameraView cameraView, final byte[] data) {
+        }
+
+        @Override
+        public void onPreviewFrame(final byte[] data, final Camera camera) {
+            if (System.currentTimeMillis() - lastModirTime <= 200 || data == null || data.length == 0) {
+                return;
+            }
+            Log.i(TAG, "onPreviewFrame " + (data == null ? null : data.length));
+            getBackgroundHandler().post(new FaceThread(data, camera));
+            lastModirTime = System.currentTimeMillis();
+        }
+    };
+
+    private Handler getBackgroundHandler() {
+        if (mBackgroundHandler == null) {
+            HandlerThread thread = new HandlerThread("background");
+            thread.start();
+            mBackgroundHandler = new Handler(thread.getLooper());
+        }
+        return mBackgroundHandler;
+    }
+
+    @Override
+    public void initParam() {
+        //获取列表传入的实体
+        Bundle mBundle = getIntent().getExtras();
+        if (mBundle != null) {
+            entity = (OrderResponseBean.ResultBean) mBundle.getSerializable("entity");
+            num = mBundle.getInt("num");
+        }
+    }
+
+    @Override
+    public OrderInfoViewModel initViewModel() {
+        //使用自定义的ViewModelFactory来创建ViewModel,如果不重写该方法,则默认会调用LoginViewModel(@NonNull Application application)构造方法
+        AppViewModelFactory factory = AppViewModelFactory.getInstance(getApplication());
+        return ViewModelProviders.of(this, factory).get(OrderInfoViewModel.class);
+    }
+
+    @Override
+    public int initContentView(Bundle savedInstanceState) {
+        return R.layout.activity_face;
+    }
+
+    @Override
+    public int initVariableId() {
+        return BR.viewModel;
+    }
+
+    @Override
+    public void initData() {
+        super.initData();
+        viewModel.order.set(entity);
+        viewModel.num = num;
+        binding.camera.addCallback(mCallback);
+        mHandler.postDelayed(new Runnable() {
+            @Override
+            public void run() {
+                isStart = true;
+            }
+        }, 3500);
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
+                == PackageManager.PERMISSION_GRANTED) {
+            binding.camera.start();
+        } else if (ActivityCompat.shouldShowRequestPermissionRationale(this,
+                Manifest.permission.CAMERA)) {
+            ToastUtils.showShort("获取相机权限失败");
+        } else {
+            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA},
+                    REQUEST_CAMERA_PERMISSION);
+        }
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        if (mBackgroundHandler != null) {
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
+                mBackgroundHandler.getLooper().quitSafely();
+            } else {
+                mBackgroundHandler.getLooper().quit();
+            }
+            mBackgroundHandler = null;
+        }
+    }
+
+    @Override
+    protected void onPause() {
+        binding.camera.stop();
+        super.onPause();
+    }
+
+    private class FaceThread implements Runnable {
+        private byte[] mData;
+        private ByteArrayOutputStream mBitmapOutput;
+        private Matrix mMatrix;
+        private Camera mCamera;
+
+        public FaceThread(byte[] data, Camera camera) {
+            mData = data;
+            mBitmapOutput = new ByteArrayOutputStream();
+            mMatrix = new Matrix();
+            int mOrienta = binding.camera.getCameraDisplayOrientation();
+            mMatrix.postRotate(mOrienta * -1);
+            mMatrix.postScale(-1, 1);//默认是前置摄像头,直接写死 -1 。
+            mCamera = camera;
+        }
+
+        @Override
+        public void run() {
+            Log.i(TAG, "thread is run");
+            Bitmap bitmap = null;
+            Bitmap roteBitmap = null;
+            try {
+                Camera.Parameters parameters = mCamera.getParameters();
+                int width = parameters.getPreviewSize().width;
+                int height = parameters.getPreviewSize().height;
+
+                YuvImage yuv = new YuvImage(mData, parameters.getPreviewFormat(), width, height, null);
+                mData = null;
+                yuv.compressToJpeg(new Rect(0, 0, width, height), 100, mBitmapOutput);
+
+                byte[] bytes = mBitmapOutput.toByteArray();
+                BitmapFactory.Options options = new BitmapFactory.Options();
+                options.inPreferredConfig = Bitmap.Config.RGB_565;//必须设置为565,否则无法检测
+                bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options);
+
+                roteBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), mMatrix, false);
+
+                //test
+                int MAX_FACES = 5;
+                FaceDetector faceDet = new FaceDetector(roteBitmap.getWidth(), roteBitmap.getHeight(), MAX_FACES);
+                FaceDetector.Face[] faceList = new FaceDetector.Face[MAX_FACES];
+                faceDet.findFaces(roteBitmap, faceList);
+                for (int i = 0; i < faceList.length; i++) {
+                    FaceDetector.Face face = faceList[i];
+                    Log.d("FaceDet", "Face [" + face + "]");
+                    if (face != null) {
+                        Log.d("FaceDet", "Face [" + i + "] - Confidence [" + face.confidence() + "]");
+                        if (isStart && face.confidence() >= 0.3) {
+
+                            PointF pf = new PointF();
+                            //getMidPoint(PointF point);
+                            //Sets the position of the mid-point between the eyes.
+                            face.getMidPoint(pf);
+                            Log.d("FaceDet", "\t Eyes distance [" + face.eyesDistance() + "] - Face midpoint [" + pf.x + "&" + pf.y + "]");
+                            RectF r = new RectF();
+                            r.left = pf.x - face.eyesDistance() - 100;
+                            r.right = pf.x + face.eyesDistance();
+                            r.top = pf.y - face.eyesDistance() - 100;
+                            r.bottom = pf.y + face.eyesDistance();
+
+                            Matrix matrix2 = new Matrix();
+                            Bitmap faceBitmap = Bitmap.createBitmap(roteBitmap, (int) r.left, (int) r.top, (int) r.right, (int) r.bottom, matrix2, false);
+
+                            Log.d("TTT", "confidence=" + face.confidence());
+//                            saveBitmapFile(faceBitmap,faceFile.getAbsolutePath());
+                            runOnUiThread(new Runnable() {
+                                @Override
+                                public void run() {
+                                    if (isStart) {
+                                        isStart = false;
+                                        viewModel.faceCompare(ImageUtil.bitmapToBase64(faceBitmap));
+                                    }
+                                }
+                            });
+
+                            break;
+                        }
+                    }
+                }
+
+            } catch (Exception e) {
+                e.printStackTrace();
+            } finally {
+                mMatrix = null;
+                if (bitmap != null) {
+                    bitmap.recycle();
+                }
+                if (mBitmapOutput != null) {
+                    try {
+                        mBitmapOutput.close();
+                        mBitmapOutput = null;
+                    } catch (IOException e) {
+                        e.printStackTrace();
+                    }
+                }
+            }
+        }
+    }
+
+
+//    /**
+//     * 把batmap 转file
+//     * @param bitmap
+//     * @param filepath
+//     */
+//    public static File saveBitmapFile(Bitmap bitmap, String filepath){
+//        File file=new File(filepath);//将要保存图片的路径
+//        try {
+//            BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file));
+//            bitmap.compress(Bitmap.CompressFormat.JPEG, 100, bos);
+//            bos.flush();
+//            bos.close();
+//        } catch (IOException e) {
+//            e.printStackTrace();
+//        }
+//        return file;
+//    }
+}

+ 36 - 240
app/src/main/java/com/goldze/mvvmhabit/ui/order/OrderInfoActivity.java

@@ -1,36 +1,26 @@
 package com.goldze.mvvmhabit.ui.order;
 
 import android.os.Bundle;
-import android.util.Log;
 import android.view.View;
 
-import com.baidu.idl.face.api.manager.FaceConst;
-import com.baidu.idl.face.api.manager.FaceServiceCallbck;
-import com.baidu.idl.face.api.manager.FaceServiceManager;
-import com.baidu.idl.face.platform.FaceEnvironment;
-import com.baidu.idl.facelive.api.FaceLiveManager;
-import com.baidu.idl.facelive.api.entity.FaceLiveConfig;
-import com.baidu.idl.facelive.api.entity.FaceLivenessType;
-import com.baidu.idl.facelive.api.entity.LivenessValueModel;
 import com.goldze.mvvmhabit.BR;
 import com.goldze.mvvmhabit.R;
 import com.goldze.mvvmhabit.app.AppViewModelFactory;
 import com.goldze.mvvmhabit.data.source.model.ConsoleConfig;
-import com.goldze.mvvmhabit.data.source.model.LivenessVsIdcardResult;
+import com.goldze.mvvmhabit.data.source.model.NextEvent;
 import com.goldze.mvvmhabit.data.source.model.OrderResponseBean;
 import com.goldze.mvvmhabit.databinding.ActivityOrderInfoBinding;
-import com.goldze.mvvmhabit.exception.FaceException;
 import com.goldze.mvvmhabit.ui.dialog.AlertDialog;
-import com.goldze.mvvmhabit.utils.ConsoleConfigManager;
-import com.goldze.mvvmhabit.utils.PoliceCheckResultParser;
 
-import java.util.HashMap;
-import java.util.Map;
+import org.greenrobot.eventbus.EventBus;
+import org.greenrobot.eventbus.Subscribe;
+import org.greenrobot.eventbus.ThreadMode;
+
+import java.io.Serializable;
 
 import androidx.lifecycle.Observer;
 import androidx.lifecycle.ViewModelProviders;
 import me.goldze.mvvmhabit.base.BaseActivity;
-import me.goldze.mvvmhabit.utils.ToastUtils;
 
 public class OrderInfoActivity extends BaseActivity<ActivityOrderInfoBinding, OrderInfoViewModel> {
 
@@ -70,23 +60,15 @@ public class OrderInfoActivity extends BaseActivity<ActivityOrderInfoBinding, Or
 
     @Override
     public void initData() {
+        if (!EventBus.getDefault().isRegistered(this)) {
+            EventBus.getDefault().register(this);
+        }
         viewModel.setEntity(entity);
     }
 
     @Override
     public void initViewObservable() {
-//        viewModel.uc.check.observe(this, new Observer<Boolean>() {
-//            @Override
-//            public void onChanged(Boolean aBoolean) {
-//                if (aBoolean) {
-//                    binding.tvStart.setEnabled(true);
-//                    binding.tvStart.setBackgroundResource(R.drawable.shape_blue_5);
-//                } else {
-//                    binding.tvStart.setEnabled(false);
-//                    binding.tvStart.setBackgroundResource(R.drawable.shape_ash_5);
-//                }
-//            }
-//        });
+
         viewModel.uc.faceCompare.observe(this, new Observer() {
             @Override
             public void onChanged(Object o) {
@@ -98,232 +80,46 @@ public class OrderInfoActivity extends BaseActivity<ActivityOrderInfoBinding, Or
                 }).setPositiveButton("确定", new View.OnClickListener() {
                     @Override
                     public void onClick(View v) {
-                        viewModel.getInfo();
+                        Bundle mBundle = new Bundle();
+                        mBundle.putSerializable("entity", (Serializable) viewModel.order.get());
+                        mBundle.putInt("num", viewModel.num);
+                        startActivity(FaceActivity.class, mBundle);
                     }
                 }).show();
             }
         });
-        viewModel.uc.nextFaceCompare.observe(this, new Observer<String>() {
-            @Override
-            public void onChanged(String s) {
-                startFaceLiveness(s);
-            }
-        });
+//        viewModel.uc.nextFaceCompare.observe(this, new Observer<String>() {
+//            @Override
+//            public void onChanged(String s) {
+//                startFaceLiveness(s);
+//            }
+//        });
     }
-/*
-    private void agreement() {
-        String s = "我已阅读,并同意";
-        String agreement = "《个人信息保护政策》";
-        String agreement2 = "《平台隐私条款》";
-        String agreement3 = "《CFCA数字证书服务协议》";
-        SpannableString spanText = new SpannableString(s + agreement + "、" + agreement2 + "和" + agreement3);
-        spanText.setSpan(new ClickableSpan() {
-
-            @Override
-            public void updateDrawState(TextPaint ds) {
-                super.updateDrawState(ds);
-                ds.setColor(getResources().getColor(R.color.credit_dialog));       //设置文件颜色
-                ds.setUnderlineText(true);      //设置下划线
-            }
-
-            @Override
-            public void onClick(View view) {
-
-            }
-        }, s.length(), spanText.length() - agreement2.length() - agreement3.length() - 2, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
 
-        spanText.setSpan(new ClickableSpan() {
-
-            @Override
-            public void updateDrawState(TextPaint ds) {
-                super.updateDrawState(ds);
-                ds.setColor(getResources().getColor(R.color.credit_dialog));       //设置文件颜色
-                ds.setUnderlineText(true);      //设置下划线
-            }
-
-            @Override
-            public void onClick(View view) {
 
-            }
-        }, s.length() + agreement.length() + 1, spanText.length() - agreement3.length() - 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
-
-        spanText.setSpan(new ClickableSpan() {
-
-            @Override
-            public void updateDrawState(TextPaint ds) {
-                super.updateDrawState(ds);
-                ds.setColor(getResources().getColor(R.color.credit_dialog));       //设置文件颜色
-                ds.setUnderlineText(true);      //设置下划线
-            }
+    @Override
+    protected void onDestroy() {
+        EventBus.getDefault().unregister(this);
+        super.onDestroy();
+    }
 
+    @Subscribe(threadMode = ThreadMode.MAIN)
+    public void NextEvent(NextEvent event) {
+        viewModel.num++;
+        myDialog.setGone().setTitle("").setMsg((viewModel.num == 0 ? entity.getApplicantName() : entity.getRespondentName()) + "人脸核身\n点击确定开始").setCancelable(false).setNegativeButton("取消", new View.OnClickListener() {
             @Override
             public void onClick(View view) {
 
             }
-        }, spanText.length() - agreement3.length(), spanText.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
-
-        binding.tvAgreement.setHighlightColor(Color.TRANSPARENT); //设置点击后的颜色为透明,否则会一直出现高亮
-        binding.tvAgreement.setText(spanText);
-        binding.tvAgreement.setMovementMethod(LinkMovementMethod.getInstance());//开始响应点击事件
-    }
- */
-
-    private void startFaceLiveness(String token) {
-        // 人脸阈值设置
-        setFaceQualityConfig();
-        // LogicServiceManager入参
-        Map<String, Object> params = new HashMap<String, Object>();
-        // 必须携带access_token
-        params.put("access_token", token);
-        // 开放平台控制台配置的方案Id
-        params.put("plan_id", consoleConfig.getPlanId());
-        FaceServiceManager.getInstance().startFaceLiveness(this, params, new FaceServiceCallbck() {
+        }).setPositiveButton("确定", new View.OnClickListener() {
             @Override
-            public void onCallback(final int resultCode, final Map<String, Object> resultMap) {
-                Log.e(TAG, "handleResult: " + resultCode);
-                runOnUiThread(new Runnable() {
-                    @Override
-                    public void run() {
-                        handleResult(resultCode, resultMap);
-                    }
-                });
+            public void onClick(View v) {
+                Bundle mBundle = new Bundle();
+                mBundle.putSerializable("entity", (Serializable) viewModel.order.get());
+                mBundle.putInt("num", viewModel.num);
+                startActivity(FaceActivity.class, mBundle);
             }
-        });
+        }).show();
     }
 
-    private void handleResult(int resultCode, Map<String, Object> resultMap) {
-        if (resultCode == 0) {
-            String data = (String) resultMap.get(FaceConst.RESULT_JSON);
-            Log.e(TAG, "handleResult: " + data);
-            PoliceCheckResultParser parser = new PoliceCheckResultParser();
-            LivenessVsIdcardResult result = null;
-            try {
-                result = parser.parse(data);
-                Log.e(TAG, "handleResult: " + result.getDecImage());
-                if (result == null) {
-                    return;
-                }
-                boolean isPass = false;
-                // 在线活体方案:活体分数
-                isPass = result.getFaceliveness() >= 0.8;
-
-                if (isPass) {
-                    //TODO 验证成功
-                    viewModel.faceCompare(result.getDecImage());
-                } else {
-                    viewModel.num = 0;
-                    ToastUtils.showShort("活体验证失败");
-                }
-            } catch (FaceException e) {
-                // 服务端错误
-                Log.e(TAG, "handleResult: " + e.getErrorMessage() + "------------" + e.getErrorCode());
-                viewModel.num = 0;
-//                ToastUtils.showShort(e.getErrorCode());
-                e.printStackTrace();
-            }
-        } else {
-            // SDK本地错误码
-            ToastUtils.showShort(
-                    resultCode + ":" + resultMap.get("resultMsg"));
-            viewModel.num = 0;
-        }
-    }
-
-
-    private void setFaceQualityConfig() {
-        consoleConfig = ConsoleConfigManager.getInstance(this).getConfig();
-        try {
-            FaceLiveConfig faceLiveConfig = new FaceLiveConfig();
-            // faceUI默认展示结果页,此处必须设置为false
-            faceLiveConfig.setShowResultView(false);
-            // 设置模糊度阈值
-            faceLiveConfig.setBlurnessValue(consoleConfig.getBlur());
-            // 设置最小光照阈值(范围0-255)
-            faceLiveConfig.setBrightnessValue(consoleConfig.getIllumination());
-            // 设置最大光照阈值(范围0-255)
-            faceLiveConfig.setBrightnessMaxValue(consoleConfig.getMaxIllumination());
-            // 设置左眼遮挡阈值
-            faceLiveConfig.setOcclusionLeftEyeValue(consoleConfig.getLeftEyeOcclu());
-            // 设置右眼遮挡阈值
-            faceLiveConfig.setOcclusionRightEyeValue(consoleConfig.getRightEyeOcclu());
-            // 设置鼻子遮挡阈值
-            faceLiveConfig.setOcclusionNoseValue(consoleConfig.getNoseOcclu());
-            // 设置嘴巴遮挡阈值
-            faceLiveConfig.setOcclusionMouthValue(consoleConfig.getMouthOcclu());
-            // 设置左脸颊遮挡阈值
-            faceLiveConfig.setOcclusionLeftContourValue(consoleConfig.getLeftCheekOcclu());
-            // 设置右脸颊遮挡阈值
-            faceLiveConfig.setOcclusionRightContourValue(consoleConfig.getRightCheekOcclu());
-            // 设置下巴遮挡阈值
-            faceLiveConfig.setOcclusionChinValue(consoleConfig.getChinOcclu());
-            // 设置人脸姿态Pitch阈值
-            faceLiveConfig.setHeadPitchValue(consoleConfig.getPitch());
-            // 设置人脸姿态Yaw阈值
-            faceLiveConfig.setHeadYawValue(consoleConfig.getYaw());
-            // 设置人脸姿态Roll阈值
-            faceLiveConfig.setHeadRollValue(consoleConfig.getRoll());
-            // 是否开启录制视频
-            faceLiveConfig.setOpenRecord(false);
-            // 设置是否显示超时弹框
-            faceLiveConfig.setIsShowTimeoutDialog(true);
-            // 输出图片类型:0原图、1抠图
-            faceLiveConfig.setOutputImageType(FaceEnvironment.VALUE_OUTPUT_IMAGE_TYPE);
-            // 是否忽略录制异常(只能忽略采集时间过短,采集后无文件输出的异常)
-            faceLiveConfig.setIgnoreRecordError(true);
-            // 眨眼张嘴遮挡开关
-            faceLiveConfig.setActiveStrict(false);
-            // 设置活体类型相关
-            setFaceLivenessConfig(faceLiveConfig);
-        } catch (Exception e) {
-            e.printStackTrace();
-        }
-    }
-
-    private void setFaceLivenessConfig(FaceLiveConfig faceLiveConfig) {
-        try {
-            // 设置活体类型:炫彩活体、动作活体、静默活体
-            FaceLivenessType faceLivenessType = null;
-            // 配置活体动作集合、动作个数,活体阈值,无活体
-            LivenessValueModel livenessValueModel = null;
-            if (consoleConfig.getFaceLiveType() == 0) {
-                faceLivenessType = FaceLivenessType.COLORLIVENESS;
-                // 是否开启炫彩活体能力
-                faceLiveConfig.setIsOpenColorLive(true);
-                // 是否开启动作活体能力
-                faceLiveConfig.setIsOpenActionLive(true);
-                livenessValueModel = faceLiveConfig.getLivenessValueModel();
-                livenessValueModel.actionList.addAll(consoleConfig.getActions());
-                livenessValueModel.livenessScore = consoleConfig.getLiveScore();
-            } else if (consoleConfig.getFaceLiveType() == 1) {
-                faceLivenessType = FaceLivenessType.ACTIONLIVENESS;
-                // 是否开启炫彩活体能力
-                faceLiveConfig.setIsOpenColorLive(false);
-                // 是否开启动作活体能力
-                faceLiveConfig.setIsOpenActionLive(true);
-                livenessValueModel = faceLiveConfig.getLivenessValueModel();
-                livenessValueModel.actionList.addAll(consoleConfig.getActions());
-                livenessValueModel.actionRandomNumber = consoleConfig.getFaceActionNum();
-                livenessValueModel.livenessScore = consoleConfig.getLiveScore();
-            } else if (consoleConfig.getFaceLiveType() == 2) {
-                faceLivenessType = FaceLivenessType.SILENTLIVENESS;
-                // 是否开启炫彩活体能力
-                faceLiveConfig.setIsOpenColorLive(false);
-                // 是否开启动作活体能力
-                faceLiveConfig.setIsOpenActionLive(false);
-                livenessValueModel = faceLiveConfig.getLivenessValueModel();
-                livenessValueModel.livenessScore = consoleConfig.getLiveScore();
-            } else if (consoleConfig.getFaceLiveType() == 3) {
-                // 是否开启炫彩活体能力
-                faceLiveConfig.setIsOpenColorLive(true);
-                // 是否开启动作活体能力
-                faceLiveConfig.setIsOpenActionLive(false);
-                livenessValueModel = faceLiveConfig.getLivenessValueModel();
-            }
-            // faceLiveConfig.setFaceLivenessType(faceLivenessType, livenessValueModel);
-            faceLiveConfig.setLivenessValueModel(livenessValueModel);
-            FaceLiveManager.getInstance().setFaceConfig(faceLiveConfig);
-        } catch (Exception e) {
-            e.printStackTrace();
-        }
-    }
 }

+ 18 - 13
app/src/main/java/com/goldze/mvvmhabit/ui/order/OrderInfoViewModel.java

@@ -2,13 +2,17 @@ package com.goldze.mvvmhabit.ui.order;
 
 import android.app.Application;
 import android.os.Bundle;
+import android.util.Log;
 
 import com.goldze.mvvmhabit.data.HttpModelRepository;
 import com.goldze.mvvmhabit.data.source.http.BaseObserver;
+import com.goldze.mvvmhabit.data.source.model.NextEvent;
 import com.goldze.mvvmhabit.data.source.model.OrderResponseBean;
 import com.goldze.mvvmhabit.request.FaceCompareRequestBean;
 import com.goldze.mvvmhabit.request.OnLineRequestBean;
 
+import org.greenrobot.eventbus.EventBus;
+
 import java.io.Serializable;
 
 import androidx.annotation.NonNull;
@@ -40,7 +44,7 @@ public class OrderInfoViewModel extends BaseViewModel<HttpModelRepository> {
         }
     });
 
-    public BindingCommand<Boolean> check=new BindingCommand<Boolean>(new BindingConsumer<Boolean>() {
+    public BindingCommand<Boolean> check = new BindingCommand<Boolean>(new BindingConsumer<Boolean>() {
         @Override
         public void call(Boolean aBoolean) {
             uc.check.setValue(aBoolean);
@@ -55,7 +59,7 @@ public class OrderInfoViewModel extends BaseViewModel<HttpModelRepository> {
 
         public SingleLiveEvent<String> nextFaceCompare = new SingleLiveEvent<>();
 
-        public SingleLiveEvent faceCompare=new SingleLiveEvent<>();
+        public SingleLiveEvent faceCompare = new SingleLiveEvent<>();
 
     }
 
@@ -74,15 +78,17 @@ public class OrderInfoViewModel extends BaseViewModel<HttpModelRepository> {
                     public void onSuccess(BaseResponse response) {
                         dismissDialog();
                         if (response.getCode() == 100) {
-                            if (num==1){
+                            if (num == 1) {
                                 Bundle mBundle = new Bundle();
                                 mBundle.putSerializable("entity", (Serializable) order.get());
-                                startActivity(VideoCallingActivity.class,mBundle);
-                            }else {
-                                num++;
-                                uc.faceCompare.call();
+                                startActivity(VideoCallingActivity.class, mBundle);
+                            } else {
+                                Log.e("aaa", "onSuccess: ");
+                                EventBus.getDefault().post(new NextEvent());
                             }
-                        }else {
+                            finish();
+                        } else {
+                            finish();
                             ToastUtils.showShort(response.getMsg());
                         }
                     }
@@ -95,7 +101,6 @@ public class OrderInfoViewModel extends BaseViewModel<HttpModelRepository> {
     }
 
 
-
     public OrderInfoViewModel(@NonNull Application application, HttpModelRepository model) {
         super(application, model);
     }
@@ -108,7 +113,7 @@ public class OrderInfoViewModel extends BaseViewModel<HttpModelRepository> {
         order.set(entity);
     }
 
-    public void onLined(){
+    public void onLined() {
         model.onLined(new OnLineRequestBean(order.get().getBusinessNo()))
                 .compose(RxUtils.schedulersTransformer()) //线程调度
                 .subscribe(new BaseObserver() {
@@ -122,7 +127,7 @@ public class OrderInfoViewModel extends BaseViewModel<HttpModelRepository> {
                         dismissDialog();
                         if (response.getCode() == 100) {
                             uc.faceCompare.call();
-                        }else {
+                        } else {
                             ToastUtils.showShort(response.getMessage());
                         }
                     }
@@ -134,7 +139,7 @@ public class OrderInfoViewModel extends BaseViewModel<HttpModelRepository> {
                 });
     }
 
-    public void getInfo(){
+    public void getInfo() {
         showDialog();
         model.getInfo()
                 .compose(RxUtils.schedulersTransformer()) //线程调度
@@ -149,7 +154,7 @@ public class OrderInfoViewModel extends BaseViewModel<HttpModelRepository> {
                         dismissDialog();
                         if (response.getCode() == 100) {
                             uc.nextFaceCompare.setValue((String) response.getData());
-                        }else {
+                        } else {
                             ToastUtils.showShort(response.getMessage());
                         }
                     }

+ 18 - 4
app/src/main/java/com/goldze/mvvmhabit/ui/view/TemplateTitleBar.java

@@ -30,6 +30,8 @@ public class TemplateTitleBar extends RelativeLayout {
     private int moreImg2;
     private int moreImg;
     private TextView tvTitle;
+    private TextView txtBt;
+    private boolean canLeft;
 //    private int imgBackSrc;
 //    private int defImgBackSrc;
 
@@ -41,6 +43,7 @@ public class TemplateTitleBar extends RelativeLayout {
         try {
             titleText = ta.getString(R.styleable.TemplateTitleBar_titleText);
             canBack = ta.getBoolean(R.styleable.TemplateTitleBar_canBack, false);
+            canLeft = ta.getBoolean(R.styleable.TemplateTitleBar_LeftText, false);
             backText = ta.getString(R.styleable.TemplateTitleBar_backText);
             moreText = ta.getString(R.styleable.TemplateTitleBar_moreText);
             moreImg2 = ta.getResourceId(R.styleable.TemplateTitleBar_moreImg, 0);
@@ -53,11 +56,13 @@ public class TemplateTitleBar extends RelativeLayout {
     }
 
     private void setUpView() {
-        tvTitle= (TextView) findViewById(R.id.title);
+        tvTitle = (TextView) findViewById(R.id.title);
         tvTitle.setText(titleText);
         backBtn = (LinearLayout) findViewById(R.id.title_back);
-        backBtn.setVisibility(canBack ? VISIBLE : INVISIBLE);
+        txtBt = findViewById(R.id.txt_bt);
         TextView tvBack = (TextView) findViewById(R.id.txt_back);
+        txtBt.setVisibility(canLeft ? VISIBLE : INVISIBLE);
+        backBtn.setVisibility(canBack ? VISIBLE : GONE);
         if (canBack) {
             tvBack.setText(backText);
         }
@@ -108,6 +113,15 @@ public class TemplateTitleBar extends RelativeLayout {
 
     }
 
+    public void setLeftText(int txt){
+        txtBt.setText(getContext().getString(txt));
+    }
+
+    public void setLeftTextOnClick(OnClickListener listener){
+//        txtBt.setVisibility(VISIBLE);
+        txtBt.setOnClickListener(listener);
+    }
+
     /**
      * 设置更多按钮事件
      *
@@ -158,10 +172,10 @@ public class TemplateTitleBar extends RelativeLayout {
         }
     }
 
-    public void setTitleImage(int id,OnClickListener listener){
+    public void setTitleImage(int id, OnClickListener listener) {
         Drawable d = getResources().getDrawable(id);
         d.setBounds(0, 0, 50, 50); //必须设置图片大小,否则不显示
-        tvTitle.setCompoundDrawables(null,null,d,null);
+        tvTitle.setCompoundDrawables(null, null, d, null);
         tvTitle.setOnClickListener(listener);
     }
 

BIN
app/src/main/res/drawable-xxhdpi/choose.png


BIN
app/src/main/res/drawable-xxhdpi/onchoose.png


BIN
app/src/main/res/drawable-xxhdpi/zhidao_face_scan_bg.png


+ 53 - 0
app/src/main/res/layout/activity_face.xml

@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+
+    <RelativeLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:background="@color/black">
+
+        <me.goldze.mvvmhabit.face.CameraView
+            android:id="@+id/camera"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_alignParentLeft="true"
+            android:layout_alignParentStart="true"
+            android:layout_alignParentTop="true"
+            app:facing="front"
+            android:adjustViewBounds="true"
+            android:background="@android:color/black"
+            android:layout_above="@+id/rl_bottom"/>
+
+        <ImageView
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_above="@+id/rl_bottom"
+            android:src="@drawable/zhidao_face_scan_bg"
+            android:scaleType="centerCrop"/>
+
+
+
+
+        <RelativeLayout
+            android:id="@+id/rl_bottom"
+            android:layout_width="match_parent"
+            android:layout_height="220dp"
+            android:background="@color/white"
+            android:layout_alignParentBottom="true">
+
+
+
+            <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:textSize="20sp"
+                android:text="请将脸对准框内"
+                android:layout_centerInParent="true"/>
+
+
+        </RelativeLayout>
+
+    </RelativeLayout>
+
+</layout>

+ 33 - 0
app/src/main/res/layout/activity_multiple_preview.xml

@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layout xmlns:tools="http://schemas.android.com/tools"
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+
+    <RelativeLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        tools:ignore="MissingConstraints" >
+
+        <com.goldze.mvvmhabit.ui.view.TemplateTitleBar
+            android:id="@+id/tt"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            app:moreImg="@drawable/user_icon"
+            app:canBack="true"
+            app:addImg="@drawable/file_management"
+            app:titleText="@string/real_time_monitoring"/>
+
+
+        <androidx.recyclerview.widget.RecyclerView
+            android:id="@+id/recycler"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_below="@id/tt"/>
+
+    </RelativeLayout>
+
+    <data>
+        <import type="me.goldze.mvvmhabit.binding.viewadapter.recyclerview.LayoutManagers" />
+        <import type="me.goldze.mvvmhabit.binding.viewadapter.recyclerview.LineManagers" />
+    </data>
+</layout>

+ 1 - 0
app/src/main/res/layout/fragment_monitoring.xml

@@ -25,6 +25,7 @@
             android:layout_height="wrap_content"
             app:moreImg="@drawable/user_icon"
             app:addImg="@drawable/file_management"
+            app:LeftText="true"
             app:titleText="@string/real_time_monitoring" />
 
 <!--        <com.scwang.smart.refresh.layout.SmartRefreshLayout-->

+ 9 - 0
app/src/main/res/layout/item_monitoring.xml

@@ -35,6 +35,7 @@
             android:layout_marginRight="50dp"/>
 
         <ImageView
+            android:id="@+id/iv_return"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:layout_centerVertical="true"
@@ -42,6 +43,14 @@
             android:layout_marginRight="20dp"
             android:src="@drawable/right_arrow"/>
 
+        <CheckBox
+            android:id="@+id/check"
+            android:layout_width="28dp"
+            android:layout_centerVertical="true"
+            android:layout_marginRight="15dp"
+            android:layout_alignParentRight="true"
+            android:layout_height="28dp"/>
+
     </RelativeLayout>
     
     <data>

+ 18 - 0
app/src/main/res/layout/item_new_monitoring.xml

@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layout xmlns:android="http://schemas.android.com/apk/res/android">
+
+
+    <SurfaceView
+        android:id="@+id/surface_view"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content" />
+
+
+    <data>
+
+        <variable
+            name="item"
+            type="com.goldze.mvvmhabit.data.source.model.MobileDatumResponseBean" />
+    </data>
+
+</layout>

+ 17 - 0
app/src/main/res/layout/widget_title.xml

@@ -32,8 +32,25 @@
                 android:text="返回"
                 android:textSize="16sp" />
 
+
+
         </LinearLayout>
 
+        <TextView
+            android:id="@+id/txt_bt"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center_vertical"
+            android:layout_marginStart="8dp"
+            android:paddingLeft="15dp"
+            android:paddingRight="10dp"
+            android:paddingTop="13dp"
+            android:textColor="#3A9BFF"
+            android:gravity="center"
+            android:visibility="gone"
+            android:text="选择"
+            android:textSize="16sp" />
+
         <ImageView
             android:id="@+id/img_life"
             android:layout_width="40dp"

+ 3 - 3
app/src/main/res/values/strings.xml

@@ -1,6 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<resources xmlns:tools="http://schemas.android.com/tools"
-    tools:ignore="MissingTranslation">
+<resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation">
     <array name="stream_array">
         <item>主辅码流</item>
         <item>主码流</item>
@@ -63,7 +62,7 @@
     <string name="fast_upper">已经达到最大快进倍数</string>
     <string name="slow_lower">已经到达最小慢放倍数</string>
     <string name="repick_date_warn">先停止回放,再重新选择日期</string>
-    <string name="operation_failed" >操作失败</string>
+    <string name="operation_failed">操作失败</string>
     <string name="play_back_normal">常速</string>
     <string name="please_first_stop_download">请先停止下载</string>
     <string name="foreign_personnel">外来人员</string>
@@ -83,4 +82,5 @@
     <string name="please_select_end_time">请选择结束时间</string>
     <string name="download_completed">下载完成</string>
     <string name="complete">完成</string>
+    <string name="choice">选择</string>
 </resources>

+ 2 - 0
app/src/main/res/values/styles.xml

@@ -54,6 +54,8 @@
         <!-- 右侧更多功能按钮文字 -->
         <attr name="moreText" format="string" />
 
+        <attr name="LeftText" format="boolean" />
+
         <!-- 文字颜色 -->
         <attr name="textColors" format="reference" />
         <!--        <attr name="imgBack" format="reference" />-->

+ 5 - 1
build.gradle

@@ -4,6 +4,8 @@ buildscript {
     repositories {
         google()
         jcenter()
+        mavenCentral()
+        maven{ url 'http://maven.aliyun.com/nexus/content/groups/public'}
     }
     dependencies {
         classpath 'com.android.tools.build:gradle:3.5.3'
@@ -18,9 +20,11 @@ buildscript {
 
 allprojects {
     repositories {
+        maven { url "https://jitpack.io" }
         google()
         jcenter()
-        maven { url 'https://jitpack.io' }
+        mavenCentral()
+        maven{ url 'http://maven.aliyun.com/nexus/content/groups/public'}
     }
 }
 

+ 194 - 0
mvvmhabit/src/main/java/me/goldze/mvvmhabit/face/AspectRatio.java

@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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 me.goldze.mvvmhabit.face;
+
+import android.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+import androidx.collection.SparseArrayCompat;
+
+
+/**
+ * Immutable class for describing proportional relationship between width and height.
+ */
+public class AspectRatio implements Comparable<AspectRatio>, Parcelable {
+
+    private final static SparseArrayCompat<SparseArrayCompat<AspectRatio>> sCache
+            = new SparseArrayCompat<>(16);
+
+    private final int mX;
+    private final int mY;
+
+    /**
+     * Returns an instance of {@link AspectRatio} specified by {@code x} and {@code y} values.
+     * The values {@code x} and {@code} will be reduced by their greatest common divider.
+     *
+     * @param x The width
+     * @param y The height
+     * @return An instance of {@link AspectRatio}
+     */
+    public static AspectRatio of(int x, int y) {
+        int gcd = gcd(x, y);
+        x /= gcd;
+        y /= gcd;
+        SparseArrayCompat<AspectRatio> arrayX = sCache.get(x);
+        if (arrayX == null) {
+            AspectRatio ratio = new AspectRatio(x, y);
+            arrayX = new SparseArrayCompat<>();
+            arrayX.put(y, ratio);
+            sCache.put(x, arrayX);
+            return ratio;
+        } else {
+            AspectRatio ratio = arrayX.get(y);
+            if (ratio == null) {
+                ratio = new AspectRatio(x, y);
+                arrayX.put(y, ratio);
+            }
+            return ratio;
+        }
+    }
+
+    /**
+     * Parse an {@link AspectRatio} from a {@link String} formatted like "4:3".
+     *
+     * @param s The string representation of the aspect ratio
+     * @return The aspect ratio
+     * @throws IllegalArgumentException when the format is incorrect.
+     */
+    public static AspectRatio parse(String s) {
+        int position = s.indexOf(':');
+        if (position == -1) {
+            throw new IllegalArgumentException("Malformed aspect ratio: " + s);
+        }
+        try {
+            int x = Integer.parseInt(s.substring(0, position));
+            int y = Integer.parseInt(s.substring(position + 1));
+            return AspectRatio.of(x, y);
+        } catch (NumberFormatException e) {
+            throw new IllegalArgumentException("Malformed aspect ratio: " + s, e);
+        }
+    }
+
+    private AspectRatio(int x, int y) {
+        mX = x;
+        mY = y;
+    }
+
+    public int getX() {
+        return mX;
+    }
+
+    public int getY() {
+        return mY;
+    }
+
+    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
+    public boolean matches(Size size) {
+        int gcd = gcd(size.getWidth(), size.getHeight());
+        int x = size.getWidth() / gcd;
+        int y = size.getHeight() / gcd;
+        return mX == x && mY == y;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o == null) {
+            return false;
+        }
+        if (this == o) {
+            return true;
+        }
+        if (o instanceof AspectRatio) {
+            AspectRatio ratio = (AspectRatio) o;
+            return mX == ratio.mX && mY == ratio.mY;
+        }
+        return false;
+    }
+
+    @Override
+    public String toString() {
+        return mX + ":" + mY;
+    }
+
+    public float toFloat() {
+        return (float) mX / mY;
+    }
+
+    @Override
+    public int hashCode() {
+        // assuming most sizes are <2^16, doing a rotate will give us perfect hashing
+        return mY ^ ((mX << (Integer.SIZE / 2)) | (mX >>> (Integer.SIZE / 2)));
+    }
+
+    @Override
+    public int compareTo(@NonNull AspectRatio another) {
+        if (equals(another)) {
+            return 0;
+        } else if (toFloat() - another.toFloat() > 0) {
+            return 1;
+        }
+        return -1;
+    }
+
+    /**
+     * @return The inverse of this {@link AspectRatio}.
+     */
+    public AspectRatio inverse() {
+        //noinspection SuspiciousNameCombination
+        return AspectRatio.of(mY, mX);
+    }
+
+    private static int gcd(int a, int b) {
+        while (b != 0) {
+            int c = b;
+            b = a % b;
+            a = c;
+        }
+        return a;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mX);
+        dest.writeInt(mY);
+    }
+
+    public static final Creator<AspectRatio> CREATOR
+            = new Creator<AspectRatio>() {
+
+        @Override
+        public AspectRatio createFromParcel(Parcel source) {
+            int x = source.readInt();
+            int y = source.readInt();
+            return AspectRatio.of(x, y);
+        }
+
+        @Override
+        public AspectRatio[] newArray(int size) {
+            return new AspectRatio[size];
+        }
+    };
+
+}

+ 516 - 0
mvvmhabit/src/main/java/me/goldze/mvvmhabit/face/Camera1.java

@@ -0,0 +1,516 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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 me.goldze.mvvmhabit.face;
+
+import android.annotation.SuppressLint;
+import android.graphics.ImageFormat;
+import android.graphics.SurfaceTexture;
+import android.hardware.Camera;
+import android.os.Build;
+import android.view.SurfaceHolder;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import androidx.collection.SparseArrayCompat;
+
+
+@SuppressWarnings("deprecation")
+class Camera1 extends CameraViewImpl {
+
+    private static final int INVALID_CAMERA_ID = -1;
+
+    private static final SparseArrayCompat<String> FLASH_MODES = new SparseArrayCompat<>();
+
+    static {
+        FLASH_MODES.put(Constants.FLASH_OFF, Camera.Parameters.FLASH_MODE_OFF);
+        FLASH_MODES.put(Constants.FLASH_ON, Camera.Parameters.FLASH_MODE_ON);
+        FLASH_MODES.put(Constants.FLASH_TORCH, Camera.Parameters.FLASH_MODE_TORCH);
+        FLASH_MODES.put(Constants.FLASH_AUTO, Camera.Parameters.FLASH_MODE_AUTO);
+        FLASH_MODES.put(Constants.FLASH_RED_EYE, Camera.Parameters.FLASH_MODE_RED_EYE);
+    }
+
+    private int mCameraId;
+
+    private final AtomicBoolean isPictureCaptureInProgress = new AtomicBoolean(false);
+
+    Camera mCamera;
+
+    private Camera.Parameters mCameraParameters;
+
+    private final Camera.CameraInfo mCameraInfo = new Camera.CameraInfo();
+
+    private final SizeMap mPreviewSizes = new SizeMap();
+
+    private final SizeMap mPictureSizes = new SizeMap();
+
+    private AspectRatio mAspectRatio;
+
+    private boolean mShowingPreview;
+
+    private boolean mAutoFocus;
+
+    private int mFacing;
+
+    private int mFlash;
+
+    private int mDisplayOrientation;
+
+    private int mCameraDisplayOrientation ;//采集到合格图像,需要对图像进行相应的旋转
+
+    private byte[] previewBuffer;
+
+    Camera1(Callback callback, PreviewImpl preview) {
+        super(callback, preview);
+        preview.setCallback(new PreviewImpl.Callback() {
+            @Override
+            public void onSurfaceChanged() {
+                if (mCamera != null) {
+                    setUpPreview();
+                    adjustCameraParameters();
+
+                    stop();
+                    start();
+                }
+            }
+        });
+    }
+
+    @Override
+    boolean start() {
+        chooseCamera();
+        openCamera();
+        if (mPreview.isReady()) {
+            setUpPreview();
+        }
+        mShowingPreview = true;
+
+
+        // print saved parameters
+        int prevWidth = mCamera.getParameters().getPreviewSize().width;
+        int prevHeight = mCamera.getParameters().getPreviewSize().height;
+
+        this.previewBuffer = new byte[prevWidth * prevHeight * ImageFormat.getBitsPerPixel(mCamera.getParameters().getPreviewFormat()) / 8];
+
+        mCamera.addCallbackBuffer(previewBuffer);
+        mCamera.setPreviewCallbackWithBuffer(new Camera.PreviewCallback() {
+            @Override
+            public void onPreviewFrame(byte[] data, Camera camera) {
+                mCallback.onPreviewFrame(data,camera);
+
+                camera.addCallbackBuffer(previewBuffer);
+            }
+        });
+        mCamera.startPreview();
+        return true;
+    }
+
+    @Override
+    void stop() {
+        if (mCamera != null) {
+            mCamera.setPreviewCallback(null);
+            mCamera.stopPreview();
+        }
+        mShowingPreview = false;
+        releaseCamera();
+    }
+
+    // Suppresses Camera#setPreviewTexture
+    @SuppressLint("NewApi")
+    void setUpPreview() {
+        try {
+            if (mPreview.getOutputClass() == SurfaceHolder.class) {
+                final boolean needsToStopPreview = mShowingPreview && Build.VERSION.SDK_INT < 14;
+                if (needsToStopPreview) {
+                    mCamera.stopPreview();
+                }
+                mCamera.setPreviewDisplay(mPreview.getSurfaceHolder());
+                if (needsToStopPreview) {
+                    mCamera.startPreview();
+                }
+            } else {
+                mCamera.setPreviewTexture((SurfaceTexture) mPreview.getSurfaceTexture());
+            }
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    boolean isCameraOpened() {
+        return mCamera != null;
+    }
+
+    @Override
+    void setFacing(int facing) {
+        if (mFacing == facing) {
+            return;
+        }
+        mFacing = facing;
+        if (isCameraOpened()) {
+            stop();
+            start();
+        }
+    }
+
+    @Override
+    int getFacing() {
+        return mFacing;
+    }
+
+    @Override
+    Set<AspectRatio> getSupportedAspectRatios() {
+        SizeMap idealAspectRatios = mPreviewSizes;
+        for (AspectRatio aspectRatio : idealAspectRatios.ratios()) {
+            if (mPictureSizes.sizes(aspectRatio) == null) {
+                idealAspectRatios.remove(aspectRatio);
+            }
+        }
+        return idealAspectRatios.ratios();
+    }
+
+    @Override
+    boolean setAspectRatio(AspectRatio ratio) {
+        if (mAspectRatio == null || !isCameraOpened()) {
+            // Handle this later when camera is opened
+            mAspectRatio = ratio;
+            return true;
+        } else if (!mAspectRatio.equals(ratio)) {
+            final Set<Size> sizes = mPreviewSizes.sizes(ratio);
+            if (sizes == null) {
+                throw new UnsupportedOperationException(ratio + " is not supported");
+            } else {
+                mAspectRatio = ratio;
+                adjustCameraParameters();
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    AspectRatio getAspectRatio() {
+        return mAspectRatio;
+    }
+
+    @Override
+    void setAutoFocus(boolean autoFocus) {
+        if (mAutoFocus == autoFocus) {
+            return;
+        }
+        if (setAutoFocusInternal(autoFocus)) {
+            mCamera.setParameters(mCameraParameters);
+        }
+    }
+
+    @Override
+    boolean getAutoFocus() {
+        if (!isCameraOpened()) {
+            return mAutoFocus;
+        }
+        String focusMode = mCameraParameters.getFocusMode();
+        return focusMode != null && focusMode.contains("continuous");
+    }
+
+    @Override
+    void setFlash(int flash) {
+        if (flash == mFlash) {
+            return;
+        }
+        if (setFlashInternal(flash)) {
+            mCamera.setParameters(mCameraParameters);
+        }
+    }
+
+    @Override
+    int getFlash() {
+        return mFlash;
+    }
+
+    @Override
+    void takePicture() {
+        if (!isCameraOpened()) {
+            throw new IllegalStateException(
+                    "Camera is not ready. Call start() before takePicture().");
+        }
+        if (getAutoFocus()) {
+            mCamera.cancelAutoFocus();
+            mCamera.autoFocus(new Camera.AutoFocusCallback() {
+                @Override
+                public void onAutoFocus(boolean success, Camera camera) {
+                    takePictureInternal();
+                }
+            });
+        } else {
+            takePictureInternal();
+        }
+    }
+
+    void takePictureInternal() {
+        if (!isPictureCaptureInProgress.getAndSet(true)) {
+            mCamera.takePicture(null, null, new Camera.PictureCallback() {
+                @Override
+                public void onPictureTaken(byte[] bytes, Camera camera) {
+
+                }
+            }, new Camera.PictureCallback() {
+                @Override
+                public void onPictureTaken(byte[] data, Camera camera) {
+                    isPictureCaptureInProgress.set(false);
+                    mCallback.onPictureTaken(data);
+                    camera.cancelAutoFocus();
+                    camera.startPreview();
+                }
+            });
+        }
+    }
+
+    @Override
+    void setDisplayOrientation(int displayOrientation) {
+        if (mDisplayOrientation == displayOrientation) {
+            return;
+        }
+        mDisplayOrientation = displayOrientation;
+        if (isCameraOpened()) {
+            mCameraParameters.setRotation(calcCameraRotation(displayOrientation));
+            mCamera.setParameters(mCameraParameters);
+            final boolean needsToStopPreview = mShowingPreview && Build.VERSION.SDK_INT < 14;
+            if (needsToStopPreview) {
+                mCamera.stopPreview();
+            }
+            mCamera.setDisplayOrientation(calcDisplayOrientation(displayOrientation));
+            if (needsToStopPreview) {
+                mCamera.startPreview();
+            }
+        }
+    }
+
+    /**
+     * This rewrites {@link #mCameraId} and {@link #mCameraInfo}.
+     */
+    private void chooseCamera() {
+        for (int i = 0, count = Camera.getNumberOfCameras(); i < count; i++) {
+            Camera.getCameraInfo(i, mCameraInfo);
+            if (mCameraInfo.facing == mFacing) {
+                mCameraId = i;
+                return;
+            }
+        }
+        mCameraId = INVALID_CAMERA_ID;
+    }
+
+    private void openCamera() {
+        if (mCamera != null) {
+            releaseCamera();
+        }
+        mCamera = Camera.open(mCameraId);
+        mCameraParameters = mCamera.getParameters();
+        // Supported preview sizes
+        mPreviewSizes.clear();
+        for (Camera.Size size : mCameraParameters.getSupportedPreviewSizes()) {
+            mPreviewSizes.add(new Size(size.width, size.height));
+        }
+        // Supported picture sizes;
+        mPictureSizes.clear();
+        for (Camera.Size size : mCameraParameters.getSupportedPictureSizes()) {
+            mPictureSizes.add(new Size(size.width, size.height));
+        }
+        // AspectRatio
+        if (mAspectRatio == null) {
+            mAspectRatio = Constants.DEFAULT_ASPECT_RATIO;
+        }
+        adjustCameraParameters();
+        mCamera.setDisplayOrientation(calcDisplayOrientation(mDisplayOrientation));
+        mCallback.onCameraOpened();
+    }
+
+    private AspectRatio chooseAspectRatio() {
+        AspectRatio r = null;
+        for (AspectRatio ratio : mPreviewSizes.ratios()) {
+            r = ratio;
+            if (ratio.equals(Constants.DEFAULT_ASPECT_RATIO)) {
+                return ratio;
+            }
+        }
+        return r;
+    }
+
+    void adjustCameraParameters() {
+        SortedSet<Size> sizes = mPreviewSizes.sizes(mAspectRatio);
+        if (sizes == null) { // Not supported
+            mAspectRatio = chooseAspectRatio();
+            sizes = mPreviewSizes.sizes(mAspectRatio);
+        }
+        Size size = chooseOptimalSize(sizes);
+
+        // Always re-apply camera parameters
+        // Largest picture size in this ratio
+        final Size pictureSize = mPictureSizes.sizes(mAspectRatio).last();
+        if (mShowingPreview) {
+            mCamera.stopPreview();
+        }
+        mCameraParameters.setPreviewSize(size.getWidth(), size.getHeight());
+        mCameraParameters.setPictureSize(pictureSize.getWidth(), pictureSize.getHeight());
+        mCameraParameters.setRotation(calcCameraRotation(mDisplayOrientation));
+        setAutoFocusInternal(mAutoFocus);
+        setFlashInternal(mFlash);
+        mCamera.setParameters(mCameraParameters);
+        if (mShowingPreview) {
+            mCamera.startPreview();
+        }
+    }
+
+    @SuppressWarnings("SuspiciousNameCombination")
+    private Size chooseOptimalSize(SortedSet<Size> sizes) {
+        if (!mPreview.isReady()) { // Not yet laid out
+            return sizes.first(); // Return the smallest size
+        }
+        int desiredWidth;
+        int desiredHeight;
+        final int surfaceWidth = mPreview.getWidth();
+        final int surfaceHeight = mPreview.getHeight();
+        if (isLandscape(mDisplayOrientation)) {
+            desiredWidth = surfaceHeight;
+            desiredHeight = surfaceWidth;
+        } else {
+            desiredWidth = surfaceWidth;
+            desiredHeight = surfaceHeight;
+        }
+        Size result = null;
+        for (Size size : sizes) { // Iterate from small to large
+            if (desiredWidth <= size.getWidth() && desiredHeight <= size.getHeight()) {
+                return size;
+
+            }
+            result = size;
+        }
+        return result;
+    }
+
+    private void releaseCamera() {
+        if (mCamera != null) {
+            mCamera.release();
+            mCamera = null;
+            mCallback.onCameraClosed();
+        }
+    }
+
+    /**
+     * Calculate display orientation
+     * https://developer.android.com/reference/android/hardware/Camera.html#setDisplayOrientation(int)
+     *
+     * This calculation is used for orienting the preview
+     *
+     * Note: This is not the same calculation as the camera rotation
+     *
+     * @param screenOrientationDegrees Screen orientation in degrees
+     * @return Number of degrees required to rotate preview
+     */
+    private int calcDisplayOrientation(int screenOrientationDegrees) {
+        if (mCameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
+            mCameraDisplayOrientation = (360 - (mCameraInfo.orientation + screenOrientationDegrees) % 360) % 360;
+        } else {  // back-facing
+            mCameraDisplayOrientation = (mCameraInfo.orientation - screenOrientationDegrees + 360) % 360;
+        }
+        return mCameraDisplayOrientation ;
+    }
+
+    /**
+     * Calculate camera rotation
+     *
+     * This calculation is applied to the output JPEG either via Exif Orientation tag
+     * or by actually transforming the bitmap. (Determined by vendor camera API implementation)
+     *
+     * Note: This is not the same calculation as the display orientation
+     *
+     * @param screenOrientationDegrees Screen orientation in degrees
+     * @return Number of degrees to rotate image in order for it to view correctly.
+     */
+    private int calcCameraRotation(int screenOrientationDegrees) {
+        if (mCameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
+            return (mCameraInfo.orientation + screenOrientationDegrees) % 360;
+        } else {  // back-facing
+            final int landscapeFlip = isLandscape(screenOrientationDegrees) ? 180 : 0;
+            return (mCameraInfo.orientation + screenOrientationDegrees + landscapeFlip) % 360;
+        }
+    }
+
+    /**
+     * Test if the supplied orientation is in landscape.
+     *
+     * @param orientationDegrees Orientation in degrees (0,90,180,270)
+     * @return True if in landscape, false if portrait
+     */
+    private boolean isLandscape(int orientationDegrees) {
+        return (orientationDegrees == Constants.LANDSCAPE_90 ||
+                orientationDegrees == Constants.LANDSCAPE_270);
+    }
+
+    /**
+     * @return {@code true} if {@link #mCameraParameters} was modified.
+     */
+    private boolean setAutoFocusInternal(boolean autoFocus) {
+        mAutoFocus = autoFocus;
+        if (isCameraOpened()) {
+            final List<String> modes = mCameraParameters.getSupportedFocusModes();
+            if (autoFocus && modes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {
+                mCameraParameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
+            } else if (modes.contains(Camera.Parameters.FOCUS_MODE_FIXED)) {
+                mCameraParameters.setFocusMode(Camera.Parameters.FOCUS_MODE_FIXED);
+            } else if (modes.contains(Camera.Parameters.FOCUS_MODE_INFINITY)) {
+                mCameraParameters.setFocusMode(Camera.Parameters.FOCUS_MODE_INFINITY);
+            } else {
+                mCameraParameters.setFocusMode(modes.get(0));
+            }
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * @return {@code true} if {@link #mCameraParameters} was modified.
+     */
+    private boolean setFlashInternal(int flash) {
+        if (isCameraOpened()) {
+            List<String> modes = mCameraParameters.getSupportedFlashModes();
+            String mode = FLASH_MODES.get(flash);
+            if (modes != null && modes.contains(mode)) {
+                mCameraParameters.setFlashMode(mode);
+                mFlash = flash;
+                return true;
+            }
+            String currentMode = FLASH_MODES.get(mFlash);
+            if (modes == null || !modes.contains(currentMode)) {
+                mCameraParameters.setFlashMode(Camera.Parameters.FLASH_MODE_OFF);
+                mFlash = Constants.FLASH_OFF;
+                return true;
+            }
+            return false;
+        } else {
+            mFlash = flash;
+            return false;
+        }
+    }
+
+    public int getCameraDisplayOrientation() {
+        return mCameraDisplayOrientation;
+    }
+}

+ 570 - 0
mvvmhabit/src/main/java/me/goldze/mvvmhabit/face/CameraView.java

@@ -0,0 +1,570 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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 me.goldze.mvvmhabit.face;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.hardware.Camera;
+import android.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.AttributeSet;
+import android.widget.FrameLayout;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Set;
+
+import androidx.annotation.IntDef;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.core.os.ParcelableCompat;
+import androidx.core.os.ParcelableCompatCreatorCallbacks;
+import androidx.core.view.ViewCompat;
+import me.goldze.mvvmhabit.R;
+
+public class CameraView extends FrameLayout {
+
+    /** The camera device faces the opposite direction as the device's screen. */
+    public static final int FACING_BACK = Constants.FACING_BACK;
+
+    /** The camera device faces the same direction as the device's screen. */
+    public static final int FACING_FRONT = Constants.FACING_FRONT;
+
+    /** Direction the camera faces relative to device screen. */
+    @IntDef({FACING_BACK, FACING_FRONT})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Facing {
+    }
+
+    /** Flash will not be fired. */
+    public static final int FLASH_OFF = Constants.FLASH_OFF;
+
+    /** Flash will always be fired during snapshot. */
+    public static final int FLASH_ON = Constants.FLASH_ON;
+
+    /** Constant emission of light during preview, auto-focus and snapshot. */
+    public static final int FLASH_TORCH = Constants.FLASH_TORCH;
+
+    /** Flash will be fired automatically when required. */
+    public static final int FLASH_AUTO = Constants.FLASH_AUTO;
+
+    /** Flash will be fired in red-eye reduction mode. */
+    public static final int FLASH_RED_EYE = Constants.FLASH_RED_EYE;
+
+    /** The mode for for the camera device's flash control */
+    @IntDef({FLASH_OFF, FLASH_ON, FLASH_TORCH, FLASH_AUTO, FLASH_RED_EYE})
+    public @interface Flash {
+    }
+
+    CameraViewImpl mImpl;
+
+    private final CallbackBridge mCallbacks;
+
+    private boolean mAdjustViewBounds;
+
+    private final DisplayOrientationDetector mDisplayOrientationDetector;
+
+    public CameraView(Context context) {
+        this(context, null);
+    }
+
+    public CameraView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    @SuppressWarnings("WrongConstant")
+    public CameraView(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        if (isInEditMode()){
+            mCallbacks = null;
+            mDisplayOrientationDetector = null;
+            return;
+        }
+        // Internal setup
+        final PreviewImpl preview = createPreviewImpl(context);
+        mCallbacks = new CallbackBridge();
+        //if (Build.VERSION.SDK_INT < 21) {
+            mImpl = new Camera1(mCallbacks, preview);
+//        } else if (Build.VERSION.SDK_INT < 23) {
+//            mImpl = new Camera2(mCallbacks, preview, APP_CONTEXT);
+//        } else {
+//            mImpl = new Camera2Api23(mCallbacks, preview, APP_CONTEXT);
+//        }
+        // Attributes
+        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CameraView, defStyleAttr,
+                R.style.Widget_CameraView);
+        mAdjustViewBounds = a.getBoolean(R.styleable.CameraView_android_adjustViewBounds, false);
+        setFacing(a.getInt(R.styleable.CameraView_facing, FACING_FRONT));
+        String aspectRatio = a.getString(R.styleable.CameraView_aspectRatio);
+        if (aspectRatio != null) {
+            setAspectRatio(AspectRatio.parse(aspectRatio));
+        } else {
+            setAspectRatio(Constants.DEFAULT_ASPECT_RATIO);
+        }
+        setAutoFocus(a.getBoolean(R.styleable.CameraView_autoFocus, true));
+        setFlash(a.getInt(R.styleable.CameraView_flash, Constants.FLASH_AUTO));
+        a.recycle();
+        // Display orientation detector
+        mDisplayOrientationDetector = new DisplayOrientationDetector(context) {
+            @Override
+            public void onDisplayOrientationChanged(int displayOrientation) {
+                mImpl.setDisplayOrientation(displayOrientation);
+            }
+        };
+    }
+
+    @NonNull
+    private PreviewImpl createPreviewImpl(Context context) {
+        PreviewImpl preview;
+        if (Build.VERSION.SDK_INT < 14) {
+            preview = new SurfaceViewPreview(context, this);
+        } else {
+            preview = new TextureViewPreview(context, this);
+        }
+        return preview;
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        if (!isInEditMode()) {
+            mDisplayOrientationDetector.enable(ViewCompat.getDisplay(this));
+        }
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        if (!isInEditMode()) {
+            mDisplayOrientationDetector.disable();
+        }
+        super.onDetachedFromWindow();
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        if (isInEditMode()){
+            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+            return;
+        }
+        // Handle android:adjustViewBounds
+        if (mAdjustViewBounds) {
+            if (!isCameraOpened()) {
+                mCallbacks.reserveRequestLayoutOnOpen();
+                super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+                return;
+            }
+            final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+            final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+            if (widthMode == MeasureSpec.EXACTLY && heightMode != MeasureSpec.EXACTLY) {
+                final AspectRatio ratio = getAspectRatio();
+                assert ratio != null;
+                int height = (int) (MeasureSpec.getSize(widthMeasureSpec) * ratio.toFloat());
+                if (heightMode == MeasureSpec.AT_MOST) {
+                    height = Math.min(height, MeasureSpec.getSize(heightMeasureSpec));
+                }
+                super.onMeasure(widthMeasureSpec,
+                        MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
+            } else if (widthMode != MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY) {
+                final AspectRatio ratio = getAspectRatio();
+                assert ratio != null;
+                int width = (int) (MeasureSpec.getSize(heightMeasureSpec) * ratio.toFloat());
+                if (widthMode == MeasureSpec.AT_MOST) {
+                    width = Math.min(width, MeasureSpec.getSize(widthMeasureSpec));
+                }
+                super.onMeasure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
+                        heightMeasureSpec);
+            } else {
+                super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+            }
+        } else {
+            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        }
+        // Measure the TextureView
+        int width = getMeasuredWidth();
+        int height = getMeasuredHeight();
+        AspectRatio ratio = getAspectRatio();
+        if (mDisplayOrientationDetector.getLastKnownDisplayOrientation() % 180 == 0) {
+            ratio = ratio.inverse();
+        }
+        assert ratio != null;
+        if (height < width * ratio.getY() / ratio.getX()) {
+            mImpl.getView().measure(
+                    MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
+                    MeasureSpec.makeMeasureSpec(width * ratio.getY() / ratio.getX(),
+                            MeasureSpec.EXACTLY));
+        } else {
+            mImpl.getView().measure(
+                    MeasureSpec.makeMeasureSpec(height * ratio.getX() / ratio.getY(),
+                            MeasureSpec.EXACTLY),
+                    MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
+        }
+    }
+
+    @Override
+    protected Parcelable onSaveInstanceState() {
+        SavedState state = new SavedState(super.onSaveInstanceState());
+        state.facing = getFacing();
+        state.ratio = getAspectRatio();
+        state.autoFocus = getAutoFocus();
+        state.flash = getFlash();
+        return state;
+    }
+
+    @Override
+    protected void onRestoreInstanceState(Parcelable state) {
+        if (!(state instanceof SavedState)) {
+            super.onRestoreInstanceState(state);
+            return;
+        }
+        SavedState ss = (SavedState) state;
+        super.onRestoreInstanceState(ss.getSuperState());
+        setFacing(ss.facing);
+        setAspectRatio(ss.ratio);
+        setAutoFocus(ss.autoFocus);
+        setFlash(ss.flash);
+    }
+
+    /**
+     * Open a camera device and start showing camera preview. This is typically called from
+     * {@link Activity#onResume()}.
+     */
+    public void start() {
+        if (!mImpl.start()) {
+            //store the state ,and restore this state after fall back o Camera1
+            Parcelable state=onSaveInstanceState();
+            // Camera2 uses legacy hardware layer; fall back to Camera1
+            mImpl = new Camera1(mCallbacks, createPreviewImpl(getContext()));
+            onRestoreInstanceState(state);
+            mImpl.start();
+        }
+    }
+
+    /**
+     * Stop camera preview and close the device. This is typically called from
+     * {@link Activity#onPause()}.
+     */
+    public void stop() {
+        mImpl.stop();
+    }
+
+    /**
+     * @return {@code true} if the camera is opened.
+     */
+    public boolean isCameraOpened() {
+        return mImpl.isCameraOpened();
+    }
+
+    /**
+     * Add a new callback.
+     *
+     * @param callback The {@link Callback} to add.
+     * @see #removeCallback(Callback)
+     */
+    public void addCallback(@NonNull Callback callback) {
+        mCallbacks.add(callback);
+    }
+
+    /**
+     * Remove a callback.
+     *
+     * @param callback The {@link Callback} to remove.
+     * @see #addCallback(Callback)
+     */
+    public void removeCallback(@NonNull Callback callback) {
+        mCallbacks.remove(callback);
+    }
+
+    /**
+     * @param adjustViewBounds {@code true} if you want the CameraView to adjust its bounds to
+     *                         preserve the aspect ratio of camera.
+     * @see #getAdjustViewBounds()
+     */
+    public void setAdjustViewBounds(boolean adjustViewBounds) {
+        if (mAdjustViewBounds != adjustViewBounds) {
+            mAdjustViewBounds = adjustViewBounds;
+            requestLayout();
+        }
+    }
+
+    /**
+     * @return True when this CameraView is adjusting its bounds to preserve the aspect ratio of
+     * camera.
+     * @see #setAdjustViewBounds(boolean)
+     */
+    public boolean getAdjustViewBounds() {
+        return mAdjustViewBounds;
+    }
+
+    /**
+     * Chooses camera by the direction it faces.
+     *
+     * @param facing The camera facing. Must be either {@link #FACING_BACK} or
+     *               {@link #FACING_FRONT}.
+     */
+    public void setFacing(@Facing int facing) {
+        mImpl.setFacing(facing);
+    }
+
+    /**
+     * Gets the direction that the current camera faces.
+     *
+     * @return The camera facing.
+     */
+    @Facing
+    public int getFacing() {
+        //noinspection WrongConstant
+        return mImpl.getFacing();
+    }
+
+    /**
+     * Gets all the aspect ratios supported by the current camera.
+     */
+    public Set<AspectRatio> getSupportedAspectRatios() {
+        return mImpl.getSupportedAspectRatios();
+    }
+
+    /**
+     * Sets the aspect ratio of camera.
+     *
+     * @param ratio The {@link AspectRatio} to be set.
+     */
+    public void setAspectRatio(@NonNull AspectRatio ratio) {
+        if (mImpl.setAspectRatio(ratio)) {
+            requestLayout();
+        }
+    }
+
+    /**
+     * Gets the current aspect ratio of camera.
+     *
+     * @return The current {@link AspectRatio}. Can be {@code null} if no camera is opened yet.
+     */
+    @Nullable
+    public AspectRatio getAspectRatio() {
+        return mImpl.getAspectRatio();
+    }
+
+    /**
+     * Enables or disables the continuous auto-focus mode. When the current camera doesn't support
+     * auto-focus, calling this method will be ignored.
+     *
+     * @param autoFocus {@code true} to enable continuous auto-focus mode. {@code false} to
+     *                  disable it.
+     */
+    public void setAutoFocus(boolean autoFocus) {
+        mImpl.setAutoFocus(autoFocus);
+    }
+
+    /**
+     * Returns whether the continuous auto-focus mode is enabled.
+     *
+     * @return {@code true} if the continuous auto-focus mode is enabled. {@code false} if it is
+     * disabled, or if it is not supported by the current camera.
+     */
+    public boolean getAutoFocus() {
+        return mImpl.getAutoFocus();
+    }
+
+    /**
+     * Sets the flash mode.
+     *
+     * @param flash The desired flash mode.
+     */
+    public void setFlash(@Flash int flash) {
+        mImpl.setFlash(flash);
+    }
+
+    /**
+     * Gets the current flash mode.
+     *
+     * @return The current flash mode.
+     */
+    @Flash
+    public int getFlash() {
+        //noinspection WrongConstant
+        return mImpl.getFlash();
+    }
+
+    /**
+     * 获取Camera的方向
+     * @return
+     */
+    public int getCameraDisplayOrientation(){
+        if(mImpl instanceof Camera1){
+            return ((Camera1)mImpl).getCameraDisplayOrientation() ;
+        }
+        return 0 ;
+    }
+
+    /**
+     * Take a picture. The result will be returned to
+     * {@link Callback#onPictureTaken(CameraView, byte[])}.
+     */
+    public void takePicture() {
+        mImpl.takePicture();
+    }
+
+    private class CallbackBridge implements CameraViewImpl.Callback {
+
+        private final ArrayList<Callback> mCallbacks = new ArrayList<>();
+
+        private boolean mRequestLayoutOnOpen;
+
+        CallbackBridge() {
+        }
+
+        public void add(Callback callback) {
+            mCallbacks.add(callback);
+        }
+
+        public void remove(Callback callback) {
+            mCallbacks.remove(callback);
+        }
+
+        @Override
+        public void onCameraOpened() {
+            if (mRequestLayoutOnOpen) {
+                mRequestLayoutOnOpen = false;
+                requestLayout();
+            }
+            for (Callback callback : mCallbacks) {
+                callback.onCameraOpened(CameraView.this);
+            }
+        }
+
+        @Override
+        public void onCameraClosed() {
+            for (Callback callback : mCallbacks) {
+                callback.onCameraClosed(CameraView.this);
+            }
+        }
+
+        @Override
+        public void onPictureTaken(byte[] data) {
+            for (Callback callback : mCallbacks) {
+                callback.onPictureTaken(CameraView.this, data);
+            }
+        }
+
+        @Override
+        public void onPreviewFrame(byte[] data, Camera camera) {
+            for (Callback callback : mCallbacks) {
+                callback.onPreviewFrame(data,camera);
+            }
+        }
+
+        public void reserveRequestLayoutOnOpen() {
+            mRequestLayoutOnOpen = true;
+        }
+    }
+
+    protected static class SavedState extends BaseSavedState {
+
+        @Facing
+        int facing;
+
+        AspectRatio ratio;
+
+        boolean autoFocus;
+
+        @Flash
+        int flash;
+
+        @SuppressWarnings("WrongConstant")
+        public SavedState(Parcel source, ClassLoader loader) {
+            super(source);
+            facing = source.readInt();
+            ratio = source.readParcelable(loader);
+            autoFocus = source.readByte() != 0;
+            flash = source.readInt();
+        }
+
+        public SavedState(Parcelable superState) {
+            super(superState);
+        }
+
+        @Override
+        public void writeToParcel(Parcel out, int flags) {
+            super.writeToParcel(out, flags);
+            out.writeInt(facing);
+            out.writeParcelable(ratio, 0);
+            out.writeByte((byte) (autoFocus ? 1 : 0));
+            out.writeInt(flash);
+        }
+
+        public static final Creator<SavedState> CREATOR
+                = ParcelableCompat.newCreator(new ParcelableCompatCreatorCallbacks<SavedState>() {
+
+            @Override
+            public SavedState createFromParcel(Parcel in, ClassLoader loader) {
+                return new SavedState(in, loader);
+            }
+
+            @Override
+            public SavedState[] newArray(int size) {
+                return new SavedState[size];
+            }
+
+        });
+
+    }
+
+    /**
+     * Callback for monitoring events about {@link CameraView}.
+     */
+    @SuppressWarnings("UnusedParameters")
+    public abstract static class Callback {
+
+        /**
+         * Called when camera is opened.
+         *
+         * @param cameraView The associated {@link CameraView}.
+         */
+        public void onCameraOpened(CameraView cameraView) {
+        }
+
+        /**
+         * Called when camera is closed.
+         *
+         * @param cameraView The associated {@link CameraView}.
+         */
+        public void onCameraClosed(CameraView cameraView) {
+        }
+
+        /**
+         * Called when a picture is taken.
+         *
+         * @param cameraView The associated {@link CameraView}.
+         * @param data       JPEG data.
+         */
+        public void onPictureTaken(CameraView cameraView, byte[] data) {
+        }
+
+        /**
+         * 获取每一帧图像
+         * @param data
+         * @param camera
+         */
+        public void onPreviewFrame(byte[] data, Camera camera) {
+
+        }
+    }
+
+}

+ 84 - 0
mvvmhabit/src/main/java/me/goldze/mvvmhabit/face/CameraViewImpl.java

@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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 me.goldze.mvvmhabit.face;
+
+import android.hardware.Camera;
+import android.view.View;
+
+import java.util.Set;
+
+abstract class CameraViewImpl {
+
+    protected final Callback mCallback;
+
+    protected final PreviewImpl mPreview;
+
+    CameraViewImpl(Callback callback, PreviewImpl preview) {
+        mCallback = callback;
+        mPreview = preview;
+    }
+
+    View getView() {
+        return mPreview.getView();
+    }
+
+    /**
+     * @return {@code true} if the implementation was able to start the camera session.
+     */
+    abstract boolean start();
+
+    abstract void stop();
+
+    abstract boolean isCameraOpened();
+
+    abstract void setFacing(int facing);
+
+    abstract int getFacing();
+
+    abstract Set<AspectRatio> getSupportedAspectRatios();
+
+    /**
+     * @return {@code true} if the aspect ratio was changed.
+     */
+    abstract boolean setAspectRatio(AspectRatio ratio);
+
+    abstract AspectRatio getAspectRatio();
+
+    abstract void setAutoFocus(boolean autoFocus);
+
+    abstract boolean getAutoFocus();
+
+    abstract void setFlash(int flash);
+
+    abstract int getFlash();
+
+    abstract void takePicture();
+
+    abstract void setDisplayOrientation(int displayOrientation);
+
+    interface Callback {
+
+        void onCameraOpened();
+
+        void onCameraClosed();
+
+        void onPictureTaken(byte[] data);
+
+        void onPreviewFrame(byte[] data, Camera camera);
+    }
+
+}

+ 35 - 0
mvvmhabit/src/main/java/me/goldze/mvvmhabit/face/Constants.java

@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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 me.goldze.mvvmhabit.face;
+
+
+interface Constants {
+
+    AspectRatio DEFAULT_ASPECT_RATIO = AspectRatio.of(4, 3);
+
+    int FACING_BACK = 0;
+    int FACING_FRONT = 1;
+
+    int FLASH_OFF = 0;
+    int FLASH_ON = 1;
+    int FLASH_TORCH = 2;
+    int FLASH_AUTO = 3;
+    int FLASH_RED_EYE = 4;
+
+    int LANDSCAPE_90 = 90;
+    int LANDSCAPE_270 = 270;
+}

+ 96 - 0
mvvmhabit/src/main/java/me/goldze/mvvmhabit/face/DisplayOrientationDetector.java

@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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 me.goldze.mvvmhabit.face;
+
+import android.content.Context;
+import android.util.SparseIntArray;
+import android.view.Display;
+import android.view.OrientationEventListener;
+import android.view.Surface;
+
+
+/**
+ * Monitors the value returned from {@link Display#getRotation()}.
+ */
+abstract class DisplayOrientationDetector {
+
+    private final OrientationEventListener mOrientationEventListener;
+
+    /** Mapping from Surface.Rotation_n to degrees. */
+    static final SparseIntArray DISPLAY_ORIENTATIONS = new SparseIntArray();
+
+    static {
+        DISPLAY_ORIENTATIONS.put(Surface.ROTATION_0, 0);
+        DISPLAY_ORIENTATIONS.put(Surface.ROTATION_90, 90);
+        DISPLAY_ORIENTATIONS.put(Surface.ROTATION_180, 180);
+        DISPLAY_ORIENTATIONS.put(Surface.ROTATION_270, 270);
+    }
+
+    Display mDisplay;
+
+    private int mLastKnownDisplayOrientation = 0;
+
+    public DisplayOrientationDetector(Context context) {
+        mOrientationEventListener = new OrientationEventListener(context) {
+
+            /** This is either Surface.Rotation_0, _90, _180, _270, or -1 (invalid). */
+            private int mLastKnownRotation = -1;
+
+            @Override
+            public void onOrientationChanged(int orientation) {
+                if (orientation == OrientationEventListener.ORIENTATION_UNKNOWN ||
+                        mDisplay == null) {
+                    return;
+                }
+                final int rotation = mDisplay.getRotation();
+                if (mLastKnownRotation != rotation) {
+                    mLastKnownRotation = rotation;
+                    dispatchOnDisplayOrientationChanged(DISPLAY_ORIENTATIONS.get(rotation));
+                }
+            }
+        };
+    }
+
+    public void enable(Display display) {
+        mDisplay = display;
+        mOrientationEventListener.enable();
+        // Immediately dispatch the first callback
+        dispatchOnDisplayOrientationChanged(DISPLAY_ORIENTATIONS.get(display.getRotation()));
+    }
+
+    public void disable() {
+        mOrientationEventListener.disable();
+        mDisplay = null;
+    }
+
+    public int getLastKnownDisplayOrientation() {
+        return mLastKnownDisplayOrientation;
+    }
+
+    void dispatchOnDisplayOrientationChanged(int displayOrientation) {
+        mLastKnownDisplayOrientation = displayOrientation;
+        onDisplayOrientationChanged(displayOrientation);
+    }
+
+    /**
+     * Called when display orientation is changed.
+     *
+     * @param displayOrientation One of 0, 90, 180, and 270.
+     */
+    public abstract void onDisplayOrientationChanged(int displayOrientation);
+
+}

+ 81 - 0
mvvmhabit/src/main/java/me/goldze/mvvmhabit/face/PreviewImpl.java

@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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 me.goldze.mvvmhabit.face;
+
+import android.view.Surface;
+import android.view.SurfaceHolder;
+import android.view.View;
+
+
+/**
+ * Encapsulates all the operations related to camera preview in a backward-compatible manner.
+ */
+abstract class PreviewImpl {
+
+    interface Callback {
+        void onSurfaceChanged();
+    }
+
+    private Callback mCallback;
+
+    private int mWidth;
+
+    private int mHeight;
+
+    void setCallback(Callback callback) {
+        mCallback = callback;
+    }
+
+    abstract Surface getSurface();
+
+    abstract View getView();
+
+    abstract Class getOutputClass();
+
+    abstract void setDisplayOrientation(int displayOrientation);
+
+    abstract boolean isReady();
+
+    protected void dispatchSurfaceChanged() {
+        mCallback.onSurfaceChanged();
+    }
+
+    SurfaceHolder getSurfaceHolder() {
+        return null;
+    }
+
+    Object getSurfaceTexture() {
+        return null;
+    }
+
+    void setBufferSize(int width, int height) {
+    }
+
+    void setSize(int width, int height) {
+        mWidth = width;
+        mHeight = height;
+    }
+
+    int getWidth() {
+        return mWidth;
+    }
+
+    int getHeight() {
+        return mHeight;
+    }
+
+}

+ 80 - 0
mvvmhabit/src/main/java/me/goldze/mvvmhabit/face/Size.java

@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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 me.goldze.mvvmhabit.face;
+
+
+import androidx.annotation.NonNull;
+
+/**
+ * Immutable class for describing width and height dimensions in pixels.
+ */
+public class Size implements Comparable<Size> {
+
+    private final int mWidth;
+    private final int mHeight;
+
+    /**
+     * Create a new immutable Size instance.
+     *
+     * @param width  The width of the size, in pixels
+     * @param height The height of the size, in pixels
+     */
+    public Size(int width, int height) {
+        mWidth = width;
+        mHeight = height;
+    }
+
+    public int getWidth() {
+        return mWidth;
+    }
+
+    public int getHeight() {
+        return mHeight;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o == null) {
+            return false;
+        }
+        if (this == o) {
+            return true;
+        }
+        if (o instanceof Size) {
+            Size size = (Size) o;
+            return mWidth == size.mWidth && mHeight == size.mHeight;
+        }
+        return false;
+    }
+
+    @Override
+    public String toString() {
+        return mWidth + "x" + mHeight;
+    }
+
+    @Override
+    public int hashCode() {
+        // assuming most sizes are <2^16, doing a rotate will give us perfect hashing
+        return mHeight ^ ((mWidth << (Integer.SIZE / 2)) | (mWidth >>> (Integer.SIZE / 2)));
+    }
+
+    @Override
+    public int compareTo(@NonNull Size another) {
+        return mWidth * mHeight - another.mWidth * another.mHeight;
+    }
+
+}

+ 82 - 0
mvvmhabit/src/main/java/me/goldze/mvvmhabit/face/SizeMap.java

@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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 me.goldze.mvvmhabit.face;
+
+import android.util.ArrayMap;
+
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+/**
+ * A collection class that automatically groups {@link Size}s by their {@link AspectRatio}s.
+ */
+class SizeMap {
+
+    private final ArrayMap<AspectRatio, SortedSet<Size>> mRatios = new ArrayMap<>();
+
+    /**
+     * Add a new {@link Size} to this collection.
+     *
+     * @param size The size to add.
+     * @return {@code true} if it is added, {@code false} if it already exists and is not added.
+     */
+    public boolean add(Size size) {
+        for (AspectRatio ratio : mRatios.keySet()) {
+            if (ratio.matches(size)) {
+                final SortedSet<Size> sizes = mRatios.get(ratio);
+                if (sizes.contains(size)) {
+                    return false;
+                } else {
+                    sizes.add(size);
+                    return true;
+                }
+            }
+        }
+        // None of the existing ratio matches the provided size; add a new key
+        SortedSet<Size> sizes = new TreeSet<>();
+        sizes.add(size);
+        mRatios.put(AspectRatio.of(size.getWidth(), size.getHeight()), sizes);
+        return true;
+    }
+
+    /**
+     * Removes the specified aspect ratio and all sizes associated with it.
+     *
+     * @param ratio The aspect ratio to be removed.
+     */
+    public void remove(AspectRatio ratio) {
+        mRatios.remove(ratio);
+    }
+
+    Set<AspectRatio> ratios() {
+        return mRatios.keySet();
+    }
+
+    SortedSet<Size> sizes(AspectRatio ratio) {
+        return mRatios.get(ratio);
+    }
+
+    void clear() {
+        mRatios.clear();
+    }
+
+    boolean isEmpty() {
+        return mRatios.isEmpty();
+    }
+
+}

+ 89 - 0
mvvmhabit/src/main/java/me/goldze/mvvmhabit/face/SurfaceViewPreview.java

@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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 me.goldze.mvvmhabit.face;
+
+import android.content.Context;
+import android.view.Surface;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.core.view.ViewCompat;
+import me.goldze.mvvmhabit.R;
+
+
+class SurfaceViewPreview extends PreviewImpl {
+
+    final SurfaceView mSurfaceView;
+
+    SurfaceViewPreview(Context context, ViewGroup parent) {
+        final View view = View.inflate(context, R.layout.surface_view, parent);
+        mSurfaceView = (SurfaceView) view.findViewById(R.id.surface_view);
+        final SurfaceHolder holder = mSurfaceView.getHolder();
+        //noinspection deprecation
+        holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
+        holder.addCallback(new SurfaceHolder.Callback() {
+            @Override
+            public void surfaceCreated(SurfaceHolder h) {
+            }
+
+            @Override
+            public void surfaceChanged(SurfaceHolder h, int format, int width, int height) {
+                setSize(width, height);
+                if (!ViewCompat.isInLayout(mSurfaceView)) {
+                    dispatchSurfaceChanged();
+                }
+            }
+
+            @Override
+            public void surfaceDestroyed(SurfaceHolder h) {
+                setSize(0, 0);
+            }
+        });
+    }
+
+    @Override
+    Surface getSurface() {
+        return getSurfaceHolder().getSurface();
+    }
+
+    @Override
+    SurfaceHolder getSurfaceHolder() {
+        return mSurfaceView.getHolder();
+    }
+
+    @Override
+    View getView() {
+        return mSurfaceView;
+    }
+
+    @Override
+    Class getOutputClass() {
+        return SurfaceHolder.class;
+    }
+
+    @Override
+    void setDisplayOrientation(int displayOrientation) {
+    }
+
+    @Override
+    boolean isReady() {
+        return getWidth() != 0 && getHeight() != 0;
+    }
+
+}

+ 146 - 0
mvvmhabit/src/main/java/me/goldze/mvvmhabit/face/TextureViewPreview.java

@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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 me.goldze.mvvmhabit.face;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.graphics.Matrix;
+import android.graphics.SurfaceTexture;
+import android.view.Surface;
+import android.view.TextureView;
+import android.view.View;
+import android.view.ViewGroup;
+
+import me.goldze.mvvmhabit.R;
+
+
+@TargetApi(14)
+class TextureViewPreview extends PreviewImpl {
+
+    private final TextureView mTextureView;
+
+    private int mDisplayOrientation;
+
+    TextureViewPreview(Context context, ViewGroup parent) {
+        final View view = View.inflate(context, R.layout.texture_view, parent);
+        mTextureView = (TextureView) view.findViewById(R.id.texture_view);
+        mTextureView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() {
+
+            @Override
+            public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
+                setSize(width, height);
+                configureTransform();
+                dispatchSurfaceChanged();
+            }
+
+            @Override
+            public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
+                setSize(width, height);
+                configureTransform();
+                dispatchSurfaceChanged();
+            }
+
+            @Override
+            public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
+                setSize(0, 0);
+                return true;
+            }
+
+            @Override
+            public void onSurfaceTextureUpdated(SurfaceTexture surface) {
+            }
+        });
+    }
+
+    // This method is called only from Camera2.
+    @TargetApi(15)
+    @Override
+    void setBufferSize(int width, int height) {
+        mTextureView.getSurfaceTexture().setDefaultBufferSize(width, height);
+    }
+
+    @Override
+    Surface getSurface() {
+        return new Surface(mTextureView.getSurfaceTexture());
+    }
+
+    @Override
+    SurfaceTexture getSurfaceTexture() {
+        return mTextureView.getSurfaceTexture();
+    }
+
+    @Override
+    View getView() {
+        return mTextureView;
+    }
+
+    @Override
+    Class getOutputClass() {
+        return SurfaceTexture.class;
+    }
+
+    @Override
+    void setDisplayOrientation(int displayOrientation) {
+        mDisplayOrientation = displayOrientation;
+        configureTransform();
+    }
+
+    @Override
+    boolean isReady() {
+        return mTextureView.getSurfaceTexture() != null;
+    }
+
+    /**
+     * Configures the transform matrix for TextureView based on {@link #mDisplayOrientation} and
+     * the surface size.
+     */
+    void configureTransform() {
+        Matrix matrix = new Matrix();
+        if (mDisplayOrientation % 180 == 90) {
+            final int width = getWidth();
+            final int height = getHeight();
+            // Rotate the camera preview when the screen is landscape.
+            matrix.setPolyToPoly(
+                    new float[]{
+                            0.f, 0.f, // top left
+                            width, 0.f, // top right
+                            0.f, height, // bottom left
+                            width, height, // bottom right
+                    }, 0,
+                    mDisplayOrientation == 90 ?
+                            // Clockwise
+                            new float[]{
+                                    0.f, height, // top left
+                                    0.f, 0.f, // top right
+                                    width, height, // bottom left
+                                    width, 0.f, // bottom right
+                            } : // mDisplayOrientation == 270
+                            // Counter-clockwise
+                            new float[]{
+                                    width, 0.f, // top left
+                                    width, height, // top right
+                                    0.f, 0.f, // bottom left
+                                    0.f, height, // bottom right
+                            }, 0,
+                    4);
+        } else if (mDisplayOrientation == 180) {
+            matrix.postRotate(180, getWidth() / 2, getHeight() / 2);
+        }
+        mTextureView.setTransform(matrix);
+    }
+
+}

+ 40 - 0
mvvmhabit/src/main/java/me/goldze/mvvmhabit/utils/ImageUtils.java

@@ -184,6 +184,46 @@ public class ImageUtils {
     }
 
     /**
+     * Return bitmap.
+     *
+     * @param file      The file.
+     * @param maxWidth  The maximum width.
+     * @param maxHeight The maximum height.
+     * @return bitmap
+     */
+    public static Bitmap getBitmap(final File file, final int maxWidth, final int maxHeight) {
+        if (file == null) return null;
+        BitmapFactory.Options options = new BitmapFactory.Options();
+        options.inJustDecodeBounds = true;
+        BitmapFactory.decodeFile(file.getAbsolutePath(), options);
+        options.inSampleSize = calculateInSampleSize(options, maxWidth, maxHeight);
+        options.inJustDecodeBounds = false;
+        return BitmapFactory.decodeFile(file.getAbsolutePath(), options);
+    }
+
+    /**
+     * Return the sample size.
+     *
+     * @param options   The options.
+     * @param maxWidth  The maximum width.
+     * @param maxHeight The maximum height.
+     * @return the sample size
+     */
+    private static int calculateInSampleSize(final BitmapFactory.Options options,
+                                             final int maxWidth,
+                                             final int maxHeight) {
+        int height = options.outHeight;
+        int width = options.outWidth;
+        int inSampleSize = 1;
+        while (height > maxHeight || width > maxWidth) {
+            height >>= 1;
+            width >>= 1;
+            inSampleSize <<= 1;
+        }
+        return inSampleSize;
+    }
+
+    /**
      * 获取bitmap
      * 
      * @param filePath

+ 23 - 0
mvvmhabit/src/main/res/layout/surface_view.xml

@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright (C) 2016 The Android Open Source Project
+    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.
+-->
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <SurfaceView
+        android:id="@+id/surface_view"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:gravity="center"
+        />
+
+</merge>

+ 23 - 0
mvvmhabit/src/main/res/layout/texture_view.xml

@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright (C) 2016 The Android Open Source Project
+    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.
+-->
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <TextureView
+        android:id="@+id/texture_view"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:gravity="center"
+        />
+
+</merge>

+ 44 - 0
mvvmhabit/src/main/res/values/attrs.xml

@@ -167,4 +167,48 @@
     <declare-styleable name="ControlDistributeLinearLayout">
         <attr name="distribute_event" format="boolean" />
     </declare-styleable>
+
+    <declare-styleable name="CameraView">
+        <!--
+          Set this to true if you want the CameraView to adjust its bounds to preserve the aspect
+          ratio of its camera preview.
+        -->
+        <attr name="android:adjustViewBounds"/>
+        <!-- Direction the camera faces relative to device screen. -->
+        <attr name="facing" format="enum">
+            <!-- The camera device faces the opposite direction as the device's screen. -->
+            <enum name="back" value="0"/>
+            <!-- The camera device faces the same direction as the device's screen. -->
+            <enum name="front" value="1"/>
+        </attr>
+        <!-- Aspect ratio of camera preview and pictures. -->
+        <attr name="aspectRatio" format="string"/>
+        <!-- Continuous auto focus mode. -->
+        <attr name="autoFocus" format="boolean"/>
+        <!-- The flash mode. -->
+        <attr name="flash" format="enum">
+            <!-- Flash will not be fired. -->
+            <enum name="off" value="0"/>
+            <!--
+              Flash will always be fired during snapshot.
+              The flash may also be fired during preview or auto-focus depending on the driver.
+            -->
+            <enum name="on" value="1"/>
+            <!--
+              Constant emission of light during preview, auto-focus and snapshot.
+              This can also be used for video recording.
+            -->
+            <enum name="torch" value="2"/>
+            <!--
+              Flash will be fired automatically when required.
+              The flash may be fired during preview, auto-focus, or snapshot depending on the
+              driver.
+            -->
+            <enum name="auto" value="3"/>
+            <!--
+              Flash will be fired in red-eye reduction mode.
+            -->
+            <enum name="redEye" value="4"/>
+        </attr>
+    </declare-styleable>
 </resources>

+ 8 - 0
mvvmhabit/src/main/res/values/styles.xml

@@ -5,4 +5,12 @@
         <item name="android:backgroundDimEnabled">false</item>
         <item name="android:windowBackground">@android:color/transparent</item>
     </style>
+
+    <style name="Widget.CameraView" parent="android:Widget">
+        <item name="android:adjustViewBounds">false</item>
+        <item name="facing">back</item>
+        <item name="aspectRatio">4:3</item>
+        <item name="autoFocus">true</item>
+        <item name="flash">auto</item>
+    </style>
 </resources>