add facefinder
diff --git a/FaceShared/src/main/java/com/libremobileos/yifan/face/scan/FaceFinder.java b/FaceShared/src/main/java/com/libremobileos/yifan/face/scan/FaceFinder.java
new file mode 100644
index 0000000..e4b1ad4
--- /dev/null
+++ b/FaceShared/src/main/java/com/libremobileos/yifan/face/scan/FaceFinder.java
@@ -0,0 +1,64 @@
+package com.libremobileos.yifan.face.scan;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.os.SystemClock;
+import android.util.Pair;
+
+import com.libremobileos.yifan.face.shared.FaceDetector;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/** lame wrapper around FaceDetector and FaceScanner */
+public class FaceFinder {
+	private final FaceDetector faceDetector;
+	private final FaceDetector.InputImageProcessor detectorInputProc;
+	private final FaceScanner faceScanner;
+	private final int sensorOrientation;
+
+	private FaceFinder(Context ctx, int inputWidth, int inputHeight, int sensorOrientation) {
+		this.faceDetector = FaceDetector.create(ctx);
+		this.faceScanner = FaceScanner.create(ctx);
+		this.sensorOrientation = sensorOrientation;
+		this.detectorInputProc = new FaceDetector.InputImageProcessor(inputWidth, inputHeight, sensorOrientation);
+	}
+
+	public static FaceFinder create(Context ctx, int inputWidth, int inputHeight, int sensorOrientation) {
+		return new FaceFinder(ctx, inputWidth, inputHeight, sensorOrientation);
+	}
+
+	public void setUseNNAPI(boolean useNNAPI) {
+		faceScanner.setUseNNAPI(useNNAPI);
+		faceDetector.setUseNNAPI(useNNAPI);
+	}
+
+	public void setNumThreads(int numThreads) {
+		faceScanner.setNumThreads(numThreads);
+		faceDetector.setNumThreads(numThreads);
+	}
+
+	public Pair<List<Pair<FaceDetector.Face, FaceScanner.Face>> /* detected faces */, Long /* processing time */> process(Bitmap input, boolean add) {
+		FaceDetector.InputImage inputImage = detectorInputProc.process(input);
+
+		final long startTime1 = SystemClock.uptimeMillis();
+		final List<FaceDetector.Face> faces = faceDetector.detectFaces(inputImage);
+		final List<Pair<FaceDetector.Face, FaceScanner.Face>> results = new ArrayList<>();
+
+		if (faces != null && faces.size() > 0) {
+			final FaceScanner.InputImageProcessor scannerInputProc = new FaceScanner.InputImageProcessor(input, sensorOrientation);
+
+			for (FaceDetector.Face face : faces) {
+				FaceScanner.InputImage faceBmp = scannerInputProc.process(face.getLocation());
+				if (faceBmp == null) continue;
+
+				final FaceScanner.Face scanned = faceScanner.detectFace(faceBmp, add);
+				if (scanned == null) continue;
+
+				results.add(new Pair<>(face, scanned));
+			}
+		}
+
+		return new Pair<>(results, /* lastProcessingTimeMs */ SystemClock.uptimeMillis() - startTime1);
+	}
+}
diff --git a/FaceShared/src/main/java/com/libremobileos/yifan/face/scan/FaceScanner.java b/FaceShared/src/main/java/com/libremobileos/yifan/face/scan/FaceScanner.java
index 8a9cd43..0b2e146 100644
--- a/FaceShared/src/main/java/com/libremobileos/yifan/face/scan/FaceScanner.java
+++ b/FaceShared/src/main/java/com/libremobileos/yifan/face/scan/FaceScanner.java
@@ -5,10 +5,8 @@
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Matrix;
-import android.graphics.Rect;
 import android.graphics.RectF;
 import android.util.Log;
-import android.util.Pair;
 
 import androidx.annotation.NonNull;
 
@@ -16,10 +14,8 @@
 import com.libremobileos.yifan.face.shared.SimilarityClassifier;
 
 import java.io.IOException;
-import java.util.LinkedList;
 import java.util.List;
 import java.util.Locale;
