[camera_android] Default to legacy recording profile when EncoderProfiles unavailable (#7073)

* Make changes, start test

* Bump versions

* Add test

* Formatting

* Add issue

* Fix test

* Address review
diff --git a/packages/camera/camera_android/CHANGELOG.md b/packages/camera/camera_android/CHANGELOG.md
index 0cb9957..4609b40 100644
--- a/packages/camera/camera_android/CHANGELOG.md
+++ b/packages/camera/camera_android/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 0.10.4
+
+* Temporarily fixes issue with requested video profiles being null by falling back to deprecated behavior in that case.
+
 ## 0.10.3
 
 * Adds back use of Optional type.
diff --git a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/Camera.java b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/Camera.java
index 7c592b9..b02d686 100644
--- a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/Camera.java
+++ b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/Camera.java
@@ -258,8 +258,11 @@
 
     MediaRecorderBuilder mediaRecorderBuilder;
 
-    if (Build.VERSION.SDK_INT >= 31) {
-      mediaRecorderBuilder = new MediaRecorderBuilder(getRecordingProfile(), outputFilePath);
+    // TODO(camsim99): Revert changes that allow legacy code to be used when recordingProfile is null
+    // once this has largely been fixed on the Android side. https://github.com/flutter/flutter/issues/119668
+    EncoderProfiles recordingProfile = getRecordingProfile();
+    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && recordingProfile != null) {
+      mediaRecorderBuilder = new MediaRecorderBuilder(recordingProfile, outputFilePath);
     } else {
       mediaRecorderBuilder = new MediaRecorderBuilder(getRecordingProfileLegacy(), outputFilePath);
     }
diff --git a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/features/resolution/ResolutionFeature.java b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/features/resolution/ResolutionFeature.java
index afbd7c3..0ec2fbe 100644
--- a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/features/resolution/ResolutionFeature.java
+++ b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/features/resolution/ResolutionFeature.java
@@ -114,19 +114,23 @@
     if (preset.ordinal() > ResolutionPreset.high.ordinal()) {
       preset = ResolutionPreset.high;
     }
-    if (Build.VERSION.SDK_INT >= 31) {
+    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
       EncoderProfiles profile =
           getBestAvailableCamcorderProfileForResolutionPreset(cameraId, preset);
       List<EncoderProfiles.VideoProfile> videoProfiles = profile.getVideoProfiles();
       EncoderProfiles.VideoProfile defaultVideoProfile = videoProfiles.get(0);
 
-      return new Size(defaultVideoProfile.getWidth(), defaultVideoProfile.getHeight());
-    } else {
-      @SuppressWarnings("deprecation")
-      CamcorderProfile profile =
-          getBestAvailableCamcorderProfileForResolutionPresetLegacy(cameraId, preset);
-      return new Size(profile.videoFrameWidth, profile.videoFrameHeight);
+      if (defaultVideoProfile != null) {
+        return new Size(defaultVideoProfile.getWidth(), defaultVideoProfile.getHeight());
+      }
     }
+
+    @SuppressWarnings("deprecation")
+    // TODO(camsim99): Suppression is currently safe because legacy code is used as a fallback for SDK >= S.
+    // This should be removed when reverting that fallback behavior: https://github.com/flutter/flutter/issues/119668.
+    CamcorderProfile profile =
+        getBestAvailableCamcorderProfileForResolutionPresetLegacy(cameraId, preset);
+    return new Size(profile.videoFrameWidth, profile.videoFrameHeight);
   }
 
   /**
@@ -234,15 +238,24 @@
     if (!checkIsSupported()) {
       return;
     }
+    boolean captureSizeCalculated = false;
 
-    if (Build.VERSION.SDK_INT >= 31) {
+    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+      recordingProfileLegacy = null;
       recordingProfile =
           getBestAvailableCamcorderProfileForResolutionPreset(cameraId, resolutionPreset);
       List<EncoderProfiles.VideoProfile> videoProfiles = recordingProfile.getVideoProfiles();
 
       EncoderProfiles.VideoProfile defaultVideoProfile = videoProfiles.get(0);
-      captureSize = new Size(defaultVideoProfile.getWidth(), defaultVideoProfile.getHeight());
-    } else {
+
+      if (defaultVideoProfile != null) {
+        captureSizeCalculated = true;
+        captureSize = new Size(defaultVideoProfile.getWidth(), defaultVideoProfile.getHeight());
+      }
+    }
+
+    if (!captureSizeCalculated) {
+      recordingProfile = null;
       @SuppressWarnings("deprecation")
       CamcorderProfile camcorderProfile =
           getBestAvailableCamcorderProfileForResolutionPresetLegacy(cameraId, resolutionPreset);
diff --git a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/media/MediaRecorderBuilder.java b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/media/MediaRecorderBuilder.java
index 0aebfee..1f9f620 100644
--- a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/media/MediaRecorderBuilder.java
+++ b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/media/MediaRecorderBuilder.java
@@ -75,7 +75,7 @@
     if (enableAudio) mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
     mediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
 
-    if (Build.VERSION.SDK_INT >= 31) {
+    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && encoderProfiles != null) {
       EncoderProfiles.VideoProfile videoProfile = encoderProfiles.getVideoProfiles().get(0);
       EncoderProfiles.AudioProfile audioProfile = encoderProfiles.getAudioProfiles().get(0);
 
diff --git a/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/features/resolution/ResolutionFeatureTest.java b/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/features/resolution/ResolutionFeatureTest.java
index 957b57a..dbc352d 100644
--- a/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/features/resolution/ResolutionFeatureTest.java
+++ b/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/features/resolution/ResolutionFeatureTest.java
@@ -5,20 +5,27 @@
 package io.flutter.plugins.camera.features.resolution;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.mockStatic;
 import static org.mockito.Mockito.when;
 
 import android.media.CamcorderProfile;
 import android.media.EncoderProfiles;
+import android.util.Size;
 import io.flutter.plugins.camera.CameraProperties;
+import java.util.ArrayList;
 import java.util.List;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.MockedStatic;
+import org.mockito.stubbing.Answer;
 import org.robolectric.RobolectricTestRunner;
 import org.robolectric.annotation.Config;
 
@@ -329,4 +336,95 @@
 
     mockedStaticProfile.verify(() -> CamcorderProfile.getAll("1", CamcorderProfile.QUALITY_QVGA));
   }
+
+  @Config(minSdk = 31)
+  @Test
+  public void computeBestPreviewSize_shouldUseLegacyBehaviorWhenEncoderProfilesNull() {
+    try (MockedStatic<ResolutionFeature> mockedResolutionFeature =
+        mockStatic(ResolutionFeature.class)) {
+      mockedResolutionFeature
+          .when(
+              () ->
+                  ResolutionFeature.getBestAvailableCamcorderProfileForResolutionPreset(
+                      anyInt(), any(ResolutionPreset.class)))
+          .thenAnswer(
+              (Answer<EncoderProfiles>)
+                  invocation -> {
+                    EncoderProfiles mockEncoderProfiles = mock(EncoderProfiles.class);
+                    List<EncoderProfiles.VideoProfile> videoProfiles =
+                        new ArrayList<EncoderProfiles.VideoProfile>() {
+                          {
+                            add(null);
+                          }
+                        };
+                    when(mockEncoderProfiles.getVideoProfiles()).thenReturn(videoProfiles);
+                    return mockEncoderProfiles;
+                  });
+      mockedResolutionFeature
+          .when(
+              () ->
+                  ResolutionFeature.getBestAvailableCamcorderProfileForResolutionPresetLegacy(
+                      anyInt(), any(ResolutionPreset.class)))
+          .thenAnswer(
+              (Answer<CamcorderProfile>)
+                  invocation -> {
+                    CamcorderProfile mockCamcorderProfile = mock(CamcorderProfile.class);
+                    mockCamcorderProfile.videoFrameWidth = 10;
+                    mockCamcorderProfile.videoFrameHeight = 50;
+                    return mockCamcorderProfile;
+                  });
+      mockedResolutionFeature
+          .when(() -> ResolutionFeature.computeBestPreviewSize(1, ResolutionPreset.max))
+          .thenCallRealMethod();
+
+      Size testPreviewSize = ResolutionFeature.computeBestPreviewSize(1, ResolutionPreset.max);
+      assertEquals(testPreviewSize.getWidth(), 10);
+      assertEquals(testPreviewSize.getHeight(), 50);
+    }
+  }
+
+  @Config(minSdk = 31)
+  @Test
+  public void resolutionFeatureShouldUseLegacyBehaviorWhenEncoderProfilesNull() {
+    beforeLegacy();
+    try (MockedStatic<ResolutionFeature> mockedResolutionFeature =
+        mockStatic(ResolutionFeature.class)) {
+      mockedResolutionFeature
+          .when(
+              () ->
+                  ResolutionFeature.getBestAvailableCamcorderProfileForResolutionPreset(
+                      anyInt(), any(ResolutionPreset.class)))
+          .thenAnswer(
+              (Answer<EncoderProfiles>)
+                  invocation -> {
+                    EncoderProfiles mockEncoderProfiles = mock(EncoderProfiles.class);
+                    List<EncoderProfiles.VideoProfile> videoProfiles =
+                        new ArrayList<EncoderProfiles.VideoProfile>() {
+                          {
+                            add(null);
+                          }
+                        };
+                    when(mockEncoderProfiles.getVideoProfiles()).thenReturn(videoProfiles);
+                    return mockEncoderProfiles;
+                  });
+      mockedResolutionFeature
+          .when(
+              () ->
+                  ResolutionFeature.getBestAvailableCamcorderProfileForResolutionPresetLegacy(
+                      anyInt(), any(ResolutionPreset.class)))
+          .thenAnswer(
+              (Answer<CamcorderProfile>)
+                  invocation -> {
+                    CamcorderProfile mockCamcorderProfile = mock(CamcorderProfile.class);
+                    return mockCamcorderProfile;
+                  });
+
+      CameraProperties mockCameraProperties = mock(CameraProperties.class);
+      ResolutionFeature resolutionFeature =
+          new ResolutionFeature(mockCameraProperties, ResolutionPreset.max, cameraName);
+
+      assertNotNull(resolutionFeature.getRecordingProfileLegacy());
+      assertNull(resolutionFeature.getRecordingProfile());
+    }
+  }
 }
diff --git a/packages/camera/camera_android/pubspec.yaml b/packages/camera/camera_android/pubspec.yaml
index fed2d29..fb33719 100644
--- a/packages/camera/camera_android/pubspec.yaml
+++ b/packages/camera/camera_android/pubspec.yaml
@@ -2,7 +2,7 @@
 description: Android implementation of the camera plugin.
 repository: https://github.com/flutter/plugins/tree/main/packages/camera/camera_android
 issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22
-version: 0.10.3
+version: 0.10.4
 
 environment:
   sdk: ">=2.14.0 <3.0.0"