[camera] android-rework part 9: Final implementation of camera class (#4059)

This PR adds the final implementation for the Camera class that incorporates all the features from previous parts.
diff --git a/packages/camera/camera/CHANGELOG.md b/packages/camera/camera/CHANGELOG.md
index 6948980..68188d6 100644
--- a/packages/camera/camera/CHANGELOG.md
+++ b/packages/camera/camera/CHANGELOG.md
@@ -1,5 +1,8 @@
-## NEXT
+## 0.9.0
 
+* Complete rewrite of Android plugin to fix many capture, focus, flash, orientation and exposure issues.
+* Fixed crash when opening front-facing cameras on some legacy android devices like Sony XZ.
+* Android Flash mode works with full precapture sequence.
 * Updated Android lint settings.
 
 ## 0.8.1+7
diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java
index 4c1370f..4724d22 100644
--- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java
+++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java
@@ -4,27 +4,19 @@
 
 package io.flutter.plugins.camera;
 
-import static io.flutter.plugins.camera.CameraUtils.computeBestPreviewSize;
-
 import android.annotation.SuppressLint;
 import android.annotation.TargetApi;
 import android.app.Activity;
 import android.content.Context;
 import android.graphics.ImageFormat;
-import android.graphics.Rect;
 import android.graphics.SurfaceTexture;
 import android.hardware.camera2.CameraAccessException;
 import android.hardware.camera2.CameraCaptureSession;
-import android.hardware.camera2.CameraCaptureSession.CaptureCallback;
-import android.hardware.camera2.CameraCharacteristics;
 import android.hardware.camera2.CameraDevice;
 import android.hardware.camera2.CameraManager;
 import android.hardware.camera2.CameraMetadata;
-import android.hardware.camera2.CaptureFailure;
 import android.hardware.camera2.CaptureRequest;
-import android.hardware.camera2.CaptureResult;
 import android.hardware.camera2.TotalCaptureResult;
-import android.hardware.camera2.params.MeteringRectangle;
 import android.hardware.camera2.params.OutputConfiguration;
 import android.hardware.camera2.params.SessionConfiguration;
 import android.media.CamcorderProfile;
@@ -35,27 +27,43 @@
 import android.os.Build.VERSION;
 import android.os.Build.VERSION_CODES;
 import android.os.Handler;
+import android.os.HandlerThread;
 import android.os.Looper;
-import android.os.SystemClock;
 import android.util.Log;
-import android.util.Range;
-import android.util.Rational;
 import android.util.Size;
+import android.view.Display;
 import android.view.Surface;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleObserver;
+import androidx.lifecycle.OnLifecycleEvent;
 import io.flutter.embedding.engine.systemchannels.PlatformChannel;
 import io.flutter.plugin.common.EventChannel;
+import io.flutter.plugin.common.MethodChannel;
 import io.flutter.plugin.common.MethodChannel.Result;
-import io.flutter.plugins.camera.PictureCaptureRequest.State;
+import io.flutter.plugins.camera.features.CameraFeature;
+import io.flutter.plugins.camera.features.CameraFeatureFactory;
+import io.flutter.plugins.camera.features.CameraFeatures;
+import io.flutter.plugins.camera.features.Point;
+import io.flutter.plugins.camera.features.autofocus.AutoFocusFeature;
+import io.flutter.plugins.camera.features.autofocus.FocusMode;
+import io.flutter.plugins.camera.features.exposurelock.ExposureLockFeature;
+import io.flutter.plugins.camera.features.exposurelock.ExposureMode;
+import io.flutter.plugins.camera.features.exposureoffset.ExposureOffsetFeature;
+import io.flutter.plugins.camera.features.exposurepoint.ExposurePointFeature;
+import io.flutter.plugins.camera.features.flash.FlashFeature;
+import io.flutter.plugins.camera.features.flash.FlashMode;
+import io.flutter.plugins.camera.features.focuspoint.FocusPointFeature;
+import io.flutter.plugins.camera.features.resolution.ResolutionFeature;
+import io.flutter.plugins.camera.features.resolution.ResolutionPreset;
+import io.flutter.plugins.camera.features.sensororientation.DeviceOrientationManager;
+import io.flutter.plugins.camera.features.sensororientation.SensorOrientationFeature;
+import io.flutter.plugins.camera.features.zoomlevel.ZoomLevelFeature;
 import io.flutter.plugins.camera.media.MediaRecorderBuilder;
-import io.flutter.plugins.camera.types.ExposureMode;
-import io.flutter.plugins.camera.types.FlashMode;
-import io.flutter.plugins.camera.types.FocusMode;
-import io.flutter.plugins.camera.types.ResolutionPreset;
+import io.flutter.plugins.camera.types.CaptureTimeoutsWrapper;
 import io.flutter.view.TextureRegistry.SurfaceTextureEntry;
 import java.io.File;
-import java.io.FileOutputStream;
 import java.io.IOException;
 import java.nio.ByteBuffer;
 import java.util.ArrayList;
@@ -71,151 +79,173 @@
   void onError(String errorCode, String errorMessage);
 }
 
-public class Camera {
+class Camera
+    implements CameraCaptureCallback.CameraCaptureStateListener,
+        ImageReader.OnImageAvailableListener,
+        LifecycleObserver {
   private static final String TAG = "Camera";
 
-  /** Timeout for the pre-capture sequence. */
-  private static final long PRECAPTURE_TIMEOUT_MS = 1000;
-
-  private final SurfaceTextureEntry flutterTexture;
-  private final CameraManager cameraManager;
-  private final DeviceOrientationManager deviceOrientationListener;
-  private final boolean isFrontFacing;
-  private final int sensorOrientation;
-  private final String cameraName;
-  private final Size captureSize;
-  private final Size previewSize;
-  private final boolean enableAudio;
-  private final Context applicationContext;
-  private final CamcorderProfile recordingProfile;
-  private final DartMessenger dartMessenger;
-  private final CameraZoom cameraZoom;
-  private final CameraCharacteristics cameraCharacteristics;
-
-  private CameraDevice cameraDevice;
-  private CameraCaptureSession cameraCaptureSession;
-  private ImageReader pictureImageReader;
-  private ImageReader imageStreamReader;
-  private CaptureRequest.Builder captureRequestBuilder;
-  private MediaRecorder mediaRecorder;
-  private boolean recordingVideo;
-  private File videoRecordingFile;
-  private FlashMode flashMode;
-  private ExposureMode exposureMode;
-  private FocusMode focusMode;
-  private PictureCaptureRequest pictureCaptureRequest;
-  private CameraRegions cameraRegions;
-  private int exposureOffset;
-  private boolean useAutoFocus = true;
-  private Range<Integer> fpsRange;
-  private PlatformChannel.DeviceOrientation lockedCaptureOrientation;
-  private long preCaptureStartTime;
-
   private static final HashMap<String, Integer> supportedImageFormats;
-  // Current supported outputs
+
+  // Current supported outputs.
   static {
     supportedImageFormats = new HashMap<>();
-    supportedImageFormats.put("yuv420", 35);
-    supportedImageFormats.put("jpeg", 256);
+    supportedImageFormats.put("yuv420", ImageFormat.YUV_420_888);
+    supportedImageFormats.put("jpeg", ImageFormat.JPEG);
   }
 
+  /**
+   * Holds all of the camera features/settings and will be used to update the request builder when
+   * one changes.
+   */
+  private final CameraFeatures cameraFeatures;
+
+  private final SurfaceTextureEntry flutterTexture;
+  private final boolean enableAudio;
+  private final Context applicationContext;
+  private final DartMessenger dartMessenger;
+  private final CameraProperties cameraProperties;
+  private final CameraFeatureFactory cameraFeatureFactory;
+  private final Activity activity;
+  /** A {@link CameraCaptureSession.CaptureCallback} that handles events related to JPEG capture. */
+  private final CameraCaptureCallback cameraCaptureCallback;
+  /** A {@link Handler} for running tasks in the background. */
+  private Handler backgroundHandler;
+
+  /** An additional thread for running tasks that shouldn't block the UI. */
+  private HandlerThread backgroundHandlerThread;
+
+  private CameraDevice cameraDevice;
+  private CameraCaptureSession captureSession;
+  private ImageReader pictureImageReader;
+  private ImageReader imageStreamReader;
+  /** {@link CaptureRequest.Builder} for the camera preview */
+  private CaptureRequest.Builder previewRequestBuilder;
+
+  private MediaRecorder mediaRecorder;
+  /** True when recording video. */
+  private boolean recordingVideo;
+
+  private File captureFile;
+
+  /** Holds the current capture timeouts */
+  private CaptureTimeoutsWrapper captureTimeouts;
+
+  private MethodChannel.Result flutterResult;
+
   public Camera(
       final Activity activity,
       final SurfaceTextureEntry flutterTexture,
+      final CameraFeatureFactory cameraFeatureFactory,
       final DartMessenger dartMessenger,
-      final String cameraName,
-      final String resolutionPreset,
-      final boolean enableAudio)
-      throws CameraAccessException {
+      final CameraProperties cameraProperties,
+      final ResolutionPreset resolutionPreset,
+      final boolean enableAudio) {
+
     if (activity == null) {
       throw new IllegalStateException("No activity available!");
     }
-    this.cameraName = cameraName;
+    this.activity = activity;
     this.enableAudio = enableAudio;
     this.flutterTexture = flutterTexture;
     this.dartMessenger = dartMessenger;
-    this.cameraManager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE);
     this.applicationContext = activity.getApplicationContext();
-    this.flashMode = FlashMode.auto;
-    this.exposureMode = ExposureMode.auto;
-    this.focusMode = FocusMode.auto;
-    this.exposureOffset = 0;
+    this.cameraProperties = cameraProperties;
+    this.cameraFeatureFactory = cameraFeatureFactory;
+    this.cameraFeatures =
+        CameraFeatures.init(
+            cameraFeatureFactory, cameraProperties, activity, dartMessenger, resolutionPreset);
 
-    cameraCharacteristics = cameraManager.getCameraCharacteristics(cameraName);
-    initFps(cameraCharacteristics);
-    sensorOrientation = cameraCharacteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
-    isFrontFacing =
-        cameraCharacteristics.get(CameraCharacteristics.LENS_FACING)
-            == CameraMetadata.LENS_FACING_FRONT;
-    ResolutionPreset preset = ResolutionPreset.valueOf(resolutionPreset);
-    recordingProfile =
-        CameraUtils.getBestAvailableCamcorderProfileForResolutionPreset(cameraName, preset);
-    captureSize = new Size(recordingProfile.videoFrameWidth, recordingProfile.videoFrameHeight);
-    previewSize = computeBestPreviewSize(cameraName, preset);
-    cameraZoom =
-        new CameraZoom(
-            cameraCharacteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE),
-            cameraCharacteristics.get(CameraCharacteristics.SCALER_AVAILABLE_MAX_DIGITAL_ZOOM));
+    // Create capture callback.
+    captureTimeouts = new CaptureTimeoutsWrapper(3000, 3000);
+    cameraCaptureCallback = CameraCaptureCallback.create(this, captureTimeouts);
 
-    deviceOrientationListener =
-        new DeviceOrientationManager(activity, dartMessenger, isFrontFacing, sensorOrientation);
-    deviceOrientationListener.start();
+    startBackgroundThread();
   }
 
