[camera] Allow logical cameras to use all physical cameras via zoom on android 11+ (#6150)

* Adding support for the android 11+ Camera2 CONTROL_ZOOM_RATIO_RANGE camera preference. This preference is essential for supporting Logical cameras on the rear which after android 11 wrap multiple cameras (ultrawide, normal, superzoom, etc) into a single rear facing camera.

This updates change how zoom functions as well as how min and max zoom is set to be compliant with those updates as long as the user device is on android 11+.

* Adding updates to the changelog and pubspec to reflect the zoom and camera updates.

* comment cleanup for min and max zoom ratio functions

* Pull request fixes

* Update packages/camera/camera_android/CHANGELOG.md

Co-authored-by: Camille Simon <43054281+camsim99@users.noreply.github.com>

* Update packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/features/zoomlevel/ZoomUtils.java

Co-authored-by: Camille Simon <43054281+camsim99@users.noreply.github.com>

* Updating comments from PR

* Fixing variable name formatting, and comment structures

* Fixing variable name formatting, and comment structures

* Autogormatter updates

Co-authored-by: Camille Simon <43054281+camsim99@users.noreply.github.com>
Co-authored-by: stuartmorgan <stuartmorgan@google.com>
diff --git a/packages/camera/camera_android/CHANGELOG.md b/packages/camera/camera_android/CHANGELOG.md
index 1a0b87e..dda28f0 100644
--- a/packages/camera/camera_android/CHANGELOG.md
+++ b/packages/camera/camera_android/CHANGELOG.md
@@ -1,3 +1,8 @@
+## 0.10.2+2
+
+* Fixes zoom computation for virtual cameras hiding physical cameras in Android 11+.
+* Removes the unused CameraZoom class from the codebase. 
+
 ## 0.10.2+1
 
 * Updates code for stricter lint checks.
diff --git a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/CameraProperties.java b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/CameraProperties.java
index 95efebb..a69bae4 100644
--- a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/CameraProperties.java
+++ b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/CameraProperties.java
@@ -150,11 +150,35 @@
    * android.hardware.camera2.CameraCharacteristics#SCALER_AVAILABLE_MAX_DIGITAL_ZOOM key.
    *
    * @return Float Maximum ratio between both active area width and crop region width, and active
-   *     area height and crop region height
+   *     area height and crop region height.
    */
   Float getScalerAvailableMaxDigitalZoom();
 
   /**
+   * Returns the minimum ratio between the default camera zoom setting and all of the available
+   * zoom.
+   *
+   * <p>By default maps to the @see
+   * android.hardware.camera2.CameraCharacteristics#CONTROL_ZOOM_RATIO_RANGE key's lower value.
+   *
+   * @return Float Minimum ratio between the default zoom ratio and the minimum possible zoom.
+   */
+  @RequiresApi(api = VERSION_CODES.R)
+  Float getScalerMinZoomRatio();
+
+  /**
+   * Returns the maximum ratio between the default camera zoom setting and all of the available
+   * zoom.
+   *
+   * <p>By default maps to the @see
+   * android.hardware.camera2.CameraCharacteristics#CONTROL_ZOOM_RATIO_RANGE key's upper value.
+   *
+   * @return Float Maximum ratio between the default zoom ratio and the maximum possible zoom.
+   */
+  @RequiresApi(api = VERSION_CODES.R)
+  Float getScalerMaxZoomRatio();
+
+  /**
    * Returns the area of the image sensor which corresponds to active pixels after any geometric
    * distortion correction has been applied.
    *
@@ -315,6 +339,18 @@
     return cameraCharacteristics.get(CameraCharacteristics.SCALER_AVAILABLE_MAX_DIGITAL_ZOOM);
   }
 
+  @RequiresApi(api = VERSION_CODES.R)
+  @Override
+  public Float getScalerMaxZoomRatio() {
+    return cameraCharacteristics.get(CameraCharacteristics.CONTROL_ZOOM_RATIO_RANGE).getUpper();
+  }
+
+  @RequiresApi(api = VERSION_CODES.R)
+  @Override
+  public Float getScalerMinZoomRatio() {
+    return cameraCharacteristics.get(CameraCharacteristics.CONTROL_ZOOM_RATIO_RANGE).getLower();
+  }
+
   @Override
   public Rect getSensorInfoActiveArraySize() {
     return cameraCharacteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE);
diff --git a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/CameraZoom.java b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/CameraZoom.java
deleted file mode 100644
index 42ad6d7..0000000
--- a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/CameraZoom.java
+++ /dev/null
@@ -1,52 +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.graphics.Rect;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.core.math.MathUtils;
-
-public final class CameraZoom {
-  public static final float DEFAULT_ZOOM_FACTOR = 1.0f;
-
-  @NonNull private final Rect cropRegion = new Rect();
-  @Nullable private final Rect sensorSize;
-
-  public final float maxZoom;
-  public final boolean hasSupport;
-
-  public CameraZoom(@Nullable final Rect sensorArraySize, final Float maxZoom) {
-    this.sensorSize = sensorArraySize;
-
-    if (this.sensorSize == null) {
-      this.maxZoom = DEFAULT_ZOOM_FACTOR;
-      this.hasSupport = false;
-      return;
-    }
-
-    this.maxZoom =
-        ((maxZoom == null) || (maxZoom < DEFAULT_ZOOM_FACTOR)) ? DEFAULT_ZOOM_FACTOR : maxZoom;
-
-    this.hasSupport = (Float.compare(this.maxZoom, DEFAULT_ZOOM_FACTOR) > 0);
-  }
-
-  public Rect computeZoom(final float zoom) {
-    if (sensorSize == null || !this.hasSupport) {
-      return null;
-    }
-
-    final float newZoom = MathUtils.clamp(zoom, DEFAULT_ZOOM_FACTOR, this.maxZoom);
-
-    final int centerX = this.sensorSize.width() / 2;
-    final int centerY = this.sensorSize.height() / 2;
-    final int deltaX = (int) ((0.5f * this.sensorSize.width()) / newZoom);
-    final int deltaY = (int) ((0.5f * this.sensorSize.height()) / newZoom);
-
-    this.cropRegion.set(centerX - deltaX, centerY - deltaY, centerX + deltaX, centerY + deltaY);
-
-    return cropRegion;
-  }
-}
diff --git a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/features/zoomlevel/ZoomLevelFeature.java b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/features/zoomlevel/ZoomLevelFeature.java
index 736fad4..2ac7082 100644
--- a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/features/zoomlevel/ZoomLevelFeature.java
+++ b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/features/zoomlevel/ZoomLevelFeature.java
@@ -6,16 +6,18 @@
 
 import android.graphics.Rect;
 import android.hardware.camera2.CaptureRequest;
+import android.os.Build;
 import io.flutter.plugins.camera.CameraProperties;
 import io.flutter.plugins.camera.features.CameraFeature;
 
 /** Controls the zoom configuration on the {@link android.hardware.camera2} API. */
 public class ZoomLevelFeature extends CameraFeature<Float> {
-  private static final float MINIMUM_ZOOM_LEVEL = 1.0f;
+  private static final Float DEFAULT_ZOOM_LEVEL = 1.0f;
   private final boolean hasSupport;
   private final Rect sensorArraySize;
-  private Float currentSetting = MINIMUM_ZOOM_LEVEL;
-  private Float maximumZoomLevel = MINIMUM_ZOOM_LEVEL;
+  private Float currentSetting = DEFAULT_ZOOM_LEVEL;
+  private Float minimumZoomLevel = currentSetting;
+  private Float maximumZoomLevel;
 
   /**
    * Creates a new instance of the {@link ZoomLevelFeature}.
@@ -28,18 +30,24 @@
     sensorArraySize = cameraProperties.getSensorInfoActiveArraySize();
 
     if (sensorArraySize == null) {
-      maximumZoomLevel = MINIMUM_ZOOM_LEVEL;
+      maximumZoomLevel = minimumZoomLevel;
       hasSupport = false;
       return;
     }
+    // On Android 11+ CONTROL_ZOOM_RATIO_RANGE should be use to get the zoom ratio directly as minimum zoom does not have to be 1.0f.
+    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+      minimumZoomLevel = cameraProperties.getScalerMinZoomRatio();
+      maximumZoomLevel = cameraProperties.getScalerMaxZoomRatio();
+    } else {
+      minimumZoomLevel = DEFAULT_ZOOM_LEVEL;
+      Float maxDigitalZoom = cameraProperties.getScalerAvailableMaxDigitalZoom();
+      maximumZoomLevel =
+          ((maxDigitalZoom == null) || (maxDigitalZoom < minimumZoomLevel))
+              ? minimumZoomLevel
+              : maxDigitalZoom;
+    }
 
-    Float maxDigitalZoom = cameraProperties.getScalerAvailableMaxDigitalZoom();
-    maximumZoomLevel =
-        ((maxDigitalZoom == null) || (maxDigitalZoom < MINIMUM_ZOOM_LEVEL))
-            ? MINIMUM_ZOOM_LEVEL
-            : maxDigitalZoom;
-
-    hasSupport = (Float.compare(maximumZoomLevel, MINIMUM_ZOOM_LEVEL) > 0);
+    hasSupport = (Float.compare(maximumZoomLevel, minimumZoomLevel) > 0);
   }
 
   @Override
@@ -67,11 +75,19 @@
     if (!checkIsSupported()) {
       return;
     }
-
-    final Rect computedZoom =
-        ZoomUtils.computeZoom(
-            currentSetting, sensorArraySize, MINIMUM_ZOOM_LEVEL, maximumZoomLevel);
-    requestBuilder.set(CaptureRequest.SCALER_CROP_REGION, computedZoom);
+    // On Android 11+ CONTROL_ZOOM_RATIO can be set to a zoom ratio and the camera feed will compute
+    // how to zoom on its own accounting for multiple logical cameras.
+    // Prior the image cropping window must be calculated and set manually.
+    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+      requestBuilder.set(
+          CaptureRequest.CONTROL_ZOOM_RATIO,
+          ZoomUtils.computeZoomRatio(currentSetting, minimumZoomLevel, maximumZoomLevel));
+    } else {
+      final Rect computedZoom =
+          ZoomUtils.computeZoomRect(
+              currentSetting, sensorArraySize, minimumZoomLevel, maximumZoomLevel);
+      requestBuilder.set(CaptureRequest.SCALER_CROP_REGION, computedZoom);
+    }
   }
 
   /**
@@ -80,7 +96,7 @@
    * @return The minimum zoom level.
    */
   public float getMinimumZoomLevel() {
-    return MINIMUM_ZOOM_LEVEL;
+    return minimumZoomLevel;
   }
 
   /**
diff --git a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/features/zoomlevel/ZoomUtils.java b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/features/zoomlevel/ZoomUtils.java
index a4890b9..af9e48f 100644
--- a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/features/zoomlevel/ZoomUtils.java
+++ b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/features/zoomlevel/ZoomUtils.java
@@ -18,7 +18,8 @@
    * Computes an image sensor area based on the supplied zoom settings.
    *
    * <p>The returned image sensor area can be applied to the {@link android.hardware.camera2} API in
-   * order to control zoom levels.
+   * order to control zoom levels. This method of zoom should only be used for Android versions <=
+   * 11 as past that, the newer {@link #computeZoomRatio()} functional can be used.
    *
    * @param zoom The desired zoom level.
    * @param sensorArraySize The current area of the image sensor.
@@ -26,9 +27,9 @@
    * @param maximumZoomLevel The maximim supported zoom level.
    * @return An image sensor area based on the supplied zoom settings
    */
-  static Rect computeZoom(
+  static Rect computeZoomRect(
       float zoom, @NonNull Rect sensorArraySize, float minimumZoomLevel, float maximumZoomLevel) {
-    final float newZoom = MathUtils.clamp(zoom, minimumZoomLevel, maximumZoomLevel);
+    final float newZoom = computeZoomRatio(zoom, minimumZoomLevel, maximumZoomLevel);
 
     final int centerX = sensorArraySize.width() / 2;
     final int centerY = sensorArraySize.height() / 2;
@@ -37,4 +38,8 @@
 
     return new Rect(centerX - deltaX, centerY - deltaY, centerX + deltaX, centerY + deltaY);
   }
+
+  static Float computeZoomRatio(float zoom, float minimumZoomLevel, float maximumZoomLevel) {
+    return MathUtils.clamp(zoom, minimumZoomLevel, maximumZoomLevel);
+  }
 }
diff --git a/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/CameraPropertiesImplTest.java b/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/CameraPropertiesImplTest.java
index 40db12e..c61be04 100644
--- a/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/CameraPropertiesImplTest.java
+++ b/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/CameraPropertiesImplTest.java
@@ -202,6 +202,30 @@
   }
 
   @Test
+  public void getScalerGetScalerMinZoomRatioTest() {
+    Range zoomRange = mock(Range.class);
+    when(mockCharacteristics.get(CameraCharacteristics.CONTROL_ZOOM_RATIO_RANGE))
+        .thenReturn(zoomRange);
+
+    Float minZoom = cameraProperties.getScalerMinZoomRatio();
+
+    verify(mockCharacteristics, times(1)).get(CameraCharacteristics.CONTROL_ZOOM_RATIO_RANGE);
+    assertEquals(zoomRange.getLower(), minZoom);
+  }
+
+  @Test
+  public void getScalerGetScalerMaxZoomRatioTest() {
+    Range zoomRange = mock(Range.class);
+    when(mockCharacteristics.get(CameraCharacteristics.CONTROL_ZOOM_RATIO_RANGE))
+        .thenReturn(zoomRange);
+
+    Float maxZoom = cameraProperties.getScalerMaxZoomRatio();
+
+    verify(mockCharacteristics, times(1)).get(CameraCharacteristics.CONTROL_ZOOM_RATIO_RANGE);
+    assertEquals(zoomRange.getUpper(), maxZoom);
+  }
+
+  @Test
   public void getSensorInfoActiveArraySizeTest() {
     Rect expectedArraySize = mock(Rect.class);
     when(mockCharacteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE))
diff --git a/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/CameraZoomTest.java b/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/CameraZoomTest.java
deleted file mode 100644
index d3e4955..0000000
--- a/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/CameraZoomTest.java
+++ /dev/null
@@ -1,125 +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.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-
-import android.graphics.Rect;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.robolectric.RobolectricTestRunner;
-
-@RunWith(RobolectricTestRunner.class)
-public class CameraZoomTest {
-
-  @Test
-  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);
-
-    assertNotNull(cameraZoom);
-    assertTrue(cameraZoom.hasSupport);
-    assertEquals(4.0f, cameraZoom.maxZoom, 0);
-    assertEquals(1.0f, CameraZoom.DEFAULT_ZOOM_FACTOR, 0);
-  }
-
-  @Test
-  public void ctor_whenSensorSizeIsNull() {
-    final Rect sensorSize = null;
-    final Float maxZoom = 4.0f;
-    final CameraZoom cameraZoom = new CameraZoom(sensorSize, maxZoom);
-
-    assertNotNull(cameraZoom);
-    assertFalse(cameraZoom.hasSupport);
-    assertEquals(cameraZoom.maxZoom, 1.0f, 0);
-  }
-
-  @Test
-  public void ctor_whenMaxZoomIsNull() {
-    final Rect sensorSize = new Rect(0, 0, 0, 0);
-    final Float maxZoom = null;
-    final CameraZoom cameraZoom = new CameraZoom(sensorSize, maxZoom);
-
-    assertNotNull(cameraZoom);
-    assertFalse(cameraZoom.hasSupport);
-    assertEquals(cameraZoom.maxZoom, 1.0f, 0);
-  }
-
-  @Test
-  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);
-
-    assertNotNull(cameraZoom);
-    assertFalse(cameraZoom.hasSupport);
-    assertEquals(cameraZoom.maxZoom, 1.0f, 0);
-  }
-
-  @Test
-  public void setZoom_whenNoSupportShouldNotSetScalerCropRegion() {
-    final CameraZoom cameraZoom = new CameraZoom(null, null);
-    final Rect computedZoom = cameraZoom.computeZoom(2f);
-
-    assertNull(computedZoom);
-  }
-
-  @Test
-  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);
-
-    assertNotNull(computedZoom);
-    assertEquals(computedZoom.left, 0);
-    assertEquals(computedZoom.top, 0);
-    assertEquals(computedZoom.right, 0);
-    assertEquals(computedZoom.bottom, 0);
-  }
-
-  @Test
-  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);
-
-    assertNotNull(computedZoom);
-    assertEquals(computedZoom.left, 48);
-    assertEquals(computedZoom.top, 48);
-    assertEquals(computedZoom.right, 52);
-    assertEquals(computedZoom.bottom, 52);
-  }
-
-  @Test
-  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);
-
-    assertNotNull(computedZoom);
-    assertEquals(computedZoom.left, 45);
-    assertEquals(computedZoom.top, 45);
-    assertEquals(computedZoom.right, 55);
-    assertEquals(computedZoom.bottom, 55);
-  }
-
-  @Test
-  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);
-
-    assertNotNull(computedZoom);
-    assertEquals(computedZoom.left, 0);
-    assertEquals(computedZoom.top, 0);
-    assertEquals(computedZoom.right, 100);
-    assertEquals(computedZoom.bottom, 100);
-  }
-}
diff --git a/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/features/zoomlevel/ZoomLevelFeatureTest.java b/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/features/zoomlevel/ZoomLevelFeatureTest.java
index 9f05cc2..4d58269 100644
--- a/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/features/zoomlevel/ZoomLevelFeatureTest.java
+++ b/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/features/zoomlevel/ZoomLevelFeatureTest.java
@@ -18,7 +18,10 @@
 
 import android.graphics.Rect;
 import android.hardware.camera2.CaptureRequest;
