[camerax] Implement resolution configuration (#3799)

Adds resolution configuration for all camera use cases. Also makes minor updates to related documentation.

Fixes https://github.com/flutter/flutter/issues/120462.
diff --git a/packages/camera/camera_android_camerax/CHANGELOG.md b/packages/camera/camera_android_camerax/CHANGELOG.md
index 1d2e9c6..61aff80 100644
--- a/packages/camera/camera_android_camerax/CHANGELOG.md
+++ b/packages/camera/camera_android_camerax/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 0.5.0+17
+
+* Implements resolution configuration for all camera use cases.
+
 ## 0.5.0+16
 
 * Adds pub topics to package metadata.
diff --git a/packages/camera/camera_android_camerax/README.md b/packages/camera/camera_android_camerax/README.md
index dd6e563..1642b81 100644
--- a/packages/camera/camera_android_camerax/README.md
+++ b/packages/camera/camera_android_camerax/README.md
@@ -24,16 +24,18 @@
 
 ## Missing features and limitations
 
-### Resolution configuration \[[Issue #120462][120462]\]
 
-Any specified `ResolutionPreset` wll go unused in favor of CameraX defaults and
-`onCameraResolutionChanged` is unimplemented.
+### 240p resolution configuration for video recording
+
+240p resolution configuration for video recording is unsupported by CameraX,
+and thus, the plugin will fall back to 480p if configured with a
+`ResolutionPreset`.
 
 ### Locking/Unlocking capture orientation \[[Issue #125915][125915]\]
 
 `lockCaptureOrientation` & `unLockCaptureOrientation` are unimplemented.
 
-### Flash mode configuration \[[Issue #120715][120715]\]
+### Torch mode \[[Issue #120715][120715]\]
 
 Calling `setFlashMode` with mode `FlashMode.torch` currently does nothing.
 
diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/FallbackStrategyHostApiImpl.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/FallbackStrategyHostApiImpl.java
index 0309d54..da9f58a 100644
--- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/FallbackStrategyHostApiImpl.java
+++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/FallbackStrategyHostApiImpl.java
@@ -9,7 +9,7 @@
 import androidx.camera.video.FallbackStrategy;
 import androidx.camera.video.Quality;
 import io.flutter.plugins.camerax.GeneratedCameraXLibrary.FallbackStrategyHostApi;
-import io.flutter.plugins.camerax.GeneratedCameraXLibrary.VideoQualityConstraint;
+import io.flutter.plugins.camerax.GeneratedCameraXLibrary.VideoQuality;
 import io.flutter.plugins.camerax.GeneratedCameraXLibrary.VideoResolutionFallbackRule;
 
 /**
@@ -28,20 +28,18 @@
   public static class FallbackStrategyProxy {
     /** Creates an instance of {@link FallbackStrategy}. */
     public @NonNull FallbackStrategy create(
-        @NonNull VideoQualityConstraint videoQualityConstraint,
-        @NonNull VideoResolutionFallbackRule fallbackRule) {
-      Quality videoQuality =
-          QualitySelectorHostApiImpl.getQualityFromVideoQualityConstraint(videoQualityConstraint);
+        @NonNull VideoQuality videoQuality, @NonNull VideoResolutionFallbackRule fallbackRule) {
+      Quality quality = QualitySelectorHostApiImpl.getQualityFromVideoQuality(videoQuality);
 
       switch (fallbackRule) {
         case HIGHER_QUALITY_OR_LOWER_THAN:
-          return FallbackStrategy.higherQualityOrLowerThan(videoQuality);
+          return FallbackStrategy.higherQualityOrLowerThan(quality);
         case HIGHER_QUALITY_THAN:
-          return FallbackStrategy.higherQualityThan(videoQuality);
+          return FallbackStrategy.higherQualityThan(quality);
         case LOWER_QUALITY_OR_HIGHER_THAN:
-          return FallbackStrategy.lowerQualityOrHigherThan(videoQuality);
+          return FallbackStrategy.lowerQualityOrHigherThan(quality);
         case LOWER_QUALITY_THAN:
-          return FallbackStrategy.lowerQualityThan(videoQuality);
+          return FallbackStrategy.lowerQualityThan(quality);
       }
       throw new IllegalArgumentException(
           "Specified fallback rule " + fallbackRule + " unrecognized.");
@@ -75,9 +73,8 @@
   @Override
   public void create(
       @NonNull Long identifier,
-      @NonNull VideoQualityConstraint videoQualityConstraint,
+      @NonNull VideoQuality videoQuality,
       @NonNull VideoResolutionFallbackRule fallbackRule) {
-    instanceManager.addDartCreatedInstance(
-        proxy.create(videoQualityConstraint, fallbackRule), identifier);
+    instanceManager.addDartCreatedInstance(proxy.create(videoQuality, fallbackRule), identifier);
   }
 }
diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/GeneratedCameraXLibrary.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/GeneratedCameraXLibrary.java
index d7d378d..fe9a935 100644
--- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/GeneratedCameraXLibrary.java
+++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/GeneratedCameraXLibrary.java
@@ -112,7 +112,7 @@
    *
    * <p>See https://developer.android.com/reference/androidx/camera/video/Quality.
    */
-  public enum VideoQualityConstraint {
+  public enum VideoQuality {
     SD(0),
     HD(1),
     FHD(2),
@@ -122,12 +122,16 @@
 
     final int index;
 
-    private VideoQualityConstraint(final int index) {
+    private VideoQuality(final int index) {
       this.index = index;
     }
   }
 
-  /** Fallback rules for selecting video resolution. */
+  /**
+   * Fallback rules for selecting video resolution.
+   *
+   * <p>See https://developer.android.com/reference/androidx/camera/video/FallbackStrategy.
+   */
   public enum VideoResolutionFallbackRule {
     HIGHER_QUALITY_OR_LOWER_THAN(0),
     HIGHER_QUALITY_THAN(1),
@@ -472,6 +476,59 @@
     }
   }
 
+  /**
+   * Convenience class for sending lists of [Quality]s.
+   *
+   * <p>Generated class from Pigeon that represents data sent in messages.
+   */
+  public static final class VideoQualityData {
+    private @NonNull VideoQuality quality;
+
+    public @NonNull VideoQuality getQuality() {
+      return quality;
+    }
+
+    public void setQuality(@NonNull VideoQuality setterArg) {
+      if (setterArg == null) {
+        throw new IllegalStateException("Nonnull field \"quality\" is null.");
+      }
+      this.quality = setterArg;
+    }
+
+    /** Constructor is non-public to enforce null safety; use Builder. */
+    VideoQualityData() {}
+
+    public static final class Builder {
+
+      private @Nullable VideoQuality quality;
+
+      public @NonNull Builder setQuality(@NonNull VideoQuality setterArg) {
+        this.quality = setterArg;
+        return this;
+      }
+
+      public @NonNull VideoQualityData build() {
+        VideoQualityData pigeonReturn = new VideoQualityData();
+        pigeonReturn.setQuality(quality);
+        return pigeonReturn;
+      }
+    }
+
+    @NonNull
+    ArrayList<Object> toList() {
+      ArrayList<Object> toListResult = new ArrayList<Object>(1);
+      toListResult.add(quality == null ? null : quality.index);
+      return toListResult;
+    }
+
+    static @NonNull VideoQualityData fromList(@NonNull ArrayList<Object> list) {
+      VideoQualityData pigeonResult = new VideoQualityData();
+      Object quality = list.get(0);
+      pigeonResult.setQuality(quality == null ? null : VideoQuality.values()[(int) quality]);
+      return pigeonResult;
+    }
+  }
+
   public interface Result<T> {
     @SuppressWarnings("UnknownNullness")
     void success(T result);
@@ -2991,6 +3048,8 @@
       switch (type) {
         case (byte) 128:
           return ResolutionInfo.fromList((ArrayList<Object>) readValue(buffer));
+        case (byte) 129:
+          return VideoQualityData.fromList((ArrayList<Object>) readValue(buffer));
         default:
           return super.readValueOfType(type, buffer);
       }
@@ -3001,6 +3060,9 @@
       if (value instanceof ResolutionInfo) {
         stream.write(128);
         writeValue(stream, ((ResolutionInfo) value).toList());
+      } else if (value instanceof VideoQualityData) {
+        stream.write(129);
+        writeValue(stream, ((VideoQualityData) value).toList());
       } else {
         super.writeValue(stream, value);
       }
@@ -3012,12 +3074,11 @@
 
     void create(
         @NonNull Long identifier,
-        @NonNull List<Long> videoQualityConstraintIndexList,
+        @NonNull List<VideoQualityData> videoQualityDataList,
         @Nullable Long fallbackStrategyId);
 
     @NonNull
-    ResolutionInfo getResolution(
-        @NonNull Long cameraInfoId, @NonNull VideoQualityConstraint quality);
+    ResolutionInfo getResolution(@NonNull Long cameraInfoId, @NonNull VideoQuality quality);
 
     /** The codec used by QualitySelectorHostApi. */
     static @NonNull MessageCodec<Object> getCodec() {
@@ -3039,12 +3100,13 @@
                 ArrayList<Object> wrapped = new ArrayList<Object>();
                 ArrayList<Object> args = (ArrayList<Object>) message;
                 Number identifierArg = (Number) args.get(0);
-                List<Long> videoQualityConstraintIndexListArg = (List<Long>) args.get(1);
+                List<VideoQualityData> videoQualityDataListArg =
+                    (List<VideoQualityData>) args.get(1);
                 Number fallbackStrategyIdArg = (Number) args.get(2);
                 try {
                   api.create(
                       (identifierArg == null) ? null : identifierArg.longValue(),
-                      videoQualityConstraintIndexListArg,
+                      videoQualityDataListArg,
                       (fallbackStrategyIdArg == null) ? null : fallbackStrategyIdArg.longValue());
                   wrapped.add(0, null);
                 } catch (Throwable exception) {
@@ -3069,8 +3131,8 @@
                 ArrayList<Object> wrapped = new ArrayList<Object>();
                 ArrayList<Object> args = (ArrayList<Object>) message;
                 Number cameraInfoIdArg = (Number) args.get(0);
-                VideoQualityConstraint qualityArg =
-                    args.get(1) == null ? null : VideoQualityConstraint.values()[(int) args.get(1)];
+                VideoQuality qualityArg =
+                    args.get(1) == null ? null : VideoQuality.values()[(int) args.get(1)];
                 try {
                   ResolutionInfo output =
                       api.getResolution(
@@ -3094,7 +3156,7 @@
 
     void create(
         @NonNull Long identifier,
-        @NonNull VideoQualityConstraint quality,
+        @NonNull VideoQuality quality,
         @NonNull VideoResolutionFallbackRule fallbackRule);
 
     /** The codec used by FallbackStrategyHostApi. */
@@ -3117,8 +3179,8 @@
                 ArrayList<Object> wrapped = new ArrayList<Object>();
                 ArrayList<Object> args = (ArrayList<Object>) message;
                 Number identifierArg = (Number) args.get(0);
-                VideoQualityConstraint qualityArg =
-                    args.get(1) == null ? null : VideoQualityConstraint.values()[(int) args.get(1)];
+                VideoQuality qualityArg =
+                    args.get(1) == null ? null : VideoQuality.values()[(int) args.get(1)];
                 VideoResolutionFallbackRule fallbackRuleArg =
                     args.get(2) == null
                         ? null
diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/QualitySelectorHostApiImpl.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/QualitySelectorHostApiImpl.java
index c747c75..6752254 100644
--- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/QualitySelectorHostApiImpl.java
+++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/QualitySelectorHostApiImpl.java
@@ -13,7 +13,8 @@
 import androidx.camera.video.QualitySelector;
 import io.flutter.plugins.camerax.GeneratedCameraXLibrary.QualitySelectorHostApi;
 import io.flutter.plugins.camerax.GeneratedCameraXLibrary.ResolutionInfo;
-import io.flutter.plugins.camerax.GeneratedCameraXLibrary.VideoQualityConstraint;
+import io.flutter.plugins.camerax.GeneratedCameraXLibrary.VideoQuality;
+import io.flutter.plugins.camerax.GeneratedCameraXLibrary.VideoQualityData;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
@@ -34,12 +35,12 @@
   public static class QualitySelectorProxy {
     /** Creates an instance of {@link QualitySelector}. */
     public @NonNull QualitySelector create(
-        @NonNull List<Long> videoQualityConstraintIndexList,
+        @NonNull List<VideoQualityData> videoQualityDataList,
         @Nullable FallbackStrategy fallbackStrategy) {
-      // Convert each index of VideoQualityConstraint to Quality.
+      // Convert each index of VideoQuality to Quality.
       List<Quality> qualityList = new ArrayList<Quality>();
-      for (Long qualityIndex : videoQualityConstraintIndexList) {
-        qualityList.add(getQualityConstant(qualityIndex));
+      for (VideoQualityData videoQualityData : videoQualityDataList) {
+        qualityList.add(getQualityFromVideoQuality(videoQualityData.getQuality()));
       }
 
       boolean fallbackStrategySpecified = fallbackStrategy != null;
@@ -57,12 +58,6 @@
           ? QualitySelector.fromOrderedList(qualityList, fallbackStrategy)
           : QualitySelector.fromOrderedList(qualityList);
     }
-
-    /** Converts from index of {@link VideoQualityConstraint} to {@link Quality}. */
-    private Quality getQualityConstant(@NonNull Long qualityIndex) {
-      VideoQualityConstraint quality = VideoQualityConstraint.values()[qualityIndex.intValue()];
-      return getQualityFromVideoQualityConstraint(quality);
-    }
   }
 
   /**
@@ -93,11 +88,11 @@
   @Override
   public void create(
       @NonNull Long identifier,
-      @NonNull List<Long> videoQualityConstraintIndexList,
+      @NonNull List<VideoQualityData> videoQualityDataList,
       @Nullable Long fallbackStrategyIdentifier) {
     instanceManager.addDartCreatedInstance(
         proxy.create(
-            videoQualityConstraintIndexList,
+            videoQualityDataList,
             fallbackStrategyIdentifier == null
                 ? null
                 : Objects.requireNonNull(instanceManager.getInstance(fallbackStrategyIdentifier))),
@@ -110,11 +105,11 @@
    */
   @Override
   public @NonNull ResolutionInfo getResolution(
-      @NonNull Long cameraInfoIdentifier, @NonNull VideoQualityConstraint quality) {
+      @NonNull Long cameraInfoIdentifier, @NonNull VideoQuality quality) {
     final Size result =
         QualitySelector.getResolution(
             Objects.requireNonNull(instanceManager.getInstance(cameraInfoIdentifier)),
-            getQualityFromVideoQualityConstraint(quality));
+            getQualityFromVideoQuality(quality));
     return new ResolutionInfo.Builder()
         .setWidth(Long.valueOf(result.getWidth()))
         .setHeight(Long.valueOf(result.getHeight()))
@@ -122,12 +117,11 @@
   }
 
   /**
-   * Converts the specified {@link VideoQualityConstraint} to a {@link Quality} that is understood
+   * Converts the specified {@link VideoQuality to a {@link Quality} that is understood
    * by CameraX.
    */
-  public static @NonNull Quality getQualityFromVideoQualityConstraint(
-      @NonNull VideoQualityConstraint videoQualityConstraint) {
-    switch (videoQualityConstraint) {
+  public static @NonNull Quality getQualityFromVideoQuality(@NonNull VideoQuality videoQuality) {
+    switch (videoQuality) {
       case SD:
         return Quality.SD;
       case HD:
@@ -142,8 +136,6 @@
         return Quality.HIGHEST;
     }
     throw new IllegalArgumentException(
-        "VideoQualityConstraint "
-            + videoQualityConstraint
-            + " is unhandled by QualitySelectorHostApiImpl.");
+        "VideoQuality " + videoQuality + " is unhandled by QualitySelectorHostApiImpl.");
   }
 }
diff --git a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/FallbackStrategyTest.java b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/FallbackStrategyTest.java
index 4a2eeef..a68384f 100644
--- a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/FallbackStrategyTest.java
+++ b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/FallbackStrategyTest.java
@@ -11,7 +11,7 @@
 
 import androidx.camera.video.FallbackStrategy;
 import androidx.camera.video.Quality;
-import io.flutter.plugins.camerax.GeneratedCameraXLibrary.VideoQualityConstraint;
+import io.flutter.plugins.camerax.GeneratedCameraXLibrary.VideoQuality;
 import io.flutter.plugins.camerax.GeneratedCameraXLibrary.VideoResolutionFallbackRule;
 import org.junit.After;
 import org.junit.Before;
@@ -48,11 +48,11 @@
 
     try (MockedStatic<FallbackStrategy> mockedFallbackStrategy =
         mockStatic(FallbackStrategy.class)) {
-      for (VideoQualityConstraint videoQualityConstraint : VideoQualityConstraint.values()) {
+      for (VideoQuality videoQuality : VideoQuality.values()) {
         for (VideoResolutionFallbackRule fallbackRule : VideoResolutionFallbackRule.values()) {
-          // Determine expected Quality based on videoQualityConstraint being tested.
+          // Determine expected Quality based on videoQuality being tested.
           Quality convertedQuality = null;
-          switch (videoQualityConstraint) {
+          switch (videoQuality) {
             case SD:
               convertedQuality = Quality.SD;
               break;
@@ -72,10 +72,7 @@
               convertedQuality = Quality.HIGHEST;
               break;
             default:
-              fail(
-                  "The VideoQualityConstraint "
-                      + videoQualityConstraint.toString()
-                      + "is unhandled by this test.");
+              fail("The VideoQuality " + videoQuality.toString() + "is unhandled by this test.");
           }
           // Set Quality as final local variable to avoid error about using non-final (or effecitvely final) local variables in lambda expressions.
           final Quality expectedQuality = convertedQuality;
@@ -108,7 +105,7 @@
                       + fallbackRule.toString()
                       + "is unhandled by this test.");
           }
-          hostApi.create(instanceIdentifier, videoQualityConstraint, fallbackRule);
+          hostApi.create(instanceIdentifier, videoQuality, fallbackRule);
           assertEquals(instanceManager.getInstance(instanceIdentifier), mockFallbackStrategy);
 
           // Clear/reset FallbackStrategy mock and InstanceManager.
diff --git a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/QualitySelectorTest.java b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/QualitySelectorTest.java
index 5519583..83617a7 100644
--- a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/QualitySelectorTest.java
+++ b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/QualitySelectorTest.java
@@ -14,7 +14,8 @@
 import androidx.camera.video.Quality;
 import androidx.camera.video.QualitySelector;
 import io.flutter.plugins.camerax.GeneratedCameraXLibrary.ResolutionInfo;
-import io.flutter.plugins.camerax.GeneratedCameraXLibrary.VideoQualityConstraint;
+import io.flutter.plugins.camerax.GeneratedCameraXLibrary.VideoQuality;
+import io.flutter.plugins.camerax.GeneratedCameraXLibrary.VideoQualityData;
 import java.util.Arrays;
 import java.util.List;
 import org.junit.After;
@@ -47,10 +48,9 @@
 
   @Test
   public void hostApiCreate_createsExpectedQualitySelectorWhenOneQualitySpecified() {
-    final Long expectedVideoQualityConstraintIndex =
-        Long.valueOf(VideoQualityConstraint.UHD.ordinal());
-    final List<Long> videoQualityConstraintList =
-        Arrays.asList(expectedVideoQualityConstraintIndex);
+    final VideoQualityData expectedVideoQualityData =
+        new VideoQualityData.Builder().setQuality(VideoQuality.UHD).build();
+    final List<VideoQualityData> videoQualityDataList = Arrays.asList(expectedVideoQualityData);
     final FallbackStrategy mockFallbackStrategy = mock(FallbackStrategy.class);
     final long fallbackStrategyIdentifier = 9;
     final QualitySelectorHostApiImpl hostApi = new QualitySelectorHostApiImpl(instanceManager);
@@ -69,7 +69,7 @@
 
       // Test with no fallback strategy.
       long instanceIdentifier = 0;
-      hostApi.create(instanceIdentifier, videoQualityConstraintList, null);
+      hostApi.create(instanceIdentifier, videoQualityDataList, null);
 
       assertEquals(
           instanceManager.getInstance(instanceIdentifier),
@@ -77,7 +77,7 @@
 
       // Test with fallback strategy.
       instanceIdentifier = 1;
-      hostApi.create(instanceIdentifier, videoQualityConstraintList, fallbackStrategyIdentifier);
+      hostApi.create(instanceIdentifier, videoQualityDataList, fallbackStrategyIdentifier);
 
       assertEquals(
           instanceManager.getInstance(instanceIdentifier), mockQualitySelectorWithFallbackStrategy);
@@ -86,14 +86,11 @@
 
   @Test
   public void hostApiCreate_createsExpectedQualitySelectorWhenOrderedListOfQualitiesSpecified() {
-    final List<Long> expectedIndices =
+    final List<VideoQualityData> videoQualityDataList =
         Arrays.asList(
-            Long.valueOf(VideoQualityConstraint.UHD.ordinal()),
-            Long.valueOf(VideoQualityConstraint.HIGHEST.ordinal()));
-    final List<VideoQualityConstraint> videoQualityConstraintList =
-        Arrays.asList(VideoQualityConstraint.UHD, VideoQualityConstraint.HIGHEST);
-    final List<Quality> expectedVideoQualityConstraintList =
-        Arrays.asList(Quality.UHD, Quality.HIGHEST);
+            new VideoQualityData.Builder().setQuality(VideoQuality.UHD).build(),
+            new VideoQualityData.Builder().setQuality(VideoQuality.HIGHEST).build());
+    final List<Quality> expectedVideoQualityList = Arrays.asList(Quality.UHD, Quality.HIGHEST);
     final FallbackStrategy mockFallbackStrategy = mock(FallbackStrategy.class);
     final long fallbackStrategyIdentifier = 9;
     final QualitySelectorHostApiImpl hostApi = new QualitySelectorHostApiImpl(instanceManager);
@@ -102,20 +99,18 @@
 
     try (MockedStatic<QualitySelector> mockedQualitySelector = mockStatic(QualitySelector.class)) {
       mockedQualitySelector
-          .when(() -> QualitySelector.fromOrderedList(expectedVideoQualityConstraintList))
+          .when(() -> QualitySelector.fromOrderedList(expectedVideoQualityList))
           .thenAnswer(
               (Answer<QualitySelector>) invocation -> mockQualitySelectorWithoutFallbackStrategy);
       mockedQualitySelector
           .when(
-              () ->
-                  QualitySelector.fromOrderedList(
-                      expectedVideoQualityConstraintList, mockFallbackStrategy))
+              () -> QualitySelector.fromOrderedList(expectedVideoQualityList, mockFallbackStrategy))
           .thenAnswer(
               (Answer<QualitySelector>) invocation -> mockQualitySelectorWithFallbackStrategy);
 
       // Test with no fallback strategy.
       long instanceIdentifier = 0;
-      hostApi.create(instanceIdentifier, expectedIndices, null);
+      hostApi.create(instanceIdentifier, videoQualityDataList, null);
 
       assertEquals(
           instanceManager.getInstance(instanceIdentifier),
@@ -123,7 +118,7 @@
 
       // Test with fallback strategy.
       instanceIdentifier = 1;
-      hostApi.create(instanceIdentifier, expectedIndices, fallbackStrategyIdentifier);
+      hostApi.create(instanceIdentifier, videoQualityDataList, fallbackStrategyIdentifier);
 
       assertEquals(
           instanceManager.getInstance(instanceIdentifier), mockQualitySelectorWithFallbackStrategy);
@@ -134,7 +129,7 @@
   public void getResolution_returnsExpectedResolutionInfo() {
     final CameraInfo mockCameraInfo = mock(CameraInfo.class);
     final long cameraInfoIdentifier = 6;
-    final VideoQualityConstraint videoQualityConstraint = VideoQualityConstraint.FHD;
+    final VideoQuality videoQuality = VideoQuality.FHD;
     final Size sizeResult = new Size(30, 40);
     final QualitySelectorHostApiImpl hostApi = new QualitySelectorHostApiImpl(instanceManager);
 
@@ -145,8 +140,7 @@
           .when(() -> QualitySelector.getResolution(mockCameraInfo, Quality.FHD))
           .thenAnswer((Answer<Size>) invocation -> sizeResult);
 
-      final ResolutionInfo result =
-          hostApi.getResolution(cameraInfoIdentifier, videoQualityConstraint);
+      final ResolutionInfo result = hostApi.getResolution(cameraInfoIdentifier, videoQuality);
 
       assertEquals(result.getWidth(), Long.valueOf(sizeResult.getWidth()));
       assertEquals(result.getHeight(), Long.valueOf(sizeResult.getHeight()));
diff --git a/packages/camera/camera_android_camerax/example/integration_test/integration_test.dart b/packages/camera/camera_android_camerax/example/integration_test/integration_test.dart
index b6cba8d..b016cf0 100644
--- a/packages/camera/camera_android_camerax/example/integration_test/integration_test.dart
+++ b/packages/camera/camera_android_camerax/example/integration_test/integration_test.dart
@@ -2,11 +2,13 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import 'dart:async';
 import 'dart:io';
 import 'dart:ui';
 
 import 'package:camera_android_camerax/camera_android_camerax.dart';
 import 'package:camera_android_camerax_example/camera_controller.dart';
+import 'package:camera_android_camerax_example/camera_image.dart';
 import 'package:camera_platform_interface/camera_platform_interface.dart';
 import 'package:flutter/painting.dart';
 import 'package:flutter_test/flutter_test.dart';
@@ -19,6 +21,46 @@
     CameraPlatform.instance = AndroidCameraCameraX();
   });
 
+  final Map<ResolutionPreset, Size> presetExpectedSizes =
+      <ResolutionPreset, Size>{
+    ResolutionPreset.low: const Size(240, 320),
+    ResolutionPreset.medium: const Size(480, 720),
+    ResolutionPreset.high: const Size(720, 1280),
+    ResolutionPreset.veryHigh: const Size(1080, 1920),
+    ResolutionPreset.ultraHigh: const Size(2160, 3840),
+    // Don't bother checking for max here since it could be anything.
+  };
+
+  /// Verify that [actual] has dimensions that are at most as large as
+  /// [expectedSize]. Allows for a mismatch in portrait vs landscape. Returns
+  /// whether the dimensions exactly match.
+  bool assertExpectedDimensions(Size expectedSize, Size actual) {
+    expect(actual.shortestSide, lessThanOrEqualTo(expectedSize.shortestSide));
+    expect(actual.longestSide, lessThanOrEqualTo(expectedSize.longestSide));
+    return actual.shortestSide == expectedSize.shortestSide &&
+        actual.longestSide == expectedSize.longestSide;
+  }
+
+  // This tests that the capture is no bigger than the preset, since we have
+  // automatic code to fall back to smaller sizes when we need to. Returns
+  // whether the image is exactly the desired resolution.
+  Future<bool> testCaptureImageResolution(
+      CameraController controller, ResolutionPreset preset) async {
+    final Size expectedSize = presetExpectedSizes[preset]!;
+
+    // Take Picture
+    final XFile file = await controller.takePicture();
+
+    // Load picture
+    final File fileImage = File(file.path);
+    final Image image = await decodeImageFromList(fileImage.readAsBytesSync());
+
+    // Verify image dimensions are as expected
+    expect(image, isNotNull);
+    return assertExpectedDimensions(
+        expectedSize, Size(image.height.toDouble(), image.width.toDouble()));
+  }
+
   testWidgets('availableCameras only supports valid back or front cameras',
       (WidgetTester tester) async {
     final List<CameraDescription> availableCameras =
@@ -31,27 +73,103 @@
     }
   });
 
-  testWidgets('takePictures stores a valid image in memory',
+  testWidgets('Capture specific image resolutions',
       (WidgetTester tester) async {
-    final List<CameraDescription> availableCameras =
+    final List<CameraDescription> cameras =
         await CameraPlatform.instance.availableCameras();
-    if (availableCameras.isEmpty) {
+    if (cameras.isEmpty) {
       return;
     }
-    for (final CameraDescription cameraDescription in availableCameras) {
-      final CameraController controller =
-          CameraController(cameraDescription, ResolutionPreset.high);
-      await controller.initialize();
+    for (final CameraDescription cameraDescription in cameras) {
+      bool previousPresetExactlySupported = true;
+      for (final MapEntry<ResolutionPreset, Size> preset
+          in presetExpectedSizes.entries) {
+        final CameraController controller =
+            CameraController(cameraDescription, preset.key);
+        await controller.initialize();
+        final bool presetExactlySupported =
+            await testCaptureImageResolution(controller, preset.key);
+        // Ensures that if a lower resolution was used for previous (lower)
+        // resolution preset, then the current (higher) preset also is adjusted,
+        // as it demands a hgher resolution.
+        expect(
+            previousPresetExactlySupported || !presetExactlySupported, isTrue,
+            reason:
+                'The camera took higher resolution pictures at a lower resolution.');
+        previousPresetExactlySupported = presetExactlySupported;
+        await controller.dispose();
+      }
+    }
+  });
 
-      // Take Picture
-      final XFile file = await controller.takePicture();
+  testWidgets('Preview takes expected resolution from preset',
+      (WidgetTester tester) async {
+    final List<CameraDescription> cameras =
+        await CameraPlatform.instance.availableCameras();
+    if (cameras.isEmpty) {
+      return;
+    }
+    for (final CameraDescription cameraDescription in cameras) {
+      bool previousPresetExactlySupported = true;
+      for (final MapEntry<ResolutionPreset, Size> preset
+          in presetExpectedSizes.entries) {
+        final CameraController controller =
+            CameraController(cameraDescription, preset.key);
 
-      // Try loading picture
-      final File fileImage = File(file.path);
-      final Image image =
-          await decodeImageFromList(fileImage.readAsBytesSync());
+        await controller.initialize();
 
-      expect(image, isNotNull);
+        while (controller.value.previewSize == null) {
+          // Wait for preview size to update.
+        }
+
+        final bool presetExactlySupported = assertExpectedDimensions(
+            preset.value, controller.value.previewSize!);
+        // Ensures that if a lower resolution was used for previous (lower)
+        // resolution preset, then the current (higher) preset also is adjusted,
+        // as it demands a hgher resolution.
+        expect(
+            previousPresetExactlySupported || !presetExactlySupported, isTrue,
+            reason: 'The preview has a lower resolution than that specified.');
+        previousPresetExactlySupported = presetExactlySupported;
+        await controller.dispose();
+      }
+    }
+  });
+
+  testWidgets('Images from streaming have expected resolution from preset',
+      (WidgetTester tester) async {
+    final List<CameraDescription> cameras =
+        await CameraPlatform.instance.availableCameras();
+    if (cameras.isEmpty) {
+      return;
+    }
+    for (final CameraDescription cameraDescription in cameras) {
+      bool previousPresetExactlySupported = true;
+      for (final MapEntry<ResolutionPreset, Size> preset
+          in presetExpectedSizes.entries) {
+        final CameraController controller =
+            CameraController(cameraDescription, preset.key);
+        final Completer<CameraImage> imageCompleter = Completer<CameraImage>();
+        await controller.initialize();
+        await controller.startImageStream((CameraImage image) {
+          imageCompleter.complete(image);
+          controller.stopImageStream();
+        });
+
+        final CameraImage image = await imageCompleter.future;
+        final bool presetExactlySupported = assertExpectedDimensions(
+            preset.value,
+            Size(image.height.toDouble(), image.width.toDouble()));
+        // Ensures that if a lower resolution was used for previous (lower)
+        // resolution preset, then the current (higher) preset also is adjusted,
+        // as it demands a hgher resolution.
+        expect(
+            previousPresetExactlySupported || !presetExactlySupported, isTrue,
+            reason: 'The preview has a lower resolution than that specified.');
+        previousPresetExactlySupported = presetExactlySupported;
+
+        await controller.dispose();
+      }
     }
   });
 }
diff --git a/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart b/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart
index 7c5bf6b..fd42cc6 100644
--- a/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart
+++ b/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart
@@ -16,6 +16,7 @@
 import 'camera_state.dart';
 import 'camerax_library.g.dart';
 import 'exposure_state.dart';
+import 'fallback_strategy.dart';
 import 'image_analysis.dart';
 import 'image_capture.dart';
 import 'image_proxy.dart';
@@ -25,8 +26,11 @@
 import 'plane_proxy.dart';
 import 'preview.dart';
 import 'process_camera_provider.dart';
+import 'quality_selector.dart';
 import 'recorder.dart';
 import 'recording.dart';
+import 'resolution_selector.dart';
+import 'resolution_strategy.dart';
 import 'surface.dart';
 import 'system_services.dart';
 import 'use_case.dart';
@@ -224,35 +228,42 @@
     // Start listening for device orientation changes preceding camera creation.
     startListeningForDeviceOrientationChange(
         cameraIsFrontFacing, cameraDescription.sensorOrientation);
+    // Determine ResolutionSelector and QualitySelector based on
+    // resolutionPreset for camera UseCases.
+    final ResolutionSelector? presetResolutionSelector =
+        _getResolutionSelectorFromPreset(resolutionPreset);
+    final QualitySelector? presetQualitySelector =
+        _getQualitySelectorFromPreset(resolutionPreset);
 
     // Retrieve a fresh ProcessCameraProvider instance.
     processCameraProvider ??= await ProcessCameraProvider.getInstance();
     processCameraProvider!.unbindAll();
 
-    // TODO(camsim99): Implement resolution configuration for UseCases
-    // configured here. https://github.com/flutter/flutter/issues/120462
-
     // Configure Preview instance.
     final int targetRotation =
         _getTargetRotation(cameraDescription.sensorOrientation);
-    preview = createPreview(targetRotation);
+    preview = createPreview(
+        targetRotation: targetRotation,
+        resolutionSelector: presetResolutionSelector);
     final int flutterSurfaceTextureId = await preview!.setSurfaceProvider();
 
     // Configure ImageCapture instance.
-    imageCapture = createImageCapture(null);
+    imageCapture = createImageCapture(presetResolutionSelector);
+
+    // Configure ImageAnalysis instance.
+    // Defaults to YUV_420_888 image format.
+    imageAnalysis = createImageAnalysis(presetResolutionSelector);
 
     // Configure VideoCapture and Recorder instances.
-    // TODO(gmackall): Enable video capture resolution configuration in createRecorder().
-    recorder = createRecorder();
+    recorder = createRecorder(presetQualitySelector);
     videoCapture = await createVideoCapture(recorder!);
 
     // Bind configured UseCases to ProcessCameraProvider instance & mark Preview
     // instance as bound but not paused. Video capture is bound at first use
     // instead of here.
-    camera = await processCameraProvider!
-        .bindToLifecycle(cameraSelector!, <UseCase>[preview!, imageCapture!]);
-    await _updateLiveCameraState(flutterSurfaceTextureId);
-    cameraInfo = await camera!.getCameraInfo();
+    camera = await processCameraProvider!.bindToLifecycle(
+        cameraSelector!, <UseCase>[preview!, imageCapture!, imageAnalysis!]);
+    await _updateCameraInfoAndLiveCameraState(flutterSurfaceTextureId);
     _previewIsPaused = false;
 
     return flutterSurfaceTextureId;
@@ -325,6 +336,14 @@
     return _cameraEvents(cameraId).whereType<CameraInitializedEvent>();
   }
 
+  /// The camera's resolution has changed.
+  ///
+  /// This stream currently has no events being added to it from this plugin.
+  @override
+  Stream<CameraResolutionChangedEvent> onCameraResolutionChanged(int cameraId) {
+    return _cameraEvents(cameraId).whereType<CameraResolutionChangedEvent>();
+  }
+
   /// The camera started to close.
   @override
   Stream<CameraClosingEvent> onCameraClosing(int cameraId) {
@@ -484,6 +503,10 @@
 
   /// Configures and starts a video recording. Returns silently without doing
   /// anything if there is currently an active recording.
+  ///
+  /// Note that the preset resolution is used to configure the recording, but
+  /// 240p ([ResolutionPreset.low]) is unsupported and will fallback to
+  /// configure the recording as the next highest available quality.
   @override
   Future<void> startVideoRecording(int cameraId,
       {Duration? maxVideoDuration}) async {
@@ -568,7 +591,7 @@
   Stream<CameraImageData> onStreamedFrameAvailable(int cameraId,
       {CameraImageStreamOptions? options}) {
     cameraImageDataStreamController = StreamController<CameraImageData>(
-      onListen: _onFrameStreamListen,
+      onListen: () => _onFrameStreamListen(cameraId),
       onCancel: _onFrameStreamCancel,
     );
     return cameraImageDataStreamController!.stream;
@@ -592,13 +615,12 @@
 
     camera = await processCameraProvider!
         .bindToLifecycle(cameraSelector!, <UseCase>[preview!]);
-    await _updateLiveCameraState(cameraId);
-    cameraInfo = await camera!.getCameraInfo();
+    await _updateCameraInfoAndLiveCameraState(cameraId);
   }
 
   /// Configures the [imageAnalysis] instance for image streaming and binds it
   /// to camera lifecycle controlled by the [processCameraProvider].
-  Future<void> _configureAndBindImageAnalysisToLifecycle() async {
+  Future<void> _configureAndBindImageAnalysisToLifecycle(int cameraId) async {
     // Create Analyzer that can read image data for image streaming.
     final WeakReference<AndroidCameraCameraX> weakThis =
         WeakReference<AndroidCameraCameraX>(this);
@@ -634,21 +656,7 @@
         ? Analyzer.detached(analyze: analyze)
         : Analyzer(analyze: analyze);
 
-    // TODO(camsim99): Support resolution configuration.
-    // Defaults to YUV_420_888 image format.
-    imageAnalysis ??= createImageAnalysis();
-    unawaited(imageAnalysis!.setAnalyzer(analyzer));
-
-    if (await processCameraProvider!.isBound(imageAnalysis!)) {
-      // No need to bind imageAnalysis to lifecycle again.
-      return;
-    }
-
-    // TODO(camsim99): Reset live camera state observers here when
-    // https://github.com/flutter/packages/pull/3419 lands.
-    camera = await processCameraProvider!
-        .bindToLifecycle(cameraSelector!, <UseCase>[imageAnalysis!]);
-    cameraInfo = await camera!.getCameraInfo();
+    await imageAnalysis!.setAnalyzer(analyzer);
   }
 
   /// Unbinds [useCase] from camera lifecycle controlled by the
@@ -666,8 +674,8 @@
 
   /// The [onListen] callback for the stream controller used for image
   /// streaming.
-  Future<void> _onFrameStreamListen() async {
-    await _configureAndBindImageAnalysisToLifecycle();
+  Future<void> _onFrameStreamListen(int cameraId) async {
+    await _configureAndBindImageAnalysisToLifecycle(cameraId);
   }
 
   /// The [onCancel] callback for the stream controller used for image
@@ -695,15 +703,16 @@
 
   // Methods concerning camera state:
 
-  /// Adds observers to the [LiveData] of the [CameraState] of the current
+  /// Updates [cameraInfo] to the information corresponding to [camera] and
+  /// adds observers to the [LiveData] of the [CameraState] of the current
   /// [camera], saved as [liveCameraState].
   ///
   /// If a previous [liveCameraState] was stored, existing observers are
   /// removed, as well.
-  Future<void> _updateLiveCameraState(int cameraId) async {
-    final CameraInfo cameraInfo = await camera!.getCameraInfo();
+  Future<void> _updateCameraInfoAndLiveCameraState(int cameraId) async {
+    cameraInfo = await camera!.getCameraInfo();
     await liveCameraState?.removeObservers();
-    liveCameraState = await cameraInfo.getCameraState();
+    liveCameraState = await cameraInfo!.getCameraState();
     await liveCameraState!.observe(_createCameraClosingObserver(cameraId));
   }
 
@@ -774,6 +783,110 @@
     }
   }
 
+  /// Returns the [ResolutionSelector] that maps to the specified resolution
+  /// preset for camera [UseCase]s.
+  ///
+  /// If the specified [preset] is unavailable, the camera will fall back to the
+  /// closest lower resolution available.
+  ResolutionSelector? _getResolutionSelectorFromPreset(
+      ResolutionPreset? preset) {
+    const int fallbackRule = ResolutionStrategy.fallbackRuleClosestLower;
+
+    Size? boundSize;
+    ResolutionStrategy? resolutionStrategy;
+    switch (preset) {
+      case ResolutionPreset.low:
+        boundSize = const Size(320, 240);
+        break;
+      case ResolutionPreset.medium:
+        boundSize = const Size(720, 480);
+        break;
+      case ResolutionPreset.high:
+        boundSize = const Size(1280, 720);
+        break;
+      case ResolutionPreset.veryHigh:
+        boundSize = const Size(1920, 1080);
+        break;
+      case ResolutionPreset.ultraHigh:
+        boundSize = const Size(3840, 2160);
+        break;
+      case ResolutionPreset.max:
+        // Automatically set strategy to choose highest available.
+        resolutionStrategy = _shouldCreateDetachedObjectForTesting
+            ? ResolutionStrategy.detachedHighestAvailableStrategy()
+            : ResolutionStrategy.highestAvailableStrategy();
+        break;
+      case null:
+        // If no preset is specified, default to CameraX's default behavior
+        // for each UseCase.
+        return null;
+    }
+
+    if (_shouldCreateDetachedObjectForTesting) {
+      resolutionStrategy ??= ResolutionStrategy.detached(
+          boundSize: boundSize, fallbackRule: fallbackRule);
+      return ResolutionSelector.detached(
+          resolutionStrategy: resolutionStrategy);
+    }
+
+    resolutionStrategy ??=
+        ResolutionStrategy(boundSize: boundSize!, fallbackRule: fallbackRule);
+    return ResolutionSelector(
+        resolutionStrategy: ResolutionStrategy(
+            boundSize: boundSize!, fallbackRule: fallbackRule));
+  }
+
+  /// Returns the [QualitySelector] that maps to the specified resolution
+  /// preset for the camera used only for video capture.
+  ///
+  /// If the specified [preset] is unavailable, the camera will fall back to the
+  /// closest lower resolution available.
+  QualitySelector? _getQualitySelectorFromPreset(ResolutionPreset? preset) {
+    VideoQuality? videoQuality;
+    switch (preset) {
+      case ResolutionPreset.low:
+      // 240p is not supported by CameraX.
+      case ResolutionPreset.medium:
+        videoQuality = VideoQuality.SD;
+        break;
+      case ResolutionPreset.high:
+        videoQuality = VideoQuality.HD;
+        break;
+      case ResolutionPreset.veryHigh:
+        videoQuality = VideoQuality.FHD;
+        break;
+      case ResolutionPreset.ultraHigh:
+        videoQuality = VideoQuality.UHD;
+        break;
+      case ResolutionPreset.max:
+        videoQuality = VideoQuality.highest;
+        break;
+      case null:
+        // If no preset is specified, default to CameraX's default behavior
+        // for each UseCase.
+        return null;
+    }
+
+    // We will choose the next highest video quality if the one desired
+    // is unavailable.
+    const VideoResolutionFallbackRule fallbackRule =
+        VideoResolutionFallbackRule.lowerQualityThan;
+    final FallbackStrategy fallbackStrategy =
+        _shouldCreateDetachedObjectForTesting
+            ? FallbackStrategy.detached(
+                quality: videoQuality, fallbackRule: fallbackRule)
+            : FallbackStrategy(
+                quality: videoQuality, fallbackRule: fallbackRule);
+
+    return _shouldCreateDetachedObjectForTesting
+        ? QualitySelector.detached(qualityList: <VideoQualityData>[
+            VideoQualityData(quality: videoQuality)
+          ], fallbackStrategy: fallbackStrategy)
+        : QualitySelector.from(
+            quality: VideoQualityData(quality: videoQuality),
+            fallbackStrategy: fallbackStrategy);
+  }
+
   // Methods for calls that need to be tested:
 
   /// Requests camera permissions.
@@ -804,23 +917,26 @@
   }
 
   /// Returns a [Preview] configured with the specified target rotation and
-  /// resolution.
+  /// specified [ResolutionSelector].
   @visibleForTesting
-  Preview createPreview(int targetRotation) {
-    return Preview(targetRotation: targetRotation);
+  Preview createPreview(
+      {required int targetRotation, ResolutionSelector? resolutionSelector}) {
+    return Preview(
+        targetRotation: targetRotation, resolutionSelector: resolutionSelector);
   }
 
   /// Returns an [ImageCapture] configured with specified flash mode and
-  /// target resolution.
+  /// the specified [ResolutionSelector].
   @visibleForTesting
-  ImageCapture createImageCapture(int? flashMode) {
-    return ImageCapture(targetFlashMode: flashMode);
+  ImageCapture createImageCapture(ResolutionSelector? resolutionSelector) {
+    return ImageCapture(resolutionSelector: resolutionSelector);
   }
 
-  /// Returns a [Recorder] for use in video capture.
+  /// Returns a [Recorder] for use in video capture configured with the
+  /// specified [QualitySelector].
   @visibleForTesting
-  Recorder createRecorder() {
-    return Recorder();
+  Recorder createRecorder(QualitySelector? qualitySelector) {
+    return Recorder(qualitySelector: qualitySelector);
   }
 
   /// Returns a [VideoCapture] associated with the provided [Recorder].
@@ -829,9 +945,10 @@
     return VideoCapture.withOutput(recorder);
   }
 
-  /// Returns an [ImageAnalysis] configured with specified target resolution.
+  /// Returns an [ImageAnalysis] configured with the specified
+  /// [ResolutionSelector].
   @visibleForTesting
-  ImageAnalysis createImageAnalysis() {
-    return ImageAnalysis();
+  ImageAnalysis createImageAnalysis(ResolutionSelector? resolutionSelector) {
+    return ImageAnalysis(resolutionSelector: resolutionSelector);
   }
 }
diff --git a/packages/camera/camera_android_camerax/lib/src/camerax_library.g.dart b/packages/camera/camera_android_camerax/lib/src/camerax_library.g.dart
index b97d193..dda3a81 100644
--- a/packages/camera/camera_android_camerax/lib/src/camerax_library.g.dart
+++ b/packages/camera/camera_android_camerax/lib/src/camerax_library.g.dart
@@ -49,7 +49,7 @@
 /// These are pre-defined quality constants that are universally used for video.
 ///
 /// See https://developer.android.com/reference/androidx/camera/video/Quality.
-enum VideoQualityConstraint {
+enum VideoQuality {
   SD,
   HD,
   FHD,
@@ -59,6 +59,8 @@
 }
 
 /// Fallback rules for selecting video resolution.
+///
+/// See https://developer.android.com/reference/androidx/camera/video/FallbackStrategy.
 enum VideoResolutionFallbackRule {
   higherQualityOrLowerThan,
   higherQualityThan,
@@ -186,6 +188,28 @@
   }
 }
 
+/// Convenience class for sending lists of [Quality]s.
+class VideoQualityData {
+  VideoQualityData({
+    required this.quality,
+  });
+
+  VideoQuality quality;
+
+  Object encode() {
+    return <Object?>[
+      quality.index,
+    ];
+  }
+
+  static VideoQualityData decode(Object result) {
+    result as List<Object?>;
+    return VideoQualityData(
+      quality: VideoQuality.values[result[0]! as int],
+    );
+  }
+}
+
 class InstanceManagerHostApi {
   /// Constructor for [InstanceManagerHostApi].  The [binaryMessenger] named argument is
   /// available for dependency injection.  If it is left null, the default
@@ -2478,6 +2502,9 @@
     if (value is ResolutionInfo) {
       buffer.putUint8(128);
       writeValue(buffer, value.encode());
+    } else if (value is VideoQualityData) {
+      buffer.putUint8(129);
+      writeValue(buffer, value.encode());
     } else {
       super.writeValue(buffer, value);
     }
@@ -2488,6 +2515,8 @@
     switch (type) {
       case 128:
         return ResolutionInfo.decode(readValue(buffer)!);
+      case 129:
+        return VideoQualityData.decode(readValue(buffer)!);
       default:
         return super.readValueOfType(type, buffer);
     }
@@ -2506,14 +2535,14 @@
 
   Future<void> create(
       int arg_identifier,
-      List<int?> arg_videoQualityConstraintIndexList,
+      List<VideoQualityData?> arg_videoQualityDataList,
       int? arg_fallbackStrategyId) async {
     final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
         'dev.flutter.pigeon.QualitySelectorHostApi.create', codec,
         binaryMessenger: _binaryMessenger);
     final List<Object?>? replyList = await channel.send(<Object?>[
       arg_identifier,
-      arg_videoQualityConstraintIndexList,
+      arg_videoQualityDataList,
       arg_fallbackStrategyId
     ]) as List<Object?>?;
     if (replyList == null) {
@@ -2533,7 +2562,7 @@
   }
 
   Future<ResolutionInfo> getResolution(
-      int arg_cameraInfoId, VideoQualityConstraint arg_quality) async {
+      int arg_cameraInfoId, VideoQuality arg_quality) async {
     final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
         'dev.flutter.pigeon.QualitySelectorHostApi.getResolution', codec,
         binaryMessenger: _binaryMessenger);
@@ -2571,7 +2600,7 @@
 
   static const MessageCodec<Object?> codec = StandardMessageCodec();
 
-  Future<void> create(int arg_identifier, VideoQualityConstraint arg_quality,
+  Future<void> create(int arg_identifier, VideoQuality arg_quality,
       VideoResolutionFallbackRule arg_fallbackRule) async {
     final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
         'dev.flutter.pigeon.FallbackStrategyHostApi.create', codec,
diff --git a/packages/camera/camera_android_camerax/lib/src/fallback_strategy.dart b/packages/camera/camera_android_camerax/lib/src/fallback_strategy.dart
index ee098fa..ad2589d 100644
--- a/packages/camera/camera_android_camerax/lib/src/fallback_strategy.dart
+++ b/packages/camera/camera_android_camerax/lib/src/fallback_strategy.dart
@@ -40,7 +40,7 @@
   late final _FallbackStrategyHostApiImpl _api;
 
   /// The input quality used to specify this fallback strategy relative to.
-  final VideoQualityConstraint quality;
+  final VideoQuality quality;
 
   /// The fallback rule that this strategy will follow.
   final VideoResolutionFallbackRule fallbackRule;
@@ -72,9 +72,7 @@
 
   /// Creates a [FallbackStrategy] instance with the specified video [quality]
   /// and [fallbackRule].
-  void createFromInstance(
-      FallbackStrategy instance,
-      VideoQualityConstraint quality,
+  void createFromInstance(FallbackStrategy instance, VideoQuality quality,
       VideoResolutionFallbackRule fallbackRule) {
     final int identifier = instanceManager.addDartCreatedInstance(instance,
         onCopy: (FallbackStrategy original) {
diff --git a/packages/camera/camera_android_camerax/lib/src/quality_selector.dart b/packages/camera/camera_android_camerax/lib/src/quality_selector.dart
index 303d4ae..6daff7d 100644
--- a/packages/camera/camera_android_camerax/lib/src/quality_selector.dart
+++ b/packages/camera/camera_android_camerax/lib/src/quality_selector.dart
@@ -22,9 +22,9 @@
   QualitySelector.from(
       {BinaryMessenger? binaryMessenger,
       InstanceManager? instanceManager,
-      required VideoQualityConstraint quality,
+      required VideoQualityData quality,
       this.fallbackStrategy})
-      : qualityList = <VideoQualityConstraint>[quality],
+      : qualityList = <VideoQualityData>[quality],
         super.detached(
             binaryMessenger: binaryMessenger,
             instanceManager: instanceManager) {
@@ -65,7 +65,7 @@
   late final _QualitySelectorHostApiImpl _api;
 
   /// Desired qualities for this selector instance.
-  final List<VideoQualityConstraint> qualityList;
+  final List<VideoQualityData> qualityList;
 
   /// Desired fallback strategy for this selector instance.
   final FallbackStrategy? fallbackStrategy;
@@ -73,7 +73,7 @@
   /// Retrieves the corresponding resolution from the input [quality] for the
   /// camera represented by [cameraInfo].
   static Future<ResolutionInfo> getResolution(
-      CameraInfo cameraInfo, VideoQualityConstraint quality,
+      CameraInfo cameraInfo, VideoQuality quality,
       {BinaryMessenger? binaryMessenger, InstanceManager? instanceManager}) {
     final _QualitySelectorHostApiImpl api = _QualitySelectorHostApiImpl(
         binaryMessenger: binaryMessenger, instanceManager: instanceManager);
@@ -107,10 +107,8 @@
 
   /// Creates a [QualitySelector] instance with the desired qualities and
   /// fallback strategy specified.
-  void createFromInstance(
-      QualitySelector instance,
-      List<VideoQualityConstraint> qualityList,
-      FallbackStrategy? fallbackStrategy) {
+  void createFromInstance(QualitySelector instance,
+      List<VideoQualityData> qualityList, FallbackStrategy? fallbackStrategy) {
     final int identifier = instanceManager.addDartCreatedInstance(instance,
         onCopy: (QualitySelector original) {
       return QualitySelector.detached(
@@ -120,13 +118,10 @@
         fallbackStrategy: original.fallbackStrategy,
       );
     });
-    final List<int> qualityIndices = qualityList
-        .map<int>((VideoQualityConstraint quality) => quality.index)
-        .toList();
 
     create(
         identifier,
-        qualityIndices,
+        qualityList,
         fallbackStrategy == null
             ? null
             : instanceManager.getIdentifier(fallbackStrategy));
@@ -135,7 +130,7 @@
   /// Retrieves the corresponding resolution from the input [quality] for the
   /// camera represented by [cameraInfo].
   Future<ResolutionInfo> getResolutionFromInstance(
-      CameraInfo cameraInfo, VideoQualityConstraint quality) async {
+      CameraInfo cameraInfo, VideoQuality quality) async {
     final int? cameraInfoIdentifier = instanceManager.getIdentifier(cameraInfo);
 
     if (cameraInfoIdentifier == null) {
diff --git a/packages/camera/camera_android_camerax/lib/src/recorder.dart b/packages/camera/camera_android_camerax/lib/src/recorder.dart
index 1395362..1fd93ec 100644
--- a/packages/camera/camera_android_camerax/lib/src/recorder.dart
+++ b/packages/camera/camera_android_camerax/lib/src/recorder.dart
@@ -59,13 +59,13 @@
     return QualitySelector.fromOrderedList(
       binaryMessenger: binaryMessenger,
       instanceManager: instanceManager,
-      qualityList: const <VideoQualityConstraint>[
-        VideoQualityConstraint.FHD,
-        VideoQualityConstraint.HD,
-        VideoQualityConstraint.SD
+      qualityList: <VideoQualityData>[
+        VideoQualityData(quality: VideoQuality.FHD),
+        VideoQualityData(quality: VideoQuality.HD),
+        VideoQualityData(quality: VideoQuality.SD),
       ],
       fallbackStrategy: FallbackStrategy(
-          quality: VideoQualityConstraint.FHD,
+          quality: VideoQuality.FHD,
           fallbackRule: VideoResolutionFallbackRule.higherQualityOrLowerThan),
     );
   }
diff --git a/packages/camera/camera_android_camerax/pigeons/camerax_library.dart b/packages/camera/camera_android_camerax/pigeons/camerax_library.dart
index 02ea78c..9ebf34b 100644
--- a/packages/camera/camera_android_camerax/pigeons/camerax_library.dart
+++ b/packages/camera/camera_android_camerax/pigeons/camerax_library.dart
@@ -102,7 +102,7 @@
 /// These are pre-defined quality constants that are universally used for video.
 ///
 /// See https://developer.android.com/reference/androidx/camera/video/Quality.
-enum VideoQualityConstraint {
+enum VideoQuality {
   SD, // 480p
   HD, // 720p
   FHD, // 1080p
@@ -111,6 +111,11 @@
   highest,
 }
 
+/// Convenience class for sending lists of [Quality]s.
+class VideoQualityData {
+  late VideoQuality quality;
+}
+
 /// Fallback rules for selecting video resolution.
 ///
 /// See https://developer.android.com/reference/androidx/camera/video/FallbackStrategy.
@@ -401,17 +406,14 @@
 
 @HostApi(dartHostTestHandler: 'TestQualitySelectorHostApi')
 abstract class QualitySelectorHostApi {
-  // TODO(camsim99): Change qualityList to List<VideoQualityConstraint> when
-  // enums are supported for collection types.
-  void create(int identifier, List<int> videoQualityConstraintIndexList,
+  void create(int identifier, List<VideoQualityData> videoQualityDataList,
       int? fallbackStrategyId);
 
-  ResolutionInfo getResolution(
-      int cameraInfoId, VideoQualityConstraint quality);
+  ResolutionInfo getResolution(int cameraInfoId, VideoQuality quality);
 }
 
 @HostApi(dartHostTestHandler: 'TestFallbackStrategyHostApi')
 abstract class FallbackStrategyHostApi {
-  void create(int identifier, VideoQualityConstraint quality,
+  void create(int identifier, VideoQuality quality,
       VideoResolutionFallbackRule fallbackRule);
 }
diff --git a/packages/camera/camera_android_camerax/pubspec.yaml b/packages/camera/camera_android_camerax/pubspec.yaml
index 0e57c52..54c895b 100644
--- a/packages/camera/camera_android_camerax/pubspec.yaml
+++ b/packages/camera/camera_android_camerax/pubspec.yaml
@@ -2,7 +2,7 @@
 description: Android implementation of the camera plugin using the CameraX library.
 repository: https://github.com/flutter/packages/tree/main/packages/camera/camera_android_camerax
 issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22
-version: 0.5.0+16
+version: 0.5.0+17
 
 environment:
   sdk: ">=2.19.0 <4.0.0"
diff --git a/packages/camera/camera_android_camerax/test/android_camera_camerax_test.dart b/packages/camera/camera_android_camerax/test/android_camera_camerax_test.dart
index a0e60b6..bece928 100644
--- a/packages/camera/camera_android_camerax/test/android_camera_camerax_test.dart
+++ b/packages/camera/camera_android_camerax/test/android_camera_camerax_test.dart
@@ -14,6 +14,7 @@
 import 'package:camera_android_camerax/src/camera_state_error.dart';
 import 'package:camera_android_camerax/src/camerax_library.g.dart';
 import 'package:camera_android_camerax/src/exposure_state.dart';
+import 'package:camera_android_camerax/src/fallback_strategy.dart';
 import 'package:camera_android_camerax/src/image_analysis.dart';
 import 'package:camera_android_camerax/src/image_capture.dart';
 import 'package:camera_android_camerax/src/image_proxy.dart';
@@ -23,8 +24,11 @@
 import 'package:camera_android_camerax/src/plane_proxy.dart';
 import 'package:camera_android_camerax/src/preview.dart';
 import 'package:camera_android_camerax/src/process_camera_provider.dart';
+import 'package:camera_android_camerax/src/quality_selector.dart';
 import 'package:camera_android_camerax/src/recorder.dart';
 import 'package:camera_android_camerax/src/recording.dart';
+import 'package:camera_android_camerax/src/resolution_selector.dart';
+import 'package:camera_android_camerax/src/resolution_strategy.dart';
 import 'package:camera_android_camerax/src/system_services.dart';
 import 'package:camera_android_camerax/src/use_case.dart';
 import 'package:camera_android_camerax/src/video_capture.dart';
@@ -181,9 +185,11 @@
     when(camera.testPreview.setSurfaceProvider())
         .thenAnswer((_) async => testSurfaceTextureId);
     when(mockProcessCameraProvider.bindToLifecycle(
-            camera.mockBackCameraSelector,
-            <UseCase>[camera.testPreview, camera.testImageCapture]))
-        .thenAnswer((_) async => mockCamera);
+        camera.mockBackCameraSelector, <UseCase>[
+      camera.testPreview,
+      camera.testImageCapture,
+      camera.testImageAnalysis
+    ])).thenAnswer((_) async => mockCamera);
     when(mockCamera.getCameraInfo()).thenAnswer((_) async => mockCameraInfo);
     when(mockCameraInfo.getCameraState())
         .thenAnswer((_) async => mockLiveCameraState);
@@ -245,9 +251,11 @@
     camera.processCameraProvider = mockProcessCameraProvider;
 
     when(mockProcessCameraProvider.bindToLifecycle(
-            camera.mockBackCameraSelector,
-            <UseCase>[camera.testPreview, camera.testImageCapture]))
-        .thenAnswer((_) async => mockCamera);
+        camera.mockBackCameraSelector, <UseCase>[
+      camera.testPreview,
+      camera.testImageCapture,
+      camera.testImageAnalysis
+    ])).thenAnswer((_) async => mockCamera);
     when(mockCamera.getCameraInfo()).thenAnswer((_) async => mockCameraInfo);
     when(mockCameraInfo.getCameraState())
         .thenAnswer((_) async => MockLiveCameraState());
@@ -257,14 +265,191 @@
         enableAudio: enableAudio);
 
     // Verify expected UseCases were bound.
-    verify(camera.processCameraProvider!.bindToLifecycle(camera.cameraSelector!,
-        <UseCase>[camera.testPreview, camera.testImageCapture]));
+    verify(camera.processCameraProvider!.bindToLifecycle(
+        camera.cameraSelector!, <UseCase>[
+      camera.testPreview,
+      camera.testImageCapture,
+      camera.testImageAnalysis
+    ]));
 
     // Verify the camera's CameraInfo instance got updated.
     expect(camera.cameraInfo, equals(mockCameraInfo));
   });
 
   test(
+      'createCamera properly sets preset resolution for non-video capture use cases',
+      () async {
+    final FakeAndroidCameraCameraX camera =
+        FakeAndroidCameraCameraX(shouldCreateDetachedObjectForTesting: true);
+    final MockProcessCameraProvider mockProcessCameraProvider =
+        MockProcessCameraProvider();
+    const CameraLensDirection testLensDirection = CameraLensDirection.back;
+    const int testSensorOrientation = 90;
+    const CameraDescription testCameraDescription = CameraDescription(
+        name: 'cameraName',
+        lensDirection: testLensDirection,
+        sensorOrientation: testSensorOrientation);
+    const bool enableAudio = true;
+    final MockCamera mockCamera = MockCamera();
+    final MockCameraInfo mockCameraInfo = MockCameraInfo();
+
+    camera.processCameraProvider = mockProcessCameraProvider;
+
+    when(mockProcessCameraProvider.bindToLifecycle(
+        camera.mockBackCameraSelector, <UseCase>[
+      camera.testPreview,
+      camera.testImageCapture,
+      camera.testImageAnalysis
+    ])).thenAnswer((_) async => mockCamera);
+    when(mockCamera.getCameraInfo()).thenAnswer((_) async => mockCameraInfo);
+    when(mockCameraInfo.getCameraState())
+        .thenAnswer((_) async => MockLiveCameraState());
+    camera.processCameraProvider = mockProcessCameraProvider;
+
+    // Test non-null resolution presets.
+    for (final ResolutionPreset resolutionPreset in ResolutionPreset.values) {
+      await camera.createCamera(testCameraDescription, resolutionPreset,
+          enableAudio: enableAudio);
+
+      Size? expectedBoundSize;
+      ResolutionStrategy? expectedResolutionStrategy;
+      switch (resolutionPreset) {
+        case ResolutionPreset.low:
+          expectedBoundSize = const Size(320, 240);
+          break;
+        case ResolutionPreset.medium:
+          expectedBoundSize = const Size(720, 480);
+          break;
+        case ResolutionPreset.high:
+          expectedBoundSize = const Size(1280, 720);
+          break;
+        case ResolutionPreset.veryHigh:
+          expectedBoundSize = const Size(1920, 1080);
+          break;
+        case ResolutionPreset.ultraHigh:
+          expectedBoundSize = const Size(3840, 2160);
+          break;
+        case ResolutionPreset.max:
+          expectedResolutionStrategy =
+              ResolutionStrategy.detachedHighestAvailableStrategy();
+          break;
+      }
+
+      // We expect the strategy to be the highest available or correspond to the
+      // expected bound size, with fallback to the closest and highest available
+      // resolution.
+      expectedResolutionStrategy ??= ResolutionStrategy.detached(
+          boundSize: expectedBoundSize,
+          fallbackRule: ResolutionStrategy.fallbackRuleClosestLower);
+
+      expect(camera.preview!.resolutionSelector!.resolutionStrategy!.boundSize,
+          equals(expectedResolutionStrategy.boundSize));
+      expect(
+          camera
+              .imageCapture!.resolutionSelector!.resolutionStrategy!.boundSize,
+          equals(expectedResolutionStrategy.boundSize));
+      expect(
+          camera
+              .imageAnalysis!.resolutionSelector!.resolutionStrategy!.boundSize,
+          equals(expectedResolutionStrategy.boundSize));
+      expect(
+          camera.preview!.resolutionSelector!.resolutionStrategy!.fallbackRule,
+          equals(expectedResolutionStrategy.fallbackRule));
+      expect(
+          camera.imageCapture!.resolutionSelector!.resolutionStrategy!
+              .fallbackRule,
+          equals(expectedResolutionStrategy.fallbackRule));
+      expect(
+          camera.imageAnalysis!.resolutionSelector!.resolutionStrategy!
+              .fallbackRule,
+          equals(expectedResolutionStrategy.fallbackRule));
+    }
+
+    // Test null case.
+    await camera.createCamera(testCameraDescription, null);
+    expect(camera.preview!.resolutionSelector, isNull);
+    expect(camera.imageCapture!.resolutionSelector, isNull);
+    expect(camera.imageAnalysis!.resolutionSelector, isNull);
+  });
+
+  test(
+      'createCamera properly sets preset resolution for video capture use case',
+      () async {
+    final FakeAndroidCameraCameraX camera =
+        FakeAndroidCameraCameraX(shouldCreateDetachedObjectForTesting: true);
+    final MockProcessCameraProvider mockProcessCameraProvider =
+        MockProcessCameraProvider();
+    const CameraLensDirection testLensDirection = CameraLensDirection.back;
+    const int testSensorOrientation = 90;
+    const CameraDescription testCameraDescription = CameraDescription(
+        name: 'cameraName',
+        lensDirection: testLensDirection,
+        sensorOrientation: testSensorOrientation);
+    const bool enableAudio = true;
+    final MockCamera mockCamera = MockCamera();
+    final MockCameraInfo mockCameraInfo = MockCameraInfo();
+
+    camera.processCameraProvider = mockProcessCameraProvider;
+
+    when(mockProcessCameraProvider.bindToLifecycle(
+        camera.mockBackCameraSelector, <UseCase>[
+      camera.testPreview,
+      camera.testImageCapture,
+      camera.testImageAnalysis
+    ])).thenAnswer((_) async => mockCamera);
+    when(mockCamera.getCameraInfo()).thenAnswer((_) async => mockCameraInfo);
+    when(mockCameraInfo.getCameraState())
+        .thenAnswer((_) async => MockLiveCameraState());
+    camera.processCameraProvider = mockProcessCameraProvider;
+
+    // Test non-null resolution presets.
+    for (final ResolutionPreset resolutionPreset in ResolutionPreset.values) {
+      await camera.createCamera(testCameraDescription, resolutionPreset,
+          enableAudio: enableAudio);
+
+      VideoQuality? expectedVideoQuality;
+      switch (resolutionPreset) {
+        case ResolutionPreset.low:
+        // 240p is not supported by CameraX.
+        case ResolutionPreset.medium:
+          expectedVideoQuality = VideoQuality.SD;
+          break;
+        case ResolutionPreset.high:
+          expectedVideoQuality = VideoQuality.HD;
+          break;
+        case ResolutionPreset.veryHigh:
+          expectedVideoQuality = VideoQuality.FHD;
+          break;
+        case ResolutionPreset.ultraHigh:
+          expectedVideoQuality = VideoQuality.UHD;
+          break;
+        case ResolutionPreset.max:
+          expectedVideoQuality = VideoQuality.highest;
+          break;
+      }
+
+      const VideoResolutionFallbackRule expectedFallbackRule =
+          VideoResolutionFallbackRule.lowerQualityThan;
+      final FallbackStrategy expectedFallbackStrategy =
+          FallbackStrategy.detached(
+              quality: expectedVideoQuality,
+              fallbackRule: expectedFallbackRule);
+
+      expect(camera.recorder!.qualitySelector!.qualityList.length, equals(1));
+      expect(camera.recorder!.qualitySelector!.qualityList.first.quality,
+          equals(expectedVideoQuality));
+      expect(camera.recorder!.qualitySelector!.fallbackStrategy!.quality,
+          equals(expectedFallbackStrategy.quality));
+      expect(camera.recorder!.qualitySelector!.fallbackStrategy!.fallbackRule,
+          equals(expectedFallbackStrategy.fallbackRule));
+    }
+
+    // Test null case.
+    await camera.createCamera(testCameraDescription, null);
+    expect(camera.recorder!.qualitySelector, isNull);
+  });
+
+  test(
       'initializeCamera throws a CameraException when createCamera has not been called before initializedCamera',
       () async {
     final AndroidCameraCameraX camera = AndroidCameraCameraX();
@@ -313,9 +498,11 @@
         .thenAnswer((_) async => cameraId);
 
     when(camera.processCameraProvider!.bindToLifecycle(
-            camera.mockBackCameraSelector,
-            <UseCase>[camera.testPreview, camera.testImageCapture]))
-        .thenAnswer((_) async => mockCamera);
+        camera.mockBackCameraSelector, <UseCase>[
+      camera.testPreview,
+      camera.testImageCapture,
+      camera.testImageAnalysis
+    ])).thenAnswer((_) async => mockCamera);
     when(mockCamera.getCameraInfo()).thenAnswer((_) async => mockCameraInfo);
     when(mockCameraInfo.getCameraState())
         .thenAnswer((_) async => MockLiveCameraState());
@@ -917,15 +1104,19 @@
     final MockProcessCameraProvider mockProcessCameraProvider =
         MockProcessCameraProvider();
     final MockCamera mockCamera = MockCamera();
+    final MockCameraInfo mockCameraInfo = MockCameraInfo();
     const int cameraId = 22;
 
     camera.processCameraProvider = mockProcessCameraProvider;
     camera.cameraSelector = MockCameraSelector();
+    camera.imageAnalysis = MockImageAnalysis();
 
     when(mockProcessCameraProvider.bindToLifecycle(any, any))
         .thenAnswer((_) => Future<Camera>.value(mockCamera));
     when(mockCamera.getCameraInfo())
-        .thenAnswer((_) => Future<CameraInfo>.value(MockCameraInfo()));
+        .thenAnswer((_) => Future<CameraInfo>.value(mockCameraInfo));
+    when(mockCameraInfo.getCameraState())
+        .thenAnswer((_) async => MockLiveCameraState());
 
     final CameraImageData mockCameraImageData = MockCameraImageData();
     final Stream<CameraImageData> imageStream =
@@ -947,15 +1138,19 @@
     final MockProcessCameraProvider mockProcessCameraProvider =
         MockProcessCameraProvider();
     final MockCamera mockCamera = MockCamera();
+    final MockCameraInfo mockCameraInfo = MockCameraInfo();
     const int cameraId = 22;
 
     camera.processCameraProvider = mockProcessCameraProvider;
     camera.cameraSelector = MockCameraSelector();
+    camera.imageAnalysis = MockImageAnalysis();
 
     when(mockProcessCameraProvider.bindToLifecycle(any, any))
         .thenAnswer((_) => Future<Camera>.value(mockCamera));
     when(mockCamera.getCameraInfo())
-        .thenAnswer((_) => Future<CameraInfo>.value(MockCameraInfo()));
+        .thenAnswer((_) => Future<CameraInfo>.value(mockCameraInfo));
+    when(mockCameraInfo.getCameraState())
+        .thenAnswer((_) async => MockLiveCameraState());
 
     final CameraImageData mockCameraImageData = MockCameraImageData();
     final Stream<CameraImageData> imageStream =
@@ -988,6 +1183,7 @@
     final ProcessCameraProvider mockProcessCameraProvider =
         MockProcessCameraProvider();
     final CameraSelector mockCameraSelector = MockCameraSelector();
+    final MockImageAnalysis mockImageAnalysis = MockImageAnalysis();
     final Camera mockCamera = MockCamera();
     final CameraInfo mockCameraInfo = MockCameraInfo();
     final MockImageProxy mockImageProxy = MockImageProxy();
@@ -1002,13 +1198,16 @@
 
     camera.processCameraProvider = mockProcessCameraProvider;
     camera.cameraSelector = mockCameraSelector;
+    camera.imageAnalysis = mockImageAnalysis;
 
-    when(mockProcessCameraProvider.isBound(camera.mockImageAnalysis))
+    when(mockProcessCameraProvider.isBound(mockImageAnalysis))
         .thenAnswer((_) async => Future<bool>.value(false));
-    when(mockProcessCameraProvider.bindToLifecycle(
-            mockCameraSelector, <UseCase>[camera.mockImageAnalysis]))
+    when(mockProcessCameraProvider
+            .bindToLifecycle(mockCameraSelector, <UseCase>[mockImageAnalysis]))
         .thenAnswer((_) async => mockCamera);
     when(mockCamera.getCameraInfo()).thenAnswer((_) async => mockCameraInfo);
+    when(mockCameraInfo.getCameraState())
+        .thenAnswer((_) async => MockLiveCameraState());
     when(mockImageProxy.getPlanes())
         .thenAnswer((_) => Future<List<PlaneProxy>>.value(mockPlanes));
     when(mockPlane.buffer).thenReturn(buffer);
@@ -1029,12 +1228,8 @@
 
     // Test ImageAnalysis use case is bound to ProcessCameraProvider.
     final Analyzer capturedAnalyzer =
-        verify(camera.mockImageAnalysis.setAnalyzer(captureAny)).captured.single
+        verify(mockImageAnalysis.setAnalyzer(captureAny)).captured.single
             as Analyzer;
-    await untilCalled(
-        mockProcessCameraProvider.isBound(camera.mockImageAnalysis));
-    await untilCalled(mockProcessCameraProvider.bindToLifecycle(
-        mockCameraSelector, <UseCase>[camera.mockImageAnalysis]));
 
     await capturedAnalyzer.analyze(mockImageProxy);
     final CameraImageData imageData = await imageDataCompleter.future;
@@ -1048,9 +1243,6 @@
     expect(imageData.height, equals(imageHeight));
     expect(imageData.width, equals(imageWidth));
 
-    // Verify camera and cameraInfo were properly updated.
-    expect(camera.camera, equals(mockCamera));
-    expect(camera.cameraInfo, equals(mockCameraInfo));
     await onStreamedFrameAvailableSubscription.cancel();
   });
 
@@ -1063,26 +1255,19 @@
     final ProcessCameraProvider mockProcessCameraProvider =
         MockProcessCameraProvider();
     final CameraSelector mockCameraSelector = MockCameraSelector();
-    final Camera mockCamera = MockCamera();
+    final MockImageAnalysis mockImageAnalysis = MockImageAnalysis();
 
     camera.processCameraProvider = mockProcessCameraProvider;
     camera.cameraSelector = mockCameraSelector;
-
-    when(mockProcessCameraProvider.bindToLifecycle(
-            mockCameraSelector, <UseCase>[camera.mockImageAnalysis]))
-        .thenAnswer((_) async => mockCamera);
-    when(mockCamera.getCameraInfo()).thenAnswer((_) async => MockCameraInfo());
+    camera.imageAnalysis = mockImageAnalysis;
 
     final StreamSubscription<CameraImageData> imageStreamSubscription = camera
         .onStreamedFrameAvailable(cameraId)
         .listen((CameraImageData data) {});
 
-    when(mockProcessCameraProvider.isBound(camera.mockImageAnalysis))
-        .thenAnswer((_) async => Future<bool>.value(true));
-
     await imageStreamSubscription.cancel();
 
-    verify(camera.mockImageAnalysis.clearAnalyzer());
+    verify(mockImageAnalysis.clearAnalyzer());
   });
 }
 
@@ -1101,7 +1286,7 @@
   final MockCameraSelector mockFrontCameraSelector = MockCameraSelector();
   final MockRecorder testRecorder = MockRecorder();
   final MockVideoCapture testVideoCapture = MockVideoCapture();
-  final MockImageAnalysis mockImageAnalysis = MockImageAnalysis();
+  final MockImageAnalysis testImageAnalysis = MockImageAnalysis();
 
   @override
   Future<void> requestCameraPermissions(bool enableAudio) async {
@@ -1127,17 +1312,21 @@
   }
 
   @override
-  Preview createPreview(int targetRotation) {
+  Preview createPreview(
+      {required int targetRotation, ResolutionSelector? resolutionSelector}) {
+    when(testPreview.resolutionSelector).thenReturn(resolutionSelector);
     return testPreview;
   }
 
   @override
-  ImageCapture createImageCapture(int? flashMode) {
+  ImageCapture createImageCapture(ResolutionSelector? resolutionSelector) {
+    when(testImageCapture.resolutionSelector).thenReturn(resolutionSelector);
     return testImageCapture;
   }
 
   @override
-  Recorder createRecorder() {
+  Recorder createRecorder(QualitySelector? qualitySelector) {
+    when(testRecorder.qualitySelector).thenReturn(qualitySelector);
     return testRecorder;
   }
 
@@ -1147,7 +1336,8 @@
   }
 
   @override
-  ImageAnalysis createImageAnalysis() {
-    return mockImageAnalysis;
+  ImageAnalysis createImageAnalysis(ResolutionSelector? resolutionSelector) {
+    when(testImageAnalysis.resolutionSelector).thenReturn(resolutionSelector);
+    return testImageAnalysis;
   }
 }
diff --git a/packages/camera/camera_android_camerax/test/fallback_strategy_test.dart b/packages/camera/camera_android_camerax/test/fallback_strategy_test.dart
index 8c6960c..f54e40d 100644
--- a/packages/camera/camera_android_camerax/test/fallback_strategy_test.dart
+++ b/packages/camera/camera_android_camerax/test/fallback_strategy_test.dart
@@ -33,14 +33,14 @@
       );
 
       FallbackStrategy.detached(
-        quality: VideoQualityConstraint.UHD,
+        quality: VideoQuality.UHD,
         fallbackRule: VideoResolutionFallbackRule.higherQualityThan,
         instanceManager: instanceManager,
       );
 
       verifyNever(mockApi.create(
         argThat(isA<int>()),
-        argThat(isA<VideoQualityConstraint>()),
+        argThat(isA<VideoQuality>()),
         argThat(isA<VideoResolutionFallbackRule>()),
       ));
     });
@@ -55,7 +55,7 @@
         onWeakReferenceRemoved: (_) {},
       );
 
-      const VideoQualityConstraint quality = VideoQualityConstraint.HD;
+      const VideoQuality quality = VideoQuality.HD;
 
       const VideoResolutionFallbackRule fallbackRule =
           VideoResolutionFallbackRule.lowerQualityThan;
diff --git a/packages/camera/camera_android_camerax/test/fallback_strategy_test.mocks.dart b/packages/camera/camera_android_camerax/test/fallback_strategy_test.mocks.dart
index 02d185a..8ba811c 100644
--- a/packages/camera/camera_android_camerax/test/fallback_strategy_test.mocks.dart
+++ b/packages/camera/camera_android_camerax/test/fallback_strategy_test.mocks.dart
@@ -33,7 +33,7 @@
   @override
   void create(
     int? identifier,
-    _i3.VideoQualityConstraint? quality,
+    _i3.VideoQuality? quality,
     _i3.VideoResolutionFallbackRule? fallbackRule,
   ) =>
       super.noSuchMethod(
diff --git a/packages/camera/camera_android_camerax/test/quality_selector_test.dart b/packages/camera/camera_android_camerax/test/quality_selector_test.dart
index 8c05aa7..bfaa568 100644
--- a/packages/camera/camera_android_camerax/test/quality_selector_test.dart
+++ b/packages/camera/camera_android_camerax/test/quality_selector_test.dart
@@ -41,7 +41,9 @@
       );
 
       QualitySelector.detached(
-        qualityList: const <VideoQualityConstraint>[VideoQualityConstraint.FHD],
+        qualityList: <VideoQualityData>[
+          VideoQualityData(quality: VideoQuality.UHD)
+        ],
         fallbackStrategy: MockFallbackStrategy(),
         instanceManager: instanceManager,
       );
@@ -60,7 +62,8 @@
         onWeakReferenceRemoved: (_) {},
       );
 
-      const VideoQualityConstraint quality = VideoQualityConstraint.FHD;
+      const VideoQuality videoQuality = VideoQuality.FHD;
+      final VideoQualityData quality = VideoQualityData(quality: videoQuality);
       final FallbackStrategy fallbackStrategy = MockFallbackStrategy();
       const int fallbackStrategyIdentifier = 9;
 
@@ -76,11 +79,15 @@
         instanceManager: instanceManager,
       );
 
-      verify(mockApi.create(
+      final VerificationResult verificationResult = verify(mockApi.create(
         instanceManager.getIdentifier(instance),
-        <int>[2],
+        captureAny,
         fallbackStrategyIdentifier,
       ));
+      final List<VideoQualityData?> videoQualityData =
+          verificationResult.captured.single as List<VideoQualityData?>;
+      expect(videoQualityData.length, equals(1));
+      expect(videoQualityData.first!.quality, equals(videoQuality));
     });
 
     test('quality list constructor calls create on the Java side', () {
@@ -93,9 +100,9 @@
         onWeakReferenceRemoved: (_) {},
       );
 
-      const List<VideoQualityConstraint> qualityList = <VideoQualityConstraint>[
-        VideoQualityConstraint.FHD,
-        VideoQualityConstraint.highest
+      final List<VideoQualityData> qualityList = <VideoQualityData>[
+        VideoQualityData(quality: VideoQuality.FHD),
+        VideoQualityData(quality: VideoQuality.highest),
       ];
 
       final FallbackStrategy fallbackStrategy = MockFallbackStrategy();
@@ -113,11 +120,16 @@
         instanceManager: instanceManager,
       );
 
-      verify(mockApi.create(
+      final VerificationResult verificationResult = verify(mockApi.create(
         instanceManager.getIdentifier(instance),
-        <int>[2, 5],
+        captureAny,
         fallbackStrategyIdentifier,
       ));
+      final List<VideoQualityData?> videoQualityData =
+          verificationResult.captured.single as List<VideoQualityData?>;
+      expect(videoQualityData.length, equals(2));
+      expect(videoQualityData.first!.quality, equals(VideoQuality.FHD));
+      expect(videoQualityData.last!.quality, equals(VideoQuality.highest));
     });
 
     test('getResolution returns expected resolution info', () async {
@@ -131,7 +143,9 @@
 
       final QualitySelector instance = QualitySelector.detached(
         instanceManager: instanceManager,
-        qualityList: const <VideoQualityConstraint>[VideoQualityConstraint.HD],
+        qualityList: <VideoQualityData>[
+          VideoQualityData(quality: VideoQuality.HD)
+        ],
         fallbackStrategy: MockFallbackStrategy(),
       );
       const int instanceIdentifier = 0;
@@ -153,7 +167,7 @@
         onCopy: (_) => MockCameraInfo(),
       );
 
-      const VideoQualityConstraint quality = VideoQualityConstraint.FHD;
+      const VideoQuality quality = VideoQuality.FHD;
       final ResolutionInfo expectedResult =
           ResolutionInfo(width: 34, height: 23);
 
diff --git a/packages/camera/camera_android_camerax/test/quality_selector_test.mocks.dart b/packages/camera/camera_android_camerax/test/quality_selector_test.mocks.dart
index ba08711..65741af 100644
--- a/packages/camera/camera_android_camerax/test/quality_selector_test.mocks.dart
+++ b/packages/camera/camera_android_camerax/test/quality_selector_test.mocks.dart
@@ -135,10 +135,10 @@
   }
 
   @override
-  _i4.VideoQualityConstraint get quality => (super.noSuchMethod(
+  _i4.VideoQuality get quality => (super.noSuchMethod(
         Invocation.getter(#quality),
-        returnValue: _i4.VideoQualityConstraint.SD,
-      ) as _i4.VideoQualityConstraint);
+        returnValue: _i4.VideoQuality.SD,
+      ) as _i4.VideoQuality);
   @override
   _i4.VideoResolutionFallbackRule get fallbackRule => (super.noSuchMethod(
         Invocation.getter(#fallbackRule),
@@ -158,7 +158,7 @@
   @override
   void create(
     int? identifier,
-    List<int?>? videoQualityConstraintIndexList,
+    List<_i4.VideoQualityData?>? videoQualityDataList,
     int? fallbackStrategyId,
   ) =>
       super.noSuchMethod(
@@ -166,7 +166,7 @@
           #create,
           [
             identifier,
-            videoQualityConstraintIndexList,
+            videoQualityDataList,
             fallbackStrategyId,
           ],
         ),
@@ -175,7 +175,7 @@
   @override
   _i4.ResolutionInfo getResolution(
     int? cameraInfoId,
-    _i4.VideoQualityConstraint? quality,
+    _i4.VideoQuality? quality,
   ) =>
       (super.noSuchMethod(
         Invocation.method(
diff --git a/packages/camera/camera_android_camerax/test/recorder_test.dart b/packages/camera/camera_android_camerax/test/recorder_test.dart
index 994c0f9..c7b189f 100644
--- a/packages/camera/camera_android_camerax/test/recorder_test.dart
+++ b/packages/camera/camera_android_camerax/test/recorder_test.dart
@@ -84,16 +84,21 @@
 
       final QualitySelector defaultQualitySelector =
           Recorder.getDefaultQualitySelector();
+      final List<VideoQuality> expectedVideoQualities = <VideoQuality>[
+        VideoQuality.FHD,
+        VideoQuality.HD,
+        VideoQuality.SD
+      ];
 
-      expect(
-          defaultQualitySelector.qualityList,
-          equals(const <VideoQualityConstraint>[
-            VideoQualityConstraint.FHD,
-            VideoQualityConstraint.HD,
-            VideoQualityConstraint.SD
-          ]));
+      expect(defaultQualitySelector.qualityList.length, equals(3));
+      for (int i = 0; i < 3; i++) {
+        final VideoQuality currentVideoQuality =
+            defaultQualitySelector.qualityList[i].quality;
+        expect(currentVideoQuality, equals(expectedVideoQualities[i]));
+      }
+
       expect(defaultQualitySelector.fallbackStrategy!.quality,
-          equals(VideoQualityConstraint.FHD));
+          equals(VideoQuality.FHD));
       expect(defaultQualitySelector.fallbackStrategy!.fallbackRule,
           equals(VideoResolutionFallbackRule.higherQualityOrLowerThan));
 
diff --git a/packages/camera/camera_android_camerax/test/recorder_test.mocks.dart b/packages/camera/camera_android_camerax/test/recorder_test.mocks.dart
index c7850c7..7a678e2 100644
--- a/packages/camera/camera_android_camerax/test/recorder_test.mocks.dart
+++ b/packages/camera/camera_android_camerax/test/recorder_test.mocks.dart
@@ -57,10 +57,10 @@
   }
 
   @override
-  List<_i2.VideoQualityConstraint> get qualityList => (super.noSuchMethod(
+  List<_i2.VideoQualityData> get qualityList => (super.noSuchMethod(
         Invocation.getter(#qualityList),
-        returnValue: <_i2.VideoQualityConstraint>[],
-      ) as List<_i2.VideoQualityConstraint>);
+        returnValue: <_i2.VideoQualityData>[],
+      ) as List<_i2.VideoQualityData>);
 }
 
 /// A class which mocks [TestInstanceManagerHostApi].
@@ -94,7 +94,7 @@
   @override
   void create(
     int? identifier,
-    _i2.VideoQualityConstraint? quality,
+    _i2.VideoQuality? quality,
     _i2.VideoResolutionFallbackRule? fallbackRule,
   ) =>
       super.noSuchMethod(
@@ -183,7 +183,7 @@
   @override
   void create(
     int? identifier,
-    List<int?>? videoQualityConstraintIndexList,
+    List<_i2.VideoQualityData?>? videoQualityDataList,
     int? fallbackStrategyId,
   ) =>
       super.noSuchMethod(
@@ -191,7 +191,7 @@
           #create,
           [
             identifier,
-            videoQualityConstraintIndexList,
+            videoQualityDataList,
             fallbackStrategyId,
           ],
         ),
@@ -200,7 +200,7 @@
   @override
   _i2.ResolutionInfo getResolution(
     int? cameraInfoId,
-    _i2.VideoQualityConstraint? quality,
+    _i2.VideoQuality? quality,
   ) =>
       (super.noSuchMethod(
         Invocation.method(
diff --git a/packages/camera/camera_android_camerax/test/test_camerax_library.g.dart b/packages/camera/camera_android_camerax/test/test_camerax_library.g.dart
index d4c935e..c649a73 100644
--- a/packages/camera/camera_android_camerax/test/test_camerax_library.g.dart
+++ b/packages/camera/camera_android_camerax/test/test_camerax_library.g.dart
@@ -1587,6 +1587,9 @@
     if (value is ResolutionInfo) {
       buffer.putUint8(128);
       writeValue(buffer, value.encode());
+    } else if (value is VideoQualityData) {
+      buffer.putUint8(129);
+      writeValue(buffer, value.encode());
     } else {
       super.writeValue(buffer, value);
     }
@@ -1597,6 +1600,8 @@
     switch (type) {
       case 128:
         return ResolutionInfo.decode(readValue(buffer)!);
+      case 129:
+        return VideoQualityData.decode(readValue(buffer)!);
       default:
         return super.readValueOfType(type, buffer);
     }
@@ -1608,11 +1613,10 @@
       TestDefaultBinaryMessengerBinding.instance;
   static const MessageCodec<Object?> codec = _TestQualitySelectorHostApiCodec();
 
-  void create(int identifier, List<int?> videoQualityConstraintIndexList,
+  void create(int identifier, List<VideoQualityData?> videoQualityDataList,
       int? fallbackStrategyId);
 
-  ResolutionInfo getResolution(
-      int cameraInfoId, VideoQualityConstraint quality);
+  ResolutionInfo getResolution(int cameraInfoId, VideoQuality quality);
 
   static void setup(TestQualitySelectorHostApi? api,
       {BinaryMessenger? binaryMessenger}) {
@@ -1633,12 +1637,12 @@
           final int? arg_identifier = (args[0] as int?);
           assert(arg_identifier != null,
               'Argument for dev.flutter.pigeon.QualitySelectorHostApi.create was null, expected non-null int.');
-          final List<int?>? arg_videoQualityConstraintIndexList =
-              (args[1] as List<Object?>?)?.cast<int?>();
-          assert(arg_videoQualityConstraintIndexList != null,
-              'Argument for dev.flutter.pigeon.QualitySelectorHostApi.create was null, expected non-null List<int?>.');
+          final List<VideoQualityData?>? arg_videoQualityDataList =
+              (args[1] as List<Object?>?)?.cast<VideoQualityData?>();
+          assert(arg_videoQualityDataList != null,
+              'Argument for dev.flutter.pigeon.QualitySelectorHostApi.create was null, expected non-null List<VideoQualityData?>.');
           final int? arg_fallbackStrategyId = (args[2] as int?);
-          api.create(arg_identifier!, arg_videoQualityConstraintIndexList!,
+          api.create(arg_identifier!, arg_videoQualityDataList!,
               arg_fallbackStrategyId);
           return <Object?>[];
         });
@@ -1661,11 +1665,10 @@
           final int? arg_cameraInfoId = (args[0] as int?);
           assert(arg_cameraInfoId != null,
               'Argument for dev.flutter.pigeon.QualitySelectorHostApi.getResolution was null, expected non-null int.');
-          final VideoQualityConstraint? arg_quality = args[1] == null
-              ? null
-              : VideoQualityConstraint.values[args[1] as int];
+          final VideoQuality? arg_quality =
+              args[1] == null ? null : VideoQuality.values[args[1] as int];
           assert(arg_quality != null,
-              'Argument for dev.flutter.pigeon.QualitySelectorHostApi.getResolution was null, expected non-null VideoQualityConstraint.');
+              'Argument for dev.flutter.pigeon.QualitySelectorHostApi.getResolution was null, expected non-null VideoQuality.');
           final ResolutionInfo output =
               api.getResolution(arg_cameraInfoId!, arg_quality!);
           return <Object?>[output];
@@ -1680,7 +1683,7 @@
       TestDefaultBinaryMessengerBinding.instance;
   static const MessageCodec<Object?> codec = StandardMessageCodec();
 
-  void create(int identifier, VideoQualityConstraint quality,
+  void create(int identifier, VideoQuality quality,
       VideoResolutionFallbackRule fallbackRule);
 
   static void setup(TestFallbackStrategyHostApi? api,
@@ -1702,11 +1705,10 @@
           final int? arg_identifier = (args[0] as int?);
           assert(arg_identifier != null,
               'Argument for dev.flutter.pigeon.FallbackStrategyHostApi.create was null, expected non-null int.');
-          final VideoQualityConstraint? arg_quality = args[1] == null
-              ? null
-              : VideoQualityConstraint.values[args[1] as int];
+          final VideoQuality? arg_quality =
+              args[1] == null ? null : VideoQuality.values[args[1] as int];
           assert(arg_quality != null,
-              'Argument for dev.flutter.pigeon.FallbackStrategyHostApi.create was null, expected non-null VideoQualityConstraint.');
+              'Argument for dev.flutter.pigeon.FallbackStrategyHostApi.create was null, expected non-null VideoQuality.');
           final VideoResolutionFallbackRule? arg_fallbackRule = args[2] == null
               ? null
               : VideoResolutionFallbackRule.values[args[2] as int];