-  private void initFps(CameraCharacteristics cameraCharacteristics) {
-    try {
-      Range<Integer>[] ranges =
-          cameraCharacteristics.get(CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES);
-      if (ranges != null) {
-        for (Range<Integer> range : ranges) {
-          int upper = range.getUpper();
-          Log.i("Camera", "[FPS Range Available] is:" + range);
-          if (upper >= 10) {
-            if (fpsRange == null || upper > fpsRange.getUpper()) {
-              fpsRange = range;
-            }
-          }
-        }
-      }
-    } catch (Exception e) {
-      e.printStackTrace();
+  @Override
+  public void onConverged() {
+    takePictureAfterPrecapture();
+  }
+
+  @Override
+  public void onPrecapture() {
+    runPrecaptureSequence();
+  }
+
+  /**
+   * Updates the builder settings with all of the available features.
+   *
+   * @param requestBuilder request builder to update.
+   */
+  private void updateBuilderSettings(CaptureRequest.Builder requestBuilder) {
+    for (CameraFeature feature : cameraFeatures.getAllFeatures()) {
+      Log.d(TAG, "Updating builder with feature: " + feature.getDebugName());
+      feature.updateBuilder(requestBuilder);
     }
-    Log.i("Camera", "[FPS Range] is:" + fpsRange);
   }
 
   private void prepareMediaRecorder(String outputFilePath) throws IOException {
+    Log.i(TAG, "prepareMediaRecorder");
+
     if (mediaRecorder != null) {
       mediaRecorder.release();
     }
 
+    final PlatformChannel.DeviceOrientation lockedOrientation =
+        ((SensorOrientationFeature) cameraFeatures.getSensorOrientation())
+            .getLockedCaptureOrientation();
+
     mediaRecorder =
-        new MediaRecorderBuilder(recordingProfile, outputFilePath)
+        new MediaRecorderBuilder(getRecordingProfile(), outputFilePath)
             .setEnableAudio(enableAudio)
             .setMediaOrientation(
-                lockedCaptureOrientation == null
-                    ? deviceOrientationListener.getMediaOrientation()
-                    : deviceOrientationListener.getMediaOrientation(lockedCaptureOrientation))
+                lockedOrientation == null
+                    ? getDeviceOrientationManager().getVideoOrientation()
+                    : getDeviceOrientationManager().getVideoOrientation(lockedOrientation))
             .build();
   }
 
   @SuppressLint("MissingPermission")
   public void open(String imageFormatGroup) throws CameraAccessException {
+    final ResolutionFeature resolutionFeature = cameraFeatures.getResolution();
+
+    if (!resolutionFeature.checkIsSupported()) {
+      // Tell the user that the camera they are trying to open is not supported,
+      // as its {@link android.media.CamcorderProfile} cannot be fetched due to the name
+      // not being a valid parsable integer.
+      dartMessenger.sendCameraErrorEvent(
+          "Camera with name \""
+              + cameraProperties.getCameraName()
+              + "\" is not supported by this plugin.");
+      return;
+    }
+
+    // Always capture using JPEG format.
     pictureImageReader =
         ImageReader.newInstance(
-            captureSize.getWidth(), captureSize.getHeight(), ImageFormat.JPEG, 2);
+            resolutionFeature.getCaptureSize().getWidth(),
+            resolutionFeature.getCaptureSize().getHeight(),
+            ImageFormat.JPEG,
+            1);
 
+    // For image streaming, use the provided image format or fall back to YUV420.
     Integer imageFormat = supportedImageFormats.get(imageFormatGroup);
     if (imageFormat == null) {
       Log.w(TAG, "The selected imageFormatGroup is not supported by Android. Defaulting to yuv420");
       imageFormat = ImageFormat.YUV_420_888;
     }
-
-    // Used to steam image byte data to dart side.
     imageStreamReader =
-        ImageReader.newInstance(previewSize.getWidth(), previewSize.getHeight(), imageFormat, 2);
+        ImageReader.newInstance(
+            resolutionFeature.getPreviewSize().getWidth(),
+            resolutionFeature.getPreviewSize().getHeight(),
+            imageFormat,
+            1);
 
+    // Open the camera.
+    CameraManager cameraManager = CameraUtils.getCameraManager(activity);
     cameraManager.openCamera(
-        cameraName,
+        cameraProperties.getCameraName(),
         new CameraDevice.StateCallback() {
           @Override
           public void onOpened(@NonNull CameraDevice device) {
@@ -223,12 +253,12 @@
             try {
               startPreview();
               dartMessenger.sendCameraInitializedEvent(
-                  previewSize.getWidth(),
-                  previewSize.getHeight(),
-                  exposureMode,
-                  focusMode,
-                  isExposurePointSupported(),
-                  isFocusPointSupported());
+                  resolutionFeature.getPreviewSize().getWidth(),
+                  resolutionFeature.getPreviewSize().getHeight(),
+                  cameraFeatures.getExposureLock().getValue(),
+                  cameraFeatures.getAutoFocus().getValue(),
+                  cameraFeatures.getExposurePoint().checkIsSupported(),
+                  cameraFeatures.getFocusPoint().checkIsSupported());
             } catch (CameraAccessException e) {
               dartMessenger.sendCameraErrorEvent(e.getMessage());
               close();
@@ -237,18 +267,24 @@
 
           @Override
           public void onClosed(@NonNull CameraDevice camera) {
+            Log.i(TAG, "open | onClosed");
+
             dartMessenger.sendCameraClosingEvent();
             super.onClosed(camera);
           }
 
           @Override
           public void onDisconnected(@NonNull CameraDevice cameraDevice) {
+            Log.i(TAG, "open | onDisconnected");
+
             close();
             dartMessenger.sendCameraErrorEvent("The camera was disconnected.");
           }
 
           @Override
           public void onError(@NonNull CameraDevice cameraDevice, int errorCode) {
+            Log.i(TAG, "open | onError");
+
             close();
             String errorDescription;
             switch (errorCode) {
@@ -273,7 +309,7 @@
             dartMessenger.sendCameraErrorEvent(errorDescription);
           }
         },
-        null);
+        backgroundHandler);
   }
 
   private void createCaptureSession(int templateType, Surface... surfaces)
@@ -288,39 +324,45 @@
     closeCaptureSession();
 
     // Create a new capture builder.
-    captureRequestBuilder = cameraDevice.createCaptureRequest(templateType);
+    previewRequestBuilder = cameraDevice.createCaptureRequest(templateType);
 
-    // Build Flutter surface to render to
+    // Build Flutter surface to render to.
+    ResolutionFeature resolutionFeature = cameraFeatures.getResolution();
     SurfaceTexture surfaceTexture = flutterTexture.surfaceTexture();
-    surfaceTexture.setDefaultBufferSize(previewSize.getWidth(), previewSize.getHeight());
+    surfaceTexture.setDefaultBufferSize(
+        resolutionFeature.getPreviewSize().getWidth(),
+        resolutionFeature.getPreviewSize().getHeight());
     Surface flutterSurface = new Surface(surfaceTexture);
-    captureRequestBuilder.addTarget(flutterSurface);
+    previewRequestBuilder.addTarget(flutterSurface);
 
     List<Surface> remainingSurfaces = Arrays.asList(surfaces);
     if (templateType != CameraDevice.TEMPLATE_PREVIEW) {
       // If it is not preview mode, add all surfaces as targets.
       for (Surface surface : remainingSurfaces) {
-        captureRequestBuilder.addTarget(surface);
+        previewRequestBuilder.addTarget(surface);
       }
     }
 
-    cameraRegions = new CameraRegions(getRegionBoundaries());
+    // Update camera regions.
+    Size cameraBoundaries =
+        CameraRegionUtils.getCameraBoundaries(cameraProperties, previewRequestBuilder);
+    cameraFeatures.getExposurePoint().setCameraBoundaries(cameraBoundaries);
+    cameraFeatures.getFocusPoint().setCameraBoundaries(cameraBoundaries);
 
-    // Prepare the callback
+    // Prepare the callback.
     CameraCaptureSession.StateCallback callback =
         new CameraCaptureSession.StateCallback() {
           @Override
           public void onConfigured(@NonNull CameraCaptureSession session) {
+            // Camera was already closed.
             if (cameraDevice == null) {
               dartMessenger.sendCameraErrorEvent("The camera was closed during configuration.");
               return;
             }
-            cameraCaptureSession = session;
+            captureSession = session;
 
-            updateFpsRange();
-            updateFocus(focusMode);
-            updateFlash(flashMode);
-            updateExposure(exposureMode);
+            Log.i(TAG, "Updating builder settings");
+            updateBuilderSettings(previewRequestBuilder);
 
             refreshPreviewCaptureSession(
                 onSuccessCallback, (code, message) -> dartMessenger.sendCameraErrorEvent(message));
@@ -332,9 +374,9 @@
           }
         };
 
-    // Start the session
+    // Start the session.
     if (VERSION.SDK_INT >= VERSION_CODES.P) {
-      // Collect all surfaces we want to render to.
+      // Collect all surfaces to render to.
       List<OutputConfiguration> configs = new ArrayList<>();
       configs.add(new OutputConfiguration(flutterSurface));
       for (Surface surface : remainingSurfaces) {
@@ -342,7 +384,7 @@
       }
       createCaptureSessionWithSessionConfig(configs, callback);
     } else {
-      // Collect all surfaces we want to render to.
+      // Collect all surfaces to render to.
       List<Surface> surfaceList = new ArrayList<>();
       surfaceList.add(flutterSurface);
       surfaceList.addAll(remainingSurfaces);
@@ -367,276 +409,273 @@
   private void createCaptureSession(
       List<Surface> surfaces, CameraCaptureSession.StateCallback callback)
       throws CameraAccessException {
-    cameraDevice.createCaptureSession(surfaces, callback, null);
+    cameraDevice.createCaptureSession(surfaces, callback, backgroundHandler);
   }
 
+  // Send a repeating request to refresh  capture session.
   private void refreshPreviewCaptureSession(
       @Nullable Runnable onSuccessCallback, @NonNull ErrorCallback onErrorCallback) {
-    if (cameraCaptureSession == null) {
+    if (captureSession == null) {
+      Log.i(
+          TAG,
+          "[refreshPreviewCaptureSession] captureSession not yet initialized, "
+              + "skipping preview capture session refresh.");
       return;
     }
 
     try {
-      cameraCaptureSession.setRepeatingRequest(
-          captureRequestBuilder.build(),
-          pictureCaptureCallback,
-          new Handler(Looper.getMainLooper()));
+      captureSession.setRepeatingRequest(
+          previewRequestBuilder.build(), cameraCaptureCallback, backgroundHandler);
 
       if (onSuccessCallback != null) {
         onSuccessCallback.run();
       }
-    } catch (CameraAccessException | IllegalStateException | IllegalArgumentException e) {
+
+    } catch (CameraAccessException e) {
       onErrorCallback.onError("cameraAccess", e.getMessage());
     }
   }
 
-  private void writeToFile(ByteBuffer buffer, File file) throws IOException {
-    try (FileOutputStream outputStream = new FileOutputStream(file)) {
-      while (0 < buffer.remaining()) {
-        outputStream.getChannel().write(buffer);
-      }
-    }
-  }
-
   public void takePicture(@NonNull final Result result) {
-    // Only take 1 picture at a time
-    if (pictureCaptureRequest != null && !pictureCaptureRequest.isFinished()) {
+    // Only take one picture at a time.
+    if (cameraCaptureCallback.getCameraState() != CameraState.STATE_PREVIEW) {
       result.error("captureAlreadyActive", "Picture is currently already being captured", null);
       return;
     }
-    // Store the result
-    this.pictureCaptureRequest = new PictureCaptureRequest(result);
 
-    // Create temporary file
+    flutterResult = result;
+
+    // Create temporary file.
     final File outputDir = applicationContext.getCacheDir();
-    final File file;
     try {
-      file = File.createTempFile("CAP", ".jpg", outputDir);
+      captureFile = File.createTempFile("CAP", ".jpg", outputDir);
+      captureTimeouts.reset();
     } catch (IOException | SecurityException e) {
-      pictureCaptureRequest.error("cannotCreateFile", e.getMessage(), null);
+      dartMessenger.error(flutterResult, "cannotCreateFile", e.getMessage(), null);
       return;
     }
 
-    // Listen for picture being taken
-    pictureImageReader.setOnImageAvailableListener(
-        reader -> {
-          try (Image image = reader.acquireLatestImage()) {
-            ByteBuffer buffer = image.getPlanes()[0].getBuffer();
-            writeToFile(buffer, file);
-            pictureCaptureRequest.finish(file.getAbsolutePath());
-          } catch (IOException e) {
-            pictureCaptureRequest.error("IOError", "Failed saving image", null);
-          }
-        },
-        null);
+    // Listen for picture being taken.
+    pictureImageReader.setOnImageAvailableListener(this, backgroundHandler);
 
-    if (useAutoFocus) {
+    final AutoFocusFeature autoFocusFeature = cameraFeatures.getAutoFocus();
+    final boolean isAutoFocusSupported = autoFocusFeature.checkIsSupported();
+    if (isAutoFocusSupported && autoFocusFeature.getValue() == FocusMode.auto) {
       runPictureAutoFocus();
     } else {
-      runPicturePreCapture();
+      runPrecaptureSequence();
     }
   }
 
-  private final CameraCaptureSession.CaptureCallback pictureCaptureCallback =
-      new CameraCaptureSession.CaptureCallback() {
-        @Override
-        public void onCaptureCompleted(
-            @NonNull CameraCaptureSession session,
-            @NonNull CaptureRequest request,
-            @NonNull TotalCaptureResult result) {
-          processCapture(result);
-        }
-
-        @Override
-        public void onCaptureProgressed(
-            @NonNull CameraCaptureSession session,
-            @NonNull CaptureRequest request,
-            @NonNull CaptureResult partialResult) {
-          processCapture(partialResult);
-        }
-
-        @Override
-        public void onCaptureFailed(
-            @NonNull CameraCaptureSession session,
-            @NonNull CaptureRequest request,
-            @NonNull CaptureFailure failure) {
-          if (pictureCaptureRequest == null || pictureCaptureRequest.isFinished()) {
-            return;
-          }
-          String reason;
-          boolean fatalFailure = false;
-          switch (failure.getReason()) {
-            case CaptureFailure.REASON_ERROR:
-              reason = "An error happened in the framework";
-              break;
-            case CaptureFailure.REASON_FLUSHED:
-              reason = "The capture has failed due to an abortCaptures() call";
-              fatalFailure = true;
-              break;
-            default:
-              reason = "Unknown reason";
-          }
-          Log.w("Camera", "pictureCaptureCallback.onCaptureFailed(): " + reason);
-          if (fatalFailure) pictureCaptureRequest.error("captureFailure", reason, null);
-        }
-
-        private void processCapture(CaptureResult result) {
-          if (pictureCaptureRequest == null) {
-            return;
-          }
-
-          Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);
-          Integer afState = result.get(CaptureResult.CONTROL_AF_STATE);
-          switch (pictureCaptureRequest.getState()) {
-            case focusing:
-              if (afState == null) {
-                return;
-              } else if (afState == CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED
-                  || afState == CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED) {
-                // Some devices might return null here, in which case we will also continue.
-                if (aeState == null || aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED) {
-                  runPictureCapture();
-                } else {
-                  runPicturePreCapture();
-                }
-              }
-              break;
-            case preCapture:
-              // Some devices might return null here, in which case we will also continue.
-              if (aeState == null
-                  || aeState == CaptureRequest.CONTROL_AE_STATE_PRECAPTURE
-                  || aeState == CaptureRequest.CONTROL_AE_STATE_FLASH_REQUIRED
-                  || aeState == CaptureRequest.CONTROL_AE_STATE_CONVERGED) {
-                pictureCaptureRequest.setState(State.waitingPreCaptureReady);
-                setPreCaptureStartTime();
-              }
-              break;
-            case waitingPreCaptureReady:
-              if (aeState == null || aeState != CaptureRequest.CONTROL_AE_STATE_PRECAPTURE) {
-                runPictureCapture();
-              } else {
-                if (hitPreCaptureTimeout()) {
-                  unlockAutoFocus();
-                }
-              }
-          }
-        }
-      };
-
-  private void runPictureAutoFocus() {
-    assert (pictureCaptureRequest != null);
-
-    pictureCaptureRequest.setState(PictureCaptureRequest.State.focusing);
-    lockAutoFocus(pictureCaptureCallback);
-  }
-
-  private void runPicturePreCapture() {
-    assert (pictureCaptureRequest != null);
-    pictureCaptureRequest.setState(PictureCaptureRequest.State.preCapture);
-
-    captureRequestBuilder.set(
-        CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER,
-        CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START);
-
-    refreshPreviewCaptureSession(
-        () ->
-            captureRequestBuilder.set(
-                CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER,
-                CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_IDLE),
-        (code, message) -> pictureCaptureRequest.error(code, message, null));
-  }
-
-  private void runPictureCapture() {
-    assert (pictureCaptureRequest != null);
-    pictureCaptureRequest.setState(PictureCaptureRequest.State.capturing);
+  /**
+   * Run the precapture sequence for capturing a still image. This method should be called when a
+   * response is received in {@link #cameraCaptureCallback} from lockFocus().
+   */
+  private void runPrecaptureSequence() {
+    Log.i(TAG, "runPrecaptureSequence");
     try {
-      final CaptureRequest.Builder captureBuilder =
-          cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
-      captureBuilder.addTarget(pictureImageReader.getSurface());
-      captureBuilder.set(
-          CaptureRequest.SCALER_CROP_REGION,
-          captureRequestBuilder.get(CaptureRequest.SCALER_CROP_REGION));
-      captureBuilder.set(
-          CaptureRequest.JPEG_ORIENTATION,
-          lockedCaptureOrientation == null
-              ? deviceOrientationListener.getMediaOrientation()
-              : deviceOrientationListener.getMediaOrientation(lockedCaptureOrientation));
+      // First set precapture state to idle or else it can hang in STATE_WAITING_PRECAPTURE_START.
+      previewRequestBuilder.set(
+          CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER,
+          CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_IDLE);
+      captureSession.capture(
+          previewRequestBuilder.build(), cameraCaptureCallback, backgroundHandler);
 
-      switch (flashMode) {
-        case off:
-          captureBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON);
-          captureBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_OFF);
-          break;
-        case auto:
-          captureBuilder.set(
-              CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
-          break;
-        case always:
-        default:
-          captureBuilder.set(
-              CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_ALWAYS_FLASH);
-          break;
-      }
-      cameraCaptureSession.stopRepeating();
-      cameraCaptureSession.capture(
-          captureBuilder.build(),
-          new CameraCaptureSession.CaptureCallback() {
-            @Override
-            public void onCaptureCompleted(
-                @NonNull CameraCaptureSession session,
-                @NonNull CaptureRequest request,
-                @NonNull TotalCaptureResult result) {
-              unlockAutoFocus();
-            }
-          },
-          null);
+      // Repeating request to refresh preview session.
+      refreshPreviewCaptureSession(
+          null,
+          (code, message) -> dartMessenger.error(flutterResult, "cameraAccess", message, null));
+
+      // Start precapture.
+      cameraCaptureCallback.setCameraState(CameraState.STATE_WAITING_PRECAPTURE_START);
+
+      previewRequestBuilder.set(
+          CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER,
+          CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START);
+
+      // Trigger one capture to start AE sequence.
+      captureSession.capture(
+          previewRequestBuilder.build(), cameraCaptureCallback, backgroundHandler);
+
     } catch (CameraAccessException e) {
-      pictureCaptureRequest.error("cameraAccess", e.getMessage(), null);
+      e.printStackTrace();
     }
   }
 
-  private void lockAutoFocus(CaptureCallback callback) {
-    captureRequestBuilder.set(
+  /**
+   * Capture a still picture. This method should be called when a response is received {@link
+   * #cameraCaptureCallback} from both lockFocus().
+   */
+  private void takePictureAfterPrecapture() {
+    Log.i(TAG, "captureStillPicture");
+    cameraCaptureCallback.setCameraState(CameraState.STATE_CAPTURING);
+
+    if (cameraDevice == null) {
+      return;
+    }
+    // This is the CaptureRequest.Builder that is used to take a picture.
+    CaptureRequest.Builder stillBuilder;
+    try {
+      stillBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
+    } catch (CameraAccessException e) {
+      dartMessenger.error(flutterResult, "cameraAccess", e.getMessage(), null);
+      return;
+    }
+    stillBuilder.addTarget(pictureImageReader.getSurface());
+
+    // Zoom.
+    stillBuilder.set(
+        CaptureRequest.SCALER_CROP_REGION,
+        previewRequestBuilder.get(CaptureRequest.SCALER_CROP_REGION));
+
+    // Have all features update the builder.
+    updateBuilderSettings(stillBuilder);
+
+    // Orientation.
+    final PlatformChannel.DeviceOrientation lockedOrientation =
+        ((SensorOrientationFeature) cameraFeatures.getSensorOrientation())
+            .getLockedCaptureOrientation();
+    stillBuilder.set(
+        CaptureRequest.JPEG_ORIENTATION,
+        lockedOrientation == null
+            ? getDeviceOrientationManager().getPhotoOrientation()
+            : getDeviceOrientationManager().getPhotoOrientation(lockedOrientation));
+
+    CameraCaptureSession.CaptureCallback captureCallback =
+        new CameraCaptureSession.CaptureCallback() {
+          @Override
+          public void onCaptureCompleted(
+              @NonNull CameraCaptureSession session,
+              @NonNull CaptureRequest request,
+              @NonNull TotalCaptureResult result) {
+            unlockAutoFocus();
+          }
+        };
+
+    try {
+      captureSession.stopRepeating();
+      captureSession.abortCaptures();
+      Log.i(TAG, "sending capture request");
+      captureSession.capture(stillBuilder.build(), captureCallback, backgroundHandler);
+    } catch (CameraAccessException e) {
+      dartMessenger.error(flutterResult, "cameraAccess", e.getMessage(), null);
+    }
+  }
+
+  @SuppressWarnings("deprecation")
+  private Display getDefaultDisplay() {
+    return activity.getWindowManager().getDefaultDisplay();
+  }
+
+  /** Starts a background thread and its {@link Handler}. */
+  @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
+  public void startBackgroundThread() {
+    backgroundHandlerThread = new HandlerThread("CameraBackground");
+    try {
+      backgroundHandlerThread.start();
+    } catch (IllegalThreadStateException e) {
+      // Ignore exception in case the thread has already started.
+    }
+    backgroundHandler = new Handler(backgroundHandlerThread.getLooper());
+  }
+
+  /** Stops the background thread and its {@link Handler}. */
+  @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
+  public void stopBackgroundThread() {
+    if (backgroundHandlerThread != null) {
+      backgroundHandlerThread.quitSafely();
+      try {
+        backgroundHandlerThread.join();
+      } catch (InterruptedException e) {
+        dartMessenger.error(flutterResult, "cameraAccess", e.getMessage(), null);
+      }
+    }
+    backgroundHandlerThread = null;
+    backgroundHandler = null;
+  }
+
+  /** Start capturing a picture, doing autofocus first. */
+  private void runPictureAutoFocus() {
+    Log.i(TAG, "runPictureAutoFocus");
+
+    cameraCaptureCallback.setCameraState(CameraState.STATE_WAITING_FOCUS);
+    lockAutoFocus();
+  }
+
+  private void lockAutoFocus() {
+    Log.i(TAG, "lockAutoFocus");
+    if (captureSession == null) {
+      Log.i(TAG, "[unlockAutoFocus] captureSession null, returning");
+      return;
+    }
+
+    // Trigger AF to start.
+    previewRequestBuilder.set(
         CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_TRIGGER_START);
 
-    refreshPreviewCaptureSession(
-        null, (code, message) -> pictureCaptureRequest.error(code, message, null));
+    try {
+      captureSession.capture(previewRequestBuilder.build(), null, backgroundHandler);
+    } catch (CameraAccessException e) {
+      dartMessenger.sendCameraErrorEvent(e.getMessage());
+    }
   }
 
+  /** Cancel and reset auto focus state and refresh the preview session. */
   private void unlockAutoFocus() {
-    captureRequestBuilder.set(
-        CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_CANCEL);
-    updateFocus(focusMode);
-    try {
-      cameraCaptureSession.capture(captureRequestBuilder.build(), null, null);
-    } catch (CameraAccessException ignored) {
+    Log.i(TAG, "unlockAutoFocus");
+    if (captureSession == null) {
+      Log.i(TAG, "[unlockAutoFocus] captureSession null, returning");
+      return;
     }
-    captureRequestBuilder.set(
-        CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_TRIGGER_IDLE);
+    try {
+      // Cancel existing AF state.
+      previewRequestBuilder.set(
+          CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_CANCEL);
+      captureSession.capture(previewRequestBuilder.build(), null, backgroundHandler);
+
+      // Set AF state to idle again.
+      previewRequestBuilder.set(
+          CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_IDLE);
+
+      captureSession.capture(previewRequestBuilder.build(), null, backgroundHandler);
+    } catch (CameraAccessException e) {
+      dartMessenger.sendCameraErrorEvent(e.getMessage());
+      return;
+    }
 
     refreshPreviewCaptureSession(
         null,
-        (errorCode, errorMessage) -> pictureCaptureRequest.error(errorCode, errorMessage, null));
+        (errorCode, errorMessage) ->
+            dartMessenger.error(flutterResult, errorCode, errorMessage, null));
   }
 
-  public void startVideoRecording(Result result) {
+  public void startVideoRecording(@NonNull Result result) {
     final File outputDir = applicationContext.getCacheDir();
     try {
-      videoRecordingFile = File.createTempFile("REC", ".mp4", outputDir);
+      captureFile = File.createTempFile("REC", ".mp4", outputDir);
     } catch (IOException | SecurityException e) {
       result.error("cannotCreateFile", e.getMessage(), null);
       return;
     }
-
     try {
-      prepareMediaRecorder(videoRecordingFile.getAbsolutePath());
-      recordingVideo = true;
+      prepareMediaRecorder(captureFile.getAbsolutePath());
+    } catch (IOException e) {
+      recordingVideo = false;
+      captureFile = null;
+      result.error("videoRecordingFailed", e.getMessage(), null);
+      return;
+    }
+    // Re-create autofocus feature so it's using video focus mode now.
+    cameraFeatures.setAutoFocus(
+        cameraFeatureFactory.createAutoFocusFeature(cameraProperties, true));
+    recordingVideo = true;
+    try {
       createCaptureSession(
           CameraDevice.TEMPLATE_RECORD, () -> mediaRecorder.start(), mediaRecorder.getSurface());
       result.success(null);
-    } catch (CameraAccessException | IOException e) {
+    } catch (CameraAccessException e) {
       recordingVideo = false;
-      videoRecordingFile = null;
+      captureFile = null;
       result.error("videoRecordingFailed", e.getMessage(), null);
     }
   }
@@ -646,24 +685,25 @@
       result.success(null);
       return;
     }
-
+    // Re-create autofocus feature so it's using continuous capture focus mode now.
+    cameraFeatures.setAutoFocus(
+        cameraFeatureFactory.createAutoFocusFeature(cameraProperties, false));
+    recordingVideo = false;
     try {
-      recordingVideo = false;
-
-      try {
-        cameraCaptureSession.abortCaptures();
-        mediaRecorder.stop();
-      } catch (CameraAccessException | IllegalStateException e) {
-        // Ignore exceptions and try to continue (changes are camera session already aborted capture)
-      }
-
-      mediaRecorder.reset();
+      captureSession.abortCaptures();
+      mediaRecorder.stop();
+    } catch (CameraAccessException | IllegalStateException e) {
+      // Ignore exceptions and try to continue (changes are camera session already aborted capture).
+    }
+    mediaRecorder.reset();
+    try {
       startPreview();
-      result.success(videoRecordingFile.getAbsolutePath());
-      videoRecordingFile = null;
     } catch (CameraAccessException | IllegalStateException e) {
       result.error("videoRecordingFailed", e.getMessage(), null);
+      return;
     }
+    result.success(captureFile.getAbsolutePath());
+    captureFile = null;
   }
 
   public void pauseVideoRecording(@NonNull final Result result) {
@@ -709,259 +749,185 @@
     result.success(null);
   }
 
-  public void setFlashMode(@NonNull final Result result, FlashMode mode)
-      throws CameraAccessException {
-    // Get the flash availability
-    Boolean flashAvailable =
-        cameraManager
-            .getCameraCharacteristics(cameraDevice.getId())
-            .get(CameraCharacteristics.FLASH_INFO_AVAILABLE);
+  /**
+   * Method handler for setting new flash modes.
+   *
+   * @param result Flutter result.
+   * @param newMode new mode.
+   */
+  public void setFlashMode(@NonNull final Result result, @NonNull FlashMode newMode) {
+    // Save the new flash mode setting.
+    final FlashFeature flashFeature = cameraFeatures.getFlash();
+    flashFeature.setValue(newMode);
+    flashFeature.updateBuilder(previewRequestBuilder);
 
-    // Check if flash is available.
-    if (flashAvailable == null || !flashAvailable) {
-      result.error("setFlashModeFailed", "Device does not have flash capabilities", null);
-      return;
-    }
-
-    // If switching directly from torch to auto or on, make sure we turn off the torch.
-    if (flashMode == FlashMode.torch && mode != FlashMode.torch && mode != FlashMode.off) {
-      updateFlash(FlashMode.off);
-
-      this.cameraCaptureSession.setRepeatingRequest(
-          captureRequestBuilder.build(),
-          new CaptureCallback() {
-            private boolean isFinished = false;
-
-            @Override
-            public void onCaptureCompleted(
-                @NonNull CameraCaptureSession session,
-                @NonNull CaptureRequest request,
-                @NonNull TotalCaptureResult captureResult) {
-              if (isFinished) {
-                return;
-              }
-
-              updateFlash(mode);
-              refreshPreviewCaptureSession(
-                  () -> {
-                    result.success(null);
-                    isFinished = true;
-                  },
-                  (code, message) ->
-                      result.error("setFlashModeFailed", "Could not set flash mode.", null));
-            }
-
-            @Override
-            public void onCaptureFailed(
-                @NonNull CameraCaptureSession session,
-                @NonNull CaptureRequest request,
-                @NonNull CaptureFailure failure) {
-              if (isFinished) {
-                return;
-              }
-
-              result.error("setFlashModeFailed", "Could not set flash mode.", null);
-              isFinished = true;
-            }
-          },
-          null);
-    } else {
-      updateFlash(mode);
-
-      refreshPreviewCaptureSession(
-          () -> result.success(null),
-          (code, message) -> result.error("setFlashModeFailed", "Could not set flash mode.", null));
-    }
-  }
-
-  public void setExposureMode(@NonNull final Result result, ExposureMode mode)
-      throws CameraAccessException {
-    updateExposure(mode);
-    cameraCaptureSession.setRepeatingRequest(captureRequestBuilder.build(), null, null);
-    result.success(null);
-  }
-
-  public void setExposurePoint(@NonNull final Result result, Double x, Double y)
-      throws CameraAccessException {
-    // Check if exposure point functionality is available.
-    if (!isExposurePointSupported()) {
-      result.error(
-          "setExposurePointFailed", "Device does not have exposure point capabilities", null);
-      return;
-    }
-    // Check if the current region boundaries are known
-    if (cameraRegions.getMaxBoundaries() == null) {
-      result.error("setExposurePointFailed", "Could not determine max region boundaries", null);
-      return;
-    }
-    // Set the metering rectangle
-    if (x == null || y == null) cameraRegions.resetAutoExposureMeteringRectangle();
-    else cameraRegions.setAutoExposureMeteringRectangleFromPoint(y, 1 - x);
-    // Apply it
-    updateExposure(exposureMode);
     refreshPreviewCaptureSession(
-        () -> result.success(null), (code, message) -> result.error("CameraAccess", message, null));
+        () -> result.success(null),
+        (code, message) -> result.error("setFlashModeFailed", "Could not set flash mode.", null));
   }
 
-  public void setFocusMode(@NonNull final Result result, FocusMode mode)
-      throws CameraAccessException {
-    this.focusMode = mode;
+  /**
+   * Method handler for setting new exposure modes.
+   *
+   * @param result Flutter result.
+   * @param newMode new mode.
+   */
+  public void setExposureMode(@NonNull final Result result, @NonNull ExposureMode newMode) {
+    final ExposureLockFeature exposureLockFeature = cameraFeatures.getExposureLock();
+    exposureLockFeature.setValue(newMode);
+    exposureLockFeature.updateBuilder(previewRequestBuilder);
 
-    updateFocus(mode);
+    refreshPreviewCaptureSession(
+        () -> result.success(null),
+        (code, message) ->
+            result.error("setExposureModeFailed", "Could not set exposure mode.", null));
+  }
 
-    switch (mode) {
-      case auto:
-        refreshPreviewCaptureSession(
-            null, (code, message) -> result.error("setFocusMode", message, null));
-        break;
+  /**
+   * Sets new exposure point from dart.
+   *
+   * @param result Flutter result.
+   * @param point The exposure point.
+   */
+  public void setExposurePoint(@NonNull final Result result, @Nullable Point point) {
+    final ExposurePointFeature exposurePointFeature = cameraFeatures.getExposurePoint();
+    exposurePointFeature.setValue(point);
+    exposurePointFeature.updateBuilder(previewRequestBuilder);
+
+    refreshPreviewCaptureSession(
+        () -> result.success(null),
+        (code, message) ->
+            result.error("setExposurePointFailed", "Could not set exposure point.", null));
+  }
+
+  /** Return the max exposure offset value supported by the camera to dart. */
+  public double getMaxExposureOffset() {
+    return cameraFeatures.getExposureOffset().getMaxExposureOffset();
+  }
+
+  /** Return the min exposure offset value supported by the camera to dart. */
+  public double getMinExposureOffset() {
+    return cameraFeatures.getExposureOffset().getMinExposureOffset();
+  }
+
+  /** Return the exposure offset step size to dart. */
+  public double getExposureOffsetStepSize() {
+    return cameraFeatures.getExposureOffset().getExposureOffsetStepSize();
+  }
+
+  /**
+   * Sets new focus mode from dart.
+   *
+   * @param result Flutter result.
+   * @param newMode New mode.
+   */
+  public void setFocusMode(final Result result, @NonNull FocusMode newMode) {
+    final AutoFocusFeature autoFocusFeature = cameraFeatures.getAutoFocus();
+    autoFocusFeature.setValue(newMode);
+    autoFocusFeature.updateBuilder(previewRequestBuilder);
+
+    /*
+     * For focus mode an extra step of actually locking/unlocking the
+     * focus has to be done, in order to ensure it goes into the correct state.
+     */
+    switch (newMode) {
       case locked:
-        lockAutoFocus(
-            new CaptureCallback() {
-              @Override
-              public void onCaptureCompleted(
-                  @NonNull CameraCaptureSession session,
-                  @NonNull CaptureRequest request,
-                  @NonNull TotalCaptureResult result) {
-                unlockAutoFocus();
-              }
-            });
+        // Perform a single focus trigger.
+        lockAutoFocus();
+        if (captureSession == null) {
+          Log.i(TAG, "[unlockAutoFocus] captureSession null, returning");
+          return;
+        }
+
+        // Set AF state to idle again.
+        previewRequestBuilder.set(
+            CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_IDLE);
+
+        try {
+          captureSession.setRepeatingRequest(
+              previewRequestBuilder.build(), null, backgroundHandler);
+        } catch (CameraAccessException e) {
+          if (result != null) {
+            result.error("setFocusModeFailed", "Error setting focus mode: " + e.getMessage(), null);
+          }
+          return;
+        }
+        break;
+      case auto:
+        // Cancel current AF trigger and set AF to idle again.
+        unlockAutoFocus();
         break;
     }
-    result.success(null);
-  }
 
-  public void setFocusPoint(@NonNull final Result result, Double x, Double y)
-      throws CameraAccessException {
-    // Check if focus point functionality is available.
-    if (!isFocusPointSupported()) {
-      result.error("setFocusPointFailed", "Device does not have focus point capabilities", null);
-      return;
+    if (result != null) {
+      result.success(null);
     }
-
-    // Check if the current region boundaries are known
-    if (cameraRegions.getMaxBoundaries() == null) {
-      result.error("setFocusPointFailed", "Could not determine max region boundaries", null);
-      return;
-    }
-
-    // Set the metering rectangle
-    if (x == null || y == null) {
-      cameraRegions.resetAutoFocusMeteringRectangle();
-    } else {
-      cameraRegions.setAutoFocusMeteringRectangleFromPoint(y, 1 - x);
-    }
-
-    // Apply the new metering rectangle
-    setFocusMode(result, focusMode);
   }
 
-  @TargetApi(VERSION_CODES.P)
-  private boolean supportsDistortionCorrection() throws CameraAccessException {
-    int[] availableDistortionCorrectionModes =
-        cameraManager
-            .getCameraCharacteristics(cameraDevice.getId())
-            .get(CameraCharacteristics.DISTORTION_CORRECTION_AVAILABLE_MODES);
-    if (availableDistortionCorrectionModes == null) availableDistortionCorrectionModes = new int[0];
-    long nonOffModesSupported =
-        Arrays.stream(availableDistortionCorrectionModes)
-            .filter((value) -> value != CaptureRequest.DISTORTION_CORRECTION_MODE_OFF)
-            .count();
-    return nonOffModesSupported > 0;
+  /**
+   * Sets new focus point from dart.
+   *
+   * @param result Flutter result.
+   * @param point the new coordinates.
+   */
+  public void setFocusPoint(@NonNull final Result result, @Nullable Point point) {
+    final FocusPointFeature focusPointFeature = cameraFeatures.getFocusPoint();
+    focusPointFeature.setValue(point);
+    focusPointFeature.updateBuilder(previewRequestBuilder);
+
+    refreshPreviewCaptureSession(
+        () -> result.success(null),
+        (code, message) -> result.error("setFocusPointFailed", "Could not set focus point.", null));
+
+    this.setFocusMode(null, cameraFeatures.getAutoFocus().getValue());
   }
 
-  private Size getRegionBoundaries() throws CameraAccessException {
-    // No distortion correction support
-    if (android.os.Build.VERSION.SDK_INT < VERSION_CODES.P || !supportsDistortionCorrection()) {
-      return cameraManager
-          .getCameraCharacteristics(cameraDevice.getId())
-          .get(CameraCharacteristics.SENSOR_INFO_PIXEL_ARRAY_SIZE);
-    }
-    // Get the current distortion correction mode
-    Integer distortionCorrectionMode =
-        captureRequestBuilder.get(CaptureRequest.DISTORTION_CORRECTION_MODE);
-    // Return the correct boundaries depending on the mode
-    android.graphics.Rect rect;
-    if (distortionCorrectionMode == null
-        || distortionCorrectionMode == CaptureRequest.DISTORTION_CORRECTION_MODE_OFF) {
-      rect =
-          cameraManager
-              .getCameraCharacteristics(cameraDevice.getId())
-              .get(CameraCharacteristics.SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE);
-    } else {
-      rect =
-          cameraManager
-              .getCameraCharacteristics(cameraDevice.getId())
-              .get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE);
-    }
-    return rect == null ? null : new Size(rect.width(), rect.height());
-  }
+  /**
+   * Sets a new exposure offset from dart. From dart the offset comes as a double, like +1.3 or
+   * -1.3.
+   *
+   * @param result flutter result.
+   * @param offset new value.
+   */
+  public void setExposureOffset(@NonNull final Result result, double offset) {
+    final ExposureOffsetFeature exposureOffsetFeature = cameraFeatures.getExposureOffset();
+    exposureOffsetFeature.setValue(offset);
+    exposureOffsetFeature.updateBuilder(previewRequestBuilder);
 
-  private boolean isExposurePointSupported() throws CameraAccessException {
-    Integer supportedRegions =
-        cameraManager
-            .getCameraCharacteristics(cameraDevice.getId())
-            .get(CameraCharacteristics.CONTROL_MAX_REGIONS_AE);
-    return supportedRegions != null && supportedRegions > 0;
-  }
-
-  private boolean isFocusPointSupported() throws CameraAccessException {
-    Integer supportedRegions =
-        cameraManager
-            .getCameraCharacteristics(cameraDevice.getId())
-            .get(CameraCharacteristics.CONTROL_MAX_REGIONS_AF);
-    return supportedRegions != null && supportedRegions > 0;
-  }
-
-  public double getMinExposureOffset() throws CameraAccessException {
-    Range<Integer> range =
-        cameraManager
-            .getCameraCharacteristics(cameraDevice.getId())
-            .get(CameraCharacteristics.CONTROL_AE_COMPENSATION_RANGE);
-    double minStepped = range == null ? 0 : range.getLower();
-    double stepSize = getExposureOffsetStepSize();
-    return minStepped * stepSize;
-  }
-
-  public double getMaxExposureOffset() throws CameraAccessException {
-    Range<Integer> range =
-        cameraManager
-            .getCameraCharacteristics(cameraDevice.getId())
-            .get(CameraCharacteristics.CONTROL_AE_COMPENSATION_RANGE);
-    double maxStepped = range == null ? 0 : range.getUpper();
-    double stepSize = getExposureOffsetStepSize();
-    return maxStepped * stepSize;
-  }
-
-  public double getExposureOffsetStepSize() throws CameraAccessException {
-    Rational stepSize =
-        cameraManager
-            .getCameraCharacteristics(cameraDevice.getId())
-            .get(CameraCharacteristics.CONTROL_AE_COMPENSATION_STEP);
-    return stepSize == null ? 0.0 : stepSize.doubleValue();
-  }
-
-  public void setExposureOffset(@NonNull final Result result, double offset)
-      throws CameraAccessException {
-    // Set the exposure offset
-    double stepSize = getExposureOffsetStepSize();
-    exposureOffset = (int) (offset / stepSize);
-    // Apply it
-    updateExposure(exposureMode);
-    this.cameraCaptureSession.setRepeatingRequest(captureRequestBuilder.build(), null, null);
-    result.success(offset);
+    refreshPreviewCaptureSession(
+        () -> result.success(null),
+        (code, message) ->
+            result.error("setExposureOffsetFailed", "Could not set exposure offset.", null));
   }
 
   public float getMaxZoomLevel() {
-    return cameraZoom.maxZoom;
+    return cameraFeatures.getZoomLevel().getMaximumZoomLevel();
   }
 
   public float getMinZoomLevel() {
-    return CameraZoom.DEFAULT_ZOOM_FACTOR;
+    return cameraFeatures.getZoomLevel().getMinimumZoomLevel();
   }
 
+  /** Shortcut to get current recording profile. */
+  CamcorderProfile getRecordingProfile() {
+    return cameraFeatures.getResolution().getRecordingProfile();
+  }
+
+  /** Shortut to get deviceOrientationListener. */
+  DeviceOrientationManager getDeviceOrientationManager() {
+    return cameraFeatures.getSensorOrientation().getDeviceOrientationManager();
+  }
+
+  /**
+   * Sets zoom level from dart.
+   *
+   * @param result Flutter result.
+   * @param zoom new value.
+   */
   public void setZoomLevel(@NonNull final Result result, float zoom) throws CameraAccessException {
-    float maxZoom = cameraZoom.maxZoom;
-    float minZoom = CameraZoom.DEFAULT_ZOOM_FACTOR;
+    final ZoomLevelFeature zoomLevel = cameraFeatures.getZoomLevel();
+    float maxZoom = zoomLevel.getMaximumZoomLevel();
+    float minZoom = zoomLevel.getMinimumZoomLevel();
 
     if (zoom > maxZoom || zoom < minZoom) {
       String errorMessage =
@@ -974,122 +940,31 @@
       return;
     }
 
-    //Zoom area is calculated relative to sensor area (activeRect)
-    if (captureRequestBuilder != null) {
-      final Rect computedZoom = cameraZoom.computeZoom(zoom);
-      captureRequestBuilder.set(CaptureRequest.SCALER_CROP_REGION, computedZoom);
-      cameraCaptureSession.setRepeatingRequest(captureRequestBuilder.build(), null, null);
-    }
+    zoomLevel.setValue(zoom);
+    zoomLevel.updateBuilder(previewRequestBuilder);
 
-    result.success(null);
+    refreshPreviewCaptureSession(
+        () -> result.success(null),
+        (code, message) -> result.error("setZoomLevelFailed", "Could not set zoom level.", null));
   }
 
+  /**
+   * Lock capture orientation from dart.
+   *
+   * @param orientation new orientation.
+   */
   public void lockCaptureOrientation(PlatformChannel.DeviceOrientation orientation) {
-    this.lockedCaptureOrientation = orientation;
+    cameraFeatures.getSensorOrientation().lockCaptureOrientation(orientation);
   }
 
+  /** Unlock capture orientation from dart. */
   public void unlockCaptureOrientation() {
-    this.lockedCaptureOrientation = null;
-  }
-
-  private void updateFpsRange() {
-    if (fpsRange == null) {
-      return;
-    }
-
-    captureRequestBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, fpsRange);
-  }
-
-  private void updateFocus(FocusMode mode) {
-    if (useAutoFocus) {
-      int[] modes = cameraCharacteristics.get(CameraCharacteristics.CONTROL_AF_AVAILABLE_MODES);
-      // Auto focus is not supported
-      if (modes == null
-          || modes.length == 0
-          || (modes.length == 1 && modes[0] == CameraCharacteristics.CONTROL_AF_MODE_OFF)) {
-        useAutoFocus = false;
-        captureRequestBuilder.set(
-            CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_OFF);
-      } else {
-        // Applying auto focus
-        switch (mode) {
-          case locked:
-            captureRequestBuilder.set(
-                CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_AUTO);
-            break;
-          case auto:
-            captureRequestBuilder.set(
-                CaptureRequest.CONTROL_AF_MODE,
-                recordingVideo
-                    ? CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_VIDEO
-                    : CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
-          default:
-            break;
-        }
-        MeteringRectangle afRect = cameraRegions.getAFMeteringRectangle();
-        captureRequestBuilder.set(
-            CaptureRequest.CONTROL_AF_REGIONS,
-            afRect == null ? null : new MeteringRectangle[] {afRect});
-      }
-    } else {
-      captureRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_OFF);
-    }
-  }
-
-  private void updateExposure(ExposureMode mode) {
-    exposureMode = mode;
-
-    // Applying auto exposure
-    MeteringRectangle aeRect = cameraRegions.getAEMeteringRectangle();
-    captureRequestBuilder.set(
-        CaptureRequest.CONTROL_AE_REGIONS,
-        aeRect == null ? null : new MeteringRectangle[] {cameraRegions.getAEMeteringRectangle()});
-
-    switch (mode) {
-      case locked:
-        captureRequestBuilder.set(CaptureRequest.CONTROL_AE_LOCK, true);
-        break;
-      case auto:
-      default:
-        captureRequestBuilder.set(CaptureRequest.CONTROL_AE_LOCK, false);
-        break;
-    }
-
-    captureRequestBuilder.set(CaptureRequest.CONTROL_AE_EXPOSURE_COMPENSATION, exposureOffset);
-  }
-
-  private void updateFlash(FlashMode mode) {
-    // Get flash
-    flashMode = mode;
-
-    // Applying flash modes
-    switch (flashMode) {
-      case off:
-        captureRequestBuilder.set(
-            CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON);
-        captureRequestBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_OFF);
-        break;
-      case auto:
-        captureRequestBuilder.set(
-            CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
-        captureRequestBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_OFF);
-        break;
-      case always:
-        captureRequestBuilder.set(
-            CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_ALWAYS_FLASH);
-        captureRequestBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_OFF);
-        break;
-      case torch:
-      default:
-        captureRequestBuilder.set(
-            CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON);
-        captureRequestBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_TORCH);
-        break;
-    }
+    cameraFeatures.getSensorOrientation().unlockCaptureOrientation();
   }
 
   public void startPreview() throws CameraAccessException {
     if (pictureImageReader == null || pictureImageReader.getSurface() == null) return;
+    Log.i(TAG, "startPreview");
 
     createCaptureSession(CameraDevice.TEMPLATE_PREVIEW, pictureImageReader.getSurface());
   }
@@ -1097,6 +972,7 @@
   public void startPreviewWithImageStream(EventChannel imageStreamChannel)
       throws CameraAccessException {
     createCaptureSession(CameraDevice.TEMPLATE_RECORD, imageStreamReader.getSurface());
+    Log.i(TAG, "startPreviewWithImageStream");
 
     imageStreamChannel.setStreamHandler(
         new EventChannel.StreamHandler() {
@@ -1107,15 +983,43 @@
 
           @Override
           public void onCancel(Object o) {
-            imageStreamReader.setOnImageAvailableListener(null, null);
+            imageStreamReader.setOnImageAvailableListener(null, backgroundHandler);
           }
         });
   }
 
+  /**
+   * This a callback object for the {@link ImageReader}. "onImageAvailable" will be called when a
+   * still image is ready to be saved.
+   */
+  @Override
+  public void onImageAvailable(ImageReader reader) {
+    Log.i(TAG, "onImageAvailable");
+
+    backgroundHandler.post(
+        new ImageSaver(
+            // Use acquireNextImage since image reader is only for one image.
+            reader.acquireNextImage(),
+            captureFile,
+            new ImageSaver.Callback() {
+              @Override
+              public void onComplete(String absolutePath) {
+                dartMessenger.finish(flutterResult, absolutePath);
+              }
+
+              @Override
+              public void onError(String errorCode, String errorMessage) {
+                dartMessenger.error(flutterResult, errorCode, errorMessage, null);
+              }
+            }));
+    cameraCaptureCallback.setCameraState(CameraState.STATE_PREVIEW);
+  }
+
   private void setImageStreamImageAvailableListener(final EventChannel.EventSink imageStreamSink) {
     imageStreamReader.setOnImageAvailableListener(
         reader -> {
-          Image img = reader.acquireLatestImage();
+          // Use acquireNextImage since image reader is only for one image.
+          Image img = reader.acquireNextImage();
           if (img == null) return;
 
           List<Map<String, Object>> planes = new ArrayList<>();
@@ -1139,41 +1043,24 @@
           imageBuffer.put("format", img.getFormat());
           imageBuffer.put("planes", planes);
 
-          imageStreamSink.success(imageBuffer);
+          final Handler handler = new Handler(Looper.getMainLooper());
+          handler.post(() -> imageStreamSink.success(imageBuffer));
           img.close();
         },
-        null);
-  }
-
-  public void stopImageStream() throws CameraAccessException {
-    if (imageStreamReader != null) {
-      imageStreamReader.setOnImageAvailableListener(null, null);
-    }
-    startPreview();
-  }
-
-  /** Sets the time the pre-capture sequence started. */
-  private void setPreCaptureStartTime() {
-    preCaptureStartTime = SystemClock.elapsedRealtime();
-  }
-
-  /**
-   * Check if the timeout for the pre-capture sequence has been reached.
-   *
-   * @return true if the timeout is reached; otherwise false is returned.
-   */
-  private boolean hitPreCaptureTimeout() {
-    return (SystemClock.elapsedRealtime() - preCaptureStartTime) > PRECAPTURE_TIMEOUT_MS;
+        backgroundHandler);
   }
 
   private void closeCaptureSession() {
-    if (cameraCaptureSession != null) {
-      cameraCaptureSession.close();
-      cameraCaptureSession = null;
+    if (captureSession != null) {
+      Log.i(TAG, "closeCaptureSession");
+
+      captureSession.close();
+      captureSession = null;
     }
   }
 
   public void close() {
+    Log.i(TAG, "close");
     closeCaptureSession();
 
     if (cameraDevice != null) {
@@ -1193,11 +1080,15 @@
       mediaRecorder.release();
       mediaRecorder = null;
     }
+
+    stopBackgroundThread();
   }
 
   public void dispose() {
+    Log.i(TAG, "dispose");
+
     close();
     flutterTexture.release();
-    deviceOrientationListener.stop();
+    getDeviceOrientationManager().stop();
   }
 }
diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraPlugin.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraPlugin.java
index 75730ab..ef3a2b9 100644
--- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraPlugin.java
+++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraPlugin.java
@@ -8,9 +8,11 @@
 import android.os.Build;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.lifecycle.Lifecycle;
 import io.flutter.embedding.engine.plugins.FlutterPlugin;
 import io.flutter.embedding.engine.plugins.activity.ActivityAware;
 import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding;
+import io.flutter.embedding.engine.plugins.lifecycle.FlutterLifecycleAdapter;
 import io.flutter.plugin.common.BinaryMessenger;
 import io.flutter.plugins.camera.CameraPermissions.PermissionsRegistry;
 import io.flutter.view.TextureRegistry;
@@ -51,7 +53,8 @@
         registrar.activity(),
         registrar.messenger(),
         registrar::addRequestPermissionsResultListener,
-        registrar.view());
+        registrar.view(),
+        null);
   }
 
   @Override
@@ -70,18 +73,17 @@
         binding.getActivity(),
         flutterPluginBinding.getBinaryMessenger(),
         binding::addRequestPermissionsResultListener,
-        flutterPluginBinding.getTextureRegistry());
+        flutterPluginBinding.getTextureRegistry(),
+        FlutterLifecycleAdapter.getActivityLifecycle(binding));
   }
 
   @Override
   public void onDetachedFromActivity() {
-    if (methodCallHandler == null) {
-      // Could be on too low of an SDK to have started listening originally.
-      return;
+    // Could be on too low of an SDK to have started listening originally.
+    if (methodCallHandler != null) {
+      methodCallHandler.stopListening();
+      methodCallHandler = null;
     }
-
-    methodCallHandler.stopListening();
-    methodCallHandler = null;
   }
 
   @Override
@@ -98,7 +100,8 @@
       Activity activity,
       BinaryMessenger messenger,
       PermissionsRegistry permissionsRegistry,
-      TextureRegistry textureRegistry) {
+      TextureRegistry textureRegistry,
+      @Nullable Lifecycle lifecycle) {
     if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
       // If the sdk is less than 21 (min sdk for Camera2) we don't register the plugin.
       return;
@@ -106,6 +109,11 @@
 
     methodCallHandler =
         new MethodCallHandlerImpl(
-            activity, messenger, new CameraPermissions(), permissionsRegistry, textureRegistry);
+            activity,
+            messenger,
+            new CameraPermissions(),
+            permissionsRegistry,
+            textureRegistry,
+            lifecycle);
   }
 }
diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraRegionUtils.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraRegionUtils.java
index ff8a49f..951a279 100644
--- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraRegionUtils.java
+++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraRegionUtils.java
@@ -11,6 +11,7 @@
 import android.util.Size;
 import androidx.annotation.NonNull;
 import androidx.annotation.VisibleForTesting;
+import io.flutter.embedding.engine.systemchannels.PlatformChannel;
 import java.util.Arrays;
 
 /**
@@ -69,11 +70,32 @@
    *     boundaries.
    */
   public static MeteringRectangle convertPointToMeteringRectangle(
-      @NonNull Size boundaries, double x, double y) {
+      @NonNull Size boundaries,
+      double x,
+      double y,
+      @NonNull PlatformChannel.DeviceOrientation orientation) {
     assert (boundaries.getWidth() > 0 && boundaries.getHeight() > 0);
     assert (x >= 0 && x <= 1);
     assert (y >= 0 && y <= 1);
-
+    // Rotate the coordinates to match the device orientation.
+    double oldX = x, oldY = y;
+    switch (orientation) {
+      case PORTRAIT_UP: // 90 ccw.
+        y = 1 - oldX;
+        x = oldY;
+        break;
+      case PORTRAIT_DOWN: // 90 cw.
+        x = 1 - oldY;
+        y = oldX;
+        break;
+      case LANDSCAPE_LEFT:
+        // No rotation required.
+        break;
+      case LANDSCAPE_RIGHT: // 180.
+        x = 1 - x;
+        y = 1 - y;
+        break;
+    }
     // Interpolate the target coordinate.
     int targetX = (int) Math.round(x * ((double) (boundaries.getWidth() - 1)));
     int targetY = (int) Math.round(y * ((double) (boundaries.getHeight() - 1)));
@@ -98,7 +120,6 @@
     if (targetY > maxTargetY) {
       targetY = maxTargetY;
     }
-
     // Build the metering rectangle.
     return MeteringRectangleFactory.create(targetX, targetY, targetWidth, targetHeight, 1);
   }