-import java.util.Map;
 
 public class FaceScanner {
 	private final AssetManager am;
@@ -53,12 +49,33 @@
 
 	public static class InputImageProcessor {
 		private final int sensorOrientation;
+		private final Bitmap portraitBmp;
+		private final Matrix transform;
 
-		public InputImageProcessor(int sensorOrientation) {
+		public InputImageProcessor(Bitmap rawImage, int sensorOrientation) {
 			this.sensorOrientation = sensorOrientation;
+			//TODO replace this mess with ImageUtils transform
+			int targetW, targetH;
+			if (sensorOrientation == 90 || sensorOrientation == 270) {
+				targetH = rawImage.getWidth();
+				targetW = rawImage.getHeight();
+			} else {
+				targetW = rawImage.getWidth();
+				targetH = rawImage.getHeight();
+			}
+			Bitmap portraitBmp = Bitmap.createBitmap(targetW, targetH, Bitmap.Config.ARGB_8888);
+			transform = createTransform(
+					rawImage.getWidth(),
+					rawImage.getHeight(),
+					targetW,
+					targetH,
+					sensorOrientation);
+			final Canvas cv = new Canvas(portraitBmp);
+			cv.drawBitmap(rawImage, transform, null);
+			this.portraitBmp = portraitBmp;
 		}
 
-		public InputImage process(Bitmap input) {
+		public static InputImage process(Bitmap input, int sensorOrientation) {
 			Matrix frameToCropTransform =
 					ImageUtils.getTransformationMatrix(
 							input.getWidth(), input.getHeight(),
@@ -70,66 +87,40 @@
 			return new InputImage(croppedBitmap, input);
 		}
 
-		// utility because everyone is lazy :D
-		public InputImage process(Bitmap fullInput, Bitmap input, RectF inputBB) {
-			//TODO cleanup
-			Matrix transform = createTransform(
-					fullInput.getWidth(),
-					fullInput.getHeight(),
-					input.getWidth(),
-					input.getHeight(),
-					sensorOrientation);
+		public InputImage process(Bitmap input) {
+			return process(input, sensorOrientation);
+		}
+
+		public InputImage process(RectF inputBB) {
 			RectF faceBB = new RectF(inputBB);
 			transform.mapRect(faceBB);
 			if (faceBB.left < 0 || faceBB.top < 0 || faceBB.bottom < 0 ||
-					faceBB.right < 0 || (faceBB.left + faceBB.width()) > input.getWidth()
-					|| (faceBB.top + faceBB.height()) > input.getHeight()) return null;
-			return process(Bitmap.createBitmap(input,
+					faceBB.right < 0 || (faceBB.left + faceBB.width()) > portraitBmp.getWidth()
+					|| (faceBB.top + faceBB.height()) > portraitBmp.getHeight()) return null;
+			return process(Bitmap.createBitmap(portraitBmp,
 					(int) faceBB.left,
 					(int) faceBB.top,
 					(int) faceBB.width(),
 					(int) faceBB.height()));
 		}
 
-		public Bitmap transformRawImageToPortrait(Bitmap input) {
-			//TODO replace this mess with ImageUtils transform
-			int targetW, targetH;
-			if (sensorOrientation == 90 || sensorOrientation == 270) {
-				targetH = input.getWidth();
-				targetW = input.getHeight();
-			} else {
-				targetW = input.getWidth();
-				targetH = input.getHeight();
+		private static Matrix createTransform( //should be removed while reworking portraitBmp creation
+		                                       final int srcWidth,
+		                                       final int srcHeight,
+		                                       final int dstWidth,
+		                                       final int dstHeight,
+		                                       final int applyRotation) {
+			Matrix matrix = new Matrix();
+			if (applyRotation != 0) {
+				matrix.postTranslate(-srcWidth / 2.0f, -srcHeight / 2.0f);
+				matrix.postRotate(applyRotation);
+				matrix.postTranslate(dstWidth / 2.0f, dstHeight / 2.0f);
 			}
-			Bitmap portraitBmp = Bitmap.createBitmap(targetW, targetH, Bitmap.Config.ARGB_8888);
-			Matrix transform = createTransform(
-					input.getWidth(),
-					input.getHeight(),
-					targetW,
-					targetH,
-					sensorOrientation);
-			final Canvas cv = new Canvas(portraitBmp);
-			cv.drawBitmap(input, transform, null);
-			return portraitBmp;
+			return matrix;
 		}
-	}
 
-	private static Matrix createTransform( //should be removed while reworking transformRawImageToPortrait
-			final int srcWidth,
-			final int srcHeight,
-			final int dstWidth,
-			final int dstHeight,
-			final int applyRotation) {
-		Matrix matrix = new Matrix();
-		if (applyRotation != 0) {
-			matrix.postTranslate(-srcWidth / 2.0f, -srcHeight / 2.0f);
-			matrix.postRotate(applyRotation);
-			matrix.postTranslate(dstWidth / 2.0f, dstHeight / 2.0f);
-		}
-		return matrix;
 	}
 
-
 	/** An immutable result returned by a FaceDetector describing what was recognized. */
 	public static class Face {
 		/**
@@ -288,7 +279,7 @@
 			SimilarityClassifier.Recognition result = results.get(0);
 			return new Face(result.getId(), result.getTitle(), result.getDistance(), null, add ? input.getUserDisplayableImage() : null, result.getExtra());
 		} catch (IOException e) {
-			Log.e("FaceDetector", Log.getStackTraceString(e));
+			Log.e("FaceScanner", Log.getStackTraceString(e));
 			return null;
 		}
 	}
diff --git a/app/src/main/java/com/libremobileos/facedetect/MainActivity.java b/app/src/main/java/com/libremobileos/facedetect/MainActivity.java
index 6758225..6936272 100644
--- a/app/src/main/java/com/libremobileos/facedetect/MainActivity.java
+++ b/app/src/main/java/com/libremobileos/facedetect/MainActivity.java
@@ -17,7 +17,6 @@
 package com.libremobileos.facedetect;
 
 import android.app.AlertDialog;
-import android.content.DialogInterface;
 import android.graphics.Bitmap;
 import android.graphics.Bitmap.Config;
 import android.graphics.Color;
@@ -28,6 +27,7 @@
 import android.media.ImageReader.OnImageAvailableListener;
 import android.os.Bundle;
 import android.os.SystemClock;
+import android.util.Pair;
 import android.util.Size;
 import android.util.TypedValue;
 import android.view.LayoutInflater;
@@ -42,12 +42,13 @@
 import com.libremobileos.facedetect.util.Logger;
 import com.libremobileos.facedetect.util.MultiBoxTracker;
 import com.libremobileos.facedetect.util.OverlayView;
+import com.libremobileos.yifan.face.scan.FaceFinder;
 import com.libremobileos.yifan.face.scan.FaceScanner;
 import com.libremobileos.yifan.face.shared.FaceDetector;
 import com.libremobileos.yifan.face.shared.SimilarityClassifier;
 
+import java.util.ArrayList;
 import java.util.HashMap;
-import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 
@@ -64,8 +65,7 @@
   OverlayView trackingOverlay;
   private Integer sensorOrientation;
 
-  private FaceScanner detector;
-  private FaceScanner.InputImageProcessor inputImageProcessor;
+  private FaceFinder detector;
 
   private long lastProcessingTimeMs;
   private Bitmap rgbFrameBitmap = null;
@@ -77,10 +77,6 @@
 
   private MultiBoxTracker tracker;
 
-  // Face detector
-  private FaceDetector faceDetector;
-  private FaceDetector.InputImageProcessor detectorInputImageProcessor;
-
   private HashMap<String, float[][]> knownFaces = new HashMap<>();
 
   @Override
@@ -110,12 +106,7 @@
 
     tracker = new MultiBoxTracker(this);
 
-
-    detector /* face auth */  =
-            FaceScanner.create(this);
-
-    faceDetector /* find face */ =
-            FaceDetector.create(this);
+    detector = FaceFinder.create(this, previewWidth, previewHeight, sensorOrientation);
 
     previewWidth = size.getWidth();
     previewHeight = size.getHeight();
@@ -135,8 +126,6 @@
               }
             });
 
-    detectorInputImageProcessor = new FaceDetector.InputImageProcessor(previewWidth, previewHeight, sensorOrientation);
-    inputImageProcessor = new FaceScanner.InputImageProcessor(sensorOrientation);
     tracker.setFrameConfiguration(previewWidth, previewHeight, sensorOrientation);
   }
 
@@ -160,17 +149,9 @@
 
     readyForNextImage();
 
-    FaceDetector.InputImage croppedBitmap = detectorInputImageProcessor.process(rgbFrameBitmap);
-
-    // 匿名类
     runInBackground(
             () -> {
-              LOGGER.i("Running detection on image " + currTimestamp);
-              final long startTime = SystemClock.uptimeMillis();
-              final List<FaceDetector.Face> results = faceDetector.detectFaces(croppedBitmap);
-              lastProcessingTimeMs = SystemClock.uptimeMillis() - startTime;
-
-              onFacesDetected(currTimestamp, results, addPending);
+              onFacesDetected(currTimestamp, rgbFrameBitmap, addPending);
               addPending = false;
             });
   }
@@ -189,7 +170,6 @@
   protected void setUseNNAPI(final boolean isChecked) {
     runInBackground(() -> {
       detector.setUseNNAPI(isChecked);
-      faceDetector.setUseNNAPI(isChecked);
     });
   }
 
@@ -197,7 +177,6 @@
   protected void setNumThreads(final int numThreads) {
     runInBackground(() -> {
       detector.setNumThreads(numThreads);
-      faceDetector.setNumThreads(numThreads);
     });
   }
 
@@ -214,18 +193,14 @@
     ivFace.setImageBitmap(rec.getCrop());
     etName.setHint("Input name");
 
-    builder.setPositiveButton("OK", new DialogInterface.OnClickListener(){
-      @Override
-      public void onClick(DialogInterface dlg, int i) {
-
-          String name = etName.getText().toString();
-          if (name.isEmpty()) {
-              return;
-          }
-          //detector.register(name, rec);
-          knownFaces.put(name, rec.getExtra());
-          dlg.dismiss();
-      }
+    builder.setPositiveButton("OK", (dlg, i) -> {
+        String name = etName.getText().toString();
+        if (name.isEmpty()) {
+            return;
+        }
+        //detector.register(name, rec);
+        knownFaces.put(name, rec.getExtra());
+        dlg.dismiss();
     });
     builder.setView(dialogLayout);
     builder.show();
@@ -249,29 +224,28 @@
     }
 
     runOnUiThread(
-            new Runnable() {
-              @Override
-              public void run() {
-                showFrameInfo(previewWidth + "x" + previewHeight);
-                showInference(lastProcessingTimeMs + "ms");
-              }
+            () -> {
+              showFrameInfo(previewWidth + "x" + previewHeight);
+              showInference(lastProcessingTimeMs + "ms");
             });
 
   }
 
-  private void onFacesDetected(long currTimestamp, List<FaceDetector.Face> faces, boolean add) {
+  private void onFacesDetected(long currTimestamp, Bitmap rgbFrameBitmap, boolean add) {
     // TODO move this to yifan.face package to make this recycle-able code
-    final List<SimilarityClassifier.Recognition> mappedRecognitions =
-            new LinkedList<>();
 
-    Bitmap portraitImage = inputImageProcessor.transformRawImageToPortrait(rgbFrameBitmap);
+    List<SimilarityClassifier.Recognition> mappedRecognitions = new ArrayList<>();
 
-    for (FaceDetector.Face face : faces) {
+    Pair<List<Pair<FaceDetector.Face, FaceScanner.Face>>, Long> faces = detector.process(rgbFrameBitmap, add);
+    lastProcessingTimeMs = faces.second;
+
+    for (Pair<FaceDetector.Face, FaceScanner.Face> faceFacePair : faces.first) {
+
+      FaceDetector.Face face = faceFacePair.first;
+      FaceScanner.Face scanned = faceFacePair.second;
 
       LOGGER.i("FACE" + face.toString());
       LOGGER.i("Running detection on face " + currTimestamp);
-      FaceScanner.InputImage faceBmp = inputImageProcessor.process(rgbFrameBitmap, portraitImage, face.getLocation());
-      if (faceBmp == null) continue;
 
       final RectF boundingBox = new RectF(face.getLocation());
 
@@ -281,10 +255,6 @@
       float[][] extra = null;
       Bitmap crop = null;
 
-      final long startTime = SystemClock.uptimeMillis();
-      final FaceScanner.Face scanned = detector.detectFace(faceBmp, add);
-      lastProcessingTimeMs = SystemClock.uptimeMillis() - startTime;
-
       FaceScanner.Face detected = null;
       for (Map.Entry<String, float[][]> possible : knownFaces.entrySet()) {
         float newdistance = scanned.compare(possible.getValue());
@@ -293,7 +263,10 @@
         }
       }
 
-      if (detected != null) {
+      if (add) {
+        extra = scanned.getExtra();
+        crop = scanned.getCrop();
+      } else if (detected != null) {
         extra = detected.getExtra();
         distance = detected.getDistance();
         label = detected.getTitle();
@@ -303,9 +276,6 @@
         } else {
           color = Color.RED;
         }
-      } else if (add) {
-        extra = scanned.getExtra();
-        crop = scanned.getCrop();
       }
 
       if (getCameraFacing() == CameraCharacteristics.LENS_FACING_FRONT) {
diff --git a/app/src/main/java/com/libremobileos/facedetect/util/CameraActivity.java b/app/src/main/java/com/libremobileos/facedetect/util/CameraActivity.java
index 232f858..73608fe 100644
--- a/app/src/main/java/com/libremobileos/facedetect/util/CameraActivity.java
+++ b/app/src/main/java/com/libremobileos/facedetect/util/CameraActivity.java
@@ -86,7 +86,7 @@
   private LinearLayout gestureLayout;
   private BottomSheetBehavior<LinearLayout> sheetBehavior;
 
-  protected TextView frameValueTextView, cropValueTextView, inferenceTimeTextView;
+  protected TextView frameValueTextView, inferenceTimeTextView;
   protected ImageView bottomSheetArrowImageView;
   private ImageView plusImageView, minusImageView;
   private SwitchCompat apiSwitchCompat;
@@ -184,7 +184,6 @@
         });
 
     frameValueTextView = findViewById(R.id.frame_info);
-    cropValueTextView = findViewById(R.id.crop_info);
     inferenceTimeTextView = findViewById(R.id.inference_info);
 
     apiSwitchCompat.setOnCheckedChangeListener(this);
@@ -622,10 +621,6 @@
     frameValueTextView.setText(frameInfo);
   }
 
-  protected void showCropInfo(String cropInfo) {
-    cropValueTextView.setText(cropInfo);
-  }
-
   protected void showInference(String inferenceTime) {
     inferenceTimeTextView.setText(inferenceTime);
   }
diff --git a/app/src/main/res/layout/tfe_od_layout_bottom_sheet.xml b/app/src/main/res/layout/tfe_od_layout_bottom_sheet.xml
index 8b3a8c5..b7334f7 100644
--- a/app/src/main/res/layout/tfe_od_layout_bottom_sheet.xml
+++ b/app/src/main/res/layout/tfe_od_layout_bottom_sheet.xml
@@ -52,30 +52,6 @@
             android:textColor="@android:color/black" />
     </LinearLayout>
 
-    <LinearLayout
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:orientation="horizontal">
-
-        <TextView
-            android:id="@+id/crop"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_marginTop="10dp"
-            android:text="Crop"
-            android:textColor="@android:color/black" />
-
-        <TextView
-            android:id="@+id/crop_info"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_marginTop="10dp"
-            android:gravity="right"
-            android:text="640*480"
-            android:textColor="@android:color/black" />
-    </LinearLayout>
-
-
 
     <LinearLayout
         android:layout_width="match_parent"