Cleanup & bug fixes
diff --git a/FaceShared/src/main/java/com/libremobileos/yifan/face/AutoFitTextureView.java b/FaceShared/src/main/java/com/libremobileos/yifan/face/AutoFitTextureView.java
index 39ae130..39bd645 100644
--- a/FaceShared/src/main/java/com/libremobileos/yifan/face/AutoFitTextureView.java
+++ b/FaceShared/src/main/java/com/libremobileos/yifan/face/AutoFitTextureView.java
@@ -1,5 +1,6 @@
 /*
  * Copyright 2019 The TensorFlow Authors. All Rights Reserved.
+ * Copyright 2023 LibreMobileOS
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/FaceShared/src/main/java/com/libremobileos/yifan/face/DirectoryFaceStorageBackend.java b/FaceShared/src/main/java/com/libremobileos/yifan/face/DirectoryFaceStorageBackend.java
index 5a3d8d6..e5f3f4a 100644
--- a/FaceShared/src/main/java/com/libremobileos/yifan/face/DirectoryFaceStorageBackend.java
+++ b/FaceShared/src/main/java/com/libremobileos/yifan/face/DirectoryFaceStorageBackend.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright 2023 LibreMobileOS
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
 package com.libremobileos.yifan.face;
 
 import android.util.Log;
@@ -15,6 +31,9 @@
 import java.util.Objects;
 import java.util.Set;
 
+/**
+ * {@link FaceStorageBackend} to store data in a directory. Directory must not contain files other than these created by this class!
+ */
 public class DirectoryFaceStorageBackend extends FaceStorageBackend {
 	private final File dir;
 
diff --git a/FaceShared/src/main/java/com/libremobileos/yifan/face/FaceDataEncoder.java b/FaceShared/src/main/java/com/libremobileos/yifan/face/FaceDataEncoder.java
index 3bda40f..851d7bc 100644
--- a/FaceShared/src/main/java/com/libremobileos/yifan/face/FaceDataEncoder.java
+++ b/FaceShared/src/main/java/com/libremobileos/yifan/face/FaceDataEncoder.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright 2023 LibreMobileOS
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
 package com.libremobileos.yifan.face;
 
 import java.nio.ByteBuffer;
diff --git a/FaceShared/src/main/java/com/libremobileos/yifan/face/FaceDetector.java b/FaceShared/src/main/java/com/libremobileos/yifan/face/FaceDetector.java
index 8510f09..570c957 100644
--- a/FaceShared/src/main/java/com/libremobileos/yifan/face/FaceDetector.java
+++ b/FaceShared/src/main/java/com/libremobileos/yifan/face/FaceDetector.java
@@ -1,4 +1,4 @@
-/**
+/*
  * Copyright 2023 LibreMobileOS
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
@@ -93,7 +93,16 @@
 					ImageUtils.getTransformationMatrix(
 							inputWidth, inputHeight,
 							TF_FD_API_INPUT_SIZE, TF_FD_API_INPUT_SIZE,
-							sensorOrientation, MAINTAIN_ASPECT);
+							0, MAINTAIN_ASPECT);
+			if (sensorOrientation != 0) {
+				Matrix myRotationMatrix =
+						ImageUtils.getTransformationMatrix(
+								inputWidth, inputHeight,
+								sensorOrientation % 180 != 0 ? inputHeight : inputWidth,
+								sensorOrientation % 180 != 0 ? inputWidth : inputHeight,
+								sensorOrientation % 360, false);
+				frameToCropTransform.setConcat(frameToCropTransform, myRotationMatrix);
+			}
 			frameToCropTransform.invert(cropToFrameTransform);
 		}
 
diff --git a/FaceShared/src/main/java/com/libremobileos/yifan/face/FaceFinder.java b/FaceShared/src/main/java/com/libremobileos/yifan/face/FaceFinder.java
index ee6de41..6b2ba04 100644
--- a/FaceShared/src/main/java/com/libremobileos/yifan/face/FaceFinder.java
+++ b/FaceShared/src/main/java/com/libremobileos/yifan/face/FaceFinder.java
@@ -1,4 +1,4 @@
-/**
+/*
  * Copyright 2023 LibreMobileOS
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
diff --git a/FaceShared/src/main/java/com/libremobileos/yifan/face/FaceRecognizer.java b/FaceShared/src/main/java/com/libremobileos/yifan/face/FaceRecognizer.java
index 8206678..8fb0ae9 100644
--- a/FaceShared/src/main/java/com/libremobileos/yifan/face/FaceRecognizer.java
+++ b/FaceShared/src/main/java/com/libremobileos/yifan/face/FaceRecognizer.java
@@ -1,4 +1,4 @@
-/**
+/*
  * Copyright 2023 LibreMobileOS
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
diff --git a/FaceShared/src/main/java/com/libremobileos/yifan/face/FaceScanner.java b/FaceShared/src/main/java/com/libremobileos/yifan/face/FaceScanner.java
index 0483c88..66e31f7 100644
--- a/FaceShared/src/main/java/com/libremobileos/yifan/face/FaceScanner.java
+++ b/FaceShared/src/main/java/com/libremobileos/yifan/face/FaceScanner.java
@@ -1,4 +1,4 @@
-/**
+/*
  * Copyright 2023 LibreMobileOS
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
@@ -89,7 +89,6 @@
 	 * @see InputImage
 	 */
 	public static class InputImageProcessor {
-		private final int sensorOrientation;
 		private final Bitmap portraitBmp;
 		private final Matrix transform;
 
@@ -99,7 +98,6 @@
 		 * @param sensorOrientation rotation if the image should be rotated, or 0.
 		 */
 		public InputImageProcessor(Bitmap rawImage, int sensorOrientation) {
-			this.sensorOrientation = sensorOrientation;
 			Bitmap portraitBmp = Bitmap.createBitmap(
 					(sensorOrientation % 180) == 90 ? rawImage.getHeight() : rawImage.getWidth(),
 					(sensorOrientation % 180) == 90 ? rawImage.getWidth() : rawImage.getHeight(), Bitmap.Config.ARGB_8888);
@@ -108,8 +106,17 @@
 					rawImage.getHeight(),
 					rawImage.getWidth(),
 					rawImage.getHeight(),
-					sensorOrientation,
+					0,
 					MAINTAIN_ASPECT);
+			if (sensorOrientation != 0) {
+				Matrix myRotationMatrix =
+						ImageUtils.getTransformationMatrix(
+								rawImage.getWidth(), rawImage.getHeight(),
+								sensorOrientation % 180 != 0 ? rawImage.getHeight() : rawImage.getWidth(),
+								sensorOrientation % 180 != 0 ? rawImage.getWidth() : rawImage.getHeight(),
+								sensorOrientation % 360, false);
+				transform.setConcat(myRotationMatrix, transform);
+			}
 			final Canvas cv = new Canvas(portraitBmp);
 			cv.drawBitmap(rawImage, transform, null);
 			this.portraitBmp = portraitBmp;
@@ -126,7 +133,16 @@
 					ImageUtils.getTransformationMatrix(
 							input.getWidth(), input.getHeight(),
 							TF_OD_API_INPUT_SIZE, TF_OD_API_INPUT_SIZE,
-							sensorOrientation, MAINTAIN_ASPECT);
+							0, MAINTAIN_ASPECT);
+			if (sensorOrientation != 0) {
+				Matrix myRotationMatrix =
+						ImageUtils.getTransformationMatrix(
+								input.getWidth(), input.getHeight(),
+								sensorOrientation % 180 != 0 ? input.getHeight() : input.getWidth(),
+								sensorOrientation % 180 != 0 ? input.getWidth() : input.getHeight(),
+								sensorOrientation % 360, false);
+				frameToCropTransform.setConcat(frameToCropTransform, myRotationMatrix);
+			}
 			Bitmap croppedBitmap = Bitmap.createBitmap(TF_OD_API_INPUT_SIZE, TF_OD_API_INPUT_SIZE, Bitmap.Config.ARGB_8888);
 			final Canvas canvas = new Canvas(croppedBitmap);
 			canvas.drawBitmap(input, frameToCropTransform, null);
@@ -141,7 +157,7 @@
 		 * @see #process(Bitmap, int)
 		 */
 		public InputImage process(Bitmap input) {
-			return process(input, sensorOrientation);
+			return process(input, 0);
 		}
 
 		/**
diff --git a/FaceShared/src/main/java/com/libremobileos/yifan/face/FaceStorageBackend.java b/FaceShared/src/main/java/com/libremobileos/yifan/face/FaceStorageBackend.java
index 4121eec..7551861 100644
--- a/FaceShared/src/main/java/com/libremobileos/yifan/face/FaceStorageBackend.java
+++ b/FaceShared/src/main/java/com/libremobileos/yifan/face/FaceStorageBackend.java
@@ -1,4 +1,4 @@
-/**
+/*
  * Copyright 2023 LibreMobileOS
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
@@ -18,8 +18,6 @@
 
 import androidx.annotation.Nullable;
 
-import java.nio.ByteBuffer;
-import java.nio.FloatBuffer;
 import java.nio.charset.StandardCharsets;
 import java.util.Base64;
 import java.util.HashMap;
diff --git a/FaceShared/src/main/java/com/libremobileos/yifan/face/ImageUtils.java b/FaceShared/src/main/java/com/libremobileos/yifan/face/ImageUtils.java
index eb7e561..526d058 100644
--- a/FaceShared/src/main/java/com/libremobileos/yifan/face/ImageUtils.java
+++ b/FaceShared/src/main/java/com/libremobileos/yifan/face/ImageUtils.java
@@ -1,4 +1,4 @@
-/**
+/*
  * Copyright 2023 LibreMobileOS
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
@@ -89,7 +89,7 @@
 
   private static int YUV2RGB(int y, int u, int v) {
     // Adjust and check YUV values
-    y = (y - 16) < 0 ? 0 : (y - 16);
+    y = Math.max((y - 16), 0);
     u -= 128;
     v -= 128;
 
@@ -104,9 +104,9 @@
     int b = (y1192 + 2066 * u);
 
     // Clipping RGB values to be inside boundaries [ 0 , kMaxChannelValue ]
-    r = r > kMaxChannelValue ? kMaxChannelValue : (r < 0 ? 0 : r);
-    g = g > kMaxChannelValue ? kMaxChannelValue : (g < 0 ? 0 : g);
-    b = b > kMaxChannelValue ? kMaxChannelValue : (b < 0 ? 0 : b);
+    r = r > kMaxChannelValue ? kMaxChannelValue : (Math.max(r, 0));
+    g = g > kMaxChannelValue ? kMaxChannelValue : (Math.max(g, 0));
+    b = b > kMaxChannelValue ? kMaxChannelValue : (Math.max(b, 0));
 
     return 0xff000000 | ((r << 6) & 0xff0000) | ((g >> 2) & 0xff00) | ((b >> 10) & 0xff);
   }
diff --git a/FaceShared/src/main/java/com/libremobileos/yifan/face/SharedPreferencesFaceStorageBackend.java b/FaceShared/src/main/java/com/libremobileos/yifan/face/SharedPreferencesFaceStorageBackend.java
index f55d113..df89724 100644
--- a/FaceShared/src/main/java/com/libremobileos/yifan/face/SharedPreferencesFaceStorageBackend.java
+++ b/FaceShared/src/main/java/com/libremobileos/yifan/face/SharedPreferencesFaceStorageBackend.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright 2023 LibreMobileOS
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
 package com.libremobileos.yifan.face;
 
 import android.content.SharedPreferences;
diff --git a/FaceShared/src/main/java/com/libremobileos/yifan/face/SimilarityClassifier.java b/FaceShared/src/main/java/com/libremobileos/yifan/face/SimilarityClassifier.java
index ee1676f..1c07077 100644
--- a/FaceShared/src/main/java/com/libremobileos/yifan/face/SimilarityClassifier.java
+++ b/FaceShared/src/main/java/com/libremobileos/yifan/face/SimilarityClassifier.java
@@ -1,4 +1,4 @@
-/**
+/*
  * Copyright 2019 The TensorFlow Authors
  * Copyright 2023 LibreMobileOS
  *
diff --git a/FaceShared/src/main/java/com/libremobileos/yifan/face/TFLiteObjectDetectionAPIModel.java b/FaceShared/src/main/java/com/libremobileos/yifan/face/TFLiteObjectDetectionAPIModel.java
index a24a5b7..dee6d53 100644
--- a/FaceShared/src/main/java/com/libremobileos/yifan/face/TFLiteObjectDetectionAPIModel.java
+++ b/FaceShared/src/main/java/com/libremobileos/yifan/face/TFLiteObjectDetectionAPIModel.java
@@ -1,4 +1,4 @@
-/**
+/*
  * Copyright 2019 The TensorFlow Authors
  * Copyright 2023 LibreMobileOS
  *
diff --git a/FaceShared/src/main/java/com/libremobileos/yifan/face/VolatileFaceStorageBackend.java b/FaceShared/src/main/java/com/libremobileos/yifan/face/VolatileFaceStorageBackend.java
index 5cc24ee..502ad2b 100644
--- a/FaceShared/src/main/java/com/libremobileos/yifan/face/VolatileFaceStorageBackend.java
+++ b/FaceShared/src/main/java/com/libremobileos/yifan/face/VolatileFaceStorageBackend.java
@@ -1,4 +1,4 @@
-/**
+/*
  * Copyright 2023 LibreMobileOS
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index ca591be..f5c0732 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -3,6 +3,10 @@
 	xmlns:tools="http://schemas.android.com/tools"
     android:sharedUserId="android.uid.system">
 
+	<uses-feature
+		android:name="android.hardware.camera"
+		android:required="true" />
+
 	<uses-permission android:name="android.permission.CAMERA" />
 
 	<application
diff --git a/app/src/main/java/com/libremobileos/facedetect/BitmapUtils.java b/app/src/main/java/com/libremobileos/facedetect/BitmapUtils.java
deleted file mode 100644
index 707ca20..0000000
--- a/app/src/main/java/com/libremobileos/facedetect/BitmapUtils.java
+++ /dev/null
@@ -1,238 +0,0 @@
-/**
- * Copyright 2020 Google LLC. All rights reserved.
- *
- * 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.
- *
- * File imported without required modifications, only fixing IDE warnings.
- * Source: https://github.com/googlesamples/mlkit/blob/d10c447f8259b59262582c30c1608cdf38f4e4a0/android/vision-quickstart/app/src/main/java/com/google/mlkit/vision/demo/BitmapUtils.java
- */
-
-package com.libremobileos.facedetect;
-
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.ImageFormat;
-import android.graphics.Matrix;
-import android.graphics.Rect;
-import android.graphics.YuvImage;
-import android.media.Image;
-import android.media.Image.Plane;
-import android.util.Log;
-import androidx.annotation.Nullable;
-
-import java.io.ByteArrayOutputStream;
-import java.nio.ByteBuffer;
-import java.util.Objects;
-
-/** Utils functions for bitmap conversions. */
-public class BitmapUtils {
-	/** Describing a frame info. */
-	public static class FrameMetadata {
-
-		private final int width;
-		private final int height;
-		private final int rotation;
-
-		public int getWidth() {
-			return width;
-		}
-
-		public int getHeight() {
-			return height;
-		}
-
-		public int getRotation() {
-			return rotation;
-		}
-
-		private FrameMetadata(int width, int height, int rotation) {
-			this.width = width;
-			this.height = height;
-			this.rotation = rotation;
-		}
-
-		/** Builder of {@link FrameMetadata}. */
-		public static class Builder {
-
-			private int width;
-			private int height;
-			private int rotation;
-
-			public Builder setWidth(int width) {
-				this.width = width;
-				return this;
-			}
-
-			public Builder setHeight(int height) {
-				this.height = height;
-				return this;
-			}
-
-			public Builder setRotation(int rotation) {
-				this.rotation = rotation;
-				return this;
-			}
-
-			public FrameMetadata build() {
-				return new FrameMetadata(width, height, rotation);
-			}
-		}
-	}
-
-	/** Converts NV21 format byte buffer to bitmap. */
-	@Nullable
-	public static Bitmap getBitmap(ByteBuffer data, FrameMetadata metadata) {
-		data.rewind();
-		byte[] imageInBuffer = new byte[data.limit()];
-		data.get(imageInBuffer, 0, imageInBuffer.length);
-		try {
-			YuvImage image =
-					new YuvImage(
-							imageInBuffer, ImageFormat.NV21, metadata.getWidth(), metadata.getHeight(), null);
-			ByteArrayOutputStream stream = new ByteArrayOutputStream();
-			image.compressToJpeg(new Rect(0, 0, metadata.getWidth(), metadata.getHeight()), 80, stream);
-
-			Bitmap bmp = BitmapFactory.decodeByteArray(stream.toByteArray(), 0, stream.size());
-
-			stream.close();
-			return rotateBitmap(bmp, metadata.getRotation());
-		} catch (Exception e) {
-			Log.e("VisionProcessorBase", "Error: " + e.getMessage());
-		}
-		return null;
-	}
-
-	/** Rotates a bitmap if it is converted from a bytebuffer. */
-	private static Bitmap rotateBitmap(
-			Bitmap bitmap, int rotationDegrees) {
-		Matrix matrix = new Matrix();
-
-		// Rotate the image back to straight.
-		matrix.postRotate(rotationDegrees);
-
-		Bitmap rotatedBitmap =
-				Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
-
-		// Recycle the old bitmap if it has changed.
-		if (rotatedBitmap != bitmap) {
-			bitmap.recycle();
-		}
-		return rotatedBitmap;
-	}
-
-	/**
-	 * Converts YUV_420_888 to NV21 bytebuffer.
-	 *
-	 * <p>The NV21 format consists of a single byte array containing the Y, U and V values. For an
-	 * image of size S, the first S positions of the array contain all the Y values. The remaining
-	 * positions contain interleaved V and U values. U and V are subsampled by a factor of 2 in both
-	 * dimensions, so there are S/4 U values and S/4 V values. In summary, the NV21 array will contain
-	 * S Y values followed by S/4 VU values: YYYYYYYYYYYYYY(...)YVUVUVUVU(...)VU
-	 *
-	 * <p>YUV_420_888 is a generic format that can describe any YUV image where U and V are subsampled
-	 * by a factor of 2 in both dimensions. {@link Image#getPlanes} returns an array with the Y, U and
-	 * V planes. The Y plane is guaranteed not to be interleaved, so we can just copy its values into
-	 * the first part of the NV21 array. The U and V planes may already have the representation in the
-	 * NV21 format. This happens if the planes share the same buffer, the V buffer is one position
-	 * before the U buffer and the planes have a pixelStride of 2. If this is case, we can just copy
-	 * them to the NV21 array.
-	 */
-	private static ByteBuffer yuv420ThreePlanesToNV21(
-			Plane[] yuv420888planes, int width, int height) {
-		int imageSize = width * height;
-		byte[] out = new byte[imageSize + 2 * (imageSize / 4)];
-
-		if (areUVPlanesNV21(yuv420888planes, width, height)) {
-			// Copy the Y values.
-			yuv420888planes[0].getBuffer().get(out, 0, imageSize);
-
-			ByteBuffer uBuffer = yuv420888planes[1].getBuffer();
-			ByteBuffer vBuffer = yuv420888planes[2].getBuffer();
-			// Get the first V value from the V buffer, since the U buffer does not contain it.
-			vBuffer.get(out, imageSize, 1);
-			// Copy the first U value and the remaining VU values from the U buffer.
-			uBuffer.get(out, imageSize + 1, 2 * imageSize / 4 - 1);
-		} else {
-			// Fallback to copying the UV values one by one, which is slower but also works.
-			// Unpack Y.
-			unpackPlane(yuv420888planes[0], width, height, out, 0, 1);
-			// Unpack U.
-			unpackPlane(yuv420888planes[1], width, height, out, imageSize + 1, 2);
-			// Unpack V.
-			unpackPlane(yuv420888planes[2], width, height, out, imageSize, 2);
-		}
-
-		return ByteBuffer.wrap(out);
-	}
-
-	/** Checks if the UV plane buffers of a YUV_420_888 image are in the NV21 format. */
-	private static boolean areUVPlanesNV21(Plane[] planes, int width, int height) {
-		int imageSize = width * height;
-
-		ByteBuffer uBuffer = planes[1].getBuffer();
-		ByteBuffer vBuffer = planes[2].getBuffer();
-
-		// Backup buffer properties.
-		int vBufferPosition = vBuffer.position();
-		int uBufferLimit = uBuffer.limit();
-
-		// Advance the V buffer by 1 byte, since the U buffer will not contain the first V value.
-		vBuffer.position(vBufferPosition + 1);
-		// Chop off the last byte of the U buffer, since the V buffer will not contain the last U value.
-		uBuffer.limit(uBufferLimit - 1);
-
-		// Check that the buffers are equal and have the expected number of elements.
-		boolean areNV21 =
-				(vBuffer.remaining() == (2 * imageSize / 4 - 2)) && (vBuffer.compareTo(uBuffer) == 0);
-
-		// Restore buffers to their initial state.
-		vBuffer.position(vBufferPosition);
-		uBuffer.limit(uBufferLimit);
-
-		return areNV21;
-	}
-
-	/**
-	 * Unpack an image plane into a byte array.
-	 *
-	 * <p>The input plane data will be copied in 'out', starting at 'offset' and every pixel will be
-	 * spaced by 'pixelStride'. Note that there is no row padding on the output.
-	 */
-	private static void unpackPlane(
-			Plane plane, int width, int height, byte[] out, int offset, int pixelStride) {
-		ByteBuffer buffer = plane.getBuffer();
-		buffer.rewind();
-
-		// Compute the size of the current plane.
-		// We assume that it has the aspect ratio as the original image.
-		int numRow = (buffer.limit() + plane.getRowStride() - 1) / plane.getRowStride();
-		if (numRow == 0) {
-			return;
-		}
-		int scaleFactor = height / numRow;
-		int numCol = width / scaleFactor;
-
-		// Extract the data in the output buffer.
-		int outputPos = offset;
-		int rowStart = 0;
-		for (int row = 0; row < numRow; row++) {
-			int inputPos = rowStart;
-			for (int col = 0; col < numCol; col++) {
-				out[outputPos] = buffer.get(inputPos);
-				outputPos += pixelStride;
-				inputPos += plane.getPixelStride();
-			}
-			rowStart += plane.getRowStride();
-		}
-	}
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/libremobileos/facedetect/CameraActivity.java b/app/src/main/java/com/libremobileos/facedetect/CameraActivity.java
index c208b62..8d12a61 100644
--- a/app/src/main/java/com/libremobileos/facedetect/CameraActivity.java
+++ b/app/src/main/java/com/libremobileos/facedetect/CameraActivity.java
@@ -5,7 +5,6 @@
 import android.content.pm.PackageManager;
 import android.content.res.Configuration;
 import android.graphics.Bitmap;
-import android.graphics.Canvas;
 import android.graphics.ImageFormat;
 import android.graphics.Matrix;
 import android.graphics.RectF;
@@ -54,32 +53,25 @@
 	 */
 	private static final int MINIMUM_PREVIEW_SIZE = 320;
 
-	protected AutoFitTextureView previewView;
+	private AutoFitTextureView previewView;
 	private Handler mBackgroundHandler;
 	private HandlerThread mBackgroundThread;
-	private String cameraId;
-	protected CameraDevice cameraDevice;
-	protected CameraCaptureSession cameraCaptureSessions;
-	protected CaptureRequest captureRequest;
-	protected CaptureRequest.Builder captureRequestBuilder;
+	private CameraDevice cameraDevice;
+	private CameraCaptureSession cameraCaptureSessions;
+	private CaptureRequest captureRequest;
+	private CaptureRequest.Builder captureRequestBuilder;
 	private ImageReader previewReader;
-	private byte[][] yuvBytes = new byte[3][];
+	private final byte[][] yuvBytes = new byte[3][];
 	private int[] rgbBytes = null;
 	private boolean isProcessingFrame = false;
 	private int yRowStride;
 	private Runnable postInferenceCallback;
 	private Runnable imageConverter;
-	private Integer sensorOrientation;
 	private Bitmap rgbFrameBitmap = null;
-	private Bitmap croppedBitmap = null;
-	private Bitmap cropCopyBitmap = null;
-	private Matrix frameToCropTransform;
-	private Matrix cropToFrameTransform;
-	private static final boolean MAINTAIN_ASPECT = false;
-
+	private int imageOrientation;
+	private Size previewSize;
 
 	protected final Size desiredInputSize = new Size(640, 480);
-	private Size previewSize;
 	// The calculated actual processing width & height
 	protected int width, height;
 
@@ -94,7 +86,7 @@
 		previewView.setSurfaceTextureListener(textureListener);
 	}
 
-	TextureView.SurfaceTextureListener textureListener = new TextureView.SurfaceTextureListener() {
+	private final TextureView.SurfaceTextureListener textureListener = new TextureView.SurfaceTextureListener() {
 		@Override
 		public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
 			//open your camera here
@@ -137,13 +129,13 @@
 		}
 	};
 
-	protected void startBackgroundThread() {
+	private void startBackgroundThread() {
 		mBackgroundThread = new HandlerThread("Camera Background");
 		mBackgroundThread.start();
 		mBackgroundHandler = new Handler(mBackgroundThread.getLooper());
 	}
 
-	protected void stopBackgroundThread() {
+	private void stopBackgroundThread() {
 		mBackgroundThread.quitSafely();
 		try {
 			mBackgroundThread.join();
@@ -154,7 +146,7 @@
 		}
 	}
 
-	protected void createCameraPreview() {
+	private void createCameraPreview() {
 		try {
 			SurfaceTexture texture = previewView.getSurfaceTexture();
 			assert texture != null;
@@ -242,7 +234,7 @@
 	}
 
 	/** Compares two {@code Size}s based on their areas. */
-	static class CompareSizesByArea implements Comparator<Size> {
+	private static class CompareSizesByArea implements Comparator<Size> {
 		@Override
 		public int compare(final Size lhs, final Size rhs) {
 			// We cast here to ensure the multiplications won't overflow
@@ -260,14 +252,14 @@
 	 * @param height The minimum desired height
 	 * @return The optimal {@code Size}, or an arbitrary one if none were big enough
 	 */
-	protected static Size chooseOptimalSize(final Size[] choices, final int width, final int height) {
+	private static Size chooseOptimalSize(final Size[] choices, final int width, final int height) {
 		final int minSize = Math.max(Math.min(width, height), MINIMUM_PREVIEW_SIZE);
 		final Size desiredSize = new Size(width, height);
 
 		// Collect the supported resolutions that are at least as big as the preview Surface
 		boolean exactSizeFound = false;
-		final List<Size> bigEnough = new ArrayList<Size>();
-		final List<Size> tooSmall = new ArrayList<Size>();
+		final List<Size> bigEnough = new ArrayList<>();
+		final List<Size> tooSmall = new ArrayList<>();
 		for (final Size option : choices) {
 			if (option.equals(desiredSize)) {
 				// Set the size but don't return yet so that remaining sizes will still be logged.
@@ -305,7 +297,7 @@
 		CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
 		Log.e(TAG, "is camera open");
 		try {
-			cameraId = manager.getCameraIdList()[0];
+			String cameraId = manager.getCameraIdList()[0];
 			for (String id : manager.getCameraIdList()) {
 				CameraCharacteristics characteristics = manager.getCameraCharacteristics(id);
 				if (characteristics.get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_FRONT) {
@@ -315,7 +307,7 @@
 			}
 			CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId);
 			StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
-			sensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
+			Integer sensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
 
 			assert map != null;
 
@@ -337,31 +329,10 @@
 				previewView.setAspectRatio(previewSize.getHeight(), previewSize.getWidth());
 			}
 
-			int imageOrientation = sensorOrientation + getScreenOrientation();
+			imageOrientation = sensorOrientation + getScreenOrientation();
 			rgbFrameBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
-			int targetW, targetH;
-			if (imageOrientation == 90 || imageOrientation == 270) {
-				targetH = width;
-				targetW = height;
-			}
-			else {
-				targetW = width;
-				targetH = height;
-			}
-			int cropW = (int) (targetW / 2.0);
-			int cropH = (int) (targetH / 2.0);
 
-			croppedBitmap = Bitmap.createBitmap(cropW, cropH, Bitmap.Config.ARGB_8888);
-
-			frameToCropTransform =
-					ImageUtils.getTransformationMatrix(
-							width, height,
-							cropW, cropH,
-							imageOrientation, MAINTAIN_ASPECT);
-			cropToFrameTransform = new Matrix();
-			frameToCropTransform.invert(cropToFrameTransform);
-
-			setupFaceRecognizer(new Size(cropW, cropH));
+			setupFaceRecognizer(new Size(width, height), imageOrientation);
 
 			// Add permission for camera and let user grant the permission
 			if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
@@ -375,6 +346,10 @@
 		Log.e(TAG, "openCamera X");
 	}
 
+	protected int getImageRotation() {
+		return imageOrientation;
+	}
+
 	private void closeCamera() {
 		if (null != cameraDevice) {
 			cameraDevice.close();
@@ -401,7 +376,7 @@
 		super.onPause();
 	}
 
-	protected void fillBytes(final Image.Plane[] planes, final byte[][] yuvBytes) {
+	private void fillBytes(final Image.Plane[] planes, final byte[][] yuvBytes) {
 		// Because of the variable row stride it's not possible to know in
 		// advance the actual necessary dimensions of the yuv planes.
 		for (int i = 0; i < planes.length; ++i) {
@@ -414,16 +389,16 @@
 		}
 	}
 
-	protected int[] getRgbBytes() {
+	private int[] getRgbBytes() {
 		imageConverter.run();
 		return rgbBytes;
 	}
 
-	protected Bitmap getCroppedBitmap() {
-		return croppedBitmap;
+	protected Bitmap getBitmap() {
+		return rgbFrameBitmap;
 	}
 
-	protected int getScreenOrientation() {
+	private int getScreenOrientation() {
 		switch (getWindowManager().getDefaultDisplay().getRotation()) {
 			case Surface.ROTATION_270:
 				return 270;
@@ -470,36 +445,26 @@
 			final int uvPixelStride = planes[1].getPixelStride();
 
 			imageConverter =
-					new Runnable() {
-						@Override
-						public void run() {
-							ImageUtils.convertYUV420ToARGB8888(
-									yuvBytes[0],
-									yuvBytes[1],
-									yuvBytes[2],
-									previewWidth,
-									previewHeight,
-									yRowStride,
-									uvRowStride,
-									uvPixelStride,
-									rgbBytes);
-						}
-					};
+					() -> ImageUtils.convertYUV420ToARGB8888(
+							yuvBytes[0],
+							yuvBytes[1],
+							yuvBytes[2],
+							previewWidth,
+							previewHeight,
+							yRowStride,
+							uvRowStride,
+							uvPixelStride,
+							rgbBytes);
 
 			postInferenceCallback =
-					new Runnable() {
-						@Override
-						public void run() {
-							image.close();
-							isProcessingFrame = false;
-						}
+					() -> {
+						image.close();
+						isProcessingFrame = false;
 					};
 
 			rgbFrameBitmap.setPixels(getRgbBytes(), 0, previewWidth, 0, 0, previewWidth, previewHeight);
-			final Canvas canvas = new Canvas(croppedBitmap);
-			canvas.drawBitmap(rgbFrameBitmap, frameToCropTransform, null);
 
-			runOnUiThread(() -> processImage());
+			runOnUiThread(this::processImage);
 		} catch (final Exception e) {
 			Log.e(TAG, "Exception!", e);
 			Trace.endSection();
@@ -508,7 +473,7 @@
 		Trace.endSection();
 	}
 
-	protected abstract void setupFaceRecognizer(final Size bitmapSize);
+	protected abstract void setupFaceRecognizer(final Size bitmapSize, final int imageRotation);
 
 	protected abstract void processImage();
 
diff --git a/app/src/main/java/com/libremobileos/facedetect/CircleOverlayView.java b/app/src/main/java/com/libremobileos/facedetect/CircleOverlayView.java
index 6ee7a23..f46bfad 100644
--- a/app/src/main/java/com/libremobileos/facedetect/CircleOverlayView.java
+++ b/app/src/main/java/com/libremobileos/facedetect/CircleOverlayView.java
@@ -1,4 +1,4 @@
-/**
+/*
  * Copyright 2023 LibreMobileOS
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
diff --git a/app/src/main/java/com/libremobileos/facedetect/FaceBoundsOverlayView.java b/app/src/main/java/com/libremobileos/facedetect/FaceBoundsOverlayView.java
index ba62ab6..db28189 100644
--- a/app/src/main/java/com/libremobileos/facedetect/FaceBoundsOverlayView.java
+++ b/app/src/main/java/com/libremobileos/facedetect/FaceBoundsOverlayView.java
@@ -1,4 +1,4 @@
-/**
+/*
  * Copyright 2023 LibreMobileOS
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
@@ -103,10 +103,10 @@
 			int newWidth = (int)((oldHeight / (float)sensorHeight) * sensorWidth);
 			// calculate out black bars
 			if (newWidth > oldWidth) {
-				extraHeight = (oldHeight - newHeight) / 2;
+				//extraHeight = (oldHeight - newHeight) / 2;
 				viewHeight = newHeight;
 			} else {
-				extraWidth = (oldWidth - newWidth) / 2;
+				//extraWidth = (oldWidth - newWidth) / 2;
 				viewWidth = newWidth;
 			}
 			// scale from image size to view size
@@ -115,8 +115,10 @@
 		}
 		// map bounds to view size
 		for (Pair<RectF, String> bound : bounds) {
+			android.util.Log.i("got0", String.valueOf(bound.first));
 			transform.mapRect(bound.first);
 			bound.first.offset(extraWidth, extraHeight);
+			android.util.Log.i("got", String.valueOf(bound.first));
 		}
 		invalidate();
 	}
diff --git a/app/src/main/java/com/libremobileos/facedetect/MainActivity.java b/app/src/main/java/com/libremobileos/facedetect/MainActivity.java
index cc532b9..fdaaa12 100644
--- a/app/src/main/java/com/libremobileos/facedetect/MainActivity.java
+++ b/app/src/main/java/com/libremobileos/facedetect/MainActivity.java
@@ -1,4 +1,4 @@
-/**
+/*
  * Copyright 2023 LibreMobileOS
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
@@ -54,10 +54,10 @@
 	}
 
 	@Override
-	protected void setupFaceRecognizer(final Size bitmapSize) {
-		// Store registered Faces in Memory
-		//FaceStorageBackend faceStorage = new VolatileFaceStorageBackend();
-		//FaceStorageBackend faceStorage = new SharedPreferencesFaceStorageBackend(getSharedPreferences("faces", 0));
+	protected void setupFaceRecognizer(final Size bitmapSize, final int imageRotation) {
+		// Store registered Faces
+		// example for in-memory: FaceStorageBackend faceStorage = new VolatileFaceStorageBackend();
+		// example for shared preferences: FaceStorageBackend faceStorage = new SharedPreferencesFaceStorageBackend(getSharedPreferences("faces", 0));
 		FaceStorageBackend faceStorage = new DirectoryFaceStorageBackend(getFilesDir());
 
 		// Create AI-based face detection
@@ -66,7 +66,7 @@
 				0.6f, /* minimum confidence to consider object as face */
 				bitmapSize.getWidth(), /* bitmap width */
 				bitmapSize.getHeight(), /* bitmap height */
-				0, /* We rotates the image, so IGNORE sensorRotation altogether */
+				imageRotation,
 				0.7f, /* maximum distance (to saved face model, not from camera) to track face */
 				1 /* minimum model count to track face */
 		);
@@ -80,7 +80,7 @@
 			return;
 		}
 		computingDetection = true;
-		List<FaceRecognizer.Face> data = faceRecognizer.recognize(getCroppedBitmap());
+		List<FaceRecognizer.Face> data = faceRecognizer.recognize(getBitmap());
 		computingDetection = false;
 
 		ArrayList<Pair<RectF, String>> bounds = new ArrayList<>();
@@ -91,7 +91,9 @@
 			// Camera is frontal so the image is flipped horizontally,
 			// so flip it again.
 			Matrix flip = new Matrix();
-			flip.postScale(-1, 1, width / 2.0f, height / 2.0f);
+			flip.postRotate((360 - getImageRotation()) % 360); // Preview is rotated 360 - cameraRotation degrees
+			flip.postScale(-1, 1, width, height);
+			android.util.Log.i("got00", String.valueOf(boundingBox) + " "+ width + " "+height);
 			flip.mapRect(boundingBox);
 
 			// Generate UI text for face
@@ -104,7 +106,7 @@
 				// Show detected object type (always "Face") and how confident the AI is that this is a Face
 				uiText = face.getTitle() + " " + face.getDetectionConfidence();
 			}
-			bounds.add(new Pair<>(boundingBox, uiText));
+			bounds.add(new Pair<>(new RectF(0, 0, width, height), uiText));
 		}
 
 		// Pass bounds to View drawing rectangles
diff --git a/app/src/main/java/com/libremobileos/facedetect/ScanActivity.java b/app/src/main/java/com/libremobileos/facedetect/ScanActivity.java
index cf5811b..6d58e0d 100644
--- a/app/src/main/java/com/libremobileos/facedetect/ScanActivity.java
+++ b/app/src/main/java/com/libremobileos/facedetect/ScanActivity.java
@@ -1,4 +1,4 @@
-/**
+/*
  * Copyright 2023 LibreMobileOS
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
@@ -66,13 +66,13 @@
 	}
 
 	@Override
-	protected void setupFaceRecognizer(final Size bitmapSize) {
+	protected void setupFaceRecognizer(final Size bitmapSize, final int imageRotation) {
 		// Create AI-based face detection
 		faceRecognizer = FaceFinder.create(this,
 				0.6f, /* minimum confidence to consider object as face */
 				bitmapSize.getWidth(), /* bitmap width */
 				bitmapSize.getHeight(), /* bitmap height */
-				0 /* We rotates the image, so IGNORE sensorRotation altogether */
+				imageRotation
 		);
 	}
 
@@ -91,7 +91,7 @@
 		}
 
 		// Return list of detected faces
-		List<Pair<FaceDetector.Face, FaceScanner.Face>> data = faceRecognizer.process(getCroppedBitmap(), false);
+		List<Pair<FaceDetector.Face, FaceScanner.Face>> data = faceRecognizer.process(getBitmap(), false);
 		computingDetection = false;
 
 		if (data.size() > 1) {
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 0b1191c..7081895 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -9,7 +9,7 @@
 	<string name="cant_find_face">Unable to find any face</string>
 	<string name="cant_scan_face">Can\'t properly scan your face, try to look into the camera directly and make sure your face is well-lit</string>
 	<string name="scan_face_now">Scan your face now</string>
-	<string name="register_failed">Registering your face has failed. We are sorry for the inconvience. Please try again later.</string>
+	<string name="register_failed">Registering your face has failed. We are sorry for the inconvenience. Please try again later.</string>
 	<string name="welcome_text">**welcome text placeholder uwu**</string>
 	<string name="finish_msg">**finish text placeholder uwu**</string>
 </resources>
\ No newline at end of file