@@ -130,7 +151,7 @@
      * @param width width >= 0.
      * @param height height >= 0.
      * @param meteringWeight weight between {@value MeteringRectangle#METERING_WEIGHT_MIN} and
-     *     {@value MeteringRectangle#METERING_WEIGHT_MAX} inclusively
+     *     {@value MeteringRectangle#METERING_WEIGHT_MAX} inclusively.
      * @return new instance of the {@link MeteringRectangle} class.
      * @throws IllegalArgumentException if any of the parameters were negative.
      */
diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraRegions.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraRegions.java
deleted file mode 100644
index 60c866c..0000000
--- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraRegions.java
+++ /dev/null
@@ -1,76 +0,0 @@
-// Copyright 2013 The Flutter Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package io.flutter.plugins.camera;
-
-import android.hardware.camera2.params.MeteringRectangle;
-import android.util.Size;
-
-public final class CameraRegions {
-  private MeteringRectangle aeMeteringRectangle;
-  private MeteringRectangle afMeteringRectangle;
-  private Size maxBoundaries;
-
-  public CameraRegions(Size maxBoundaries) {
-    assert (maxBoundaries == null || maxBoundaries.getWidth() > 0);
-    assert (maxBoundaries == null || maxBoundaries.getHeight() > 0);
-    this.maxBoundaries = maxBoundaries;
-  }
-
-  public MeteringRectangle getAEMeteringRectangle() {
-    return aeMeteringRectangle;
-  }
-
-  public MeteringRectangle getAFMeteringRectangle() {
-    return afMeteringRectangle;
-  }
-
-  public Size getMaxBoundaries() {
-    return this.maxBoundaries;
-  }
-
-  public void resetAutoExposureMeteringRectangle() {
-    this.aeMeteringRectangle = null;
-  }
-
-  public void setAutoExposureMeteringRectangleFromPoint(double x, double y) {
-    this.aeMeteringRectangle = getMeteringRectangleForPoint(maxBoundaries, x, y);
-  }
-
-  public void resetAutoFocusMeteringRectangle() {
-    this.afMeteringRectangle = null;
-  }
-
-  public void setAutoFocusMeteringRectangleFromPoint(double x, double y) {
-    this.afMeteringRectangle = getMeteringRectangleForPoint(maxBoundaries, x, y);
-  }
-
-  public MeteringRectangle getMeteringRectangleForPoint(Size maxBoundaries, double x, double y) {
-    assert (x >= 0 && x <= 1);
-    assert (y >= 0 && y <= 1);
-    if (maxBoundaries == null)
-      throw new IllegalStateException(
-          "Functionality for managing metering rectangles is unavailable as this CameraRegions instance was initialized with null boundaries.");
-
-    // Interpolate the target coordinate
-    int targetX = (int) Math.round(x * ((double) (maxBoundaries.getWidth() - 1)));
-    int targetY = (int) Math.round(y * ((double) (maxBoundaries.getHeight() - 1)));
-    // Determine the dimensions of the metering triangle (10th of the viewport)
-    int targetWidth = (int) Math.round(((double) maxBoundaries.getWidth()) / 10d);
-    int targetHeight = (int) Math.round(((double) maxBoundaries.getHeight()) / 10d);
-    // Adjust target coordinate to represent top-left corner of metering rectangle
-    targetX -= targetWidth / 2;
-    targetY -= targetHeight / 2;
-    // Adjust target coordinate as to not fall out of bounds
-    if (targetX < 0) targetX = 0;
-    if (targetY < 0) targetY = 0;
-    int maxTargetX = maxBoundaries.getWidth() - 1 - targetWidth;
-    int maxTargetY = maxBoundaries.getHeight() - 1 - targetHeight;
-    if (targetX > maxTargetX) targetX = maxTargetX;
-    if (targetY > maxTargetY) targetY = maxTargetY;
-
-    // Build the metering rectangle
-    return new MeteringRectangle(targetX, targetY, targetWidth, targetHeight, 1);
-  }
-}
diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraUtils.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraUtils.java
index b4d4689..003d80a 100644
--- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraUtils.java
+++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraUtils.java
@@ -6,20 +6,12 @@
 
 import android.app.Activity;
 import android.content.Context;
-import android.graphics.ImageFormat;
 import android.hardware.camera2.CameraAccessException;
 import android.hardware.camera2.CameraCharacteristics;
 import android.hardware.camera2.CameraManager;
 import android.hardware.camera2.CameraMetadata;
-import android.hardware.camera2.params.StreamConfigurationMap;
-import android.media.CamcorderProfile;
-import android.util.Size;
 import io.flutter.embedding.engine.systemchannels.PlatformChannel;
-import io.flutter.plugins.camera.types.ResolutionPreset;
 import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Comparator;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -29,23 +21,24 @@
 
   private CameraUtils() {}
 
-  static PlatformChannel.DeviceOrientation getDeviceOrientationFromDegrees(int degrees) {
-    // Round to the nearest 90 degrees.
-    degrees = (int) (Math.round(degrees / 90.0) * 90) % 360;
-    // Determine the corresponding device orientation.
-    switch (degrees) {
-      case 90:
-        return PlatformChannel.DeviceOrientation.LANDSCAPE_LEFT;
-      case 180:
-        return PlatformChannel.DeviceOrientation.PORTRAIT_DOWN;
-      case 270:
-        return PlatformChannel.DeviceOrientation.LANDSCAPE_RIGHT;
-      case 0:
-      default:
-        return PlatformChannel.DeviceOrientation.PORTRAIT_UP;
-    }
+  /**
+   * Gets the {@link CameraManager} singleton.
+   *
+   * @param context The context to get the {@link CameraManager} singleton from.
+   * @return The {@link CameraManager} singleton.
+   */
+  static CameraManager getCameraManager(Context context) {
+    return (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
   }
 
+  /**
+   * Serializes the {@link PlatformChannel.DeviceOrientation} to a string value.
+   *
+   * @param orientation The orientation to serialize.
+   * @return The serialized orientation.
+   * @throws UnsupportedOperationException when the provided orientation not have a corresponding
+   *     string value.
+   */
   static String serializeDeviceOrientation(PlatformChannel.DeviceOrientation orientation) {
     if (orientation == null)
       throw new UnsupportedOperationException("Could not serialize null device orientation.");
@@ -64,6 +57,15 @@
     }
   }
 
+  /**
+   * Deserializes a string value to its corresponding {@link PlatformChannel.DeviceOrientation}
+   * value.
+   *
+   * @param orientation The string value to deserialize.
+   * @return The deserialized orientation.
+   * @throws UnsupportedOperationException when the provided string value does not have a
+   *     corresponding {@link PlatformChannel.DeviceOrientation}.
+   */
   static PlatformChannel.DeviceOrientation deserializeDeviceOrientation(String orientation) {
     if (orientation == null)
       throw new UnsupportedOperationException("Could not deserialize null device orientation.");
@@ -82,23 +84,13 @@
     }
   }
 
-  static Size computeBestPreviewSize(String cameraName, ResolutionPreset preset) {
-    if (preset.ordinal() > ResolutionPreset.high.ordinal()) {
-      preset = ResolutionPreset.high;
-    }
-
-    CamcorderProfile profile =
-        getBestAvailableCamcorderProfileForResolutionPreset(cameraName, preset);
-    return new Size(profile.videoFrameWidth, profile.videoFrameHeight);
-  }
-
-  static Size computeBestCaptureSize(StreamConfigurationMap streamConfigurationMap) {
-    // For still image captures, we use the largest available size.
-    return Collections.max(
-        Arrays.asList(streamConfigurationMap.getOutputSizes(ImageFormat.JPEG)),
-        new CompareSizesByArea());
-  }
-
+  /**
+   * Gets all the available cameras for the device.
+   *
+   * @param activity The current Android activity.
+   * @return A map of all the available cameras, with their name as their key.
+   * @throws CameraAccessException when the camera could not be accessed.
+   */
   public static List<Map<String, Object>> getAvailableCameras(Activity activity)
       throws CameraAccessException {
     CameraManager cameraManager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE);
@@ -127,52 +119,4 @@
     }
     return cameras;
   }
-
-  static CamcorderProfile getBestAvailableCamcorderProfileForResolutionPreset(
-      String cameraName, ResolutionPreset preset) {
-    int cameraId = Integer.parseInt(cameraName);
-    switch (preset) {
-        // All of these cases deliberately fall through to get the best available profile.
-      case max:
-        if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_HIGH)) {
-          return CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_HIGH);
-        }
-      case ultraHigh:
-        if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_2160P)) {
-          return CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_2160P);
-        }
-      case veryHigh:
-        if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_1080P)) {
-          return CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_1080P);
-        }
-      case high:
-        if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_720P)) {
-          return CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_720P);
-        }
-      case medium:
-        if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_480P)) {
-          return CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_480P);
-        }
-      case low:
-        if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_QVGA)) {
-          return CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_QVGA);
-        }
-      default:
-        if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_LOW)) {
-          return CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_LOW);
-        } else {
-          throw new IllegalArgumentException(
-              "No capture session available for current capture session.");
-        }
-    }
-  }
-
-  private static class CompareSizesByArea implements Comparator<Size> {
-    @Override
-    public int compare(Size lhs, Size rhs) {
-      // We cast here to ensure the multiplications won't overflow.
-      return Long.signum(
-          (long) lhs.getWidth() * lhs.getHeight() - (long) rhs.getWidth() * rhs.getHeight());
-    }
-  }
 }
diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/DartMessenger.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/DartMessenger.java
index 93b963e..dc62fce 100644
--- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/DartMessenger.java
+++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/DartMessenger.java
@@ -11,8 +11,8 @@
 import io.flutter.embedding.engine.systemchannels.PlatformChannel;
 import io.flutter.plugin.common.BinaryMessenger;
 import io.flutter.plugin.common.MethodChannel;
-import io.flutter.plugins.camera.types.ExposureMode;
-import io.flutter.plugins.camera.types.FocusMode;
+import io.flutter.plugins.camera.features.autofocus.FocusMode;
+import io.flutter.plugins.camera.features.exposurelock.ExposureMode;
 import java.util.HashMap;
 import java.util.Map;
 
@@ -178,4 +178,28 @@
           }
         });
   }
+
+  /**
+   * Send a success payload to a {@link MethodChannel.Result} on the main thread.
+   *
+   * @param payload The payload to send.
+   */
+  public void finish(MethodChannel.Result result, Object payload) {
+    handler.post(() -> result.success(payload));
+  }
+
+  /**
+   * Send an error payload to a {@link MethodChannel.Result} on the main thread.
+   *
+   * @param errorCode error code.
+   * @param errorMessage error message.
+   * @param errorDetails error details.
+   */
+  public void error(
+      MethodChannel.Result result,
+      String errorCode,
+      @Nullable String errorMessage,
+      @Nullable Object errorDetails) {
+    handler.post(() -> result.error(errorCode, errorMessage, errorDetails));
+  }
 }
diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/DeviceOrientationManager.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/DeviceOrientationManager.java
deleted file mode 100644
index 634596d..0000000
--- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/DeviceOrientationManager.java
+++ /dev/null
@@ -1,200 +0,0 @@
-// Copyright 2013 The Flutter Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package io.flutter.plugins.camera;
-
-import android.app.Activity;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.res.Configuration;
-import android.hardware.SensorManager;
-import android.provider.Settings;
-import android.view.Display;
-import android.view.OrientationEventListener;
-import android.view.Surface;
-import android.view.WindowManager;
-import io.flutter.embedding.engine.systemchannels.PlatformChannel;
-
-class DeviceOrientationManager {
-
-  private static final IntentFilter orientationIntentFilter =
-      new IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED);
-
-  private final Activity activity;
-  private final DartMessenger messenger;
-  private final boolean isFrontFacing;
-  private final int sensorOrientation;
-  private PlatformChannel.DeviceOrientation lastOrientation;
-  private OrientationEventListener orientationEventListener;
-  private BroadcastReceiver broadcastReceiver;
-
-  public DeviceOrientationManager(
-      Activity activity, DartMessenger messenger, boolean isFrontFacing, int sensorOrientation) {
-    this.activity = activity;
-    this.messenger = messenger;
-    this.isFrontFacing = isFrontFacing;
-    this.sensorOrientation = sensorOrientation;
-  }
-
-  public void start() {
-    startSensorListener();
-    startUIListener();
-  }
-
-  public void stop() {
-    stopSensorListener();
-    stopUIListener();
-  }
-
-  public int getMediaOrientation() {
-    return this.getMediaOrientation(this.lastOrientation);
-  }
-
-  public int getMediaOrientation(PlatformChannel.DeviceOrientation orientation) {
-    int angle = 0;
-
-    // Fallback to device orientation when the orientation value is null
-    if (orientation == null) {
-      orientation = getUIOrientation();
-    }
-
-    switch (orientation) {
-      case PORTRAIT_UP:
-        angle = 0;
-        break;
-      case PORTRAIT_DOWN:
-        angle = 180;
-        break;
-      case LANDSCAPE_LEFT:
-        angle = 90;
-        break;
-      case LANDSCAPE_RIGHT:
-        angle = 270;
-        break;
-    }
-    if (isFrontFacing) angle *= -1;
-    return (angle + sensorOrientation + 360) % 360;
-  }
-
-  private void startSensorListener() {
-    if (orientationEventListener != null) return;
-    orientationEventListener =
-        new OrientationEventListener(activity, SensorManager.SENSOR_DELAY_NORMAL) {
-          @Override
-          public void onOrientationChanged(int angle) {
-            if (!isSystemAutoRotationLocked()) {
-              PlatformChannel.DeviceOrientation newOrientation = calculateSensorOrientation(angle);
-              if (!newOrientation.equals(lastOrientation)) {
-                lastOrientation = newOrientation;
-                messenger.sendDeviceOrientationChangeEvent(newOrientation);
-              }
-            }
-          }
-        };
-    if (orientationEventListener.canDetectOrientation()) {
-      orientationEventListener.enable();
-    }
-  }
-
-  private void startUIListener() {
-    if (broadcastReceiver != null) return;
-    broadcastReceiver =
-        new BroadcastReceiver() {
-          @Override
-          public void onReceive(Context context, Intent intent) {
-            if (isSystemAutoRotationLocked()) {
-              PlatformChannel.DeviceOrientation orientation = getUIOrientation();
-              if (!orientation.equals(lastOrientation)) {
-                lastOrientation = orientation;
-                messenger.sendDeviceOrientationChangeEvent(orientation);
-              }
-            }
-          }
-        };
-    activity.registerReceiver(broadcastReceiver, orientationIntentFilter);
-    broadcastReceiver.onReceive(activity, null);
-  }
-
-  private void stopSensorListener() {
-    if (orientationEventListener == null) return;
-    orientationEventListener.disable();
-    orientationEventListener = null;
-  }
-
-  private void stopUIListener() {
-    if (broadcastReceiver == null) return;
-    activity.unregisterReceiver(broadcastReceiver);
-    broadcastReceiver = null;
-  }
-
-  private boolean isSystemAutoRotationLocked() {
-    return android.provider.Settings.System.getInt(
-            activity.getContentResolver(), Settings.System.ACCELEROMETER_ROTATION, 0)
-        != 1;
-  }
-
-  private PlatformChannel.DeviceOrientation getUIOrientation() {
-    final int rotation = getDisplay().getRotation();
-    final int orientation = activity.getResources().getConfiguration().orientation;
-
-    switch (orientation) {
-      case Configuration.ORIENTATION_PORTRAIT:
-        if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_90) {
-          return PlatformChannel.DeviceOrientation.PORTRAIT_UP;
-        } else {
-          return PlatformChannel.DeviceOrientation.PORTRAIT_DOWN;
-        }
-      case Configuration.ORIENTATION_LANDSCAPE:
-        if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_90) {
-          return PlatformChannel.DeviceOrientation.LANDSCAPE_LEFT;
-        } else {
-          return PlatformChannel.DeviceOrientation.LANDSCAPE_RIGHT;
-        }
-      default:
-        return PlatformChannel.DeviceOrientation.PORTRAIT_UP;
-    }
-  }
-
-  private PlatformChannel.DeviceOrientation calculateSensorOrientation(int angle) {
-    final int tolerance = 45;
-    angle += tolerance;
-
-    // Orientation is 0 in the default orientation mode. This is portait-mode for phones
-    // and landscape for tablets. We have to compensate for this by calculating the default
-    // orientation, and apply an offset accordingly.
-    int defaultDeviceOrientation = getDeviceDefaultOrientation();
-    if (defaultDeviceOrientation == Configuration.ORIENTATION_LANDSCAPE) {
-      angle += 90;
-    }
-    // Determine the orientation
-    angle = angle % 360;
-    return new PlatformChannel.DeviceOrientation[] {
-          PlatformChannel.DeviceOrientation.PORTRAIT_UP,
-          PlatformChannel.DeviceOrientation.LANDSCAPE_LEFT,
-          PlatformChannel.DeviceOrientation.PORTRAIT_DOWN,
-          PlatformChannel.DeviceOrientation.LANDSCAPE_RIGHT,
-        }
-        [angle / 90];
-  }
-
-  private int getDeviceDefaultOrientation() {
-    Configuration config = activity.getResources().getConfiguration();
-    int rotation = getDisplay().getRotation();
-    if (((rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180)
-            && config.orientation == Configuration.ORIENTATION_LANDSCAPE)
-        || ((rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270)
-            && config.orientation == Configuration.ORIENTATION_PORTRAIT)) {
-      return Configuration.ORIENTATION_LANDSCAPE;
-    } else {
-      return Configuration.ORIENTATION_PORTRAIT;
-    }
-  }
-
-  @SuppressWarnings("deprecation")
-  private Display getDisplay() {
-    return ((WindowManager) activity.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
-  }
-}
diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/MethodCallHandlerImpl.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/MethodCallHandlerImpl.java
index 50bca63..893785f 100644
--- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/MethodCallHandlerImpl.java
+++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/MethodCallHandlerImpl.java
@@ -10,6 +10,8 @@
 import android.os.Looper;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleObserver;
 import io.flutter.embedding.engine.systemchannels.PlatformChannel;
 import io.flutter.plugin.common.BinaryMessenger;
 import io.flutter.plugin.common.EventChannel;
@@ -17,14 +19,17 @@
 import io.flutter.plugin.common.MethodChannel;
 import io.flutter.plugin.common.MethodChannel.Result;
 import io.flutter.plugins.camera.CameraPermissions.PermissionsRegistry;
-import io.flutter.plugins.camera.types.ExposureMode;
-import io.flutter.plugins.camera.types.FlashMode;
-import io.flutter.plugins.camera.types.FocusMode;
+import io.flutter.plugins.camera.features.CameraFeatureFactoryImpl;
+import io.flutter.plugins.camera.features.Point;
+import io.flutter.plugins.camera.features.autofocus.FocusMode;
+import io.flutter.plugins.camera.features.exposurelock.ExposureMode;
+import io.flutter.plugins.camera.features.flash.FlashMode;
+import io.flutter.plugins.camera.features.resolution.ResolutionPreset;
 import io.flutter.view.TextureRegistry;
 import java.util.HashMap;
 import java.util.Map;
 