+import android.os.Build;
 import io.flutter.plugins.camera.CameraProperties;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -40,7 +43,7 @@
     mockSensorArray = mock(Rect.class);
 
     mockedStaticCameraZoom
-        .when(() -> ZoomUtils.computeZoom(anyFloat(), any(), anyFloat(), anyFloat()))
+        .when(() -> ZoomUtils.computeZoomRect(anyFloat(), any(), anyFloat(), anyFloat()))
         .thenReturn(mockZoomArea);
   }
 
@@ -148,6 +151,22 @@
   }
 
   @Test
+  public void updateBuilder_shouldControlZoomRatioWhenCheckIsSupportIsTrue() throws Exception {
+    setSdkVersion(Build.VERSION_CODES.R);
+    when(mockCameraProperties.getSensorInfoActiveArraySize()).thenReturn(mockSensorArray);
+    when(mockCameraProperties.getScalerMaxZoomRatio()).thenReturn(42f);
+    when(mockCameraProperties.getScalerMinZoomRatio()).thenReturn(1.0f);
+
+    ZoomLevelFeature zoomLevelFeature = new ZoomLevelFeature(mockCameraProperties);
+
+    CaptureRequest.Builder mockBuilder = mock(CaptureRequest.Builder.class);
+
+    zoomLevelFeature.updateBuilder(mockBuilder);
+
+    verify(mockBuilder, times(1)).set(CaptureRequest.CONTROL_ZOOM_RATIO, 0.0f);
+  }
+
+  @Test
   public void getMinimumZoomLevel() {
     ZoomLevelFeature zoomLevelFeature = new ZoomLevelFeature(mockCameraProperties);
 
@@ -163,4 +182,38 @@
 
     assertEquals(42f, zoomLevelFeature.getMaximumZoomLevel(), 0);
   }