-final class MethodCallHandlerImpl implements MethodChannel.MethodCallHandler {
+final class MethodCallHandlerImpl implements MethodChannel.MethodCallHandler, LifecycleObserver {
   private final Activity activity;
   private final BinaryMessenger messenger;
   private final CameraPermissions cameraPermissions;
@@ -32,6 +37,7 @@
   private final TextureRegistry textureRegistry;
   private final MethodChannel methodChannel;
   private final EventChannel imageStreamChannel;
+  private final Lifecycle lifecycle;
   private @Nullable Camera camera;
 
   MethodCallHandlerImpl(
@@ -39,12 +45,14 @@
       BinaryMessenger messenger,
       CameraPermissions cameraPermissions,
       PermissionsRegistry permissionsAdder,
-      TextureRegistry textureRegistry) {
+      TextureRegistry textureRegistry,
+      @Nullable Lifecycle lifecycle) {
     this.activity = activity;
     this.messenger = messenger;
     this.cameraPermissions = cameraPermissions;
     this.permissionsRegistry = permissionsAdder;
     this.textureRegistry = textureRegistry;
+    this.lifecycle = lifecycle;
 
     methodChannel = new MethodChannel(messenger, "plugins.flutter.io/camera");
     imageStreamChannel = new EventChannel(messenger, "plugins.flutter.io/camera/imageStream");
@@ -172,7 +180,7 @@
             y = call.argument("y");
           }
           try {
-            camera.setExposurePoint(result, x, y);
+            camera.setExposurePoint(result, new Point(x, y));
           } catch (Exception e) {
             handleException(e, result);
           }
@@ -239,7 +247,7 @@
             y = call.argument("y");
           }
           try {
-            camera.setFocusPoint(result, x, y);
+            camera.setFocusPoint(result, new Point(x, y));
           } catch (Exception e) {
             handleException(e, result);
           }
@@ -351,22 +359,36 @@
 
   private void instantiateCamera(MethodCall call, Result result) throws CameraAccessException {
     String cameraName = call.argument("cameraName");
-    String resolutionPreset = call.argument("resolutionPreset");
+    String preset = call.argument("resolutionPreset");
     boolean enableAudio = call.argument("enableAudio");
+
     TextureRegistry.SurfaceTextureEntry flutterSurfaceTexture =
         textureRegistry.createSurfaceTexture();
     DartMessenger dartMessenger =
         new DartMessenger(
             messenger, flutterSurfaceTexture.id(), new Handler(Looper.getMainLooper()));
+    CameraProperties cameraProperties =
+        new CameraPropertiesImpl(cameraName, CameraUtils.getCameraManager(activity));
+    ResolutionPreset resolutionPreset = ResolutionPreset.valueOf(preset);
+
+    if (camera != null && lifecycle != null) {
+      lifecycle.removeObserver(camera);
+    }
+
     camera =
         new Camera(
             activity,
             flutterSurfaceTexture,
+            new CameraFeatureFactoryImpl(),
             dartMessenger,
-            cameraName,
+            cameraProperties,
             resolutionPreset,
             enableAudio);
 
+    if (lifecycle != null) {
+      lifecycle.addObserver(camera);
+    }
+
     Map<String, Object> reply = new HashMap<>();
     reply.put("cameraId", flutterSurfaceTexture.id());
     result.success(reply);
diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/PictureCaptureRequest.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/PictureCaptureRequest.java
deleted file mode 100644
index 4c11e2d..0000000
--- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/PictureCaptureRequest.java
+++ /dev/null
@@ -1,96 +0,0 @@
-// Copyright 2013 The Flutter Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package io.flutter.plugins.camera;
-
-import android.os.Handler;
-import android.os.Looper;
-import androidx.annotation.Nullable;
-import io.flutter.plugin.common.MethodChannel;
-
-class PictureCaptureRequest {
-
-  enum State {
-    idle,
-    focusing,
-    preCapture,
-    waitingPreCaptureReady,
-    capturing,
-    finished,
-    error,
-  }
-
-  private final Runnable timeoutCallback =
-      new Runnable() {
-        @Override
-        public void run() {
-          error("captureTimeout", "Picture capture request timed out", state.toString());
-        }
-      };
-
-  private final MethodChannel.Result result;
-  private final TimeoutHandler timeoutHandler;
-  private State state;
-
-  public PictureCaptureRequest(MethodChannel.Result result) {
-    this(result, new TimeoutHandler());
-  }
-
-  public PictureCaptureRequest(MethodChannel.Result result, TimeoutHandler timeoutHandler) {
-    this.result = result;
-    this.state = State.idle;
-    this.timeoutHandler = timeoutHandler;
-  }
-
-  public void setState(State state) {
-    if (isFinished()) throw new IllegalStateException("Request has already been finished");
-    this.state = state;
-    if (state != State.idle && state != State.finished && state != State.error) {
-      this.timeoutHandler.resetTimeout(timeoutCallback);
-    } else {
-      this.timeoutHandler.clearTimeout(timeoutCallback);
-    }
-  }
-
-  public State getState() {
-    return state;
-  }
-
-  public boolean isFinished() {
-    return state == State.finished || state == State.error;
-  }
-
-  public void finish(String absolutePath) {
-    if (isFinished()) throw new IllegalStateException("Request has already been finished");
-    this.timeoutHandler.clearTimeout(timeoutCallback);
-    result.success(absolutePath);
-    state = State.finished;
-  }
-
-  public void error(
-      String errorCode, @Nullable String errorMessage, @Nullable Object errorDetails) {
-    if (isFinished()) throw new IllegalStateException("Request has already been finished");
-    this.timeoutHandler.clearTimeout(timeoutCallback);
-    result.error(errorCode, errorMessage, errorDetails);
-    state = State.error;
-  }
-
-  static class TimeoutHandler {
-    private static final int REQUEST_TIMEOUT = 5000;
-    private final Handler handler;
-
-    TimeoutHandler() {
-      this.handler = new Handler(Looper.getMainLooper());
-    }
-
-    public void resetTimeout(Runnable runnable) {
-      clearTimeout(runnable);
-      handler.postDelayed(runnable, REQUEST_TIMEOUT);
-    }
-
-    public void clearTimeout(Runnable runnable) {
-      handler.removeCallbacks(runnable);
-    }
-  }
-}
diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/CameraFeatureFactory.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/CameraFeatureFactory.java
index 8d10c44..b91f9a1 100644
--- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/CameraFeatureFactory.java
+++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/CameraFeatureFactory.java
@@ -84,9 +84,13 @@
    *
    * @param cameraProperties instance of the CameraProperties class containing information about the
    *     cameras features.
+   * @param sensorOrientationFeature instance of the SensorOrientationFeature class containing
+   *     information about the sensor and device orientation.
    * @return newly created instance of the FocusPointFeature class.
    */
-  FocusPointFeature createFocusPointFeature(@NonNull CameraProperties cameraProperties);
+  FocusPointFeature createFocusPointFeature(
+      @NonNull CameraProperties cameraProperties,
+      @NonNull SensorOrientationFeature sensorOrientationFeature);
 
   /**
    * Creates a new instance of the FPS range feature.
@@ -126,9 +130,13 @@
    *
    * @param cameraProperties instance of the CameraProperties class containing information about the
    *     cameras features.
+   * @param sensorOrientationFeature instance of the SensorOrientationFeature class containing
+   *     information about the sensor and device orientation.
    * @return newly created instance of the ExposurePointFeature class.
    */
-  ExposurePointFeature createExposurePointFeature(@NonNull CameraProperties cameraProperties);
+  ExposurePointFeature createExposurePointFeature(
+      @NonNull CameraProperties cameraProperties,
+      @NonNull SensorOrientationFeature sensorOrientationFeature);
 
   /**
    * Creates a new instance of the noise reduction feature.
diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/CameraFeatureFactoryImpl.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/CameraFeatureFactoryImpl.java
index b12ad36..95a8c06 100644
--- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/CameraFeatureFactoryImpl.java
+++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/CameraFeatureFactoryImpl.java
@@ -59,8 +59,10 @@
   }
 
   @Override
-  public FocusPointFeature createFocusPointFeature(@NonNull CameraProperties cameraProperties) {
-    return new FocusPointFeature(cameraProperties);
+  public FocusPointFeature createFocusPointFeature(
+      @NonNull CameraProperties cameraProperties,
+      @NonNull SensorOrientationFeature sensorOrientationFeature) {
+    return new FocusPointFeature(cameraProperties, sensorOrientationFeature);
   }
 
   @Override
@@ -83,8 +85,9 @@
 
   @Override
   public ExposurePointFeature createExposurePointFeature(
-      @NonNull CameraProperties cameraProperties) {
-    return new ExposurePointFeature(cameraProperties);
+      @NonNull CameraProperties cameraProperties,
+      @NonNull SensorOrientationFeature sensorOrientationFeature) {
+    return new ExposurePointFeature(cameraProperties, sensorOrientationFeature);
   }
 
   @Override
diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/CameraFeatures.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/CameraFeatures.java
index 0ee8969..659fd15 100644
--- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/CameraFeatures.java
+++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/CameraFeatures.java
@@ -4,6 +4,9 @@
 
 package io.flutter.plugins.camera.features;
 
+import android.app.Activity;
+import io.flutter.plugins.camera.CameraProperties;
+import io.flutter.plugins.camera.DartMessenger;
 import io.flutter.plugins.camera.features.autofocus.AutoFocusFeature;
 import io.flutter.plugins.camera.features.exposurelock.ExposureLockFeature;
 import io.flutter.plugins.camera.features.exposureoffset.ExposureOffsetFeature;
@@ -13,6 +16,7 @@
 import io.flutter.plugins.camera.features.fpsrange.FpsRangeFeature;
 import io.flutter.plugins.camera.features.noisereduction.NoiseReductionFeature;
 import io.flutter.plugins.camera.features.resolution.ResolutionFeature;
+import io.flutter.plugins.camera.features.resolution.ResolutionPreset;
 import io.flutter.plugins.camera.features.sensororientation.SensorOrientationFeature;
 import io.flutter.plugins.camera.features.zoomlevel.ZoomLevelFeature;
 import java.util.Collection;
@@ -37,6 +41,39 @@
   private static final String SENSOR_ORIENTATION = "SENSOR_ORIENTATION";
   private static final String ZOOM_LEVEL = "ZOOM_LEVEL";
 
+  public static CameraFeatures init(
+      CameraFeatureFactory cameraFeatureFactory,
+      CameraProperties cameraProperties,
+      Activity activity,
+      DartMessenger dartMessenger,
+      ResolutionPreset resolutionPreset) {
+    CameraFeatures cameraFeatures = new CameraFeatures();
+    cameraFeatures.setAutoFocus(
+        cameraFeatureFactory.createAutoFocusFeature(cameraProperties, false));
+    cameraFeatures.setExposureLock(
+        cameraFeatureFactory.createExposureLockFeature(cameraProperties));
+    cameraFeatures.setExposureOffset(
+        cameraFeatureFactory.createExposureOffsetFeature(cameraProperties));
+    SensorOrientationFeature sensorOrientationFeature =
+        cameraFeatureFactory.createSensorOrientationFeature(
+            cameraProperties, activity, dartMessenger);
+    cameraFeatures.setSensorOrientation(sensorOrientationFeature);
+    cameraFeatures.setExposurePoint(
+        cameraFeatureFactory.createExposurePointFeature(
+            cameraProperties, sensorOrientationFeature));
+    cameraFeatures.setFlash(cameraFeatureFactory.createFlashFeature(cameraProperties));
+    cameraFeatures.setFocusPoint(
+        cameraFeatureFactory.createFocusPointFeature(cameraProperties, sensorOrientationFeature));
+    cameraFeatures.setFpsRange(cameraFeatureFactory.createFpsRangeFeature(cameraProperties));
+    cameraFeatures.setNoiseReduction(
+        cameraFeatureFactory.createNoiseReductionFeature(cameraProperties));
+    cameraFeatures.setResolution(
+        cameraFeatureFactory.createResolutionFeature(
+            cameraProperties, resolutionPreset, cameraProperties.getCameraName()));
+    cameraFeatures.setZoomLevel(cameraFeatureFactory.createZoomLevelFeature(cameraProperties));
+    return cameraFeatures;
+  }
+
   private Map<String, CameraFeature> featureMap = new HashMap<>();
 
   /**
diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/exposurepoint/ExposurePointFeature.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/exposurepoint/ExposurePointFeature.java
index 8c2ee61..336e756 100644
--- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/exposurepoint/ExposurePointFeature.java
+++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/exposurepoint/ExposurePointFeature.java
@@ -8,10 +8,12 @@
 import android.hardware.camera2.params.MeteringRectangle;
 import android.util.Size;
 import androidx.annotation.NonNull;
+import io.flutter.embedding.engine.systemchannels.PlatformChannel;
 import io.flutter.plugins.camera.CameraProperties;
 import io.flutter.plugins.camera.CameraRegionUtils;
 import io.flutter.plugins.camera.features.CameraFeature;
 import io.flutter.plugins.camera.features.Point;
+import io.flutter.plugins.camera.features.sensororientation.SensorOrientationFeature;
 
 /** Exposure point controls where in the frame exposure metering will come from. */
 public class ExposurePointFeature extends CameraFeature<Point> {
@@ -19,14 +21,17 @@
   private Size cameraBoundaries;
   private Point exposurePoint;
   private MeteringRectangle exposureRectangle;
+  private final SensorOrientationFeature sensorOrientationFeature;
 
   /**
    * Creates a new instance of the {@link ExposurePointFeature}.
    *
    * @param cameraProperties Collection of the characteristics for the current camera device.
    */
-  public ExposurePointFeature(CameraProperties cameraProperties) {
+  public ExposurePointFeature(
+      CameraProperties cameraProperties, SensorOrientationFeature sensorOrientationFeature) {
     super(cameraProperties);
+    this.sensorOrientationFeature = sensorOrientationFeature;
   }
 
   /**
@@ -80,9 +85,15 @@
     if (this.exposurePoint == null) {
       this.exposureRectangle = null;
     } else {
+      PlatformChannel.DeviceOrientation orientation =
+          this.sensorOrientationFeature.getLockedCaptureOrientation();
+      if (orientation == null) {
+        orientation =
+            this.sensorOrientationFeature.getDeviceOrientationManager().getLastUIOrientation();
+      }
       this.exposureRectangle =
           CameraRegionUtils.convertPointToMeteringRectangle(
-              this.cameraBoundaries, this.exposurePoint.x, this.exposurePoint.y);
+              this.cameraBoundaries, this.exposurePoint.x, this.exposurePoint.y, orientation);
     }
   }
 }
diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/focuspoint/FocusPointFeature.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/focuspoint/FocusPointFeature.java
index 92fcfa9..a3a0172 100644
--- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/focuspoint/FocusPointFeature.java
+++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/focuspoint/FocusPointFeature.java
@@ -8,10 +8,12 @@
 import android.hardware.camera2.params.MeteringRectangle;
 import android.util.Size;
 import androidx.annotation.NonNull;
+import io.flutter.embedding.engine.systemchannels.PlatformChannel;
 import io.flutter.plugins.camera.CameraProperties;
 import io.flutter.plugins.camera.CameraRegionUtils;
 import io.flutter.plugins.camera.features.CameraFeature;
 import io.flutter.plugins.camera.features.Point;
+import io.flutter.plugins.camera.features.sensororientation.SensorOrientationFeature;
 
 /** Focus point controls where in the frame focus will come from. */
 public class FocusPointFeature extends CameraFeature<Point> {
@@ -19,14 +21,17 @@
   private Size cameraBoundaries;
   private Point focusPoint;
   private MeteringRectangle focusRectangle;
+  private final SensorOrientationFeature sensorOrientationFeature;
 
   /**
    * Creates a new instance of the {@link FocusPointFeature}.
    *
    * @param cameraProperties Collection of the characteristics for the current camera device.
    */
-  public FocusPointFeature(CameraProperties cameraProperties) {
+  public FocusPointFeature(
+      CameraProperties cameraProperties, SensorOrientationFeature sensorOrientationFeature) {
     super(cameraProperties);
+    this.sensorOrientationFeature = sensorOrientationFeature;
   }
 
   /**
@@ -80,9 +85,15 @@
     if (this.focusPoint == null) {
       this.focusRectangle = null;
     } else {
+      PlatformChannel.DeviceOrientation orientation =
+          this.sensorOrientationFeature.getLockedCaptureOrientation();
+      if (orientation == null) {
+        orientation =
+            this.sensorOrientationFeature.getDeviceOrientationManager().getLastUIOrientation();
+      }
       this.focusRectangle =
           CameraRegionUtils.convertPointToMeteringRectangle(
-              this.cameraBoundaries, this.focusPoint.x, this.focusPoint.y);
+              this.cameraBoundaries, this.focusPoint.x, this.focusPoint.y, orientation);
     }
   }
 }
diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/noisereduction/NoiseReductionFeature.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/noisereduction/NoiseReductionFeature.java
index 847a817..408575b 100644
--- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/noisereduction/NoiseReductionFeature.java
+++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/noisereduction/NoiseReductionFeature.java
@@ -20,9 +20,15 @@
 public class NoiseReductionFeature extends CameraFeature<NoiseReductionMode> {
   private NoiseReductionMode currentSetting = NoiseReductionMode.fast;
 
-  private static final HashMap<NoiseReductionMode, Integer> NOISE_REDUCTION_MODES = new HashMap<>();
+  private final HashMap<NoiseReductionMode, Integer> NOISE_REDUCTION_MODES = new HashMap<>();
 
-  static {
+  /**
+   * Creates a new instance of the {@link NoiseReductionFeature}.
+   *
+   * @param cameraProperties Collection of the characteristics for the current camera device.
+   */
+  public NoiseReductionFeature(CameraProperties cameraProperties) {
+    super(cameraProperties);
     NOISE_REDUCTION_MODES.put(NoiseReductionMode.off, CaptureRequest.NOISE_REDUCTION_MODE_OFF);
     NOISE_REDUCTION_MODES.put(NoiseReductionMode.fast, CaptureRequest.NOISE_REDUCTION_MODE_FAST);
     NOISE_REDUCTION_MODES.put(
@@ -35,15 +41,6 @@
     }
   }
 
-  /**
-   * Creates a new instance of the {@link NoiseReductionFeature}.
-   *
-   * @param cameraProperties Collection of the characteristics for the current camera device.
-   */
-  public NoiseReductionFeature(CameraProperties cameraProperties) {
-    super(cameraProperties);
-  }
-
   @Override
   public String getDebugName() {
     return "NoiseReductionFeature";
diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/sensororientation/DeviceOrientationManager.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/sensororientation/DeviceOrientationManager.java
index 2a04caa..dd1e489 100644
--- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/sensororientation/DeviceOrientationManager.java
+++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/sensororientation/DeviceOrientationManager.java
@@ -10,10 +10,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.res.Configuration;
-import android.hardware.SensorManager;
-import android.provider.Settings;
 import android.view.Display;
-import android.view.OrientationEventListener;
 import android.view.Surface;
 import android.view.WindowManager;
 import androidx.annotation.NonNull;
@@ -35,7 +32,6 @@
   private final boolean isFrontFacing;
   private final int sensorOrientation;
   private PlatformChannel.DeviceOrientation lastOrientation;
-  private OrientationEventListener orientationEventListener;
   private BroadcastReceiver broadcastReceiver;
 
   /** Factory method to create a device orientation manager. */
@@ -63,7 +59,7 @@
    *
    * <p>When orientation information is updated the new orientation is send to the client using the
    * {@link DartMessenger}. This latest value can also be retrieved through the {@link
-   * #getMediaOrientation()} accessor.
+   * #getVideoOrientation()} accessor.
    *
    * <p>If the device's ACCELEROMETER_ROTATION setting is enabled the {@link
    * DeviceOrientationManager} will report orientation updates based on the sensor information. If
@@ -71,55 +67,106 @@
    * the deliver orientation updates based on the UI orientation.
    */
   public void start() {
-    startSensorListener();
-    startUIListener();
+    if (broadcastReceiver != null) {
+      return;
+    }
+    broadcastReceiver =
+        new BroadcastReceiver() {
+          @Override
+          public void onReceive(Context context, Intent intent) {
+            handleUIOrientationChange();
+          }
+        };
+    activity.registerReceiver(broadcastReceiver, orientationIntentFilter);
+    broadcastReceiver.onReceive(activity, null);
   }
 
   /** Stops listening for orientation updates. */
   public void stop() {
-    stopSensorListener();
-    stopUIListener();
+    if (broadcastReceiver == null) {
+      return;
+    }
+    activity.unregisterReceiver(broadcastReceiver);
+    broadcastReceiver = null;
   }
 
   /**
-   * Returns the last captured orientation in degrees based on sensor or UI information.
+   * Returns the device's photo orientation in degrees based on the sensor orientation and the last
+   * known UI orientation.
    *
-   * <p>The orientation is returned in degrees and could be one of the following values:
+   * <p>Returns one of 0, 90, 180 or 270.
    *
-   * <ul>
-   *   <li>0: Indicates the device is currently in portrait.
-   *   <li>90: Indicates the device is currently in landscape left.
-   *   <li>180: Indicates the device is currently in portrait down.
-   *   <li>270: Indicates the device is currently in landscape right.
-   * </ul>
-   *
-   * @return The last captured orientation in degrees
+   * @return The device's photo orientation in degrees.
    */
-  public int getMediaOrientation() {
-    return this.getMediaOrientation(this.lastOrientation);
+  public int getPhotoOrientation() {
+    return this.getPhotoOrientation(this.lastOrientation);
   }
 
   /**
-   * Returns the device's orientation in degrees based on the supplied {@link
-   * PlatformChannel.DeviceOrientation} value.
+   * Returns the device's photo orientation in degrees based on the sensor orientation and the
+   * supplied {@link PlatformChannel.DeviceOrientation} value.
    *
-   * <p>
-   *
-   * <ul>
-   *   <li>PORTRAIT_UP: converts to 0 degrees.
-   *   <li>LANDSCAPE_LEFT: converts to 90 degrees.
-   *   <li>PORTRAIT_DOWN: converts to 180 degrees.
-   *   <li>LANDSCAPE_RIGHT: converts to 270 degrees.
-   * </ul>
+   * <p>Returns one of 0, 90, 180 or 270.
    *
    * @param orientation The {@link PlatformChannel.DeviceOrientation} value that is to be converted
    *     into degrees.
-   * @return The device's orientation in degrees.
+   * @return The device's photo orientation in degrees.
    */
-  public int getMediaOrientation(PlatformChannel.DeviceOrientation orientation) {
+  public int getPhotoOrientation(PlatformChannel.DeviceOrientation orientation) {
+    int angle = 0;
+    // Fallback to device orientation when the orientation value is null.
+    if (orientation == null) {
+      orientation = getUIOrientation();
+    }
+
+    switch (orientation) {
+      case PORTRAIT_UP:
+        angle = 90;
+        break;
+      case PORTRAIT_DOWN:
+        angle = 270;
+        break;
+      case LANDSCAPE_LEFT:
+        angle = isFrontFacing ? 180 : 0;
+        break;
+      case LANDSCAPE_RIGHT:
+        angle = isFrontFacing ? 0 : 180;
+        break;
+    }
+
+    // Sensor orientation is 90 for most devices, or 270 for some devices (eg. Nexus 5X).
+    // This has to be taken into account so the JPEG is rotated properly.
+    // For devices with orientation of 90, this simply returns the mapping from ORIENTATIONS.
+    // For devices with orientation of 270, the JPEG is rotated 180 degrees instead.
+    return (angle + sensorOrientation + 270) % 360;
+  }
+
+  /**
+   * Returns the device's video orientation in degrees based on the sensor orientation and the last
+   * known UI orientation.
+   *
+   * <p>Returns one of 0, 90, 180 or 270.
+   *
+   * @return The device's video orientation in degrees.
+   */
+  public int getVideoOrientation() {
+    return this.getVideoOrientation(this.lastOrientation);
+  }
+
+  /**
+   * Returns the device's video orientation in degrees based on the sensor orientation and the
+   * supplied {@link PlatformChannel.DeviceOrientation} value.
+   *
+   * <p>Returns one of 0, 90, 180 or 270.
+   *
+   * @param orientation The {@link PlatformChannel.DeviceOrientation} value that is to be converted
+   *     into degrees.
+   * @return The device's video orientation in degrees.
+   */
+  public int getVideoOrientation(PlatformChannel.DeviceOrientation orientation) {
     int angle = 0;
 
-    // Fallback to device orientation when the orientation value is null
+    // Fallback to device orientation when the orientation value is null.
     if (orientation == null) {
       orientation = getUIOrientation();
     }
@@ -146,51 +193,9 @@
     return (angle + sensorOrientation + 360) % 360;
   }
 
-  private void startSensorListener() {
-    if (orientationEventListener != null) {
-      return;
-    }
-    orientationEventListener =
-        new OrientationEventListener(activity, SensorManager.SENSOR_DELAY_NORMAL) {
-          @Override
-          public void onOrientationChanged(int angle) {
-            handleSensorOrientationChange(angle);
-          }
-        };
-    if (orientationEventListener.canDetectOrientation()) {
-      orientationEventListener.enable();
-    }
-  }
-
-  private void startUIListener() {
-    if (broadcastReceiver != null) {
-      return;
-    }
-    broadcastReceiver =
-        new BroadcastReceiver() {
-          @Override
-          public void onReceive(Context context, Intent intent) {
-            handleUIOrientationChange();
-          }
-        };
-    activity.registerReceiver(broadcastReceiver, orientationIntentFilter);
-    broadcastReceiver.onReceive(activity, null);
-  }
-
-  /**
-   * Handles orientation changes based on information from the device's sensors.
-   *
-   * <p>This method is visible for testing purposes only and should never be used outside this
-   * class.
-   *
-   * @param angle of the current orientation.
-   */
-  @VisibleForTesting
-  void handleSensorOrientationChange(int angle) {
-    if (!isAccelerometerRotationLocked()) {
-      PlatformChannel.DeviceOrientation orientation = calculateSensorOrientation(angle);
-      lastOrientation = handleOrientationChange(orientation, lastOrientation, messenger);
-    }
+  /** @return the last received UI orientation. */
+  public PlatformChannel.DeviceOrientation getLastUIOrientation() {
+    return this.lastOrientation;
   }
 
   /**
@@ -201,10 +206,9 @@
    */
   @VisibleForTesting
   void handleUIOrientationChange() {
-    if (isAccelerometerRotationLocked()) {
-      PlatformChannel.DeviceOrientation orientation = getUIOrientation();
-      lastOrientation = handleOrientationChange(orientation, lastOrientation, messenger);
-    }
+    PlatformChannel.DeviceOrientation orientation = getUIOrientation();
+    handleOrientationChange(orientation, lastOrientation, messenger);
+    lastOrientation = orientation;
   }
 
   /**
@@ -215,37 +219,13 @@
    * class.
    */
   @VisibleForTesting
-  static DeviceOrientation handleOrientationChange(
+  static void handleOrientationChange(
       DeviceOrientation newOrientation,
       DeviceOrientation previousOrientation,
       DartMessenger messenger) {
     if (!newOrientation.equals(previousOrientation)) {
       messenger.sendDeviceOrientationChangeEvent(newOrientation);
     }
-
-    return newOrientation;
-  }
-
-  private void stopSensorListener() {
-    if (orientationEventListener == null) {
-      return;
-    }
-    orientationEventListener.disable();
-    orientationEventListener = null;
-  }
-
-  private void stopUIListener() {
-    if (broadcastReceiver == null) {
-      return;
-    }
-    activity.unregisterReceiver(broadcastReceiver);
-    broadcastReceiver = null;
-  }
-
-  private boolean isAccelerometerRotationLocked() {
-    return android.provider.Settings.System.getInt(
-            activity.getContentResolver(), Settings.System.ACCELEROMETER_ROTATION, 0)
-        != 1;
   }
 
   /**
diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/CameraPropertiesImplTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/CameraPropertiesImplTest.java
index 2c03817..40db12e 100644
--- a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/CameraPropertiesImplTest.java
+++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/CameraPropertiesImplTest.java
@@ -41,7 +41,7 @@
   }
 
   @Test
-  public void ctor_Should_return_valid_instance() throws CameraAccessException {
+  public void ctor_shouldReturnValidInstance() throws CameraAccessException {
     verify(mockCameraManager, times(1)).getCameraCharacteristics(CAMERA_NAME);
     assertNotNull(cameraProperties);
   }
@@ -76,8 +76,7 @@
   }
 
   @Test
-  public void
-      getControlAutoExposureCompensationStep_Should_return_double_When_rational_is_not_null() {
+  public void getControlAutoExposureCompensationStep_shouldReturnDoubleWhenRationalIsNotNull() {
     double expectedStep = 3.1415926535;
     Rational mockRational = mock(Rational.class);
 
@@ -92,7 +91,7 @@
   }
 
   @Test
-  public void getControlAutoExposureCompensationStep_Should_return_zero_When_rational_is_null() {
+  public void getControlAutoExposureCompensationStep_shouldReturnZeroWhenRationalIsNull() {
     double expectedStep = 0.0;
 
     when(mockCharacteristics.get(CameraCharacteristics.CONTROL_AE_COMPENSATION_STEP))
diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/CameraRegionUtils_convertPointToMeteringRectangleTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/CameraRegionUtils_convertPointToMeteringRectangleTest.java
new file mode 100644
index 0000000..2c6d9d9
--- /dev/null
+++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/CameraRegionUtils_convertPointToMeteringRectangleTest.java
@@ -0,0 +1,197 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+package io.flutter.plugins.camera;
+
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.mockStatic;
+import static org.mockito.Mockito.when;
+
+import android.hardware.camera2.params.MeteringRectangle;
+import android.util.Size;
+import io.flutter.embedding.engine.systemchannels.PlatformChannel;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.MockedStatic;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+public class CameraRegionUtils_convertPointToMeteringRectangleTest {
+  private MockedStatic<CameraRegionUtils.MeteringRectangleFactory> mockedMeteringRectangleFactory;
+  private Size mockCameraBoundaries;
+
+  @Before
+  public void setUp() {
+    this.mockCameraBoundaries = mock(Size.class);
+    when(this.mockCameraBoundaries.getWidth()).thenReturn(100);
+    when(this.mockCameraBoundaries.getHeight()).thenReturn(100);
+    mockedMeteringRectangleFactory = mockStatic(CameraRegionUtils.MeteringRectangleFactory.class);
+
+    mockedMeteringRectangleFactory
+        .when(
+            () ->
+                CameraRegionUtils.MeteringRectangleFactory.create(
+                    anyInt(), anyInt(), anyInt(), anyInt(), anyInt()))
+        .thenAnswer(
+            new Answer<MeteringRectangle>() {
+              @Override
+              public MeteringRectangle answer(InvocationOnMock createInvocation) throws Throwable {
+                MeteringRectangle mockMeteringRectangle = mock(MeteringRectangle.class);
+                when(mockMeteringRectangle.getX()).thenReturn(createInvocation.getArgument(0));
+                when(mockMeteringRectangle.getY()).thenReturn(createInvocation.getArgument(1));
+                when(mockMeteringRectangle.getWidth()).thenReturn(createInvocation.getArgument(2));
+                when(mockMeteringRectangle.getHeight()).thenReturn(createInvocation.getArgument(3));
+                when(mockMeteringRectangle.getMeteringWeight())
+                    .thenReturn(createInvocation.getArgument(4));
+                when(mockMeteringRectangle.equals(any()))
+                    .thenAnswer(
+                        new Answer<Boolean>() {
+                          @Override
+                          public Boolean answer(InvocationOnMock equalsInvocation)
+                              throws Throwable {
+                            MeteringRectangle otherMockMeteringRectangle =
+                                equalsInvocation.getArgument(0);
+                            return mockMeteringRectangle.getX() == otherMockMeteringRectangle.getX()
+                                && mockMeteringRectangle.getY() == otherMockMeteringRectangle.getY()
+                                && mockMeteringRectangle.getWidth()
+                                    == otherMockMeteringRectangle.getWidth()
+                                && mockMeteringRectangle.getHeight()
+                                    == otherMockMeteringRectangle.getHeight()
+                                && mockMeteringRectangle.getMeteringWeight()
+                                    == otherMockMeteringRectangle.getMeteringWeight();
+                          }
+                        });
+                return mockMeteringRectangle;
+              }
+            });
+  }
+
+  @After
+  public void tearDown() {
+    mockedMeteringRectangleFactory.close();
+  }
+
+  @Test
+  public void convertPointToMeteringRectangle_shouldReturnValidMeteringRectangleForCenterCoord() {
+    MeteringRectangle r =
+        CameraRegionUtils.convertPointToMeteringRectangle(
+            this.mockCameraBoundaries, 0.5, 0.5, PlatformChannel.DeviceOrientation.LANDSCAPE_LEFT);
+    assertTrue(CameraRegionUtils.MeteringRectangleFactory.create(45, 45, 10, 10, 1).equals(r));
+  }
+
+  @Test
+  public void convertPointToMeteringRectangle_shouldReturnValidMeteringRectangleForTopLeftCoord() {
+    MeteringRectangle r =
+        CameraRegionUtils.convertPointToMeteringRectangle(
+            this.mockCameraBoundaries, 0, 0, PlatformChannel.DeviceOrientation.LANDSCAPE_LEFT);
+    assertTrue(CameraRegionUtils.MeteringRectangleFactory.create(0, 0, 10, 10, 1).equals(r));
+  }
+
+  @Test
+  public void convertPointToMeteringRectangle_ShouldReturnValidMeteringRectangleForTopRightCoord() {
+    MeteringRectangle r =
+        CameraRegionUtils.convertPointToMeteringRectangle(
+            this.mockCameraBoundaries, 1, 0, PlatformChannel.DeviceOrientation.LANDSCAPE_LEFT);
+    assertTrue(CameraRegionUtils.MeteringRectangleFactory.create(89, 0, 10, 10, 1).equals(r));
+  }
+
+  @Test
+  public void
+      convertPointToMeteringRectangle_shouldReturnValidMeteringRectangleForBottomLeftCoord() {
+    MeteringRectangle r =
+        CameraRegionUtils.convertPointToMeteringRectangle(
+            this.mockCameraBoundaries, 0, 1, PlatformChannel.DeviceOrientation.LANDSCAPE_LEFT);
+    assertTrue(CameraRegionUtils.MeteringRectangleFactory.create(0, 89, 10, 10, 1).equals(r));
+  }
+
+  @Test
+  public void
+      convertPointToMeteringRectangle_shouldReturnValidMeteringRectangleForBottomRightCoord() {
+    MeteringRectangle r =
+        CameraRegionUtils.convertPointToMeteringRectangle(
+            this.mockCameraBoundaries, 1, 1, PlatformChannel.DeviceOrientation.LANDSCAPE_LEFT);
+    assertTrue(CameraRegionUtils.MeteringRectangleFactory.create(89, 89, 10, 10, 1).equals(r));
+  }
+
+  @Test(expected = AssertionError.class)
+  public void convertPointToMeteringRectangle_shouldThrowForXUpperBound() {
+    CameraRegionUtils.convertPointToMeteringRectangle(
+        this.mockCameraBoundaries, 1.5, 0, PlatformChannel.DeviceOrientation.PORTRAIT_UP);
+  }
+
+  @Test(expected = AssertionError.class)
+  public void convertPointToMeteringRectangle_shouldThrowForXLowerBound() {
+    CameraRegionUtils.convertPointToMeteringRectangle(
+        this.mockCameraBoundaries, -0.5, 0, PlatformChannel.DeviceOrientation.PORTRAIT_UP);
+  }
+
+  @Test(expected = AssertionError.class)
+  public void convertPointToMeteringRectangle_shouldThrowForYUpperBound() {
+    CameraRegionUtils.convertPointToMeteringRectangle(
+        this.mockCameraBoundaries, 0, 1.5, PlatformChannel.DeviceOrientation.PORTRAIT_UP);
+  }
+
+  @Test(expected = AssertionError.class)
+  public void convertPointToMeteringRectangle_shouldThrowForYLowerBound() {
+    CameraRegionUtils.convertPointToMeteringRectangle(
+        this.mockCameraBoundaries, 0, -0.5, PlatformChannel.DeviceOrientation.PORTRAIT_UP);
+  }
+
+  @Test()
+  public void
+      convertPointToMeteringRectangle_shouldRotateMeteringRectangleAccordingToUiOrientationForPortraitUp() {
+    MeteringRectangle r =
+        CameraRegionUtils.convertPointToMeteringRectangle(
+            this.mockCameraBoundaries, 1, 1, PlatformChannel.DeviceOrientation.PORTRAIT_UP);
+    assertTrue(CameraRegionUtils.MeteringRectangleFactory.create(89, 0, 10, 10, 1).equals(r));
+  }
+
+  @Test()
+  public void
+      convertPointToMeteringRectangle_shouldRotateMeteringRectangleAccordingToUiOrientationForPortraitDown() {
+    MeteringRectangle r =
+        CameraRegionUtils.convertPointToMeteringRectangle(
+            this.mockCameraBoundaries, 1, 1, PlatformChannel.DeviceOrientation.PORTRAIT_DOWN);
+    assertTrue(CameraRegionUtils.MeteringRectangleFactory.create(0, 89, 10, 10, 1).equals(r));
+  }
+
+  @Test()
+  public void
+      convertPointToMeteringRectangle_shouldRotateMeteringRectangleAccordingToUiOrientationForLandscapeLeft() {
+    MeteringRectangle r =
+        CameraRegionUtils.convertPointToMeteringRectangle(
+            this.mockCameraBoundaries, 1, 1, PlatformChannel.DeviceOrientation.LANDSCAPE_LEFT);
+    assertTrue(CameraRegionUtils.MeteringRectangleFactory.create(89, 89, 10, 10, 1).equals(r));
+  }
+
+  @Test()
+  public void
+      convertPointToMeteringRectangle_shouldRotateMeteringRectangleAccordingToUiOrientationForLandscapeRight() {
+    MeteringRectangle r =
+        CameraRegionUtils.convertPointToMeteringRectangle(
+            this.mockCameraBoundaries, 1, 1, PlatformChannel.DeviceOrientation.LANDSCAPE_RIGHT);
+    assertTrue(CameraRegionUtils.MeteringRectangleFactory.create(0, 0, 10, 10, 1).equals(r));
+  }
+
+  @Test(expected = AssertionError.class)
+  public void convertPointToMeteringRectangle_shouldThrowFor0WidthBoundary() {
+    Size mockCameraBoundaries = mock(Size.class);
+    when(mockCameraBoundaries.getWidth()).thenReturn(0);
+    when(mockCameraBoundaries.getHeight()).thenReturn(50);
+    CameraRegionUtils.convertPointToMeteringRectangle(
+        mockCameraBoundaries, 0, -0.5, PlatformChannel.DeviceOrientation.PORTRAIT_UP);
+  }
+
+  @Test(expected = AssertionError.class)
+  public void convertPointToMeteringRectangle_shouldThrowFor0HeightBoundary() {
+    Size mockCameraBoundaries = mock(Size.class);
+    when(mockCameraBoundaries.getWidth()).thenReturn(50);
+    when(mockCameraBoundaries.getHeight()).thenReturn(0);
+    CameraRegionUtils.convertPointToMeteringRectangle(
+        this.mockCameraBoundaries, 0, -0.5, PlatformChannel.DeviceOrientation.PORTRAIT_UP);
+  }
+}
diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/CameraRegionUtilsTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/CameraRegionUtils_getCameraBoundariesTest.java
similarity index 61%
rename from packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/CameraRegionUtilsTest.java
rename to packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/CameraRegionUtils_getCameraBoundariesTest.java
index 2d65c4e..4c01649 100644
--- a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/CameraRegionUtilsTest.java
+++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/CameraRegionUtils_getCameraBoundariesTest.java
@@ -4,8 +4,6 @@
 package io.flutter.plugins.camera;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.mockStatic;
@@ -15,17 +13,15 @@
 
 import android.graphics.Rect;
 import android.hardware.camera2.CaptureRequest;
-import android.hardware.camera2.params.MeteringRectangle;
 import android.os.Build;
 import android.util.Size;
 import io.flutter.plugins.camera.utils.TestUtils;
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.MockedStatic;
-import org.mockito.invocation.InvocationOnMock;
 import org.mockito.stubbing.Answer;
 
-public class CameraRegionUtilsTest {
+public class CameraRegionUtils_getCameraBoundariesTest {
 
   Size mockCameraBoundaries;
 
@@ -37,8 +33,7 @@
   }
 
   @Test
-  public void
-      getCameraBoundaries_should_return_sensor_info_pixel_array_size_when_running_pre_android_p() {
+  public void getCameraBoundaries_shouldReturnSensorInfoPixelArraySizeWhenRunningPreAndroidP() {
     updateSdkVersion(Build.VERSION_CODES.O_MR1);
 
     try {
@@ -58,7 +53,7 @@
 
   @Test
   public void
-      getCameraBoundaries_should_return_sensor_info_pixel_array_size_when_distortion_correction_is_null() {
+      getCameraBoundaries_shouldReturnSensorInfoPixelArraySizeWhenDistortionCorrectionIsNull() {
     updateSdkVersion(Build.VERSION_CODES.P);
 
     try {
@@ -80,7 +75,7 @@
 
   @Test
   public void
-      getCameraBoundaries_should_return_sensor_info_pixel_array_size_when_distortion_correction_is_off() {
+      getCameraBoundaries_shouldReturnSensorInfoPixelArraySizeWhenDistortionCorrectionIsOff() {
     updateSdkVersion(Build.VERSION_CODES.P);
 
     try {
@@ -103,7 +98,7 @@
 
   @Test
   public void
-      getCameraBoundaries_should_return_info_pre_correction_active_array_size_when_distortion_correction_mode_is_set_to_null() {
+      getCameraBoundaries_shouldReturnInfoPreCorrectionActiveArraySizeWhenDistortionCorrectionModeIsSetToNull() {
     updateSdkVersion(Build.VERSION_CODES.P);
 
     try {
@@ -150,7 +145,7 @@
 
   @Test
   public void
-      getCameraBoundaries_should_return_info_pre_correction_active_array_size_when_distortion_correction_mode_is_set_to_off() {
+      getCameraBoundaries_shouldReturnInfoPreCorrectionActiveArraySizeWhenDistortionCorrectionModeIsSetToOff() {
     updateSdkVersion(Build.VERSION_CODES.P);
 
     try {
@@ -199,7 +194,7 @@
 
   @Test
   public void
-      getCameraBoundaries_should_return_sensor_info_active_array_size_when_distortion_correction_mode_is_set() {
+      getCameraBoundaries_shouldReturnSensorInfoActiveArraySizeWhenDistortionCorrectionModeIsSet() {
     updateSdkVersion(Build.VERSION_CODES.P);
 
     try {
@@ -246,107 +241,6 @@
     }
   }
 
-  @Test(expected = AssertionError.class)
-  public void getMeteringRectangleForPoint_should_throw_for_x_upper_bound() {
-    CameraRegionUtils.convertPointToMeteringRectangle(this.mockCameraBoundaries, 1.5, 0);
-  }
-
-  @Test(expected = AssertionError.class)
-  public void getMeteringRectangleForPoint_should_throw_for_x_lower_bound() {
-    CameraRegionUtils.convertPointToMeteringRectangle(this.mockCameraBoundaries, -0.5, 0);
-  }
-
-  @Test(expected = AssertionError.class)
-  public void getMeteringRectangleForPoint_should_throw_for_y_upper_bound() {
-    CameraRegionUtils.convertPointToMeteringRectangle(this.mockCameraBoundaries, 0, 1.5);
-  }
-
-  @Test(expected = AssertionError.class)
-  public void getMeteringRectangleForPoint_should_throw_for_y_lower_bound() {
-    CameraRegionUtils.convertPointToMeteringRectangle(this.mockCameraBoundaries, 0, -0.5);
-  }
-
-  @Test
-  public void getMeteringRectangleForPoint_should_return_valid_MeteringRectangle() {
-    try (MockedStatic<CameraRegionUtils.MeteringRectangleFactory> mockedMeteringRectangleFactory =
-        mockStatic(CameraRegionUtils.MeteringRectangleFactory.class)) {
-
-      mockedMeteringRectangleFactory
-          .when(
-              () ->
-                  CameraRegionUtils.MeteringRectangleFactory.create(
-                      anyInt(), anyInt(), anyInt(), anyInt(), anyInt()))
-          .thenAnswer(
-              new Answer<MeteringRectangle>() {
-                @Override
-                public MeteringRectangle answer(InvocationOnMock createInvocation)
-                    throws Throwable {
-                  MeteringRectangle mockMeteringRectangle = mock(MeteringRectangle.class);
-                  when(mockMeteringRectangle.getX()).thenReturn(createInvocation.getArgument(0));
-                  when(mockMeteringRectangle.getY()).thenReturn(createInvocation.getArgument(1));
-                  when(mockMeteringRectangle.getWidth())
-                      .thenReturn(createInvocation.getArgument(2));
-                  when(mockMeteringRectangle.getHeight())
-                      .thenReturn(createInvocation.getArgument(3));
-                  when(mockMeteringRectangle.getMeteringWeight())
-                      .thenReturn(createInvocation.getArgument(4));
-                  when(mockMeteringRectangle.equals(any()))
-                      .thenAnswer(
-                          new Answer<Boolean>() {
-                            @Override
-                            public Boolean answer(InvocationOnMock equalsInvocation)
-                                throws Throwable {
-                              MeteringRectangle otherMockMeteringRectangle =
-                                  equalsInvocation.getArgument(0);
-                              return mockMeteringRectangle.getX()
-                                      == otherMockMeteringRectangle.getX()
-                                  && mockMeteringRectangle.getY()
-                                      == otherMockMeteringRectangle.getY()
-                                  && mockMeteringRectangle.getWidth()
-                                      == otherMockMeteringRectangle.getWidth()
-                                  && mockMeteringRectangle.getHeight()
-                                      == otherMockMeteringRectangle.getHeight()
-                                  && mockMeteringRectangle.getMeteringWeight()
-                                      == otherMockMeteringRectangle.getMeteringWeight();
-                            }
-                          });
-                  return mockMeteringRectangle;
-                }
-              });
-
-      MeteringRectangle r;
-      // Center
-      r = CameraRegionUtils.convertPointToMeteringRectangle(this.mockCameraBoundaries, 0.5, 0.5);
-      assertTrue(CameraRegionUtils.MeteringRectangleFactory.create(45, 45, 10, 10, 1).equals(r));
-
-      // Top left
-      r = CameraRegionUtils.convertPointToMeteringRectangle(this.mockCameraBoundaries, 0.0, 0.0);
-      assertTrue(CameraRegionUtils.MeteringRectangleFactory.create(0, 0, 10, 10, 1).equals(r));
-
-      // Bottom right
-      r = CameraRegionUtils.convertPointToMeteringRectangle(this.mockCameraBoundaries, 1.0, 1.0);
-      assertTrue(CameraRegionUtils.MeteringRectangleFactory.create(89, 89, 10, 10, 1).equals(r));
-
-      // Top left
-      r = CameraRegionUtils.convertPointToMeteringRectangle(this.mockCameraBoundaries, 0.0, 1.0);
-      assertTrue(CameraRegionUtils.MeteringRectangleFactory.create(0, 89, 10, 10, 1).equals(r));
-
-      // Top right
-      r = CameraRegionUtils.convertPointToMeteringRectangle(this.mockCameraBoundaries, 1.0, 0.0);
-      assertTrue(CameraRegionUtils.MeteringRectangleFactory.create(89, 0, 10, 10, 1).equals(r));
-    }
-  }
-
-  @Test(expected = AssertionError.class)
-  public void getMeteringRectangleForPoint_should_throw_for_0_width_boundary() {
-    new io.flutter.plugins.camera.CameraRegions(new Size(0, 50));
-  }
-
-  @Test(expected = AssertionError.class)
-  public void getMeteringRectangleForPoint_should_throw_for_0_height_boundary() {
-    new io.flutter.plugins.camera.CameraRegions(new Size(100, 0));
-  }
-
   private static void updateSdkVersion(int version) {
     TestUtils.setFinalStatic(Build.VERSION.class, "SDK_INT", version);
   }
diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/CameraTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/CameraTest.java
new file mode 100644
index 0000000..cab2ae8
--- /dev/null
+++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/CameraTest.java
@@ -0,0 +1,843 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package io.flutter.plugins.camera;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.Activity;
+import android.hardware.camera2.CameraAccessException;
+import android.hardware.camera2.CameraCaptureSession;
+import android.hardware.camera2.CameraMetadata;
+import android.hardware.camera2.CaptureRequest;
+import android.media.CamcorderProfile;
+import android.media.MediaRecorder;
+import android.os.Build;
+import androidx.annotation.NonNull;
+import io.flutter.embedding.engine.systemchannels.PlatformChannel;
+import io.flutter.plugin.common.MethodChannel;
+import io.flutter.plugins.camera.features.CameraFeatureFactory;
+import io.flutter.plugins.camera.features.Point;
+import io.flutter.plugins.camera.features.autofocus.AutoFocusFeature;
+import io.flutter.plugins.camera.features.autofocus.FocusMode;
+import io.flutter.plugins.camera.features.exposurelock.ExposureLockFeature;
+import io.flutter.plugins.camera.features.exposurelock.ExposureMode;
+import io.flutter.plugins.camera.features.exposureoffset.ExposureOffsetFeature;
+import io.flutter.plugins.camera.features.exposurepoint.ExposurePointFeature;
+import io.flutter.plugins.camera.features.flash.FlashFeature;
+import io.flutter.plugins.camera.features.flash.FlashMode;
+import io.flutter.plugins.camera.features.focuspoint.FocusPointFeature;
+import io.flutter.plugins.camera.features.fpsrange.FpsRangeFeature;
+import io.flutter.plugins.camera.features.noisereduction.NoiseReductionFeature;
+import io.flutter.plugins.camera.features.resolution.ResolutionFeature;
+import io.flutter.plugins.camera.features.resolution.ResolutionPreset;
+import io.flutter.plugins.camera.features.sensororientation.DeviceOrientationManager;
+import io.flutter.plugins.camera.features.sensororientation.SensorOrientationFeature;
+import io.flutter.plugins.camera.features.zoomlevel.ZoomLevelFeature;
+import io.flutter.plugins.camera.utils.TestUtils;
+import io.flutter.view.TextureRegistry;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public class CameraTest {
+  private CameraProperties mockCameraProperties;
+  private CameraFeatureFactory mockCameraFeatureFactory;
+  private DartMessenger mockDartMessenger;
+  private Camera camera;
+  private CameraCaptureSession mockCaptureSession;
+  private CaptureRequest.Builder mockPreviewRequestBuilder;
+
+  @Before
+  public void before() {
+    mockCameraProperties = mock(CameraProperties.class);
+    mockCameraFeatureFactory = new TestCameraFeatureFactory();
+    mockDartMessenger = mock(DartMessenger.class);
+    mockCaptureSession = mock(CameraCaptureSession.class);
+    mockPreviewRequestBuilder = mock(CaptureRequest.Builder.class);
+
+    final Activity mockActivity = mock(Activity.class);
+    final TextureRegistry.SurfaceTextureEntry mockFlutterTexture =
+        mock(TextureRegistry.SurfaceTextureEntry.class);
+    final String cameraName = "1";
+    final ResolutionPreset resolutionPreset = ResolutionPreset.high;
+    final boolean enableAudio = false;
+
+    when(mockCameraProperties.getCameraName()).thenReturn(cameraName);
+
+    camera =
+        new Camera(
+            mockActivity,
+            mockFlutterTexture,
+            mockCameraFeatureFactory,
+            mockDartMessenger,
+            mockCameraProperties,
+            resolutionPreset,
+            enableAudio);
+
+    TestUtils.setPrivateField(camera, "captureSession", mockCaptureSession);
+    TestUtils.setPrivateField(camera, "previewRequestBuilder", mockPreviewRequestBuilder);
+  }
+
+  @After
+  public void after() {
+    TestUtils.setFinalStatic(Build.VERSION.class, "SDK_INT", 0);
+  }
+
+  @Test
+  public void shouldCreateCameraPluginAndSetAllFeatures() {
+    final Activity mockActivity = mock(Activity.class);
+    final TextureRegistry.SurfaceTextureEntry mockFlutterTexture =
+        mock(TextureRegistry.SurfaceTextureEntry.class);
+    final CameraFeatureFactory mockCameraFeatureFactory = mock(CameraFeatureFactory.class);
+    final String cameraName = "1";
+    final ResolutionPreset resolutionPreset = ResolutionPreset.high;
+    final boolean enableAudio = false;
+
+    when(mockCameraProperties.getCameraName()).thenReturn(cameraName);
+    SensorOrientationFeature mockSensorOrientationFeature = mock(SensorOrientationFeature.class);
+    when(mockCameraFeatureFactory.createSensorOrientationFeature(any(), any(), any()))
+        .thenReturn(mockSensorOrientationFeature);
+
+    Camera camera =
+        new Camera(
+            mockActivity,
+            mockFlutterTexture,
+            mockCameraFeatureFactory,
+            mockDartMessenger,
+            mockCameraProperties,
+            resolutionPreset,
+            enableAudio);
+
+    verify(mockCameraFeatureFactory, times(1))
+        .createSensorOrientationFeature(mockCameraProperties, mockActivity, mockDartMessenger);
+    verify(mockCameraFeatureFactory, times(1)).createAutoFocusFeature(mockCameraProperties, false);
+    verify(mockCameraFeatureFactory, times(1)).createExposureLockFeature(mockCameraProperties);
+    verify(mockCameraFeatureFactory, times(1))
+        .createExposurePointFeature(eq(mockCameraProperties), eq(mockSensorOrientationFeature));
+    verify(mockCameraFeatureFactory, times(1)).createExposureOffsetFeature(mockCameraProperties);
+    verify(mockCameraFeatureFactory, times(1)).createFlashFeature(mockCameraProperties);
+    verify(mockCameraFeatureFactory, times(1))
+        .createFocusPointFeature(eq(mockCameraProperties), eq(mockSensorOrientationFeature));
+    verify(mockCameraFeatureFactory, times(1)).createFpsRangeFeature(mockCameraProperties);
+    verify(mockCameraFeatureFactory, times(1)).createNoiseReductionFeature(mockCameraProperties);
+    verify(mockCameraFeatureFactory, times(1))
+        .createResolutionFeature(mockCameraProperties, resolutionPreset, cameraName);
+    verify(mockCameraFeatureFactory, times(1)).createZoomLevelFeature(mockCameraProperties);
+    assertNotNull("should create a camera", camera);
+  }
+
+  @Test
+  public void getDeviceOrientationManager() {
+    SensorOrientationFeature mockSensorOrientationFeature =
+        mockCameraFeatureFactory.createSensorOrientationFeature(mockCameraProperties, null, null);
+    DeviceOrientationManager mockDeviceOrientationManager = mock(DeviceOrientationManager.class);
+
+    when(mockSensorOrientationFeature.getDeviceOrientationManager())
+        .thenReturn(mockDeviceOrientationManager);
+
+    DeviceOrientationManager actualDeviceOrientationManager = camera.getDeviceOrientationManager();
+
+    verify(mockSensorOrientationFeature, times(1)).getDeviceOrientationManager();
+    assertEquals(mockDeviceOrientationManager, actualDeviceOrientationManager);
+  }
+
+  @Test
+  public void getExposureOffsetStepSize() {
+    ExposureOffsetFeature mockExposureOffsetFeature =
+        mockCameraFeatureFactory.createExposureOffsetFeature(mockCameraProperties);
+    double stepSize = 2.3;
+
+    when(mockExposureOffsetFeature.getExposureOffsetStepSize()).thenReturn(stepSize);
+
+    double actualSize = camera.getExposureOffsetStepSize();
+
+    verify(mockExposureOffsetFeature, times(1)).getExposureOffsetStepSize();
+    assertEquals(stepSize, actualSize, 0);
+  }
+
+  @Test
+  public void getMaxExposureOffset() {
+    ExposureOffsetFeature mockExposureOffsetFeature =
+        mockCameraFeatureFactory.createExposureOffsetFeature(mockCameraProperties);
+    double expectedMaxOffset = 42.0;
+
+    when(mockExposureOffsetFeature.getMaxExposureOffset()).thenReturn(expectedMaxOffset);
+
+    double actualMaxOffset = camera.getMaxExposureOffset();
+
+    verify(mockExposureOffsetFeature, times(1)).getMaxExposureOffset();
+    assertEquals(expectedMaxOffset, actualMaxOffset, 0);
+  }
+
+  @Test
+  public void getMinExposureOffset() {
+    ExposureOffsetFeature mockExposureOffsetFeature =
+        mockCameraFeatureFactory.createExposureOffsetFeature(mockCameraProperties);
+    double expectedMinOffset = 21.5;
+
+    when(mockExposureOffsetFeature.getMinExposureOffset()).thenReturn(21.5);
+
+    double actualMinOffset = camera.getMinExposureOffset();
+
+    verify(mockExposureOffsetFeature, times(1)).getMinExposureOffset();
+    assertEquals(expectedMinOffset, actualMinOffset, 0);
+  }
+
+  @Test
+  public void getMaxZoomLevel() {
+    ZoomLevelFeature mockZoomLevelFeature =
+        mockCameraFeatureFactory.createZoomLevelFeature(mockCameraProperties);
+    float expectedMaxZoomLevel = 4.2f;
+
+    when(mockZoomLevelFeature.getMaximumZoomLevel()).thenReturn(expectedMaxZoomLevel);
+
+    float actualMaxZoomLevel = camera.getMaxZoomLevel();
+
+    verify(mockZoomLevelFeature, times(1)).getMaximumZoomLevel();
+    assertEquals(expectedMaxZoomLevel, actualMaxZoomLevel, 0);
+  }
+
+  @Test
+  public void getMinZoomLevel() {
+    ZoomLevelFeature mockZoomLevelFeature =
+        mockCameraFeatureFactory.createZoomLevelFeature(mockCameraProperties);
+    float expectedMinZoomLevel = 4.2f;
+
+    when(mockZoomLevelFeature.getMinimumZoomLevel()).thenReturn(expectedMinZoomLevel);
+
+    float actualMinZoomLevel = camera.getMinZoomLevel();
+
+    verify(mockZoomLevelFeature, times(1)).getMinimumZoomLevel();
+    assertEquals(expectedMinZoomLevel, actualMinZoomLevel, 0);
+  }
+
+  @Test
+  public void getRecordingProfile() {
+    ResolutionFeature mockResolutionFeature =
+        mockCameraFeatureFactory.createResolutionFeature(mockCameraProperties, null, null);
+    CamcorderProfile mockCamcorderProfile = mock(CamcorderProfile.class);
+
+    when(mockResolutionFeature.getRecordingProfile()).thenReturn(mockCamcorderProfile);
+
+    CamcorderProfile actualRecordingProfile = camera.getRecordingProfile();
+
+    verify(mockResolutionFeature, times(1)).getRecordingProfile();
+    assertEquals(mockCamcorderProfile, actualRecordingProfile);
+  }
+
+  @Test
+  public void setExposureMode_shouldUpdateExposureLockFeature() {
+    ExposureLockFeature mockExposureLockFeature =
+        mockCameraFeatureFactory.createExposureLockFeature(mockCameraProperties);
+    MethodChannel.Result mockResult = mock(MethodChannel.Result.class);
+    ExposureMode exposureMode = ExposureMode.locked;
+
+    camera.setExposureMode(mockResult, exposureMode);
+
+    verify(mockExposureLockFeature, times(1)).setValue(exposureMode);
+    verify(mockResult, never()).error(any(), any(), any());
+    verify(mockResult, times(1)).success(null);
+  }
+
+  @Test
+  public void setExposureMode_shouldUpdateBuilder() {
+    ExposureLockFeature mockExposureLockFeature =
+        mockCameraFeatureFactory.createExposureLockFeature(mockCameraProperties);
+    MethodChannel.Result mockResult = mock(MethodChannel.Result.class);
+    ExposureMode exposureMode = ExposureMode.locked;
+
+    camera.setExposureMode(mockResult, exposureMode);
+
+    verify(mockExposureLockFeature, times(1)).updateBuilder(any());
+  }
+
+  @Test
+  public void setExposureMode_shouldCallErrorOnResultOnCameraAccessException()
+      throws CameraAccessException {
+    MethodChannel.Result mockResult = mock(MethodChannel.Result.class);
+    ExposureMode exposureMode = ExposureMode.locked;
+    when(mockCaptureSession.setRepeatingRequest(any(), any(), any()))
+        .thenThrow(new CameraAccessException(0, ""));
+
+    camera.setExposureMode(mockResult, exposureMode);
+
+    verify(mockResult, never()).success(any());
+    verify(mockResult, times(1))
+        .error("setExposureModeFailed", "Could not set exposure mode.", null);
+  }
+
+  @Test
+  public void setExposurePoint_shouldUpdateExposurePointFeature() {
+    SensorOrientationFeature mockSensorOrientationFeature = mock(SensorOrientationFeature.class);
+    ExposurePointFeature mockExposurePointFeature =
+        mockCameraFeatureFactory.createExposurePointFeature(
+            mockCameraProperties, mockSensorOrientationFeature);
+    MethodChannel.Result mockResult = mock(MethodChannel.Result.class);
+    Point point = new Point(42d, 42d);
+
+    camera.setExposurePoint(mockResult, point);
+
+    verify(mockExposurePointFeature, times(1)).setValue(point);
+    verify(mockResult, never()).error(any(), any(), any());
+    verify(mockResult, times(1)).success(null);
+  }
+
+  @Test
+  public void setExposurePoint_shouldUpdateBuilder() {
+    SensorOrientationFeature mockSensorOrientationFeature = mock(SensorOrientationFeature.class);
+    ExposurePointFeature mockExposurePointFeature =
+        mockCameraFeatureFactory.createExposurePointFeature(
+            mockCameraProperties, mockSensorOrientationFeature);
+    MethodChannel.Result mockResult = mock(MethodChannel.Result.class);
+    Point point = new Point(42d, 42d);
+
+    camera.setExposurePoint(mockResult, point);
+
+    verify(mockExposurePointFeature, times(1)).updateBuilder(any());
+  }
+
+  @Test
+  public void setExposurePoint_shouldCallErrorOnResultOnCameraAccessException()
+      throws CameraAccessException {
+    MethodChannel.Result mockResult = mock(MethodChannel.Result.class);
+    Point point = new Point(42d, 42d);
+    when(mockCaptureSession.setRepeatingRequest(any(), any(), any()))
+        .thenThrow(new CameraAccessException(0, ""));
+
+    camera.setExposurePoint(mockResult, point);
+
+    verify(mockResult, never()).success(any());
+    verify(mockResult, times(1))
+        .error("setExposurePointFailed", "Could not set exposure point.", null);
+  }
+
+  @Test
+  public void setFlashMode_shouldUpdateFlashFeature() {
+    FlashFeature mockFlashFeature =
+        mockCameraFeatureFactory.createFlashFeature(mockCameraProperties);
+    MethodChannel.Result mockResult = mock(MethodChannel.Result.class);
+    FlashMode flashMode = FlashMode.always;
+
+    camera.setFlashMode(mockResult, flashMode);
+
+    verify(mockFlashFeature, times(1)).setValue(flashMode);
+    verify(mockResult, never()).error(any(), any(), any());
+    verify(mockResult, times(1)).success(null);
+  }
+
+  @Test
+  public void setFlashMode_shouldUpdateBuilder() {
+    FlashFeature mockFlashFeature =
+        mockCameraFeatureFactory.createFlashFeature(mockCameraProperties);
+    MethodChannel.Result mockResult = mock(MethodChannel.Result.class);
+    FlashMode flashMode = FlashMode.always;
+
+    camera.setFlashMode(mockResult, flashMode);
+
+    verify(mockFlashFeature, times(1)).updateBuilder(any());
+  }
+
+  @Test
+  public void setFlashMode_shouldCallErrorOnResultOnCameraAccessException()
+      throws CameraAccessException {
+    MethodChannel.Result mockResult = mock(MethodChannel.Result.class);
+    FlashMode flashMode = FlashMode.always;
+    when(mockCaptureSession.setRepeatingRequest(any(), any(), any()))
+        .thenThrow(new CameraAccessException(0, ""));
+
+    camera.setFlashMode(mockResult, flashMode);
+
+    verify(mockResult, never()).success(any());
+    verify(mockResult, times(1)).error("setFlashModeFailed", "Could not set flash mode.", null);
+  }
+
+  @Test
+  public void setFocusPoint_shouldUpdateFocusPointFeature() {
+    SensorOrientationFeature mockSensorOrientationFeature = mock(SensorOrientationFeature.class);
+    FocusPointFeature mockFocusPointFeature =
+        mockCameraFeatureFactory.createFocusPointFeature(
+            mockCameraProperties, mockSensorOrientationFeature);
+    AutoFocusFeature mockAutoFocusFeature =
+        mockCameraFeatureFactory.createAutoFocusFeature(mockCameraProperties, false);
+    MethodChannel.Result mockResult = mock(MethodChannel.Result.class);
+    Point point = new Point(42d, 42d);
+    when(mockAutoFocusFeature.getValue()).thenReturn(FocusMode.auto);
+
+    camera.setFocusPoint(mockResult, point);
+
+    verify(mockFocusPointFeature, times(1)).setValue(point);
+    verify(mockResult, never()).error(any(), any(), any());
+    verify(mockResult, times(1)).success(null);
+  }
+
+  @Test
+  public void setFocusPoint_shouldUpdateBuilder() {
+    SensorOrientationFeature mockSensorOrientationFeature = mock(SensorOrientationFeature.class);
+    FocusPointFeature mockFocusPointFeature =
+        mockCameraFeatureFactory.createFocusPointFeature(
+            mockCameraProperties, mockSensorOrientationFeature);
+    AutoFocusFeature mockAutoFocusFeature =
+        mockCameraFeatureFactory.createAutoFocusFeature(mockCameraProperties, false);
+    MethodChannel.Result mockResult = mock(MethodChannel.Result.class);
+    Point point = new Point(42d, 42d);
+    when(mockAutoFocusFeature.getValue()).thenReturn(FocusMode.auto);
+
+    camera.setFocusPoint(mockResult, point);
+
+    verify(mockFocusPointFeature, times(1)).updateBuilder(any());
+  }
+
+  @Test
+  public void setFocusPoint_shouldCallErrorOnResultOnCameraAccessException()
+      throws CameraAccessException {
+    AutoFocusFeature mockAutoFocusFeature =
+        mockCameraFeatureFactory.createAutoFocusFeature(mockCameraProperties, false);
+    MethodChannel.Result mockResult = mock(MethodChannel.Result.class);
+    Point point = new Point(42d, 42d);
+    when(mockAutoFocusFeature.getValue()).thenReturn(FocusMode.auto);
+    when(mockCaptureSession.setRepeatingRequest(any(), any(), any()))
+        .thenThrow(new CameraAccessException(0, ""));
+
+    camera.setFocusPoint(mockResult, point);
+
+    verify(mockResult, never()).success(any());
+    verify(mockResult, times(1)).error("setFocusPointFailed", "Could not set focus point.", null);
+  }
+
+  @Test
+  public void setZoomLevel_shouldUpdateZoomLevelFeature() throws CameraAccessException {
+    ZoomLevelFeature mockZoomLevelFeature =
+        mockCameraFeatureFactory.createZoomLevelFeature(mockCameraProperties);
+    MethodChannel.Result mockResult = mock(MethodChannel.Result.class);
+    float zoomLevel = 1.0f;
+
+    when(mockZoomLevelFeature.getValue()).thenReturn(zoomLevel);
+    when(mockZoomLevelFeature.getMinimumZoomLevel()).thenReturn(0f);
+    when(mockZoomLevelFeature.getMaximumZoomLevel()).thenReturn(2f);
+
+    camera.setZoomLevel(mockResult, zoomLevel);
+
+    verify(mockZoomLevelFeature, times(1)).setValue(zoomLevel);
+    verify(mockResult, never()).error(any(), any(), any());
+    verify(mockResult, times(1)).success(null);
+  }
+
+  @Test
+  public void setZoomLevel_shouldUpdateBuilder() throws CameraAccessException {
+    ZoomLevelFeature mockZoomLevelFeature =
+        mockCameraFeatureFactory.createZoomLevelFeature(mockCameraProperties);
+    MethodChannel.Result mockResult = mock(MethodChannel.Result.class);
+    float zoomLevel = 1.0f;
+
+    when(mockZoomLevelFeature.getValue()).thenReturn(zoomLevel);
+    when(mockZoomLevelFeature.getMinimumZoomLevel()).thenReturn(0f);
+    when(mockZoomLevelFeature.getMaximumZoomLevel()).thenReturn(2f);
+
+    camera.setZoomLevel(mockResult, zoomLevel);
+
+    verify(mockZoomLevelFeature, times(1)).updateBuilder(any());
+  }
+
+  @Test
+  public void setZoomLevel_shouldCallErrorOnResultOnCameraAccessException()
+      throws CameraAccessException {
+    ZoomLevelFeature mockZoomLevelFeature =
+        mockCameraFeatureFactory.createZoomLevelFeature(mockCameraProperties);
+    MethodChannel.Result mockResult = mock(MethodChannel.Result.class);
+    float zoomLevel = 1.0f;
+
+    when(mockZoomLevelFeature.getValue()).thenReturn(zoomLevel);
+    when(mockZoomLevelFeature.getMinimumZoomLevel()).thenReturn(0f);
+    when(mockZoomLevelFeature.getMaximumZoomLevel()).thenReturn(2f);
+    when(mockCaptureSession.setRepeatingRequest(any(), any(), any()))
+        .thenThrow(new CameraAccessException(0, ""));
+
+    camera.setZoomLevel(mockResult, zoomLevel);
+
+    verify(mockResult, never()).success(any());
+    verify(mockResult, times(1)).error("setZoomLevelFailed", "Could not set zoom level.", null);
+  }
+
+  @Test
+  public void pauseVideoRecording_shouldSendNullResultWhenNotRecording() {
+    TestUtils.setPrivateField(camera, "recordingVideo", false);
+    MethodChannel.Result mockResult = mock(MethodChannel.Result.class);
+
+    camera.pauseVideoRecording(mockResult);
+
+    verify(mockResult, times(1)).success(null);
+    verify(mockResult, never()).error(any(), any(), any());
+  }
+
+  @Test
+  public void pauseVideoRecording_shouldCallPauseWhenRecordingAndOnAPIN() {
+    MethodChannel.Result mockResult = mock(MethodChannel.Result.class);
+    MediaRecorder mockMediaRecorder = mock(MediaRecorder.class);
+    TestUtils.setPrivateField(camera, "mediaRecorder", mockMediaRecorder);
+    TestUtils.setPrivateField(camera, "recordingVideo", true);
+    TestUtils.setFinalStatic(Build.VERSION.class, "SDK_INT", 24);
+
+    camera.pauseVideoRecording(mockResult);
+
+    verify(mockMediaRecorder, times(1)).pause();
+    verify(mockResult, times(1)).success(null);
+    verify(mockResult, never()).error(any(), any(), any());
+  }
+
+  @Test
+  public void pauseVideoRecording_shouldSendVideoRecordingFailedErrorWhenVersionCodeSmallerThenN() {
+    TestUtils.setPrivateField(camera, "recordingVideo", true);
+    TestUtils.setFinalStatic(Build.VERSION.class, "SDK_INT", 23);
+    MethodChannel.Result mockResult = mock(MethodChannel.Result.class);
+
+    camera.pauseVideoRecording(mockResult);
+
+    verify(mockResult, times(1))
+        .error("videoRecordingFailed", "pauseVideoRecording requires Android API +24.", null);
+    verify(mockResult, never()).success(any());
+  }
+
+  @Test
+  public void
+      pauseVideoRecording_shouldSendVideoRecordingFailedErrorWhenMediaRecorderPauseThrowsIllegalStateException() {
+    MediaRecorder mockMediaRecorder = mock(MediaRecorder.class);
+    TestUtils.setPrivateField(camera, "mediaRecorder", mockMediaRecorder);
+    TestUtils.setPrivateField(camera, "recordingVideo", true);
+    TestUtils.setFinalStatic(Build.VERSION.class, "SDK_INT", 24);
+
+    IllegalStateException expectedException = new IllegalStateException("Test error message");
+
+    doThrow(expectedException).when(mockMediaRecorder).pause();
+
+    MethodChannel.Result mockResult = mock(MethodChannel.Result.class);
+
+    camera.pauseVideoRecording(mockResult);
+
+    verify(mockResult, times(1)).error("videoRecordingFailed", "Test error message", null);
+    verify(mockResult, never()).success(any());
+  }
+
+  @Test
+  public void resumeVideoRecording_shouldSendNullResultWhenNotRecording() {
+    MethodChannel.Result mockResult = mock(MethodChannel.Result.class);
+    TestUtils.setPrivateField(camera, "recordingVideo", false);
+
+    camera.resumeVideoRecording(mockResult);
+
+    verify(mockResult, times(1)).success(null);
+    verify(mockResult, never()).error(any(), any(), any());
+  }
+
+  @Test
+  public void resumeVideoRecording_shouldCallPauseWhenRecordingAndOnAPIN() {
+    MethodChannel.Result mockResult = mock(MethodChannel.Result.class);
+    MediaRecorder mockMediaRecorder = mock(MediaRecorder.class);
+    TestUtils.setPrivateField(camera, "mediaRecorder", mockMediaRecorder);
+    TestUtils.setPrivateField(camera, "recordingVideo", true);
+    TestUtils.setFinalStatic(Build.VERSION.class, "SDK_INT", 24);
+
+    camera.resumeVideoRecording(mockResult);
+
+    verify(mockMediaRecorder, times(1)).resume();
+    verify(mockResult, times(1)).success(null);
+    verify(mockResult, never()).error(any(), any(), any());
+  }
+
+  @Test
+  public void
+      resumeVideoRecording_shouldSendVideoRecordingFailedErrorWhenVersionCodeSmallerThanN() {
+    TestUtils.setPrivateField(camera, "recordingVideo", true);
+    TestUtils.setFinalStatic(Build.VERSION.class, "SDK_INT", 23);
+
+    MethodChannel.Result mockResult = mock(MethodChannel.Result.class);
+
+    camera.resumeVideoRecording(mockResult);
+
+    verify(mockResult, times(1))
+        .error("videoRecordingFailed", "resumeVideoRecording requires Android API +24.", null);
+    verify(mockResult, never()).success(any());
+  }
+
+  @Test
+  public void
+      resumeVideoRecording_shouldSendVideoRecordingFailedErrorWhenMediaRecorderPauseThrowsIllegalStateException() {
+    MediaRecorder mockMediaRecorder = mock(MediaRecorder.class);
+    TestUtils.setPrivateField(camera, "mediaRecorder", mockMediaRecorder);
+    TestUtils.setPrivateField(camera, "recordingVideo", true);
+    TestUtils.setFinalStatic(Build.VERSION.class, "SDK_INT", 24);
+
+    IllegalStateException expectedException = new IllegalStateException("Test error message");
+
+    doThrow(expectedException).when(mockMediaRecorder).resume();
+
+    MethodChannel.Result mockResult = mock(MethodChannel.Result.class);
+
+    camera.resumeVideoRecording(mockResult);
+
+    verify(mockResult, times(1)).error("videoRecordingFailed", "Test error message", null);
+    verify(mockResult, never()).success(any());
+  }
+
+  @Test
+  public void setFocusMode_shouldUpdateAutoFocusFeature() {
+    AutoFocusFeature mockAutoFocusFeature =
+        mockCameraFeatureFactory.createAutoFocusFeature(mockCameraProperties, false);
+    MethodChannel.Result mockResult = mock(MethodChannel.Result.class);
+
+    camera.setFocusMode(mockResult, FocusMode.auto);
+
+    verify(mockAutoFocusFeature, times(1)).setValue(FocusMode.auto);
+    verify(mockResult, never()).error(any(), any(), any());
+    verify(mockResult, times(1)).success(null);
+  }
+
+  @Test
+  public void setFocusMode_shouldUpdateBuilder() {
+    AutoFocusFeature mockAutoFocusFeature =
+        mockCameraFeatureFactory.createAutoFocusFeature(mockCameraProperties, false);
+    MethodChannel.Result mockResult = mock(MethodChannel.Result.class);
+
+    camera.setFocusMode(mockResult, FocusMode.auto);
+
+    verify(mockAutoFocusFeature, times(1)).updateBuilder(any());
+  }
+
+  @Test
+  public void setFocusMode_shouldUnlockAutoFocusForAutoMode() {
+    camera.setFocusMode(mock(MethodChannel.Result.class), FocusMode.auto);
+    verify(mockPreviewRequestBuilder, times(1))
+        .set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_CANCEL);
+    verify(mockPreviewRequestBuilder, times(1))
+        .set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_IDLE);
+  }
+
+  @Test
+  public void setFocusMode_shouldSkipUnlockAutoFocusWhenNullCaptureSession() {
+    TestUtils.setPrivateField(camera, "captureSession", null);
+    camera.setFocusMode(mock(MethodChannel.Result.class), FocusMode.auto);
+    verify(mockPreviewRequestBuilder, never())
+        .set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_CANCEL);
+    verify(mockPreviewRequestBuilder, never())
+        .set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_IDLE);
+  }
+
+  @Test
+  public void setFocusMode_shouldSendErrorEventOnUnlockAutoFocusCameraAccessException()
+      throws CameraAccessException {
+    when(mockCaptureSession.capture(any(), any(), any()))
+        .thenThrow(new CameraAccessException(0, ""));
+    camera.setFocusMode(mock(MethodChannel.Result.class), FocusMode.auto);
+    verify(mockDartMessenger, times(1)).sendCameraErrorEvent(any());
+  }
+
+  @Test
+  public void setFocusMode_shouldLockAutoFocusForLockedMode() throws CameraAccessException {
+    camera.setFocusMode(mock(MethodChannel.Result.class), FocusMode.locked);
+    verify(mockPreviewRequestBuilder, times(1))
+        .set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_START);
+    verify(mockCaptureSession, times(1)).capture(any(), any(), any());
+    verify(mockCaptureSession, times(1)).setRepeatingRequest(any(), any(), any());
+  }
+
+  @Test
+  public void setFocusMode_shouldSkipLockAutoFocusWhenNullCaptureSession() {
+    TestUtils.setPrivateField(camera, "captureSession", null);
+    camera.setFocusMode(mock(MethodChannel.Result.class), FocusMode.locked);
+    verify(mockPreviewRequestBuilder, never())
+        .set(CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_TRIGGER_START);
+  }
+
+  @Test
+  public void setFocusMode_shouldSendErrorEventOnLockAutoFocusCameraAccessException()
+      throws CameraAccessException {
+    when(mockCaptureSession.capture(any(), any(), any()))
+        .thenThrow(new CameraAccessException(0, ""));
+    camera.setFocusMode(mock(MethodChannel.Result.class), FocusMode.locked);
+    verify(mockDartMessenger, times(1)).sendCameraErrorEvent(any());
+  }
+
+  @Test
+  public void setFocusMode_shouldCallErrorOnResultOnCameraAccessException()
+      throws CameraAccessException {
+    MethodChannel.Result mockResult = mock(MethodChannel.Result.class);
+    when(mockCaptureSession.setRepeatingRequest(any(), any(), any()))
+        .thenThrow(new CameraAccessException(0, ""));
+
+    camera.setFocusMode(mockResult, FocusMode.locked);
+
+    verify(mockResult, never()).success(any());
+    verify(mockResult, times(1))
+        .error("setFocusModeFailed", "Error setting focus mode: null", null);
+  }
+
+  @Test
+  public void setExposureOffset_shouldUpdateExposureOffsetFeature() {
+    ExposureOffsetFeature mockExposureOffsetFeature =
+        mockCameraFeatureFactory.createExposureOffsetFeature(mockCameraProperties);
+    MethodChannel.Result mockResult = mock(MethodChannel.Result.class);
+
+    camera.setExposureOffset(mockResult, 1.0);
+
+    verify(mockExposureOffsetFeature, times(1)).setValue(1.0);
+    verify(mockResult, never()).error(any(), any(), any());
+    verify(mockResult, times(1)).success(null);
+  }
+
+  @Test
+  public void setExposureOffset_shouldAndUpdateBuilder() {
+    ExposureOffsetFeature mockExposureOffsetFeature =
+        mockCameraFeatureFactory.createExposureOffsetFeature(mockCameraProperties);
+    MethodChannel.Result mockResult = mock(MethodChannel.Result.class);
+
+    camera.setExposureOffset(mockResult, 1.0);
+
+    verify(mockExposureOffsetFeature, times(1)).updateBuilder(any());
+  }
+
+  @Test
+  public void setExposureOffset_shouldCallErrorOnResultOnCameraAccessException()
+      throws CameraAccessException {
+    MethodChannel.Result mockResult = mock(MethodChannel.Result.class);
+    when(mockCaptureSession.setRepeatingRequest(any(), any(), any()))
+        .thenThrow(new CameraAccessException(0, ""));
+
+    camera.setExposureOffset(mockResult, 1.0);
+
+    verify(mockResult, never()).success(any());
+    verify(mockResult, times(1))
+        .error("setExposureOffsetFailed", "Could not set exposure offset.", null);
+  }
+
+  @Test
+  public void lockCaptureOrientation_shouldLockCaptureOrientation() {
+    final Activity mockActivity = mock(Activity.class);
+    SensorOrientationFeature mockSensorOrientationFeature =
+        mockCameraFeatureFactory.createSensorOrientationFeature(
+            mockCameraProperties, mockActivity, mockDartMessenger);
+
+    camera.lockCaptureOrientation(PlatformChannel.DeviceOrientation.PORTRAIT_UP);
+
+    verify(mockSensorOrientationFeature, times(1))
+        .lockCaptureOrientation(PlatformChannel.DeviceOrientation.PORTRAIT_UP);
+  }
+
+  @Test
+  public void unlockCaptureOrientation_shouldUnlockCaptureOrientation() {
+    final Activity mockActivity = mock(Activity.class);
+    SensorOrientationFeature mockSensorOrientationFeature =
+        mockCameraFeatureFactory.createSensorOrientationFeature(
+            mockCameraProperties, mockActivity, mockDartMessenger);
+
+    camera.unlockCaptureOrientation();
+
+    verify(mockSensorOrientationFeature, times(1)).unlockCaptureOrientation();
+  }
+
+  private static class TestCameraFeatureFactory implements CameraFeatureFactory {
+    private final AutoFocusFeature mockAutoFocusFeature;
+    private final ExposureLockFeature mockExposureLockFeature;
+    private final ExposureOffsetFeature mockExposureOffsetFeature;
+    private final ExposurePointFeature mockExposurePointFeature;
+    private final FlashFeature mockFlashFeature;
+    private final FocusPointFeature mockFocusPointFeature;
+    private final FpsRangeFeature mockFpsRangeFeature;
+    private final NoiseReductionFeature mockNoiseReductionFeature;
+    private final ResolutionFeature mockResolutionFeature;
+    private final SensorOrientationFeature mockSensorOrientationFeature;
+    private final ZoomLevelFeature mockZoomLevelFeature;
+
+    public TestCameraFeatureFactory() {
+      this.mockAutoFocusFeature = mock(AutoFocusFeature.class);
+      this.mockExposureLockFeature = mock(ExposureLockFeature.class);
+      this.mockExposureOffsetFeature = mock(ExposureOffsetFeature.class);
+      this.mockExposurePointFeature = mock(ExposurePointFeature.class);
+      this.mockFlashFeature = mock(FlashFeature.class);
+      this.mockFocusPointFeature = mock(FocusPointFeature.class);
+      this.mockFpsRangeFeature = mock(FpsRangeFeature.class);
+      this.mockNoiseReductionFeature = mock(NoiseReductionFeature.class);
+      this.mockResolutionFeature = mock(ResolutionFeature.class);
+      this.mockSensorOrientationFeature = mock(SensorOrientationFeature.class);
+      this.mockZoomLevelFeature = mock(ZoomLevelFeature.class);
+    }
+
+    @Override
+    public AutoFocusFeature createAutoFocusFeature(
+        @NonNull CameraProperties cameraProperties, boolean recordingVideo) {
+      return mockAutoFocusFeature;
+    }
+
+    @Override
+    public ExposureLockFeature createExposureLockFeature(
+        @NonNull CameraProperties cameraProperties) {
+      return mockExposureLockFeature;
+    }
+
+    @Override
+    public ExposureOffsetFeature createExposureOffsetFeature(
+        @NonNull CameraProperties cameraProperties) {
+      return mockExposureOffsetFeature;
+    }
+
+    @Override
+    public FlashFeature createFlashFeature(@NonNull CameraProperties cameraProperties) {
+      return mockFlashFeature;
+    }
+
+    @Override
+    public ResolutionFeature createResolutionFeature(
+        @NonNull CameraProperties cameraProperties,
+        ResolutionPreset initialSetting,
+        String cameraName) {
+      return mockResolutionFeature;
+    }
+
+    @Override
+    public FocusPointFeature createFocusPointFeature(
+        @NonNull CameraProperties cameraProperties,
+        @NonNull SensorOrientationFeature sensorOrienttionFeature) {
+      return mockFocusPointFeature;
+    }
+
+    @Override
+    public FpsRangeFeature createFpsRangeFeature(@NonNull CameraProperties cameraProperties) {
+      return mockFpsRangeFeature;
+    }
+
+    @Override
+    public SensorOrientationFeature createSensorOrientationFeature(
+        @NonNull CameraProperties cameraProperties,
+        @NonNull Activity activity,
+        @NonNull DartMessenger dartMessenger) {
+      return mockSensorOrientationFeature;
+    }
+
+    @Override
+    public ZoomLevelFeature createZoomLevelFeature(@NonNull CameraProperties cameraProperties) {
+      return mockZoomLevelFeature;
+    }
+
+    @Override
+    public ExposurePointFeature createExposurePointFeature(
+        @NonNull CameraProperties cameraProperties,
+        @NonNull SensorOrientationFeature sensorOrientationFeature) {
+      return mockExposurePointFeature;
+    }
+
+    @Override
+    public NoiseReductionFeature createNoiseReductionFeature(
+        @NonNull CameraProperties cameraProperties) {
+      return mockNoiseReductionFeature;
+    }
+  }
+}
diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/CameraUtilsTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/CameraUtilsTest.java
index b97192b..6b714ce 100644
--- a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/CameraUtilsTest.java
+++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/CameraUtilsTest.java
@@ -12,7 +12,7 @@
 public class CameraUtilsTest {
 
   @Test
-  public void serializeDeviceOrientation_serializes_correctly() {
+  public void serializeDeviceOrientation_serializesCorrectly() {
     assertEquals(
         "portraitUp",
         CameraUtils.serializeDeviceOrientation(PlatformChannel.DeviceOrientation.PORTRAIT_UP));
@@ -33,7 +33,7 @@
   }
 
   @Test
-  public void deserializeDeviceOrientation_deserializes_correctly() {
+  public void deserializeDeviceOrientation_deserializesCorrectly() {
     assertEquals(
         PlatformChannel.DeviceOrientation.PORTRAIT_UP,
         CameraUtils.deserializeDeviceOrientation("portraitUp"));
@@ -49,54 +49,7 @@
   }
 
   @Test(expected = UnsupportedOperationException.class)
-  public void deserializeDeviceOrientation_throws_for_null() {
+  public void deserializeDeviceOrientation_throwsForNull() {
     CameraUtils.deserializeDeviceOrientation(null);
   }
-
-  @Test
-  public void getDeviceOrientationFromDegrees_converts_correctly() {
-    // Portrait UP
-    assertEquals(
-        PlatformChannel.DeviceOrientation.PORTRAIT_UP,
-        CameraUtils.getDeviceOrientationFromDegrees(0));
-    assertEquals(
-        PlatformChannel.DeviceOrientation.PORTRAIT_UP,
-        CameraUtils.getDeviceOrientationFromDegrees(315));
-    assertEquals(
-        PlatformChannel.DeviceOrientation.PORTRAIT_UP,
-        CameraUtils.getDeviceOrientationFromDegrees(44));
-    assertEquals(
-        PlatformChannel.DeviceOrientation.PORTRAIT_UP,
-        CameraUtils.getDeviceOrientationFromDegrees(-45));
-    // Portrait DOWN
-    assertEquals(
-        PlatformChannel.DeviceOrientation.PORTRAIT_DOWN,
-        CameraUtils.getDeviceOrientationFromDegrees(180));
-    assertEquals(
-        PlatformChannel.DeviceOrientation.PORTRAIT_DOWN,
-        CameraUtils.getDeviceOrientationFromDegrees(135));
-    assertEquals(
-        PlatformChannel.DeviceOrientation.PORTRAIT_DOWN,
-        CameraUtils.getDeviceOrientationFromDegrees(224));
-    // Landscape LEFT
-    assertEquals(
-        PlatformChannel.DeviceOrientation.LANDSCAPE_LEFT,
-        CameraUtils.getDeviceOrientationFromDegrees(90));
-    assertEquals(
-        PlatformChannel.DeviceOrientation.LANDSCAPE_LEFT,
-        CameraUtils.getDeviceOrientationFromDegrees(45));
-    assertEquals(
-        PlatformChannel.DeviceOrientation.LANDSCAPE_LEFT,
-        CameraUtils.getDeviceOrientationFromDegrees(134));
-    // Landscape RIGHT
-    assertEquals(
-        PlatformChannel.DeviceOrientation.LANDSCAPE_RIGHT,
-        CameraUtils.getDeviceOrientationFromDegrees(270));
-    assertEquals(
-        PlatformChannel.DeviceOrientation.LANDSCAPE_RIGHT,
-        CameraUtils.getDeviceOrientationFromDegrees(225));
-    assertEquals(
-        PlatformChannel.DeviceOrientation.LANDSCAPE_RIGHT,
-        CameraUtils.getDeviceOrientationFromDegrees(314));
-  }
 }
diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/CameraZoomTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/CameraZoomTest.java
index 1385c2e..d3e4955 100644
--- a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/CameraZoomTest.java
+++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/CameraZoomTest.java
@@ -19,7 +19,7 @@
 public class CameraZoomTest {
 
   @Test
-  public void ctor_when_parameters_are_valid() {
+  public void ctor_whenParametersAreValid() {
     final Rect sensorSize = new Rect(0, 0, 0, 0);
     final Float maxZoom = 4.0f;
     final CameraZoom cameraZoom = new CameraZoom(sensorSize, maxZoom);
@@ -31,7 +31,7 @@
   }
 
   @Test
-  public void ctor_when_sensor_size_is_null() {
+  public void ctor_whenSensorSizeIsNull() {
     final Rect sensorSize = null;
     final Float maxZoom = 4.0f;
     final CameraZoom cameraZoom = new CameraZoom(sensorSize, maxZoom);
@@ -42,7 +42,7 @@
   }
 
   @Test
-  public void ctor_when_max_zoom_is_null() {
+  public void ctor_whenMaxZoomIsNull() {
     final Rect sensorSize = new Rect(0, 0, 0, 0);
     final Float maxZoom = null;
     final CameraZoom cameraZoom = new CameraZoom(sensorSize, maxZoom);
@@ -53,7 +53,7 @@
   }
 
   @Test
-  public void ctor_when_max_zoom_is_smaller_then_default_zoom_factor() {
+  public void ctor_whenMaxZoomIsSmallerThenDefaultZoomFactor() {
     final Rect sensorSize = new Rect(0, 0, 0, 0);
     final Float maxZoom = 0.5f;
     final CameraZoom cameraZoom = new CameraZoom(sensorSize, maxZoom);
@@ -64,7 +64,7 @@
   }
 
   @Test
-  public void setZoom_when_no_support_should_not_set_scaler_crop_region() {
+  public void setZoom_whenNoSupportShouldNotSetScalerCropRegion() {
     final CameraZoom cameraZoom = new CameraZoom(null, null);
     final Rect computedZoom = cameraZoom.computeZoom(2f);
 
@@ -72,7 +72,7 @@
   }
 
   @Test
-  public void setZoom_when_sensor_size_equals_zero_should_return_crop_region_of_zero() {
+  public void setZoom_whenSensorSizeEqualsZeroShouldReturnCropRegionOfZero() {
     final Rect sensorSize = new Rect(0, 0, 0, 0);
     final CameraZoom cameraZoom = new CameraZoom(sensorSize, 20f);
     final Rect computedZoom = cameraZoom.computeZoom(18f);
@@ -85,7 +85,7 @@
   }
 
   @Test
-  public void setZoom_when_sensor_size_is_valid_should_return_crop_region() {
+  public void setZoom_whenSensorSizeIsValidShouldReturnCropRegion() {
     final Rect sensorSize = new Rect(0, 0, 100, 100);
     final CameraZoom cameraZoom = new CameraZoom(sensorSize, 20f);
     final Rect computedZoom = cameraZoom.computeZoom(18f);
@@ -98,7 +98,7 @@
   }
 
   @Test
-  public void setZoom_when_zoom_is_greater_then_max_zoom_clamp_to_max_zoom() {
+  public void setZoom_whenZoomIsGreaterThenMaxZoomClampToMaxZoom() {
     final Rect sensorSize = new Rect(0, 0, 100, 100);
     final CameraZoom cameraZoom = new CameraZoom(sensorSize, 10f);
     final Rect computedZoom = cameraZoom.computeZoom(25f);
@@ -111,7 +111,7 @@
   }
 
   @Test
-  public void setZoom_when_zoom_is_smaller_then_min_zoom_clamp_to_min_zoom() {
+  public void setZoom_whenZoomIsSmallerThenMinZoomClampToMinZoom() {
     final Rect sensorSize = new Rect(0, 0, 100, 100);
     final CameraZoom cameraZoom = new CameraZoom(sensorSize, 10f);
     final Rect computedZoom = cameraZoom.computeZoom(0.5f);
diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/DartMessengerTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/DartMessengerTest.java
index 25f5df9..0a2fc43 100644
--- a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/DartMessengerTest.java
+++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/DartMessengerTest.java
@@ -16,8 +16,8 @@
 import io.flutter.plugin.common.BinaryMessenger;
 import io.flutter.plugin.common.MethodCall;
 import io.flutter.plugin.common.StandardMethodCodec;
-import io.flutter.plugins.camera.types.ExposureMode;
-import io.flutter.plugins.camera.types.FocusMode;
+import io.flutter.plugins.camera.features.autofocus.FocusMode;
+import io.flutter.plugins.camera.features.exposurelock.ExposureMode;
 import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.List;
diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/ImageSaverTests.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/ImageSaverTests.java
index d2c9f44..0358ce6 100644
--- a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/ImageSaverTests.java
+++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/ImageSaverTests.java
@@ -80,7 +80,7 @@
   }
 
   @Test
-  public void run_writes_bytes_to_file_and_finishes_with_path() throws IOException {
+  public void runWritesBytesToFileAndFinishesWithPath() throws IOException {
     imageSaver.run();
 
     verify(mockFileOutputStream, times(1)).write(new byte[] {0x42, 0x00, 0x13});
@@ -89,7 +89,7 @@
   }
 
   @Test
-  public void run_calls_error_on_write_ioexception() throws IOException {
+  public void runCallsErrorOnWriteIoexception() throws IOException {
     doThrow(new IOException()).when(mockFileOutputStream).write(any());
     imageSaver.run();
     verify(mockCallback, times(1)).onError("IOError", "Failed saving image");
@@ -97,7 +97,7 @@
   }
 
   @Test
-  public void run_calls_error_on_close_ioexception() throws IOException {
+  public void runCallsErrorOnCloseIoexception() throws IOException {
     doThrow(new IOException("message")).when(mockFileOutputStream).close();
     imageSaver.run();
     verify(mockCallback, times(1)).onError("cameraAccess", "message");
diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/PictureCaptureRequestTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/PictureCaptureRequestTest.java
deleted file mode 100644
index f257a7f..0000000
--- a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/PictureCaptureRequestTest.java
+++ /dev/null
@@ -1,152 +0,0 @@
-// Copyright 2013 The Flutter Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package io.flutter.plugins.camera;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-
-import io.flutter.plugin.common.MethodChannel;
-import org.junit.Test;
-
-public class PictureCaptureRequestTest {
-
-  @Test
-  public void state_is_idle_by_default() {
-    PictureCaptureRequest req = new PictureCaptureRequest(null);
-    assertEquals("Default state is idle", req.getState(), PictureCaptureRequest.State.idle);
-  }
-
-  @Test
-  public void setState_sets_state() {
-    PictureCaptureRequest req = new PictureCaptureRequest(null);
-    req.setState(PictureCaptureRequest.State.focusing);
-    assertEquals("State is focusing", req.getState(), PictureCaptureRequest.State.focusing);
-    req.setState(PictureCaptureRequest.State.preCapture);
-    assertEquals("State is preCapture", req.getState(), PictureCaptureRequest.State.preCapture);
-    req.setState(PictureCaptureRequest.State.waitingPreCaptureReady);
-    assertEquals(
-        "State is waitingPreCaptureReady",
-        req.getState(),
-        PictureCaptureRequest.State.waitingPreCaptureReady);
-    req.setState(PictureCaptureRequest.State.capturing);
-    assertEquals(
-        "State is awaitingPreCapture", req.getState(), PictureCaptureRequest.State.capturing);
-  }
-
-  @Test
-  public void setState_resets_timeout() {
-    PictureCaptureRequest.TimeoutHandler mockTimeoutHandler =
-        mock(PictureCaptureRequest.TimeoutHandler.class);
-    PictureCaptureRequest req = new PictureCaptureRequest(null, mockTimeoutHandler);
-    req.setState(PictureCaptureRequest.State.focusing);
-    req.setState(PictureCaptureRequest.State.preCapture);
-    req.setState(PictureCaptureRequest.State.waitingPreCaptureReady);
-    req.setState(PictureCaptureRequest.State.capturing);
-    verify(mockTimeoutHandler, times(4)).resetTimeout(any());
-    verify(mockTimeoutHandler, never()).clearTimeout(any());
-  }
-
-  @Test
-  public void setState_clears_timeout() {
-    PictureCaptureRequest.TimeoutHandler mockTimeoutHandler =
-        mock(PictureCaptureRequest.TimeoutHandler.class);
-    PictureCaptureRequest req = new PictureCaptureRequest(null, mockTimeoutHandler);
-    req.setState(PictureCaptureRequest.State.idle);
-    req.setState(PictureCaptureRequest.State.finished);
-    req = new PictureCaptureRequest(null, mockTimeoutHandler);
-    req.setState(PictureCaptureRequest.State.error);
-    verify(mockTimeoutHandler, never()).resetTimeout(any());
-    verify(mockTimeoutHandler, times(3)).clearTimeout(any());
-  }
-
-  @Test
-  public void finish_sets_result_and_state() {
-    // Setup
-    MethodChannel.Result mockResult = mock(MethodChannel.Result.class);
-    PictureCaptureRequest req = new PictureCaptureRequest(mockResult);
-    // Act
-    req.finish("/test/path");
-    // Test
-    verify(mockResult).success("/test/path");
-    assertEquals("State is finished", req.getState(), PictureCaptureRequest.State.finished);
-  }
-
-  @Test
-  public void finish_clears_timeout() {
-    PictureCaptureRequest.TimeoutHandler mockTimeoutHandler =
-        mock(PictureCaptureRequest.TimeoutHandler.class);
-    MethodChannel.Result mockResult = mock(MethodChannel.Result.class);
-    PictureCaptureRequest req = new PictureCaptureRequest(mockResult, mockTimeoutHandler);
-    req.finish("/test/path");
-    verify(mockTimeoutHandler, never()).resetTimeout(any());
-    verify(mockTimeoutHandler).clearTimeout(any());
-  }
-
-  @Test
-  public void isFinished_is_true_When_state_is_finished_or_error() {
-    // Setup
-    PictureCaptureRequest req = new PictureCaptureRequest(null);
-    // Test false states
-    req.setState(PictureCaptureRequest.State.idle);
-    assertFalse(req.isFinished());
-    req.setState(PictureCaptureRequest.State.preCapture);
-    assertFalse(req.isFinished());
-    req.setState(PictureCaptureRequest.State.capturing);
-    assertFalse(req.isFinished());
-    // Test true states
-    req.setState(PictureCaptureRequest.State.finished);
-    assertTrue(req.isFinished());
-    req = new PictureCaptureRequest(null); // Refresh
-    req.setState(PictureCaptureRequest.State.error);
-    assertTrue(req.isFinished());
-  }
-
-  @Test(expected = IllegalStateException.class)
-  public void finish_throws_When_already_finished() {
-    // Setup
-    PictureCaptureRequest req = new PictureCaptureRequest(null);
-    req.setState(PictureCaptureRequest.State.finished);
-    // Act
-    req.finish("/test/path");
-  }
-
-  @Test
-  public void error_sets_result_and_state() {
-    // Setup
-    MethodChannel.Result mockResult = mock(MethodChannel.Result.class);
-    PictureCaptureRequest req = new PictureCaptureRequest(mockResult);
-    // Act
-    req.error("ERROR_CODE", "Error Message", null);
-    // Test
-    verify(mockResult).error("ERROR_CODE", "Error Message", null);
-    assertEquals("State is error", req.getState(), PictureCaptureRequest.State.error);
-  }
-
-  @Test
-  public void error_clears_timeout() {
-    PictureCaptureRequest.TimeoutHandler mockTimeoutHandler =
-        mock(PictureCaptureRequest.TimeoutHandler.class);
-    MethodChannel.Result mockResult = mock(MethodChannel.Result.class);
-    PictureCaptureRequest req = new PictureCaptureRequest(mockResult, mockTimeoutHandler);
-    req.error("ERROR_CODE", "Error Message", null);
-    verify(mockTimeoutHandler, never()).resetTimeout(any());
-    verify(mockTimeoutHandler).clearTimeout(any());
-  }
-
-  @Test(expected = IllegalStateException.class)
-  public void error_throws_When_already_finished() {
-    // Setup
-    PictureCaptureRequest req = new PictureCaptureRequest(null);
-    req.setState(PictureCaptureRequest.State.finished);
-    // Act
-    req.error(null, null, null);
-  }
-}
diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/autofocus/AutoFocusFeatureTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/autofocus/AutoFocusFeatureTest.java
index 84e4ad0..fd8ef7c 100644
--- a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/autofocus/AutoFocusFeatureTest.java
+++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/autofocus/AutoFocusFeatureTest.java
@@ -28,7 +28,7 @@
       };
 
   @Test
-  public void getDebugName_should_return_the_name_of_the_feature() {
+  public void getDebugName_shouldReturnTheNameOfTheFeature() {
     CameraProperties mockCameraProperties = mock(CameraProperties.class);
     AutoFocusFeature autoFocusFeature = new AutoFocusFeature(mockCameraProperties, false);
 
@@ -36,7 +36,7 @@
   }
 
   @Test
-  public void getValue_should_return_auto_if_not_set() {
+  public void getValue_shouldReturnAutoIfNotSet() {
     CameraProperties mockCameraProperties = mock(CameraProperties.class);
     AutoFocusFeature autoFocusFeature = new AutoFocusFeature(mockCameraProperties, false);
 
@@ -44,7 +44,7 @@
   }
 
   @Test
-  public void getValue_should_echo_the_set_value() {
+  public void getValue_shouldEchoTheSetValue() {
     CameraProperties mockCameraProperties = mock(CameraProperties.class);
     AutoFocusFeature autoFocusFeature = new AutoFocusFeature(mockCameraProperties, false);
     FocusMode expectedValue = FocusMode.locked;
@@ -56,7 +56,7 @@
   }
 
   @Test
-  public void checkIsSupported_should_return_false_when_minimum_focus_distance_is_zero() {
+  public void checkIsSupported_shouldReturnFalseWhenMinimumFocusDistanceIsZero() {
     CameraProperties mockCameraProperties = mock(CameraProperties.class);
     AutoFocusFeature autoFocusFeature = new AutoFocusFeature(mockCameraProperties, false);
 
@@ -67,7 +67,7 @@
   }
 
   @Test
-  public void checkIsSupported_should_return_false_when_minimum_focus_distance_is_null() {
+  public void checkIsSupported_shouldReturnFalseWhenMinimumFocusDistanceIsNull() {
     CameraProperties mockCameraProperties = mock(CameraProperties.class);
     AutoFocusFeature autoFocusFeature = new AutoFocusFeature(mockCameraProperties, false);
 
@@ -78,7 +78,7 @@
   }
 
   @Test
-  public void checkIsSupport_should_return_false_when_no_focus_modes_are_available() {
+  public void checkIsSupport_shouldReturnFalseWhenNoFocusModesAreAvailable() {
     CameraProperties mockCameraProperties = mock(CameraProperties.class);
     AutoFocusFeature autoFocusFeature = new AutoFocusFeature(mockCameraProperties, false);
 
@@ -89,7 +89,7 @@
   }
 
   @Test
-  public void checkIsSupport_should_return_false_when_only_focus_off_is_available() {
+  public void checkIsSupport_shouldReturnFalseWhenOnlyFocusOffIsAvailable() {
     CameraProperties mockCameraProperties = mock(CameraProperties.class);
     AutoFocusFeature autoFocusFeature = new AutoFocusFeature(mockCameraProperties, false);
 
@@ -100,7 +100,7 @@
   }
 
   @Test
-  public void checkIsSupport_should_return_true_when_only_multiple_focus_modes_are_available() {
+  public void checkIsSupport_shouldReturnTrueWhenOnlyMultipleFocusModesAreAvailable() {
     CameraProperties mockCameraProperties = mock(CameraProperties.class);
     AutoFocusFeature autoFocusFeature = new AutoFocusFeature(mockCameraProperties, false);
 
@@ -111,7 +111,7 @@
   }
 
   @Test
-  public void updateBuilder_should_return_when_checkIsSupported_is_false() {
+  public void updateBuilderShouldReturnWhenCheckIsSupportedIsFalse() {
     CameraProperties mockCameraProperties = mock(CameraProperties.class);
     CaptureRequest.Builder mockBuilder = mock(CaptureRequest.Builder.class);
     AutoFocusFeature autoFocusFeature = new AutoFocusFeature(mockCameraProperties, false);
@@ -125,7 +125,7 @@
   }
 
   @Test
-  public void updateBuilder_should_set_control_mode_to_auto_when_focus_is_locked() {
+  public void updateBuilder_shouldSetControlModeToAutoWhenFocusIsLocked() {
     CameraProperties mockCameraProperties = mock(CameraProperties.class);
     CaptureRequest.Builder mockBuilder = mock(CaptureRequest.Builder.class);
     AutoFocusFeature autoFocusFeature = new AutoFocusFeature(mockCameraProperties, false);
@@ -142,7 +142,7 @@
 
   @Test
   public void
-      updateBuilder_should_set_control_mode_to_continuous_video_when_focus_is_auto_and_recording_video() {
+      updateBuilder_shouldSetControlModeToContinuousVideoWhenFocusIsAutoAndRecordingVideo() {
     CameraProperties mockCameraProperties = mock(CameraProperties.class);
     CaptureRequest.Builder mockBuilder = mock(CaptureRequest.Builder.class);
     AutoFocusFeature autoFocusFeature = new AutoFocusFeature(mockCameraProperties, true);
@@ -159,7 +159,7 @@
 
   @Test
   public void
-      updateBuilder_should_set_control_mode_to_continuous_video_when_focus_is_auto_and_not_recording_video() {
+      updateBuilder_shouldSetControlModeToContinuousVideoWhenFocusIsAutoAndNotRecordingVideo() {
     CameraProperties mockCameraProperties = mock(CameraProperties.class);
     CaptureRequest.Builder mockBuilder = mock(CaptureRequest.Builder.class);
     AutoFocusFeature autoFocusFeature = new AutoFocusFeature(mockCameraProperties, false);
diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/autofocus/FocusModeTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/autofocus/FocusModeTest.java
index 70d52d4..f68ae71 100644
--- a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/autofocus/FocusModeTest.java
+++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/autofocus/FocusModeTest.java
@@ -11,7 +11,7 @@
 public class FocusModeTest {
 
   @Test
-  public void getValueForString_returns_correct_values() {
+  public void getValueForString_returnsCorrectValues() {
     assertEquals(
         "Returns FocusMode.auto for 'auto'", FocusMode.getValueForString("auto"), FocusMode.auto);
     assertEquals(
@@ -21,13 +21,13 @@
   }
 
   @Test
-  public void getValueForString_returns_null_for_nonexistant_value() {
+  public void getValueForString_returnsNullForNonexistantValue() {
     assertEquals(
         "Returns null for 'nonexistant'", FocusMode.getValueForString("nonexistant"), null);
   }
 
   @Test
-  public void toString_returns_correct_value() {
+  public void toString_returnsCorrectValue() {
     assertEquals("Returns 'auto' for FocusMode.auto", FocusMode.auto.toString(), "auto");
     assertEquals("Returns 'locked' for FocusMode.locked", FocusMode.locked.toString(), "locked");
   }
diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/exposurelock/ExposureLockFeatureTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/exposurelock/ExposureLockFeatureTest.java
index d9e0a8d..1cda0a8 100644
--- a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/exposurelock/ExposureLockFeatureTest.java
+++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/exposurelock/ExposureLockFeatureTest.java
@@ -16,7 +16,7 @@
 
 public class ExposureLockFeatureTest {
   @Test
-  public void getDebugName_should_return_the_name_of_the_feature() {
+  public void getDebugName_shouldReturnTheNameOfTheFeature() {
     CameraProperties mockCameraProperties = mock(CameraProperties.class);
     ExposureLockFeature exposureLockFeature = new ExposureLockFeature(mockCameraProperties);
 
@@ -24,7 +24,7 @@
   }
 
   @Test
-  public void getValue_should_return_auto_if_not_set() {
+  public void getValue_shouldReturnAutoIfNotSet() {
     CameraProperties mockCameraProperties = mock(CameraProperties.class);
     ExposureLockFeature exposureLockFeature = new ExposureLockFeature(mockCameraProperties);
 
@@ -32,7 +32,7 @@
   }
 
   @Test
-  public void getValue_should_echo_the_set_value() {
+  public void getValue_shouldEchoTheSetValue() {
     CameraProperties mockCameraProperties = mock(CameraProperties.class);
     ExposureLockFeature exposureLockFeature = new ExposureLockFeature(mockCameraProperties);
     ExposureMode expectedValue = ExposureMode.locked;
@@ -44,7 +44,7 @@
   }
 
   @Test
-  public void checkIsSupported_should_return_true() {
+  public void checkIsSupported_shouldReturnTrue() {
     CameraProperties mockCameraProperties = mock(CameraProperties.class);
     ExposureLockFeature exposureLockFeature = new ExposureLockFeature(mockCameraProperties);
 
@@ -52,8 +52,7 @@
   }
 
   @Test
-  public void
-      updateBuilder_should_set_control_ae_lock_to_false_when_auto_exposure_is_set_to_auto() {
+  public void updateBuilder_shouldSetControlAeLockToFalseWhenAutoExposureIsSetToAuto() {
     CameraProperties mockCameraProperties = mock(CameraProperties.class);
     CaptureRequest.Builder mockBuilder = mock(CaptureRequest.Builder.class);
     ExposureLockFeature exposureLockFeature = new ExposureLockFeature(mockCameraProperties);
@@ -65,8 +64,7 @@
   }
 
   @Test
-  public void
-      updateBuilder_should_set_control_ae_lock_to_false_when_auto_exposure_is_set_to_locked() {
+  public void updateBuilder_shouldSetControlAeLockToFalseWhenAutoExposureIsSetToLocked() {
     CameraProperties mockCameraProperties = mock(CameraProperties.class);
     CaptureRequest.Builder mockBuilder = mock(CaptureRequest.Builder.class);
     ExposureLockFeature exposureLockFeature = new ExposureLockFeature(mockCameraProperties);
diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/exposurelock/ExposureModeTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/exposurelock/ExposureModeTest.java
index ad1d3d9..d5d4769 100644
--- a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/exposurelock/ExposureModeTest.java
+++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/exposurelock/ExposureModeTest.java
@@ -11,7 +11,7 @@
 public class ExposureModeTest {
 
   @Test
-  public void getValueForString_returns_correct_values() {
+  public void getValueForString_returnsCorrectValues() {
     assertEquals(
         "Returns ExposureMode.auto for 'auto'",
         ExposureMode.getValueForString("auto"),
@@ -23,13 +23,13 @@
   }
 
   @Test
-  public void getValueForString_returns_null_for_nonexistant_value() {
+  public void getValueForString_returnsNullForNonexistantValue() {
     assertEquals(
         "Returns null for 'nonexistant'", ExposureMode.getValueForString("nonexistant"), null);
   }
 
   @Test
-  public void toString_returns_correct_value() {
+  public void toString_returnsCorrectValue() {
     assertEquals("Returns 'auto' for ExposureMode.auto", ExposureMode.auto.toString(), "auto");
     assertEquals(
         "Returns 'locked' for ExposureMode.locked", ExposureMode.locked.toString(), "locked");
diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/exposureoffset/ExposureOffsetFeatureTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/exposureoffset/ExposureOffsetFeatureTest.java
index 40d17fd..ee428f3 100644
--- a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/exposureoffset/ExposureOffsetFeatureTest.java
+++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/exposureoffset/ExposureOffsetFeatureTest.java
@@ -17,7 +17,7 @@
 
 public class ExposureOffsetFeatureTest {
   @Test
-  public void getDebugName_should_return_the_name_of_the_feature() {
+  public void getDebugName_shouldReturnTheNameOfTheFeature() {
     CameraProperties mockCameraProperties = mock(CameraProperties.class);
     ExposureOffsetFeature exposureOffsetFeature = new ExposureOffsetFeature(mockCameraProperties);
 
@@ -25,7 +25,7 @@
   }
 
   @Test
-  public void getValue_should_return_zero_if_not_set() {
+  public void getValue_shouldReturnZeroIfNotSet() {
     CameraProperties mockCameraProperties = mock(CameraProperties.class);
     ExposureOffsetFeature exposureOffsetFeature = new ExposureOffsetFeature(mockCameraProperties);
 
@@ -35,7 +35,7 @@
   }
 
   @Test
-  public void getValue_should_echo_the_set_value() {
+  public void getValue_shouldEchoTheSetValue() {
     CameraProperties mockCameraProperties = mock(CameraProperties.class);
     ExposureOffsetFeature exposureOffsetFeature = new ExposureOffsetFeature(mockCameraProperties);
     double expectedValue = 4.0;
@@ -49,8 +49,7 @@
   }
 
   @Test
-  public void
-      getExposureOffsetStepSize_should_return_the_control_exposure_compensation_step_value() {
+  public void getExposureOffsetStepSize_shouldReturnTheControlExposureCompensationStepValue() {
     CameraProperties mockCameraProperties = mock(CameraProperties.class);
     ExposureOffsetFeature exposureOffsetFeature = new ExposureOffsetFeature(mockCameraProperties);
 
@@ -60,7 +59,7 @@
   }
 
   @Test
-  public void checkIsSupported_should_return_true() {
+  public void checkIsSupported_shouldReturnTrue() {
     CameraProperties mockCameraProperties = mock(CameraProperties.class);
     ExposureOffsetFeature exposureOffsetFeature = new ExposureOffsetFeature(mockCameraProperties);
 
@@ -68,7 +67,7 @@
   }
 
   @Test
-  public void updateBuilder_should_set_control_ae_exposure_compensation_to_offset() {
+  public void updateBuilder_shouldSetControlAeExposureCompensationToOffset() {
     CameraProperties mockCameraProperties = mock(CameraProperties.class);
     CaptureRequest.Builder mockBuilder = mock(CaptureRequest.Builder.class);
     ExposureOffsetFeature exposureOffsetFeature = new ExposureOffsetFeature(mockCameraProperties);
diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/exposurepoint/ExposurePointFeatureTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/exposurepoint/ExposurePointFeatureTest.java
index 4a515c6..b34a04f 100644
--- a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/exposurepoint/ExposurePointFeatureTest.java
+++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/exposurepoint/ExposurePointFeatureTest.java
@@ -19,9 +19,12 @@
 import android.hardware.camera2.CaptureRequest;
 import android.hardware.camera2.params.MeteringRectangle;
 import android.util.Size;
+import io.flutter.embedding.engine.systemchannels.PlatformChannel;
 import io.flutter.plugins.camera.CameraProperties;
 import io.flutter.plugins.camera.CameraRegionUtils;
 import io.flutter.plugins.camera.features.Point;
+import io.flutter.plugins.camera.features.sensororientation.DeviceOrientationManager;
+import io.flutter.plugins.camera.features.sensororientation.SensorOrientationFeature;
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.MockedStatic;
@@ -30,35 +33,44 @@
 public class ExposurePointFeatureTest {
 
   Size mockCameraBoundaries;
+  SensorOrientationFeature mockSensorOrientationFeature;
+  DeviceOrientationManager mockDeviceOrientationManager;
 
   @Before
   public void setUp() {
     this.mockCameraBoundaries = mock(Size.class);
     when(this.mockCameraBoundaries.getWidth()).thenReturn(100);
     when(this.mockCameraBoundaries.getHeight()).thenReturn(100);
+    mockSensorOrientationFeature = mock(SensorOrientationFeature.class);
+    mockDeviceOrientationManager = mock(DeviceOrientationManager.class);
+    when(mockSensorOrientationFeature.getDeviceOrientationManager())
+        .thenReturn(mockDeviceOrientationManager);
+    when(mockDeviceOrientationManager.getLastUIOrientation())
+        .thenReturn(PlatformChannel.DeviceOrientation.LANDSCAPE_LEFT);
   }
 
   @Test
-  public void getDebugName_should_return_the_name_of_the_feature() {
+  public void getDebugName_shouldReturnTheNameOfTheFeature() {
     CameraProperties mockCameraProperties = mock(CameraProperties.class);
-    CameraRegionUtils mockCameraRegions = mock(CameraRegionUtils.class);
-    ExposurePointFeature exposurePointFeature = new ExposurePointFeature(mockCameraProperties);
+    ExposurePointFeature exposurePointFeature =
+        new ExposurePointFeature(mockCameraProperties, mockSensorOrientationFeature);
 
     assertEquals("ExposurePointFeature", exposurePointFeature.getDebugName());
   }
 
   @Test
-  public void getValue_should_return_null_if_not_set() {
+  public void getValue_shouldReturnNullIfNotSet() {
     CameraProperties mockCameraProperties = mock(CameraProperties.class);
-    ExposurePointFeature exposurePointFeature = new ExposurePointFeature(mockCameraProperties);
-    Point actualPoint = exposurePointFeature.getValue();
+    ExposurePointFeature exposurePointFeature =
+        new ExposurePointFeature(mockCameraProperties, mockSensorOrientationFeature);
     assertNull(exposurePointFeature.getValue());
   }
 
   @Test
-  public void getValue_should_echo_the_set_value() {
+  public void getValue_shouldEchoTheSetValue() {
     CameraProperties mockCameraProperties = mock(CameraProperties.class);
-    ExposurePointFeature exposurePointFeature = new ExposurePointFeature(mockCameraProperties);
+    ExposurePointFeature exposurePointFeature =
+        new ExposurePointFeature(mockCameraProperties, mockSensorOrientationFeature);
     exposurePointFeature.setCameraBoundaries(this.mockCameraBoundaries);
     Point expectedPoint = new Point(0.0, 0.0);
 
@@ -69,9 +81,10 @@
   }
 
   @Test
-  public void setValue_should_reset_point_when_x_coord_is_null() {
+  public void setValue_shouldResetPointWhenXCoordIsNull() {
     CameraProperties mockCameraProperties = mock(CameraProperties.class);
-    ExposurePointFeature exposurePointFeature = new ExposurePointFeature(mockCameraProperties);
+    ExposurePointFeature exposurePointFeature =
+        new ExposurePointFeature(mockCameraProperties, mockSensorOrientationFeature);
     exposurePointFeature.setCameraBoundaries(this.mockCameraBoundaries);
 
     exposurePointFeature.setValue(new Point(null, 0.0));
@@ -80,9 +93,10 @@
   }
 
   @Test
-  public void setValue_should_reset_point_when_y_coord_is_null() {
+  public void setValue_shouldResetPointWhenYCoordIsNull() {
     CameraProperties mockCameraProperties = mock(CameraProperties.class);
-    ExposurePointFeature exposurePointFeature = new ExposurePointFeature(mockCameraProperties);
+    ExposurePointFeature exposurePointFeature =
+        new ExposurePointFeature(mockCameraProperties, mockSensorOrientationFeature);
     exposurePointFeature.setCameraBoundaries(this.mockCameraBoundaries);
 
     exposurePointFeature.setValue(new Point(0.0, null));
@@ -91,9 +105,10 @@
   }
 
   @Test
-  public void setValue_should_set_point_when_valid_coords_are_supplied() {
+  public void setValue_shouldSetPointWhenValidCoordsAreSupplied() {
     CameraProperties mockCameraProperties = mock(CameraProperties.class);
-    ExposurePointFeature exposurePointFeature = new ExposurePointFeature(mockCameraProperties);
+    ExposurePointFeature exposurePointFeature =
+        new ExposurePointFeature(mockCameraProperties, mockSensorOrientationFeature);
     exposurePointFeature.setCameraBoundaries(this.mockCameraBoundaries);
     Point point = new Point(0.0, 0.0);
 
@@ -103,11 +118,11 @@
   }
 
   @Test
-  public void
-      setValue_should_determine_metering_rectangle_when_valid_boundaries_and_coords_are_supplied() {
+  public void setValue_shouldDetermineMeteringRectangleWhenValidBoundariesAndCoordsAreSupplied() {
     CameraProperties mockCameraProperties = mock(CameraProperties.class);
     when(mockCameraProperties.getControlMaxRegionsAutoExposure()).thenReturn(1);
-    ExposurePointFeature exposurePointFeature = new ExposurePointFeature(mockCameraProperties);
+    ExposurePointFeature exposurePointFeature =
+        new ExposurePointFeature(mockCameraProperties, mockSensorOrientationFeature);
     Size mockedCameraBoundaries = mock(Size.class);
     exposurePointFeature.setCameraBoundaries(mockedCameraBoundaries);
 
@@ -117,16 +132,22 @@
       exposurePointFeature.setValue(new Point(0.5, 0.5));
 
       mockedCameraRegionUtils.verify(
-          () -> CameraRegionUtils.convertPointToMeteringRectangle(mockedCameraBoundaries, 0.5, 0.5),
+          () ->
+              CameraRegionUtils.convertPointToMeteringRectangle(
+                  mockedCameraBoundaries,
+                  0.5,
+                  0.5,
+                  PlatformChannel.DeviceOrientation.LANDSCAPE_LEFT),
           times(1));
     }
   }
 
   @Test(expected = AssertionError.class)
-  public void setValue_should_throw_assertion_error_when_no_valid_boundaries_are_set() {
+  public void setValue_shouldThrowAssertionErrorWhenNoValidBoundariesAreSet() {
     CameraProperties mockCameraProperties = mock(CameraProperties.class);
     when(mockCameraProperties.getControlMaxRegionsAutoExposure()).thenReturn(1);
-    ExposurePointFeature exposurePointFeature = new ExposurePointFeature(mockCameraProperties);
+    ExposurePointFeature exposurePointFeature =
+        new ExposurePointFeature(mockCameraProperties, mockSensorOrientationFeature);
 
     try (MockedStatic<CameraRegionUtils> mockedCameraRegionUtils =
         Mockito.mockStatic(CameraRegionUtils.class)) {
@@ -135,10 +156,11 @@
   }
 
   @Test
-  public void setValue_should_not_determine_metering_rectangle_when_null_coords_are_set() {
+  public void setValue_shouldNotDetermineMeteringRectangleWhenNullCoordsAreSet() {
     CameraProperties mockCameraProperties = mock(CameraProperties.class);
     when(mockCameraProperties.getControlMaxRegionsAutoExposure()).thenReturn(1);
-    ExposurePointFeature exposurePointFeature = new ExposurePointFeature(mockCameraProperties);
+    ExposurePointFeature exposurePointFeature =
+        new ExposurePointFeature(mockCameraProperties, mockSensorOrientationFeature);
     Size mockedCameraBoundaries = mock(Size.class);
     exposurePointFeature.setCameraBoundaries(mockedCameraBoundaries);
 
@@ -155,10 +177,11 @@
 
   @Test
   public void
-      setCameraBoundaries_should_determine_metering_rectangle_when_valid_boundaries_and_coords_are_supplied() {
+      setCameraBoundaries_shouldDetermineMeteringRectangleWhenValidBoundariesAndCoordsAreSupplied() {
     CameraProperties mockCameraProperties = mock(CameraProperties.class);
     when(mockCameraProperties.getControlMaxRegionsAutoExposure()).thenReturn(1);
-    ExposurePointFeature exposurePointFeature = new ExposurePointFeature(mockCameraProperties);
+    ExposurePointFeature exposurePointFeature =
+        new ExposurePointFeature(mockCameraProperties, mockSensorOrientationFeature);
     exposurePointFeature.setCameraBoundaries(this.mockCameraBoundaries);
     exposurePointFeature.setValue(new Point(0.5, 0.5));
     Size mockedCameraBoundaries = mock(Size.class);
@@ -169,15 +192,21 @@
       exposurePointFeature.setCameraBoundaries(mockedCameraBoundaries);
 
       mockedCameraRegionUtils.verify(
-          () -> CameraRegionUtils.convertPointToMeteringRectangle(mockedCameraBoundaries, 0.5, 0.5),
+          () ->
+              CameraRegionUtils.convertPointToMeteringRectangle(
+                  mockedCameraBoundaries,
+                  0.5,
+                  0.5,
+                  PlatformChannel.DeviceOrientation.LANDSCAPE_LEFT),
           times(1));
     }
   }
 
   @Test
-  public void checkIsSupported_should_return_false_when_max_regions_is_null() {
+  public void checkIsSupported_shouldReturnFalseWhenMaxRegionsIsNull() {
     CameraProperties mockCameraProperties = mock(CameraProperties.class);
-    ExposurePointFeature exposurePointFeature = new ExposurePointFeature(mockCameraProperties);
+    ExposurePointFeature exposurePointFeature =
+        new ExposurePointFeature(mockCameraProperties, mockSensorOrientationFeature);
     exposurePointFeature.setCameraBoundaries(new Size(100, 100));
 
     when(mockCameraProperties.getControlMaxRegionsAutoExposure()).thenReturn(null);
@@ -186,9 +215,10 @@
   }
 
   @Test
-  public void checkIsSupported_should_return_false_when_max_regions_is_zero() {
+  public void checkIsSupported_shouldReturnFalseWhenMaxRegionsIsZero() {
     CameraProperties mockCameraProperties = mock(CameraProperties.class);
-    ExposurePointFeature exposurePointFeature = new ExposurePointFeature(mockCameraProperties);
+    ExposurePointFeature exposurePointFeature =
+        new ExposurePointFeature(mockCameraProperties, mockSensorOrientationFeature);
     exposurePointFeature.setCameraBoundaries(new Size(100, 100));
 
     when(mockCameraProperties.getControlMaxRegionsAutoExposure()).thenReturn(0);
@@ -197,9 +227,10 @@
   }
 
   @Test
-  public void checkIsSupported_should_return_true_when_max_regions_is_bigger_then_zero() {
+  public void checkIsSupported_shouldReturnTrueWhenMaxRegionsIsBiggerThenZero() {
     CameraProperties mockCameraProperties = mock(CameraProperties.class);
-    ExposurePointFeature exposurePointFeature = new ExposurePointFeature(mockCameraProperties);
+    ExposurePointFeature exposurePointFeature =
+        new ExposurePointFeature(mockCameraProperties, mockSensorOrientationFeature);
     exposurePointFeature.setCameraBoundaries(new Size(100, 100));
 
     when(mockCameraProperties.getControlMaxRegionsAutoExposure()).thenReturn(1);
@@ -208,10 +239,11 @@
   }
 
   @Test
-  public void updateBuilder_should_return_when_checkIsSupported_is_false() {
+  public void updateBuilder_shouldReturnWhenCheckIsSupportedIsFalse() {
     CameraProperties mockCameraProperties = mock(CameraProperties.class);
     CaptureRequest.Builder mockCaptureRequestBuilder = mock(CaptureRequest.Builder.class);
-    ExposurePointFeature exposurePointFeature = new ExposurePointFeature(mockCameraProperties);
+    ExposurePointFeature exposurePointFeature =
+        new ExposurePointFeature(mockCameraProperties, mockSensorOrientationFeature);
 
     when(mockCameraProperties.getControlMaxRegionsAutoExposure()).thenReturn(0);
 
@@ -221,12 +253,12 @@
   }
 
   @Test
-  public void
-      updateBuilder_should_set_metering_rectangle_when_valid_boundaries_and_coords_are_supplied() {
+  public void updateBuilder_shouldSetMeteringRectangleWhenValidBoundariesAndCoordsAreSupplied() {
     CameraProperties mockCameraProperties = mock(CameraProperties.class);
     when(mockCameraProperties.getControlMaxRegionsAutoExposure()).thenReturn(1);
     CaptureRequest.Builder mockCaptureRequestBuilder = mock(CaptureRequest.Builder.class);
-    ExposurePointFeature exposurePointFeature = new ExposurePointFeature(mockCameraProperties);
+    ExposurePointFeature exposurePointFeature =
+        new ExposurePointFeature(mockCameraProperties, mockSensorOrientationFeature);
     Size mockedCameraBoundaries = mock(Size.class);
     MeteringRectangle mockedMeteringRectangle = mock(MeteringRectangle.class);
 
@@ -236,7 +268,10 @@
           .when(
               () ->
                   CameraRegionUtils.convertPointToMeteringRectangle(
-                      mockedCameraBoundaries, 0.5, 0.5))
+                      mockedCameraBoundaries,
+                      0.5,
+                      0.5,
+                      PlatformChannel.DeviceOrientation.LANDSCAPE_LEFT))
           .thenReturn(mockedMeteringRectangle);
       exposurePointFeature.setCameraBoundaries(mockedCameraBoundaries);
       exposurePointFeature.setValue(new Point(0.5, 0.5));
@@ -249,13 +284,12 @@
   }
 
   @Test
-  public void
-      updateBuilder_should_not_set_metering_rectangle_when_no_valid_boundaries_are_supplied() {
+  public void updateBuilder_shouldNotSetMeteringRectangleWhenNoValidBoundariesAreSupplied() {
     CameraProperties mockCameraProperties = mock(CameraProperties.class);
     when(mockCameraProperties.getControlMaxRegionsAutoExposure()).thenReturn(1);
     CaptureRequest.Builder mockCaptureRequestBuilder = mock(CaptureRequest.Builder.class);
-    ExposurePointFeature exposurePointFeature = new ExposurePointFeature(mockCameraProperties);
-    MeteringRectangle mockedMeteringRectangle = mock(MeteringRectangle.class);
+    ExposurePointFeature exposurePointFeature =
+        new ExposurePointFeature(mockCameraProperties, mockSensorOrientationFeature);
 
     exposurePointFeature.updateBuilder(mockCaptureRequestBuilder);
 
@@ -263,11 +297,12 @@
   }
 
   @Test
-  public void updateBuilder_should_not_set_metering_rectangle_when_no_valid_coords_are_supplied() {
+  public void updateBuilder_shouldNotSetMeteringRectangleWhenNoValidCoordsAreSupplied() {
     CameraProperties mockCameraProperties = mock(CameraProperties.class);
     when(mockCameraProperties.getControlMaxRegionsAutoExposure()).thenReturn(1);
     CaptureRequest.Builder mockCaptureRequestBuilder = mock(CaptureRequest.Builder.class);
-    ExposurePointFeature exposurePointFeature = new ExposurePointFeature(mockCameraProperties);
+    ExposurePointFeature exposurePointFeature =
+        new ExposurePointFeature(mockCameraProperties, mockSensorOrientationFeature);
     exposurePointFeature.setCameraBoundaries(this.mockCameraBoundaries);
 
     exposurePointFeature.setValue(null);
diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/flash/FlashFeatureTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/flash/FlashFeatureTest.java
index eccfb07..f2b4ffc 100644
--- a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/flash/FlashFeatureTest.java
+++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/flash/FlashFeatureTest.java
@@ -20,7 +20,7 @@
 
 public class FlashFeatureTest {
   @Test
-  public void getDebugName_should_return_the_name_of_the_feature() {
+  public void getDebugName_shouldReturnTheNameOfTheFeature() {
     CameraProperties mockCameraProperties = mock(CameraProperties.class);
     FlashFeature flashFeature = new FlashFeature(mockCameraProperties);
 
@@ -28,7 +28,7 @@
   }
 
   @Test
-  public void getValue_should_return_auto_if_not_set() {
+  public void getValue_shouldReturnAutoIfNotSet() {
     CameraProperties mockCameraProperties = mock(CameraProperties.class);
     FlashFeature flashFeature = new FlashFeature(mockCameraProperties);
 
@@ -36,7 +36,7 @@
   }
 
   @Test
-  public void getValue_should_echo_the_set_value() {
+  public void getValue_shouldEchoTheSetValue() {
     CameraProperties mockCameraProperties = mock(CameraProperties.class);
     FlashFeature flashFeature = new FlashFeature(mockCameraProperties);
     FlashMode expectedValue = FlashMode.torch;
@@ -48,7 +48,7 @@
   }
 
   @Test
-  public void checkIsSupported_should_return_false_when_flash_info_available_is_null() {
+  public void checkIsSupported_shouldReturnFalseWhenFlashInfoAvailableIsNull() {
     CameraProperties mockCameraProperties = mock(CameraProperties.class);
     FlashFeature flashFeature = new FlashFeature(mockCameraProperties);
 
@@ -58,7 +58,7 @@
   }
 
   @Test
-  public void checkIsSupported_should_return_false_when_flash_info_available_is_false() {
+  public void checkIsSupported_shouldReturnFalseWhenFlashInfoAvailableIsFalse() {
     CameraProperties mockCameraProperties = mock(CameraProperties.class);
     FlashFeature flashFeature = new FlashFeature(mockCameraProperties);
 
@@ -68,7 +68,7 @@
   }
 
   @Test
-  public void checkIsSupported_should_return_true_when_flash_info_available_is_true() {
+  public void checkIsSupported_shouldReturnTrueWhenFlashInfoAvailableIsTrue() {
     CameraProperties mockCameraProperties = mock(CameraProperties.class);
     FlashFeature flashFeature = new FlashFeature(mockCameraProperties);
 
@@ -78,7 +78,7 @@
   }
 
   @Test
-  public void updateBuilder_should_return_when_checkIsSupported_is_false() {
+  public void updateBuilder_shouldReturnWhenCheckIsSupportedIsFalse() {
     CameraProperties mockCameraProperties = mock(CameraProperties.class);
     CaptureRequest.Builder mockBuilder = mock(CaptureRequest.Builder.class);
     FlashFeature flashFeature = new FlashFeature(mockCameraProperties);
@@ -91,7 +91,7 @@
   }
 
   @Test
-  public void updateBuilder_should_set_ae_mode_and_flash_mode_when_flash_mode_is_off() {
+  public void updateBuilder_shouldSetAeModeAndFlashModeWhenFlashModeIsOff() {
     CameraProperties mockCameraProperties = mock(CameraProperties.class);
     CaptureRequest.Builder mockBuilder = mock(CaptureRequest.Builder.class);
     FlashFeature flashFeature = new FlashFeature(mockCameraProperties);
@@ -107,7 +107,7 @@
   }
 
   @Test
-  public void updateBuilder_should_set_ae_mode_and_flash_mode_when_flash_mode_is_always() {
+  public void updateBuilder_shouldSetAeModeAndFlashModeWhenFlashModeIsAlways() {
     CameraProperties mockCameraProperties = mock(CameraProperties.class);
     CaptureRequest.Builder mockBuilder = mock(CaptureRequest.Builder.class);
     FlashFeature flashFeature = new FlashFeature(mockCameraProperties);
@@ -123,7 +123,7 @@
   }
 
   @Test
-  public void updateBuilder_should_set_ae_mode_and_flash_mode_when_flash_mode_is_torch() {
+  public void updateBuilder_shouldSetAeModeAndFlashModeWhenFlashModeIsTorch() {
     CameraProperties mockCameraProperties = mock(CameraProperties.class);
     CaptureRequest.Builder mockBuilder = mock(CaptureRequest.Builder.class);
     FlashFeature flashFeature = new FlashFeature(mockCameraProperties);
@@ -139,7 +139,7 @@
   }
 
   @Test
-  public void updateBuilder_should_set_ae_mode_and_flash_mode_when_flash_mode_is_auto() {
+  public void updateBuilder_shouldSetAeModeAndFlashModeWhenFlashModeIsAuto() {
     CameraProperties mockCameraProperties = mock(CameraProperties.class);
     CaptureRequest.Builder mockBuilder = mock(CaptureRequest.Builder.class);
     FlashFeature flashFeature = new FlashFeature(mockCameraProperties);
diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/focuspoint/FocusPointFeatureTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/focuspoint/FocusPointFeatureTest.java
index d158336..f03dc9f 100644
--- a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/focuspoint/FocusPointFeatureTest.java
+++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/focuspoint/FocusPointFeatureTest.java
@@ -19,9 +19,12 @@
 import android.hardware.camera2.CaptureRequest;
 import android.hardware.camera2.params.MeteringRectangle;
 import android.util.Size;
+import io.flutter.embedding.engine.systemchannels.PlatformChannel;
 import io.flutter.plugins.camera.CameraProperties;
 import io.flutter.plugins.camera.CameraRegionUtils;
 import io.flutter.plugins.camera.features.Point;
+import io.flutter.plugins.camera.features.sensororientation.DeviceOrientationManager;
+import io.flutter.plugins.camera.features.sensororientation.SensorOrientationFeature;
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.MockedStatic;
@@ -30,35 +33,45 @@
 public class FocusPointFeatureTest {
 
   Size mockCameraBoundaries;
+  SensorOrientationFeature mockSensorOrientationFeature;
+  DeviceOrientationManager mockDeviceOrientationManager;
 
   @Before
   public void setUp() {
     this.mockCameraBoundaries = mock(Size.class);
     when(this.mockCameraBoundaries.getWidth()).thenReturn(100);
     when(this.mockCameraBoundaries.getHeight()).thenReturn(100);
+    mockSensorOrientationFeature = mock(SensorOrientationFeature.class);
+    mockDeviceOrientationManager = mock(DeviceOrientationManager.class);
+    when(mockSensorOrientationFeature.getDeviceOrientationManager())
+        .thenReturn(mockDeviceOrientationManager);
+    when(mockDeviceOrientationManager.getLastUIOrientation())
+        .thenReturn(PlatformChannel.DeviceOrientation.LANDSCAPE_LEFT);
   }
 
   @Test
-  public void getDebugName_should_return_the_name_of_the_feature() {
+  public void getDebugName_shouldReturnTheNameOfTheFeature() {
     CameraProperties mockCameraProperties = mock(CameraProperties.class);
-    CameraRegionUtils mockCameraRegions = mock(CameraRegionUtils.class);
-    FocusPointFeature focusPointFeature = new FocusPointFeature(mockCameraProperties);
+    FocusPointFeature focusPointFeature =
+        new FocusPointFeature(mockCameraProperties, mockSensorOrientationFeature);
 
     assertEquals("FocusPointFeature", focusPointFeature.getDebugName());
   }
 
   @Test
-  public void getValue_should_return_null_if_not_set() {
+  public void getValue_shouldReturnNullIfNotSet() {
     CameraProperties mockCameraProperties = mock(CameraProperties.class);
-    FocusPointFeature focusPointFeature = new FocusPointFeature(mockCameraProperties);
+    FocusPointFeature focusPointFeature =
+        new FocusPointFeature(mockCameraProperties, mockSensorOrientationFeature);
     Point actualPoint = focusPointFeature.getValue();
     assertNull(focusPointFeature.getValue());
   }
 
   @Test
-  public void getValue_should_echo_the_set_value() {
+  public void getValue_shouldEchoTheSetValue() {
     CameraProperties mockCameraProperties = mock(CameraProperties.class);
-    FocusPointFeature focusPointFeature = new FocusPointFeature(mockCameraProperties);
+    FocusPointFeature focusPointFeature =
+        new FocusPointFeature(mockCameraProperties, mockSensorOrientationFeature);
     focusPointFeature.setCameraBoundaries(this.mockCameraBoundaries);
     Point expectedPoint = new Point(0.0, 0.0);
 
@@ -69,9 +82,10 @@
   }
 
   @Test
-  public void setValue_should_reset_point_when_x_coord_is_null() {
+  public void setValue_shouldResetPointWhenXCoordIsNull() {
     CameraProperties mockCameraProperties = mock(CameraProperties.class);
-    FocusPointFeature focusPointFeature = new FocusPointFeature(mockCameraProperties);
+    FocusPointFeature focusPointFeature =
+        new FocusPointFeature(mockCameraProperties, mockSensorOrientationFeature);
     focusPointFeature.setCameraBoundaries(this.mockCameraBoundaries);
 
     focusPointFeature.setValue(new Point(null, 0.0));
@@ -80,9 +94,10 @@
   }
 
   @Test
-  public void setValue_should_reset_point_when_y_coord_is_null() {
+  public void setValue_shouldResetPointWhenYCoordIsNull() {
     CameraProperties mockCameraProperties = mock(CameraProperties.class);
-    FocusPointFeature focusPointFeature = new FocusPointFeature(mockCameraProperties);
+    FocusPointFeature focusPointFeature =
+        new FocusPointFeature(mockCameraProperties, mockSensorOrientationFeature);
     focusPointFeature.setCameraBoundaries(this.mockCameraBoundaries);
 
     focusPointFeature.setValue(new Point(0.0, null));
@@ -91,9 +106,10 @@
   }
 
   @Test
-  public void setValue_should_set_point_when_valid_coords_are_supplied() {
+  public void setValue_shouldSetPointWhenValidCoordsAreSupplied() {
     CameraProperties mockCameraProperties = mock(CameraProperties.class);
-    FocusPointFeature focusPointFeature = new FocusPointFeature(mockCameraProperties);
+    FocusPointFeature focusPointFeature =
+        new FocusPointFeature(mockCameraProperties, mockSensorOrientationFeature);
     focusPointFeature.setCameraBoundaries(this.mockCameraBoundaries);
     Point point = new Point(0.0, 0.0);
 
@@ -103,11 +119,11 @@
   }
 
   @Test
-  public void
-      setValue_should_determine_metering_rectangle_when_valid_boundaries_and_coords_are_supplied() {
+  public void setValue_shouldDetermineMeteringRectangleWhenValidBoundariesAndCoordsAreSupplied() {
     CameraProperties mockCameraProperties = mock(CameraProperties.class);
     when(mockCameraProperties.getControlMaxRegionsAutoFocus()).thenReturn(1);
-    FocusPointFeature focusPointFeature = new FocusPointFeature(mockCameraProperties);
+    FocusPointFeature focusPointFeature =
+        new FocusPointFeature(mockCameraProperties, mockSensorOrientationFeature);
     Size mockedCameraBoundaries = mock(Size.class);
     focusPointFeature.setCameraBoundaries(mockedCameraBoundaries);
 
@@ -117,16 +133,22 @@
       focusPointFeature.setValue(new Point(0.5, 0.5));
 
       mockedCameraRegionUtils.verify(
-          () -> CameraRegionUtils.convertPointToMeteringRectangle(mockedCameraBoundaries, 0.5, 0.5),
+          () ->
+              CameraRegionUtils.convertPointToMeteringRectangle(
+                  mockedCameraBoundaries,
+                  0.5,
+                  0.5,
+                  PlatformChannel.DeviceOrientation.LANDSCAPE_LEFT),
           times(1));
     }
   }
 
   @Test(expected = AssertionError.class)
-  public void setValue_should_throw_assertion_error_when_no_valid_boundaries_are_set() {
+  public void setValue_shouldThrowAssertionErrorWhenNoValidBoundariesAreSet() {
     CameraProperties mockCameraProperties = mock(CameraProperties.class);
     when(mockCameraProperties.getControlMaxRegionsAutoFocus()).thenReturn(1);
-    FocusPointFeature focusPointFeature = new FocusPointFeature(mockCameraProperties);
+    FocusPointFeature focusPointFeature =
+        new FocusPointFeature(mockCameraProperties, mockSensorOrientationFeature);
 
     try (MockedStatic<CameraRegionUtils> mockedCameraRegionUtils =
         Mockito.mockStatic(CameraRegionUtils.class)) {
@@ -135,10 +157,11 @@
   }
 
   @Test
-  public void setValue_should_not_determine_metering_rectangle_when_null_coords_are_set() {
+  public void setValue_shouldNotDetermineMeteringRectangleWhenNullCoordsAreSet() {
     CameraProperties mockCameraProperties = mock(CameraProperties.class);
     when(mockCameraProperties.getControlMaxRegionsAutoFocus()).thenReturn(1);
-    FocusPointFeature focusPointFeature = new FocusPointFeature(mockCameraProperties);
+    FocusPointFeature focusPointFeature =
+        new FocusPointFeature(mockCameraProperties, mockSensorOrientationFeature);
     Size mockedCameraBoundaries = mock(Size.class);
     focusPointFeature.setCameraBoundaries(mockedCameraBoundaries);
 
@@ -155,10 +178,11 @@
 
   @Test
   public void
-      setCameraBoundaries_should_determine_metering_rectangle_when_valid_boundaries_and_coords_are_supplied() {
+      setCameraBoundaries_shouldDetermineMeteringRectangleWhenValidBoundariesAndCoordsAreSupplied() {
     CameraProperties mockCameraProperties = mock(CameraProperties.class);
     when(mockCameraProperties.getControlMaxRegionsAutoFocus()).thenReturn(1);
-    FocusPointFeature focusPointFeature = new FocusPointFeature(mockCameraProperties);
+    FocusPointFeature focusPointFeature =
+        new FocusPointFeature(mockCameraProperties, mockSensorOrientationFeature);
     focusPointFeature.setCameraBoundaries(this.mockCameraBoundaries);
     focusPointFeature.setValue(new Point(0.5, 0.5));
     Size mockedCameraBoundaries = mock(Size.class);
@@ -169,15 +193,21 @@
       focusPointFeature.setCameraBoundaries(mockedCameraBoundaries);
 
       mockedCameraRegionUtils.verify(
-          () -> CameraRegionUtils.convertPointToMeteringRectangle(mockedCameraBoundaries, 0.5, 0.5),
+          () ->
+              CameraRegionUtils.convertPointToMeteringRectangle(
+                  mockedCameraBoundaries,
+                  0.5,
+                  0.5,
+                  PlatformChannel.DeviceOrientation.LANDSCAPE_LEFT),
           times(1));
     }
   }
 
   @Test
-  public void checkIsSupported_should_return_false_when_max_regions_is_null() {
+  public void checkIsSupported_shouldReturnFalseWhenMaxRegionsIsNull() {
     CameraProperties mockCameraProperties = mock(CameraProperties.class);
-    FocusPointFeature focusPointFeature = new FocusPointFeature(mockCameraProperties);
+    FocusPointFeature focusPointFeature =
+        new FocusPointFeature(mockCameraProperties, mockSensorOrientationFeature);
     focusPointFeature.setCameraBoundaries(new Size(100, 100));
 
     when(mockCameraProperties.getControlMaxRegionsAutoFocus()).thenReturn(null);
@@ -186,9 +216,10 @@
   }
 
   @Test
-  public void checkIsSupported_should_return_false_when_max_regions_is_zero() {
+  public void checkIsSupported_shouldReturnFalseWhenMaxRegionsIsZero() {
     CameraProperties mockCameraProperties = mock(CameraProperties.class);
-    FocusPointFeature focusPointFeature = new FocusPointFeature(mockCameraProperties);
+    FocusPointFeature focusPointFeature =
+        new FocusPointFeature(mockCameraProperties, mockSensorOrientationFeature);
     focusPointFeature.setCameraBoundaries(new Size(100, 100));
 
     when(mockCameraProperties.getControlMaxRegionsAutoFocus()).thenReturn(0);
@@ -197,9 +228,10 @@
   }
 
   @Test
-  public void checkIsSupported_should_return_true_when_max_regions_is_bigger_then_zero() {
+  public void checkIsSupported_shouldReturnTrueWhenMaxRegionsIsBiggerThenZero() {
     CameraProperties mockCameraProperties = mock(CameraProperties.class);
-    FocusPointFeature focusPointFeature = new FocusPointFeature(mockCameraProperties);
+    FocusPointFeature focusPointFeature =
+        new FocusPointFeature(mockCameraProperties, mockSensorOrientationFeature);
     focusPointFeature.setCameraBoundaries(new Size(100, 100));
 
     when(mockCameraProperties.getControlMaxRegionsAutoFocus()).thenReturn(1);
@@ -208,10 +240,11 @@
   }
 
   @Test
-  public void updateBuilder_should_return_when_checkIsSupported_is_false() {
+  public void updateBuilder_shouldReturnWhenCheckIsSupportedIsFalse() {
     CameraProperties mockCameraProperties = mock(CameraProperties.class);
     CaptureRequest.Builder mockCaptureRequestBuilder = mock(CaptureRequest.Builder.class);
-    FocusPointFeature focusPointFeature = new FocusPointFeature(mockCameraProperties);
+    FocusPointFeature focusPointFeature =
+        new FocusPointFeature(mockCameraProperties, mockSensorOrientationFeature);
 
     when(mockCameraProperties.getControlMaxRegionsAutoFocus()).thenReturn(0);
 
@@ -221,12 +254,12 @@
   }
 
   @Test
-  public void
-      updateBuilder_should_set_metering_rectangle_when_valid_boundaries_and_coords_are_supplied() {
+  public void updateBuilder_shouldSetMeteringRectangleWhenValidBoundariesAndCoordsAreSupplied() {
     CameraProperties mockCameraProperties = mock(CameraProperties.class);
     when(mockCameraProperties.getControlMaxRegionsAutoFocus()).thenReturn(1);
     CaptureRequest.Builder mockCaptureRequestBuilder = mock(CaptureRequest.Builder.class);
-    FocusPointFeature focusPointFeature = new FocusPointFeature(mockCameraProperties);
+    FocusPointFeature focusPointFeature =
+        new FocusPointFeature(mockCameraProperties, mockSensorOrientationFeature);
     Size mockedCameraBoundaries = mock(Size.class);
     MeteringRectangle mockedMeteringRectangle = mock(MeteringRectangle.class);
 
@@ -236,7 +269,10 @@
           .when(
               () ->
                   CameraRegionUtils.convertPointToMeteringRectangle(
-                      mockedCameraBoundaries, 0.5, 0.5))
+                      mockedCameraBoundaries,
+                      0.5,
+                      0.5,
+                      PlatformChannel.DeviceOrientation.LANDSCAPE_LEFT))
           .thenReturn(mockedMeteringRectangle);
       focusPointFeature.setCameraBoundaries(mockedCameraBoundaries);
       focusPointFeature.setValue(new Point(0.5, 0.5));
@@ -249,12 +285,12 @@
   }
 
   @Test
-  public void
-      updateBuilder_should_not_set_metering_rectangle_when_no_valid_boundaries_are_supplied() {
+  public void updateBuilder_shouldNotSetMeteringRectangleWhenNoValidBoundariesAreSupplied() {
     CameraProperties mockCameraProperties = mock(CameraProperties.class);
     when(mockCameraProperties.getControlMaxRegionsAutoFocus()).thenReturn(1);
     CaptureRequest.Builder mockCaptureRequestBuilder = mock(CaptureRequest.Builder.class);
-    FocusPointFeature focusPointFeature = new FocusPointFeature(mockCameraProperties);
+    FocusPointFeature focusPointFeature =
+        new FocusPointFeature(mockCameraProperties, mockSensorOrientationFeature);
     MeteringRectangle mockedMeteringRectangle = mock(MeteringRectangle.class);
 
     focusPointFeature.updateBuilder(mockCaptureRequestBuilder);
@@ -263,11 +299,12 @@
   }
 
   @Test
-  public void updateBuilder_should_not_set_metering_rectangle_when_no_valid_coords_are_supplied() {
+  public void updateBuilder_shouldNotSetMeteringRectangleWhenNoValidCoordsAreSupplied() {
     CameraProperties mockCameraProperties = mock(CameraProperties.class);
     when(mockCameraProperties.getControlMaxRegionsAutoFocus()).thenReturn(1);
     CaptureRequest.Builder mockCaptureRequestBuilder = mock(CaptureRequest.Builder.class);
-    FocusPointFeature focusPointFeature = new FocusPointFeature(mockCameraProperties);
+    FocusPointFeature focusPointFeature =
+        new FocusPointFeature(mockCameraProperties, mockSensorOrientationFeature);
     focusPointFeature.setCameraBoundaries(this.mockCameraBoundaries);
 
     focusPointFeature.setValue(null);
diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/fpsrange/FpsRangeFeaturePixel4aTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/fpsrange/FpsRangeFeaturePixel4aTest.java
index 7b6e70f..93cfe55 100644
--- a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/fpsrange/FpsRangeFeaturePixel4aTest.java
+++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/fpsrange/FpsRangeFeaturePixel4aTest.java
@@ -18,7 +18,7 @@
 @RunWith(RobolectricTestRunner.class)
 public class FpsRangeFeaturePixel4aTest {
   @Test
-  public void ctor_should_initialize_fps_range_with_30_when_device_is_pixel_4a() {
+  public void ctor_shouldInitializeFpsRangeWith30WhenDeviceIsPixel4a() {
     TestUtils.setFinalStatic(Build.class, "BRAND", "google");
     TestUtils.setFinalStatic(Build.class, "MODEL", "Pixel 4a");
 
diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/fpsrange/FpsRangeFeatureTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/fpsrange/FpsRangeFeatureTest.java
index 77937b5..2bb4d84 100644
--- a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/fpsrange/FpsRangeFeatureTest.java
+++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/fpsrange/FpsRangeFeatureTest.java
@@ -35,19 +35,19 @@
   }
 
   @Test
-  public void ctor_should_initialize_fps_range_with_highest_upper_value_from_range_array() {
+  public void ctor_shouldInitializeFpsRangeWithHighestUpperValueFromRangeArray() {
     FpsRangeFeature fpsRangeFeature = createTestInstance();
     assertEquals(13, (int) fpsRangeFeature.getValue().getUpper());
   }
 
   @Test
-  public void getDebugName_should_return_the_name_of_the_feature() {
+  public void getDebugName_shouldReturnTheNameOfTheFeature() {
     FpsRangeFeature fpsRangeFeature = createTestInstance();
     assertEquals("FpsRangeFeature", fpsRangeFeature.getDebugName());
   }
 
   @Test
-  public void getValue_should_return_highest_upper_range_if_not_set() {
+  public void getValue_shouldReturnHighestUpperRangeIfNotSet() {
     CameraProperties mockCameraProperties = mock(CameraProperties.class);
     FpsRangeFeature fpsRangeFeature = createTestInstance();
 
@@ -55,7 +55,7 @@
   }
 
   @Test
-  public void getValue_should_echo_the_set_value() {
+  public void getValue_shouldEchoTheSetValue() {
     CameraProperties mockCameraProperties = mock(CameraProperties.class);
     FpsRangeFeature fpsRangeFeature = new FpsRangeFeature(mockCameraProperties);
     @SuppressWarnings("unchecked")
@@ -68,14 +68,14 @@
   }
 
   @Test
-  public void checkIsSupported_should_return_true() {
+  public void checkIsSupported_shouldReturnTrue() {
     FpsRangeFeature fpsRangeFeature = createTestInstance();
     assertTrue(fpsRangeFeature.checkIsSupported());
   }
 
   @Test
   @SuppressWarnings("unchecked")
-  public void updateBuilder_should_set_ae_target_fps_range() {
+  public void updateBuilder_shouldSetAeTargetFpsRange() {
     CaptureRequest.Builder mockBuilder = mock(CaptureRequest.Builder.class);
     FpsRangeFeature fpsRangeFeature = createTestInstance();
 
diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/noisereduction/NoiseReductionFeatureTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/noisereduction/NoiseReductionFeatureTest.java
index eb1a639..b89aad0 100644
--- a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/noisereduction/NoiseReductionFeatureTest.java
+++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/noisereduction/NoiseReductionFeatureTest.java
@@ -37,7 +37,7 @@
   }
 
   @Test
-  public void getDebugName_should_return_the_name_of_the_feature() {
+  public void getDebugName_shouldReturnTheNameOfTheFeature() {
     CameraProperties mockCameraProperties = mock(CameraProperties.class);
     NoiseReductionFeature noiseReductionFeature = new NoiseReductionFeature(mockCameraProperties);
 
@@ -45,7 +45,7 @@
   }
 
   @Test
-  public void getValue_should_return_fast_if_not_set() {
+  public void getValue_shouldReturnFastIfNotSet() {
     CameraProperties mockCameraProperties = mock(CameraProperties.class);
     NoiseReductionFeature noiseReductionFeature = new NoiseReductionFeature(mockCameraProperties);
 
@@ -53,7 +53,7 @@
   }
 
   @Test
-  public void getValue_should_echo_the_set_value() {
+  public void getValue_shouldEchoTheSetValue() {
     CameraProperties mockCameraProperties = mock(CameraProperties.class);
     NoiseReductionFeature noiseReductionFeature = new NoiseReductionFeature(mockCameraProperties);
     NoiseReductionMode expectedValue = NoiseReductionMode.fast;
@@ -65,7 +65,7 @@
   }
 
   @Test
-  public void checkIsSupported_should_return_false_when_available_noise_reduction_modes_is_null() {
+  public void checkIsSupported_shouldReturnFalseWhenAvailableNoiseReductionModesIsNull() {
     CameraProperties mockCameraProperties = mock(CameraProperties.class);
     NoiseReductionFeature noiseReductionFeature = new NoiseReductionFeature(mockCameraProperties);
 
@@ -76,7 +76,7 @@
 
   @Test
   public void
-      checkIsSupported_should_return_false_when_available_noise_reduction_modes_returns_an_empty_array() {
+      checkIsSupported_shouldReturnFalseWhenAvailableNoiseReductionModesReturnsAnEmptyArray() {
     CameraProperties mockCameraProperties = mock(CameraProperties.class);
     NoiseReductionFeature noiseReductionFeature = new NoiseReductionFeature(mockCameraProperties);
 
@@ -87,7 +87,7 @@
 
   @Test
   public void
-      checkIsSupported_should_return_true_when_available_noise_reduction_modes_returns_at_least_one_item() {
+      checkIsSupported_shouldReturnTrueWhenAvailableNoiseReductionModesReturnsAtLeastOneItem() {
     CameraProperties mockCameraProperties = mock(CameraProperties.class);
     NoiseReductionFeature noiseReductionFeature = new NoiseReductionFeature(mockCameraProperties);
 
@@ -97,7 +97,7 @@
   }
 
   @Test
-  public void updateBuilder_should_return_when_checkIsSupported_is_false() {
+  public void updateBuilder_shouldReturnWhenCheckIsSupportedIsFalse() {
     CameraProperties mockCameraProperties = mock(CameraProperties.class);
     CaptureRequest.Builder mockBuilder = mock(CaptureRequest.Builder.class);
     NoiseReductionFeature noiseReductionFeature = new NoiseReductionFeature(mockCameraProperties);
@@ -110,29 +110,28 @@
   }
 
   @Test
-  public void updateBuilder_should_set_noise_reduction_mode_off_when_off() {
+  public void updateBuilder_shouldSetNoiseReductionModeOffWhenOff() {
     testUpdateBuilderWith(NoiseReductionMode.off, CaptureRequest.NOISE_REDUCTION_MODE_OFF);
   }
 
   @Test
-  public void updateBuilder_should_set_noise_reduction_mode_fast_when_fast() {
+  public void updateBuilder_shouldSetNoiseReductionModeFastWhenFast() {
     testUpdateBuilderWith(NoiseReductionMode.fast, CaptureRequest.NOISE_REDUCTION_MODE_FAST);
   }
 
   @Test
-  public void updateBuilder_should_set_noise_reduction_mode_high_quality_when_high_quality() {
+  public void updateBuilder_shouldSetNoiseReductionModeHighQualityWhenHighQuality() {
     testUpdateBuilderWith(
         NoiseReductionMode.highQuality, CaptureRequest.NOISE_REDUCTION_MODE_HIGH_QUALITY);
   }
 
   @Test
-  public void updateBuilder_should_set_noise_reduction_mode_minimal_when_minimal() {
+  public void updateBuilder_shouldSetNoiseReductionModeMinimalWhenMinimal() {
     testUpdateBuilderWith(NoiseReductionMode.minimal, CaptureRequest.NOISE_REDUCTION_MODE_MINIMAL);
   }
 
   @Test
-  public void
-      updateBuilder_should_set_noise_reduction_mode_zero_shutter_lag_when_zero_shutter_lag() {
+  public void updateBuilder_shouldSetNoiseReductionModeZeroShutterLagWhenZeroShutterLag() {
     testUpdateBuilderWith(
         NoiseReductionMode.zeroShutterLag, CaptureRequest.NOISE_REDUCTION_MODE_ZERO_SHUTTER_LAG);
   }
diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/resolution/ResolutionFeatureTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/resolution/ResolutionFeatureTest.java
index bb9cb61..e09223d 100644
--- a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/resolution/ResolutionFeatureTest.java
+++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/resolution/ResolutionFeatureTest.java
@@ -79,7 +79,7 @@
   }
 
   @Test
-  public void getDebugName_should_return_the_name_of_the_feature() {
+  public void getDebugName_shouldReturnTheNameOfTheFeature() {
     CameraProperties mockCameraProperties = mock(CameraProperties.class);
     ResolutionFeature resolutionFeature =
         new ResolutionFeature(mockCameraProperties, ResolutionPreset.max, cameraName);
@@ -88,7 +88,7 @@
   }
 
   @Test
-  public void getValue_should_return_initial_value_when_not_set() {
+  public void getValue_shouldReturnInitialValueWhenNotSet() {
     CameraProperties mockCameraProperties = mock(CameraProperties.class);
     ResolutionFeature resolutionFeature =
         new ResolutionFeature(mockCameraProperties, ResolutionPreset.max, cameraName);
@@ -97,7 +97,7 @@
   }
 
   @Test
-  public void getValue_should_echo_setValue() {
+  public void getValue_shouldEchoSetValue() {
     CameraProperties mockCameraProperties = mock(CameraProperties.class);
     ResolutionFeature resolutionFeature =
         new ResolutionFeature(mockCameraProperties, ResolutionPreset.max, cameraName);
@@ -108,7 +108,7 @@
   }
 
   @Test
-  public void checkIsSupport_returns_true() {
+  public void checkIsSupport_returnsTrue() {
     CameraProperties mockCameraProperties = mock(CameraProperties.class);
     ResolutionFeature resolutionFeature =
         new ResolutionFeature(mockCameraProperties, ResolutionPreset.max, cameraName);
@@ -117,7 +117,7 @@
   }
 
   @Test
-  public void getBestAvailableCamcorderProfileForResolutionPreset_should_fall_through() {
+  public void getBestAvailableCamcorderProfileForResolutionPreset_shouldFallThrough() {
     mockedStaticProfile
         .when(() -> CamcorderProfile.hasProfile(1, CamcorderProfile.QUALITY_HIGH))
         .thenReturn(false);
@@ -147,42 +147,42 @@
   }
 
   @Test
-  public void computeBestPreviewSize_should_use_720P_when_resolution_preset_max() {
+  public void computeBestPreviewSize_shouldUse720PWhenResolutionPresetMax() {
     ResolutionFeature.computeBestPreviewSize(1, ResolutionPreset.max);
 
     mockedStaticProfile.verify(() -> CamcorderProfile.get(1, CamcorderProfile.QUALITY_720P));
   }
 
   @Test
-  public void computeBestPreviewSize_should_use_720P_when_resolution_preset_ultraHigh() {
+  public void computeBestPreviewSize_shouldUse720PWhenResolutionPresetUltraHigh() {
     ResolutionFeature.computeBestPreviewSize(1, ResolutionPreset.ultraHigh);
 
     mockedStaticProfile.verify(() -> CamcorderProfile.get(1, CamcorderProfile.QUALITY_720P));
   }
 
   @Test
-  public void computeBestPreviewSize_should_use_720P_when_resolution_preset_veryHigh() {
+  public void computeBestPreviewSize_shouldUse720PWhenResolutionPresetVeryHigh() {
     ResolutionFeature.computeBestPreviewSize(1, ResolutionPreset.veryHigh);
 
     mockedStaticProfile.verify(() -> CamcorderProfile.get(1, CamcorderProfile.QUALITY_720P));
   }
 
   @Test
-  public void computeBestPreviewSize_should_use_720P_when_resolution_preset_high() {
+  public void computeBestPreviewSize_shouldUse720PWhenResolutionPresetHigh() {
     ResolutionFeature.computeBestPreviewSize(1, ResolutionPreset.high);
 
     mockedStaticProfile.verify(() -> CamcorderProfile.get(1, CamcorderProfile.QUALITY_720P));
   }
 
   @Test
-  public void computeBestPreviewSize_should_use_480P_when_resolution_preset_medium() {
+  public void computeBestPreviewSize_shouldUse480PWhenResolutionPresetMedium() {
     ResolutionFeature.computeBestPreviewSize(1, ResolutionPreset.medium);
 
     mockedStaticProfile.verify(() -> CamcorderProfile.get(1, CamcorderProfile.QUALITY_480P));
   }
 
   @Test
-  public void computeBestPreviewSize_should_use_QVGA_when_resolution_preset_low() {
+  public void computeBestPreviewSize_shouldUseQVGAWhenResolutionPresetLow() {
     ResolutionFeature.computeBestPreviewSize(1, ResolutionPreset.low);
 
     mockedStaticProfile.verify(() -> CamcorderProfile.get(1, CamcorderProfile.QUALITY_QVGA));
diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/sensororientation/DeviceOrientationManagerTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/sensororientation/DeviceOrientationManagerTest.java
index 6e8d04d..58f17cb 100644
--- a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/sensororientation/DeviceOrientationManagerTest.java
+++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/sensororientation/DeviceOrientationManagerTest.java
@@ -50,15 +50,15 @@
   }
 
   @Test
-  public void getMediaOrientation_when_natural_screen_orientation_equals_portrait_up() {
+  public void getVideoOrientation_whenNaturalScreenOrientationEqualsPortraitUp() {
     int degreesPortraitUp =
-        deviceOrientationManager.getMediaOrientation(DeviceOrientation.PORTRAIT_UP);
+        deviceOrientationManager.getVideoOrientation(DeviceOrientation.PORTRAIT_UP);
     int degreesPortraitDown =
-        deviceOrientationManager.getMediaOrientation(DeviceOrientation.PORTRAIT_DOWN);
+        deviceOrientationManager.getVideoOrientation(DeviceOrientation.PORTRAIT_DOWN);
     int degreesLandscapeLeft =
-        deviceOrientationManager.getMediaOrientation(DeviceOrientation.LANDSCAPE_LEFT);
+        deviceOrientationManager.getVideoOrientation(DeviceOrientation.LANDSCAPE_LEFT);
     int degreesLandscapeRight =
-        deviceOrientationManager.getMediaOrientation(DeviceOrientation.LANDSCAPE_RIGHT);
+        deviceOrientationManager.getVideoOrientation(DeviceOrientation.LANDSCAPE_RIGHT);
 
     assertEquals(0, degreesPortraitUp);
     assertEquals(90, degreesLandscapeLeft);
@@ -67,17 +67,17 @@
   }
 
   @Test
-  public void getMediaOrientation_when_natural_screen_orientation_equals_landscape_left() {
+  public void getVideoOrientation_whenNaturalScreenOrientationEqualsLandscapeLeft() {
     DeviceOrientationManager orientationManager =
         DeviceOrientationManager.create(mockActivity, mockDartMessenger, false, 90);
 
-    int degreesPortraitUp = orientationManager.getMediaOrientation(DeviceOrientation.PORTRAIT_UP);
+    int degreesPortraitUp = orientationManager.getVideoOrientation(DeviceOrientation.PORTRAIT_UP);
     int degreesPortraitDown =
-        orientationManager.getMediaOrientation(DeviceOrientation.PORTRAIT_DOWN);
+        orientationManager.getVideoOrientation(DeviceOrientation.PORTRAIT_DOWN);
     int degreesLandscapeLeft =
-        orientationManager.getMediaOrientation(DeviceOrientation.LANDSCAPE_LEFT);
+        orientationManager.getVideoOrientation(DeviceOrientation.LANDSCAPE_LEFT);
     int degreesLandscapeRight =
-        orientationManager.getMediaOrientation(DeviceOrientation.LANDSCAPE_RIGHT);
+        orientationManager.getVideoOrientation(DeviceOrientation.LANDSCAPE_RIGHT);
 
     assertEquals(90, degreesPortraitUp);
     assertEquals(180, degreesLandscapeLeft);
@@ -86,50 +86,61 @@
   }
 
   @Test
-  public void getMediaOrientation_should_fallback_to_sensor_orientation_when_orientation_is_null() {
+  public void getVideoOrientation_shouldFallbackToSensorOrientationWhenOrientationIsNull() {
     setUpUIOrientationMocks(Configuration.ORIENTATION_LANDSCAPE, Surface.ROTATION_0);
 
-    int degrees = deviceOrientationManager.getMediaOrientation(null);
+    int degrees = deviceOrientationManager.getVideoOrientation(null);
 
     assertEquals(90, degrees);
   }
 
   @Test
-  public void handleSensorOrientationChange_should_send_message_when_sensor_access_is_allowed() {
-    try (MockedStatic<Settings.System> mockedSystem = mockStatic(Settings.System.class)) {
-      mockedSystem
-          .when(
-              () ->
-                  Settings.System.getInt(any(), eq(Settings.System.ACCELEROMETER_ROTATION), eq(0)))
-          .thenReturn(1);
-      setUpUIOrientationMocks(Configuration.ORIENTATION_PORTRAIT, Surface.ROTATION_0);
+  public void getPhotoOrientation_whenNaturalScreenOrientationEqualsPortraitUp() {
+    int degreesPortraitUp =
+        deviceOrientationManager.getPhotoOrientation(DeviceOrientation.PORTRAIT_UP);
+    int degreesPortraitDown =
+        deviceOrientationManager.getPhotoOrientation(DeviceOrientation.PORTRAIT_DOWN);
+    int degreesLandscapeLeft =
+        deviceOrientationManager.getPhotoOrientation(DeviceOrientation.LANDSCAPE_LEFT);
+    int degreesLandscapeRight =
+        deviceOrientationManager.getPhotoOrientation(DeviceOrientation.LANDSCAPE_RIGHT);
 
-      deviceOrientationManager.handleSensorOrientationChange(90);
-    }
-
-    verify(mockDartMessenger, times(1))
-        .sendDeviceOrientationChangeEvent(DeviceOrientation.LANDSCAPE_LEFT);
+    assertEquals(0, degreesPortraitUp);
+    assertEquals(90, degreesLandscapeRight);
+    assertEquals(180, degreesPortraitDown);
+    assertEquals(270, degreesLandscapeLeft);
   }
 
   @Test
-  public void
-      handleSensorOrientationChange_should_send_message_when_sensor_access_is_not_allowed() {
-    try (MockedStatic<Settings.System> mockedSystem = mockStatic(Settings.System.class)) {
-      mockedSystem
-          .when(
-              () ->
-                  Settings.System.getInt(any(), eq(Settings.System.ACCELEROMETER_ROTATION), eq(0)))
-          .thenReturn(0);
-      setUpUIOrientationMocks(Configuration.ORIENTATION_PORTRAIT, Surface.ROTATION_0);
+  public void getPhotoOrientation_whenNaturalScreenOrientationEqualsLandscapeLeft() {
+    DeviceOrientationManager orientationManager =
+        DeviceOrientationManager.create(mockActivity, mockDartMessenger, false, 90);
 
-      deviceOrientationManager.handleSensorOrientationChange(90);
-    }
+    int degreesPortraitUp = orientationManager.getPhotoOrientation(DeviceOrientation.PORTRAIT_UP);
+    int degreesPortraitDown =
+        orientationManager.getPhotoOrientation(DeviceOrientation.PORTRAIT_DOWN);
+    int degreesLandscapeLeft =
+        orientationManager.getPhotoOrientation(DeviceOrientation.LANDSCAPE_LEFT);
+    int degreesLandscapeRight =
+        orientationManager.getPhotoOrientation(DeviceOrientation.LANDSCAPE_RIGHT);
 
-    verify(mockDartMessenger, never()).sendDeviceOrientationChangeEvent(any());
+    assertEquals(90, degreesPortraitUp);
+    assertEquals(180, degreesLandscapeRight);
+    assertEquals(270, degreesPortraitDown);
+    assertEquals(0, degreesLandscapeLeft);
   }
 
   @Test
-  public void handleUIOrientationChange_should_send_message_when_sensor_access_is_allowed() {
+  public void getPhotoOrientation_shouldFallbackToCurrentOrientationWhenOrientationIsNull() {
+    setUpUIOrientationMocks(Configuration.ORIENTATION_LANDSCAPE, Surface.ROTATION_0);
+
+    int degrees = deviceOrientationManager.getPhotoOrientation(null);
+
+    assertEquals(270, degrees);
+  }
+
+  @Test
+  public void handleUIOrientationChange_shouldSendMessageWhenSensorAccessIsAllowed() {
     try (MockedStatic<Settings.System> mockedSystem = mockStatic(Settings.System.class)) {
       mockedSystem
           .when(
@@ -146,45 +157,25 @@
   }
 
   @Test
-  public void handleUIOrientationChange_should_send_message_when_sensor_access_is_not_allowed() {
-    try (MockedStatic<Settings.System> mockedSystem = mockStatic(Settings.System.class)) {
-      mockedSystem
-          .when(
-              () ->
-                  Settings.System.getInt(any(), eq(Settings.System.ACCELEROMETER_ROTATION), eq(0)))
-          .thenReturn(1);
-      setUpUIOrientationMocks(Configuration.ORIENTATION_LANDSCAPE, Surface.ROTATION_0);
-
-      deviceOrientationManager.handleUIOrientationChange();
-    }
-
-    verify(mockDartMessenger, never()).sendDeviceOrientationChangeEvent(any());
-  }
-
-  @Test
-  public void handleOrientationChange_should_send_message_when_orientation_is_updated() {
+  public void handleOrientationChange_shouldSendMessageWhenOrientationIsUpdated() {
     DeviceOrientation previousOrientation = DeviceOrientation.PORTRAIT_UP;
     DeviceOrientation newOrientation = DeviceOrientation.LANDSCAPE_LEFT;
 
-    DeviceOrientation orientation =
-        DeviceOrientationManager.handleOrientationChange(
-            newOrientation, previousOrientation, mockDartMessenger);
+    DeviceOrientationManager.handleOrientationChange(
+        newOrientation, previousOrientation, mockDartMessenger);
 
     verify(mockDartMessenger, times(1)).sendDeviceOrientationChangeEvent(newOrientation);
-    assertEquals(newOrientation, orientation);
   }
 
   @Test
-  public void handleOrientationChange_should_not_send_message_when_orientation_is_not_updated() {
+  public void handleOrientationChange_shouldNotSendMessageWhenOrientationIsNotUpdated() {
     DeviceOrientation previousOrientation = DeviceOrientation.PORTRAIT_UP;
     DeviceOrientation newOrientation = DeviceOrientation.PORTRAIT_UP;
 
-    DeviceOrientation orientation =
-        DeviceOrientationManager.handleOrientationChange(
-            newOrientation, previousOrientation, mockDartMessenger);
+    DeviceOrientationManager.handleOrientationChange(
+        newOrientation, previousOrientation, mockDartMessenger);
 
     verify(mockDartMessenger, never()).sendDeviceOrientationChangeEvent(any());
-    assertEquals(newOrientation, orientation);
   }
 
   @Test
diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/sensororientation/SensorOrientationFeatureTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/sensororientation/SensorOrientationFeatureTest.java
index ce2bb7b..2c3a5ab 100644
--- a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/sensororientation/SensorOrientationFeatureTest.java
+++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/sensororientation/SensorOrientationFeatureTest.java
@@ -52,7 +52,7 @@
   }
 
   @Test
-  public void ctor_should_start_device_orientation_manager() {
+  public void ctor_shouldStartDeviceOrientationManager() {
     SensorOrientationFeature sensorOrientationFeature =
         new SensorOrientationFeature(mockCameraProperties, mockActivity, mockDartMessenger);
 
@@ -60,7 +60,7 @@
   }
 
   @Test
-  public void getDebugName_should_return_the_name_of_the_feature() {
+  public void getDebugName_shouldReturnTheNameOfTheFeature() {
     SensorOrientationFeature sensorOrientationFeature =
         new SensorOrientationFeature(mockCameraProperties, mockActivity, mockDartMessenger);
 
@@ -68,7 +68,7 @@
   }
 
   @Test
-  public void getValue_should_return_null_if_not_set() {
+  public void getValue_shouldReturnNullIfNotSet() {
     SensorOrientationFeature sensorOrientationFeature =
         new SensorOrientationFeature(mockCameraProperties, mockActivity, mockDartMessenger);
 
@@ -76,7 +76,7 @@
   }
 
   @Test
-  public void getValue_should_echo_setValue() {
+  public void getValue_shouldEchoSetValue() {
     SensorOrientationFeature sensorOrientationFeature =
         new SensorOrientationFeature(mockCameraProperties, mockActivity, mockDartMessenger);
 
@@ -86,7 +86,7 @@
   }
 
   @Test
-  public void checkIsSupport_returns_true() {
+  public void checkIsSupport_returnsTrue() {
     SensorOrientationFeature sensorOrientationFeature =
         new SensorOrientationFeature(mockCameraProperties, mockActivity, mockDartMessenger);
 
@@ -94,8 +94,7 @@
   }
 
   @Test
-  public void
-      getDeviceOrientationManager_should_return_initialized_DartOrientationManager_instance() {
+  public void getDeviceOrientationManager_shouldReturnInitializedDartOrientationManagerInstance() {
     SensorOrientationFeature sensorOrientationFeature =
         new SensorOrientationFeature(mockCameraProperties, mockActivity, mockDartMessenger);
 
@@ -104,7 +103,7 @@
   }
 
   @Test
-  public void lockCaptureOrientation_should_lock_to_specified_orientation() {
+  public void lockCaptureOrientation_shouldLockToSpecifiedOrientation() {
     SensorOrientationFeature sensorOrientationFeature =
         new SensorOrientationFeature(mockCameraProperties, mockActivity, mockDartMessenger);
 
@@ -115,7 +114,7 @@
   }
 
   @Test
-  public void unlockCaptureOrientation_should_set_lock_to_null() {
+  public void unlockCaptureOrientation_shouldSetLockToNull() {
     SensorOrientationFeature sensorOrientationFeature =
         new SensorOrientationFeature(mockCameraProperties, mockActivity, mockDartMessenger);
 
diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/zoomlevel/ZoomLevelFeatureTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/zoomlevel/ZoomLevelFeatureTest.java
index c76708a..9f05cc2 100644
--- a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/zoomlevel/ZoomLevelFeatureTest.java
+++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/zoomlevel/ZoomLevelFeatureTest.java
@@ -50,7 +50,7 @@
   }
 
   @Test
-  public void ctor_when_parameters_are_valid() {
+  public void ctor_whenParametersAreValid() {
     when(mockCameraProperties.getSensorInfoActiveArraySize()).thenReturn(mockSensorArray);
     when(mockCameraProperties.getScalerAvailableMaxDigitalZoom()).thenReturn(42f);
 
@@ -63,7 +63,7 @@
   }
 
   @Test
-  public void ctor_when_sensor_size_is_null() {
+  public void ctor_whenSensorSizeIsNull() {
     when(mockCameraProperties.getSensorInfoActiveArraySize()).thenReturn(null);
     when(mockCameraProperties.getScalerAvailableMaxDigitalZoom()).thenReturn(42f);
 
@@ -77,7 +77,7 @@
   }
 
   @Test
-  public void ctor_when_max_zoom_is_null() {
+  public void ctor_whenMaxZoomIsNull() {
     when(mockCameraProperties.getSensorInfoActiveArraySize()).thenReturn(mockSensorArray);
     when(mockCameraProperties.getScalerAvailableMaxDigitalZoom()).thenReturn(null);
 
@@ -91,7 +91,7 @@
   }
 
   @Test
-  public void ctor_when_max_zoom_is_smaller_then_default_zoom_factor() {
+  public void ctor_whenMaxZoomIsSmallerThenDefaultZoomFactor() {
     when(mockCameraProperties.getSensorInfoActiveArraySize()).thenReturn(mockSensorArray);
     when(mockCameraProperties.getScalerAvailableMaxDigitalZoom()).thenReturn(0.5f);
 
@@ -105,21 +105,21 @@
   }
 
   @Test
-  public void getDebugName_should_return_the_name_of_the_feature() {
+  public void getDebugName_shouldReturnTheNameOfTheFeature() {
     ZoomLevelFeature zoomLevelFeature = new ZoomLevelFeature(mockCameraProperties);
 
     assertEquals("ZoomLevelFeature", zoomLevelFeature.getDebugName());
   }
 
   @Test
-  public void getValue_should_return_null_if_not_set() {
+  public void getValue_shouldReturnNullIfNotSet() {
     ZoomLevelFeature zoomLevelFeature = new ZoomLevelFeature(mockCameraProperties);
 
     assertEquals(1.0, (float) zoomLevelFeature.getValue(), 0);
   }
 
   @Test
-  public void getValue_should_echo_setValue() {
+  public void getValue_shouldEchoSetValue() {
     ZoomLevelFeature zoomLevelFeature = new ZoomLevelFeature(mockCameraProperties);
 
     zoomLevelFeature.setValue(2.3f);
@@ -128,14 +128,14 @@
   }
 
   @Test
-  public void checkIsSupport_returns_false_by_default() {
+  public void checkIsSupport_returnsFalseByDefault() {
     ZoomLevelFeature zoomLevelFeature = new ZoomLevelFeature(mockCameraProperties);
 
     assertFalse(zoomLevelFeature.checkIsSupported());
   }
 
   @Test
-  public void updateBuilder_should_set_scalar_crop_region_when_checkIsSupport_is_true() {
+  public void updateBuilder_shouldSetScalarCropRegionWhenCheckIsSupportIsTrue() {
     when(mockCameraProperties.getSensorInfoActiveArraySize()).thenReturn(mockSensorArray);
     when(mockCameraProperties.getScalerAvailableMaxDigitalZoom()).thenReturn(42f);
 
diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/zoomlevel/ZoomUtilsTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/zoomlevel/ZoomUtilsTest.java
index f83e5fb..28160ff 100644
--- a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/zoomlevel/ZoomUtilsTest.java
+++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/zoomlevel/ZoomUtilsTest.java
@@ -15,7 +15,7 @@
 @RunWith(RobolectricTestRunner.class)
 public class ZoomUtilsTest {
   @Test
-  public void setZoom_when_sensor_size_equals_zero_should_return_crop_region_of_zero() {
+  public void setZoom_whenSensorSizeEqualsZeroShouldReturnCropRegionOfZero() {
     final Rect sensorSize = new Rect(0, 0, 0, 0);
     final Rect computedZoom = ZoomUtils.computeZoom(18f, sensorSize, 1f, 20f);
 
@@ -27,7 +27,7 @@
   }
 
   @Test
-  public void setZoom_when_sensor_size_is_valid_should_return_crop_region() {
+  public void setZoom_whenSensorSizeIsValidShouldReturnCropRegion() {
     final Rect sensorSize = new Rect(0, 0, 100, 100);
     final Rect computedZoom = ZoomUtils.computeZoom(18f, sensorSize, 1f, 20f);
 
@@ -39,7 +39,7 @@
   }
 
   @Test
-  public void setZoom_when_zoom_is_greater_then_max_zoom_clamp_to_max_zoom() {
+  public void setZoom_whenZoomIsGreaterThenMaxZoomClampToMaxZoom() {
     final Rect sensorSize = new Rect(0, 0, 100, 100);
     final Rect computedZoom = ZoomUtils.computeZoom(25f, sensorSize, 1f, 10f);
 
@@ -51,7 +51,7 @@
   }
 
   @Test
-  public void setZoom_when_zoom_is_smaller_then_min_zoom_clamp_to_min_zoom() {
+  public void setZoom_whenZoomIsSmallerThenMinZoomClampToMinZoom() {
     final Rect sensorSize = new Rect(0, 0, 100, 100);
     final Rect computedZoom = ZoomUtils.computeZoom(0.5f, sensorSize, 1f, 10f);
 
diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/media/MediaRecorderBuilderTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/media/MediaRecorderBuilderTest.java
index 9b8b54c..5425409 100644
--- a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/media/MediaRecorderBuilderTest.java
+++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/media/MediaRecorderBuilderTest.java
@@ -24,7 +24,7 @@
   }
 
   @Test
-  public void build_Should_set_values_in_correct_order_When_audio_is_disabled() throws IOException {
+  public void build_shouldSetValuesInCorrectOrderWhenAudioIsDisabled() throws IOException {
     CamcorderProfile recorderProfile = getEmptyCamcorderProfile();
     MediaRecorderBuilder.MediaRecorderFactory mockFactory =
         mock(MediaRecorderBuilder.MediaRecorderFactory.class);
@@ -55,7 +55,7 @@
   }
 
   @Test
-  public void build_Should_set_values_in_correct_order_When_audio_is_enabled() throws IOException {
+  public void build_shouldSetValuesInCorrectOrderWhenAudioIsEnabled() throws IOException {
     CamcorderProfile recorderProfile = getEmptyCamcorderProfile();
     MediaRecorderBuilder.MediaRecorderFactory mockFactory =
         mock(MediaRecorderBuilder.MediaRecorderFactory.class);
diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/types/ExposureModeTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/types/ExposureModeTest.java
index 5f4bd9f..dbef851 100644
--- a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/types/ExposureModeTest.java
+++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/types/ExposureModeTest.java
@@ -11,7 +11,7 @@
 public class ExposureModeTest {
 
   @Test
-  public void getValueForString_returns_correct_values() {
+  public void getValueForString_returnsCorrectValues() {
     assertEquals(
         "Returns ExposureMode.auto for 'auto'",
         ExposureMode.getValueForString("auto"),
@@ -23,13 +23,13 @@
   }
 
   @Test
-  public void getValueForString_returns_null_for_nonexistant_value() {
+  public void getValueForString_returnsNullForNonexistantValue() {
     assertEquals(
         "Returns null for 'nonexistant'", ExposureMode.getValueForString("nonexistant"), null);
   }
 
   @Test
-  public void toString_returns_correct_value() {
+  public void toString_returnsCorrectValue() {
     assertEquals("Returns 'auto' for ExposureMode.auto", ExposureMode.auto.toString(), "auto");
     assertEquals(
         "Returns 'locked' for ExposureMode.locked", ExposureMode.locked.toString(), "locked");
diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/types/FlashModeTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/types/FlashModeTest.java
index 5a53648..7ae175e 100644
--- a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/types/FlashModeTest.java
+++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/types/FlashModeTest.java
@@ -11,7 +11,7 @@
 public class FlashModeTest {
 
   @Test
-  public void getValueForString_returns_correct_values() {
+  public void getValueForString_returnsCorrectValues() {
     assertEquals(
         "Returns FlashMode.off for 'off'", FlashMode.getValueForString("off"), FlashMode.off);
     assertEquals(
@@ -27,13 +27,13 @@
   }
 
   @Test
-  public void getValueForString_returns_null_for_nonexistant_value() {
+  public void getValueForString_returnsNullForNonexistantValue() {
     assertEquals(
         "Returns null for 'nonexistant'", FlashMode.getValueForString("nonexistant"), null);
   }
 
   @Test
-  public void toString_returns_correct_value() {
+  public void toString_returnsCorrectValue() {
     assertEquals("Returns 'off' for FlashMode.off", FlashMode.off.toString(), "off");
     assertEquals("Returns 'auto' for FlashMode.auto", FlashMode.auto.toString(), "auto");
     assertEquals("Returns 'always' for FlashMode.always", FlashMode.always.toString(), "always");
diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/types/FocusModeTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/types/FocusModeTest.java
index 58e6d7c..1d7b95c 100644
--- a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/types/FocusModeTest.java
+++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/types/FocusModeTest.java
@@ -11,7 +11,7 @@
 public class FocusModeTest {
 
   @Test
-  public void getValueForString_returns_correct_values() {
+  public void getValueForString_returnsCorrectValues() {
     assertEquals(
         "Returns FocusMode.auto for 'auto'", FocusMode.getValueForString("auto"), FocusMode.auto);
     assertEquals(
@@ -21,13 +21,13 @@
   }
 
   @Test
-  public void getValueForString_returns_null_for_nonexistant_value() {
+  public void getValueForString_returnsNullForNonexistantValue() {
     assertEquals(
         "Returns null for 'nonexistant'", FocusMode.getValueForString("nonexistant"), null);
   }
 
   @Test
-  public void toString_returns_correct_value() {
+  public void toString_returnsCorrectValue() {
     assertEquals("Returns 'auto' for FocusMode.auto", FocusMode.auto.toString(), "auto");
     assertEquals("Returns 'locked' for FocusMode.locked", FocusMode.locked.toString(), "locked");
   }
diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/utils/TestUtils.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/utils/TestUtils.java
index 9fc6695..dbf9d11 100644
--- a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/utils/TestUtils.java
+++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/utils/TestUtils.java
@@ -23,4 +23,14 @@
       Assert.fail("Unable to mock static field: " + fieldName);
     }
   }
+
+  public static <T> void setPrivateField(T instance, String fieldName, Object newValue) {
+    try {
+      Field field = instance.getClass().getDeclaredField(fieldName);
+      field.setAccessible(true);
+      field.set(instance, newValue);
+    } catch (Exception e) {
+      Assert.fail("Unable to mock private field: " + fieldName);
+    }
+  }
 }
diff --git a/packages/camera/camera/lib/src/camera_controller.dart b/packages/camera/camera/lib/src/camera_controller.dart
index 3284a9b..37869fe 100644
--- a/packages/camera/camera/lib/src/camera_controller.dart
+++ b/packages/camera/camera/lib/src/camera_controller.dart
@@ -118,7 +118,7 @@
   /// Whether setting the focus point is supported.
   final bool focusPointSupported;
 
-  /// The current device orientation.
+  /// The current device UI orientation.
   final DeviceOrientation deviceOrientation;
 
   /// The currently locked capture orientation.
diff --git a/packages/camera/camera/lib/src/camera_preview.dart b/packages/camera/camera/lib/src/camera_preview.dart
index ad3175a..1df9f8e 100644
--- a/packages/camera/camera/lib/src/camera_preview.dart
+++ b/packages/camera/camera/lib/src/camera_preview.dart
@@ -61,9 +61,9 @@
   int _getQuarterTurns() {
     Map<DeviceOrientation, int> turns = {
       DeviceOrientation.portraitUp: 0,
-      DeviceOrientation.landscapeLeft: 1,
+      DeviceOrientation.landscapeRight: 1,
       DeviceOrientation.portraitDown: 2,
-      DeviceOrientation.landscapeRight: 3,
+      DeviceOrientation.landscapeLeft: 3,
     };
     return turns[_getApplicableOrientation()]!;
   }
diff --git a/packages/camera/camera/pubspec.yaml b/packages/camera/camera/pubspec.yaml
index 5716165..a7c6a61 100644
--- a/packages/camera/camera/pubspec.yaml
+++ b/packages/camera/camera/pubspec.yaml
@@ -4,7 +4,7 @@
   and streaming image buffers to dart.
 repository: https://github.com/flutter/plugins/tree/master/packages/camera/camera
 issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22
-version: 0.8.1+7
+version: 0.9.0
 
 environment:
   sdk: ">=2.12.0 <3.0.0"
@@ -25,6 +25,7 @@
     sdk: flutter
   pedantic: ^1.10.0
   quiver: ^3.0.0
+  flutter_plugin_android_lifecycle: ^2.0.2
 
 dev_dependencies:
   flutter_test:
diff --git a/packages/camera/camera/test/camera_preview_test.dart b/packages/camera/camera/test/camera_preview_test.dart
index d579341..8275461 100644
--- a/packages/camera/camera/test/camera_preview_test.dart
+++ b/packages/camera/camera/test/camera_preview_test.dart
@@ -146,7 +146,7 @@
 
       RotatedBox rotatedBox =
           tester.widget<RotatedBox>(find.byType(RotatedBox));
-      expect(rotatedBox.quarterTurns, 1);
+      expect(rotatedBox.quarterTurns, 3);
 
       debugDefaultTargetPlatformOverride = null;
     });
@@ -179,7 +179,7 @@
 
       RotatedBox rotatedBox =
           tester.widget<RotatedBox>(find.byType(RotatedBox));
-      expect(rotatedBox.quarterTurns, 3);
+      expect(rotatedBox.quarterTurns, 1);
 
       debugDefaultTargetPlatformOverride = null;
     });
diff --git a/packages/camera/camera_platform_interface/lib/src/events/device_event.dart b/packages/camera/camera_platform_interface/lib/src/events/device_event.dart
index c6cedd1..ac1c66e 100644
--- a/packages/camera/camera_platform_interface/lib/src/events/device_event.dart
+++ b/packages/camera/camera_platform_interface/lib/src/events/device_event.dart
@@ -20,8 +20,7 @@
 /// They can be (and in fact, are) filtered by the `instanceof`-operator.
 abstract class DeviceEvent {}
 
-/// The [DeviceOrientationChangedEvent] is fired every time the user changes the
-/// physical orientation of the device.
+/// The [DeviceOrientationChangedEvent] is fired every time the orientation of the device UI changes.
 class DeviceOrientationChangedEvent extends DeviceEvent {
   /// The new orientation of the device
   final DeviceOrientation orientation;
diff --git a/packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart b/packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart
index 9e84e8f..7a7bbf3 100644
--- a/packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart
+++ b/packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart
@@ -96,11 +96,10 @@
     throw UnimplementedError('onCameraTimeLimitReached() is not implemented.');
   }
 
-  /// The device orientation changed.
+  /// The ui orientation changed.
   ///
   /// Implementations for this:
   /// - Should support all 4 orientations.
-  /// - Should not emit new values when the screen orientation is locked.
   Stream<DeviceOrientationChangedEvent> onDeviceOrientationChanged() {
     throw UnimplementedError(
         'onDeviceOrientationChanged() is not implemented.');