+
+  @Test
+  public void checkZoomLevelFeature_callsMaxDigitalZoomOnAndroidQ() throws Exception {
+    setSdkVersion(Build.VERSION_CODES.Q);
+
+    when(mockCameraProperties.getSensorInfoActiveArraySize()).thenReturn(mockSensorArray);
+
+    new ZoomLevelFeature(mockCameraProperties);
+
+    verify(mockCameraProperties, times(0)).getScalerMaxZoomRatio();
+    verify(mockCameraProperties, times(0)).getScalerMinZoomRatio();
+    verify(mockCameraProperties, times(1)).getScalerAvailableMaxDigitalZoom();
+  }
+
+  @Test
+  public void checkZoomLevelFeature_callsScalarMaxZoomRatioOnAndroidR() throws Exception {
+    setSdkVersion(Build.VERSION_CODES.R);
+    when(mockCameraProperties.getSensorInfoActiveArraySize()).thenReturn(mockSensorArray);
+
+    new ZoomLevelFeature(mockCameraProperties);
+
+    verify(mockCameraProperties, times(1)).getScalerMaxZoomRatio();
+    verify(mockCameraProperties, times(1)).getScalerMinZoomRatio();
+    verify(mockCameraProperties, times(0)).getScalerAvailableMaxDigitalZoom();
+  }
+
+  static void setSdkVersion(int sdkVersion) throws Exception {
+    Field sdkInt = Build.VERSION.class.getField("SDK_INT");
+    sdkInt.setAccessible(true);
+    Field modifiersField = Field.class.getDeclaredField("modifiers");
+    modifiersField.setAccessible(true);
+    modifiersField.setInt(sdkInt, sdkInt.getModifiers() & ~Modifier.FINAL);
+    sdkInt.set(null, sdkVersion);
+  }
 }
diff --git a/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/features/zoomlevel/ZoomUtilsTest.java b/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/features/zoomlevel/ZoomUtilsTest.java
index 28160ff..2f61608 100644
--- a/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/features/zoomlevel/ZoomUtilsTest.java
+++ b/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/features/zoomlevel/ZoomUtilsTest.java
@@ -15,9 +15,9 @@
 @RunWith(RobolectricTestRunner.class)
 public class ZoomUtilsTest {
   @Test
-  public void setZoom_whenSensorSizeEqualsZeroShouldReturnCropRegionOfZero() {
+  public void setZoomRect_whenSensorSizeEqualsZeroShouldReturnCropRegionOfZero() {
     final Rect sensorSize = new Rect(0, 0, 0, 0);
-    final Rect computedZoom = ZoomUtils.computeZoom(18f, sensorSize, 1f, 20f);
+    final Rect computedZoom = ZoomUtils.computeZoomRect(18f, sensorSize, 1f, 20f);
 
     assertNotNull(computedZoom);
     assertEquals(computedZoom.left, 0);
@@ -27,9 +27,9 @@
   }
 
   @Test
-  public void setZoom_whenSensorSizeIsValidShouldReturnCropRegion() {
+  public void setZoomRect_whenSensorSizeIsValidShouldReturnCropRegion() {
     final Rect sensorSize = new Rect(0, 0, 100, 100);
-    final Rect computedZoom = ZoomUtils.computeZoom(18f, sensorSize, 1f, 20f);
+    final Rect computedZoom = ZoomUtils.computeZoomRect(18f, sensorSize, 1f, 20f);
 
     assertNotNull(computedZoom);
     assertEquals(computedZoom.left, 48);
@@ -39,9 +39,9 @@
   }
 
   @Test
-  public void setZoom_whenZoomIsGreaterThenMaxZoomClampToMaxZoom() {
+  public void setZoomRect_whenZoomIsGreaterThenMaxZoomClampToMaxZoom() {
     final Rect sensorSize = new Rect(0, 0, 100, 100);
-    final Rect computedZoom = ZoomUtils.computeZoom(25f, sensorSize, 1f, 10f);
+    final Rect computedZoom = ZoomUtils.computeZoomRect(25f, sensorSize, 1f, 10f);
 
     assertNotNull(computedZoom);
     assertEquals(computedZoom.left, 45);
@@ -51,9 +51,9 @@
   }
 
   @Test
-  public void setZoom_whenZoomIsSmallerThenMinZoomClampToMinZoom() {
+  public void setZoomRect_whenZoomIsSmallerThenMinZoomClampToMinZoom() {
     final Rect sensorSize = new Rect(0, 0, 100, 100);
-    final Rect computedZoom = ZoomUtils.computeZoom(0.5f, sensorSize, 1f, 10f);
+    final Rect computedZoom = ZoomUtils.computeZoomRect(0.5f, sensorSize, 1f, 10f);
 
     assertNotNull(computedZoom);
     assertEquals(computedZoom.left, 0);
@@ -61,4 +61,25 @@
     assertEquals(computedZoom.right, 100);
     assertEquals(computedZoom.bottom, 100);
   }
+
+  @Test
+  public void setZoomRatio_whenNewZoomGreaterThanMaxZoomClampToMaxZoom() {
+    final Float computedZoom = ZoomUtils.computeZoomRatio(21f, 1f, 20f);
+    assertNotNull(computedZoom);
+    assertEquals(computedZoom, 20f, 0.0f);
+  }
+
+  @Test
+  public void setZoomRatio_whenNewZoomLesserThanMinZoomClampToMinZoom() {
+    final Float computedZoom = ZoomUtils.computeZoomRatio(0.7f, 1f, 20f);
+    assertNotNull(computedZoom);
+    assertEquals(computedZoom, 1f, 0.0f);
+  }
+
+  @Test
+  public void setZoomRatio_whenNewZoomValidReturnNewZoom() {
+    final Float computedZoom = ZoomUtils.computeZoomRatio(2.0f, 1f, 20f);
+    assertNotNull(computedZoom);
+    assertEquals(computedZoom, 2.0f, 0.0f);
+  }
 }
diff --git a/packages/camera/camera_android/pubspec.yaml b/packages/camera/camera_android/pubspec.yaml
index 9d86a00..20a86ad 100644
--- a/packages/camera/camera_android/pubspec.yaml
+++ b/packages/camera/camera_android/pubspec.yaml
@@ -2,7 +2,7 @@
 description: Android implementation of the camera plugin.
 repository: https://github.com/flutter/plugins/tree/main/packages/camera/camera_android
 issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22
-version: 0.10.2+1
+version: 0.10.2+2
 
 environment:
   sdk: ">=2.14.0 <3.0.0"