Create VideoValidatedEncoderProfilesProxy

Create an implementation of EncoderProfilesProxy in camera-video
that guarantees to provide video information. It is expected to be
used to replace the using of CamcorderProfileProxy in camera-video.

This CL also modify EncoderProfilesProxy into an interface and add
an implementation for compat.

Bug: 264514713
Test: manual test and ./gradlew bOS
Change-Id: Id89e9bc2d7d50f8d8515112a747a52b96b4b4562
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/impl/EncoderProfilesProxy.java b/camera/camera-core/src/main/java/androidx/camera/core/impl/EncoderProfilesProxy.java
index 963e3d6..f37cb38 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/impl/EncoderProfilesProxy.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/impl/EncoderProfilesProxy.java
@@ -53,49 +53,31 @@
  * {@link EncoderProfiles}.
  */
 @RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
-@AutoValue
-public abstract class EncoderProfilesProxy {
+public interface EncoderProfilesProxy {
 
     /** Constant representing no codec profile. */
-    public static final int CODEC_PROFILE_NONE = -1;
-
-    /** Creates an EncoderProfilesProxy instance. */
-    @NonNull
-    public static EncoderProfilesProxy create(
-            int defaultDurationSeconds,
-            int recommendedFileFormat,
-            @NonNull List<AudioProfileProxy> audioProfiles,
-            @NonNull List<VideoProfileProxy> videoProfiles) {
-        return new AutoValue_EncoderProfilesProxy(
-                defaultDurationSeconds,
-                recommendedFileFormat,
-                unmodifiableList(new ArrayList<>(audioProfiles)),
-                unmodifiableList(new ArrayList<>(videoProfiles))
-        );
-    }
+    int CODEC_PROFILE_NONE = -1;
 
     /** @see EncoderProfiles#getDefaultDurationSeconds() */
-    public abstract int getDefaultDurationSeconds();
+    int getDefaultDurationSeconds();
 
     /** @see EncoderProfiles#getRecommendedFileFormat() */
-    public abstract int getRecommendedFileFormat();
+    int getRecommendedFileFormat();
 
     /** @see EncoderProfiles#getAudioProfiles() */
-    @SuppressWarnings("AutoValueImmutableFields")
     @NonNull
-    public abstract List<AudioProfileProxy> getAudioProfiles();
+    List<AudioProfileProxy> getAudioProfiles();
 
     /** @see EncoderProfiles#getVideoProfiles() */
-    @SuppressWarnings("AutoValueImmutableFields")
     @NonNull
-    public abstract List<VideoProfileProxy> getVideoProfiles();
+    List<VideoProfileProxy> getVideoProfiles();
 
     /**
      * VideoProfileProxy defines the get methods that is mapping to the fields of
      * {@link EncoderProfiles.VideoProfile}.
      */
     @AutoValue
-    public abstract static class VideoProfileProxy {
+    abstract class VideoProfileProxy {
 
         /** Constant representing no media type. */
         public static final String MEDIA_TYPE_NONE = "video/none";
@@ -174,7 +156,7 @@
      * {@link EncoderProfiles.AudioProfile}.
      */
     @AutoValue
-    public abstract static class AudioProfileProxy {
+    abstract class AudioProfileProxy {
 
         /** Constant representing no media type. */
         public static final String MEDIA_TYPE_NONE = "audio/none";
@@ -224,4 +206,26 @@
         /** @see EncoderProfiles.AudioProfile#getProfile() */
         public abstract int getProfile();
     }
+
+    /**
+     * An implementation of {@link EncoderProfilesProxy} that is immutable.
+     */
+    @AutoValue
+    abstract class ImmutableEncoderProfilesProxy implements EncoderProfilesProxy {
+
+        /** Creates an EncoderProfilesProxy instance. */
+        @NonNull
+        public static ImmutableEncoderProfilesProxy create(
+                int defaultDurationSeconds,
+                int recommendedFileFormat,
+                @NonNull List<AudioProfileProxy> audioProfiles,
+                @NonNull List<VideoProfileProxy> videoProfiles) {
+            return new AutoValue_EncoderProfilesProxy_ImmutableEncoderProfilesProxy(
+                    defaultDurationSeconds,
+                    recommendedFileFormat,
+                    unmodifiableList(new ArrayList<>(audioProfiles)),
+                    unmodifiableList(new ArrayList<>(videoProfiles))
+            );
+        }
+    }
 }
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/impl/EncoderProfilesResolutionValidator.java b/camera/camera-core/src/main/java/androidx/camera/core/impl/EncoderProfilesResolutionValidator.java
index 0d84cd4..1c54463 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/impl/EncoderProfilesResolutionValidator.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/impl/EncoderProfilesResolutionValidator.java
@@ -22,6 +22,7 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
+import androidx.camera.core.impl.EncoderProfilesProxy.ImmutableEncoderProfilesProxy;
 import androidx.camera.core.impl.EncoderProfilesProxy.VideoProfileProxy;
 import androidx.camera.core.impl.quirk.ProfileResolutionQuirk;
 
@@ -114,7 +115,7 @@
             }
         }
 
-        return validVideoProfiles.isEmpty() ? null : EncoderProfilesProxy.create(
+        return validVideoProfiles.isEmpty() ? null : ImmutableEncoderProfilesProxy.create(
                 profiles.getDefaultDurationSeconds(),
                 profiles.getRecommendedFileFormat(),
                 profiles.getAudioProfiles(),
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/impl/compat/EncoderProfilesProxyCompatApi31Impl.java b/camera/camera-core/src/main/java/androidx/camera/core/impl/compat/EncoderProfilesProxyCompatApi31Impl.java
index 6e63f4b..57a5476 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/impl/compat/EncoderProfilesProxyCompatApi31Impl.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/impl/compat/EncoderProfilesProxyCompatApi31Impl.java
@@ -24,6 +24,7 @@
 import androidx.annotation.RequiresApi;
 import androidx.camera.core.impl.EncoderProfilesProxy;
 import androidx.camera.core.impl.EncoderProfilesProxy.AudioProfileProxy;
+import androidx.camera.core.impl.EncoderProfilesProxy.ImmutableEncoderProfilesProxy;
 import androidx.camera.core.impl.EncoderProfilesProxy.VideoProfileProxy;
 
 import java.util.ArrayList;
@@ -36,7 +37,7 @@
     @NonNull
     public static EncoderProfilesProxy from(
             @NonNull EncoderProfiles encoderProfiles) {
-        return EncoderProfilesProxy.create(
+        return ImmutableEncoderProfilesProxy.create(
                 encoderProfiles.getDefaultDurationSeconds(),
                 encoderProfiles.getRecommendedFileFormat(),
                 fromAudioProfiles(encoderProfiles.getAudioProfiles()),
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/impl/compat/EncoderProfilesProxyCompatApi33Impl.java b/camera/camera-core/src/main/java/androidx/camera/core/impl/compat/EncoderProfilesProxyCompatApi33Impl.java
index 9dd5e2d..e69305b 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/impl/compat/EncoderProfilesProxyCompatApi33Impl.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/impl/compat/EncoderProfilesProxyCompatApi33Impl.java
@@ -24,6 +24,7 @@
 import androidx.annotation.RequiresApi;
 import androidx.camera.core.impl.EncoderProfilesProxy;
 import androidx.camera.core.impl.EncoderProfilesProxy.AudioProfileProxy;
+import androidx.camera.core.impl.EncoderProfilesProxy.ImmutableEncoderProfilesProxy;
 import androidx.camera.core.impl.EncoderProfilesProxy.VideoProfileProxy;
 
 import java.util.ArrayList;
@@ -36,7 +37,7 @@
     @NonNull
     public static EncoderProfilesProxy from(
             @NonNull EncoderProfiles encoderProfiles) {
-        return EncoderProfilesProxy.create(
+        return ImmutableEncoderProfilesProxy.create(
                 encoderProfiles.getDefaultDurationSeconds(),
                 encoderProfiles.getRecommendedFileFormat(),
                 fromAudioProfiles(encoderProfiles.getAudioProfiles()),
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/impl/compat/EncoderProfilesProxyCompatBaseImpl.java b/camera/camera-core/src/main/java/androidx/camera/core/impl/compat/EncoderProfilesProxyCompatBaseImpl.java
index cacc8d6..7e4b830 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/impl/compat/EncoderProfilesProxyCompatBaseImpl.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/impl/compat/EncoderProfilesProxyCompatBaseImpl.java
@@ -42,6 +42,7 @@
 import androidx.annotation.RequiresApi;
 import androidx.camera.core.impl.EncoderProfilesProxy;
 import androidx.camera.core.impl.EncoderProfilesProxy.AudioProfileProxy;
+import androidx.camera.core.impl.EncoderProfilesProxy.ImmutableEncoderProfilesProxy;
 import androidx.camera.core.impl.EncoderProfilesProxy.VideoProfileProxy;
 
 import java.util.ArrayList;
@@ -54,7 +55,7 @@
     @NonNull
     public static EncoderProfilesProxy from(
             @NonNull CamcorderProfile camcorderProfile) {
-        return EncoderProfilesProxy.create(
+        return ImmutableEncoderProfilesProxy.create(
                 camcorderProfile.duration,
                 camcorderProfile.fileFormat,
                 toAudioProfiles(camcorderProfile),
diff --git a/camera/camera-testing/src/main/java/androidx/camera/testing/EncoderProfilesUtil.java b/camera/camera-testing/src/main/java/androidx/camera/testing/EncoderProfilesUtil.java
index f3d56b2..4c84b93 100644
--- a/camera/camera-testing/src/main/java/androidx/camera/testing/EncoderProfilesUtil.java
+++ b/camera/camera-testing/src/main/java/androidx/camera/testing/EncoderProfilesUtil.java
@@ -25,6 +25,7 @@
 import androidx.annotation.RequiresApi;
 import androidx.camera.core.impl.EncoderProfilesProxy;
 import androidx.camera.core.impl.EncoderProfilesProxy.AudioProfileProxy;
+import androidx.camera.core.impl.EncoderProfilesProxy.ImmutableEncoderProfilesProxy;
 import androidx.camera.core.impl.EncoderProfilesProxy.VideoProfileProxy;
 
 import java.util.Collections;
@@ -175,7 +176,7 @@
                 DEFAULT_AUDIO_PROFILE
         );
 
-        return EncoderProfilesProxy.create(
+        return ImmutableEncoderProfilesProxy.create(
                 DEFAULT_DURATION,
                 DEFAULT_OUTPUT_FORMAT,
                 Collections.singletonList(audioProfile),
diff --git a/camera/camera-video/src/main/java/androidx/camera/video/internal/VideoValidatedEncoderProfilesProxy.java b/camera/camera-video/src/main/java/androidx/camera/video/internal/VideoValidatedEncoderProfilesProxy.java
new file mode 100644
index 0000000..025c0c2
--- /dev/null
+++ b/camera/camera-video/src/main/java/androidx/camera/video/internal/VideoValidatedEncoderProfilesProxy.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.video.internal;
+
+import static java.util.Collections.unmodifiableList;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
+import androidx.camera.core.impl.EncoderProfilesProxy;
+import androidx.core.util.Preconditions;
+
+import com.google.auto.value.AutoValue;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * VideoValidatedEncoderProfilesProxy is an implementation of {@link EncoderProfilesProxy} that
+ * guarantees to provide video information.
+ */
+@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
+@AutoValue
+public abstract class VideoValidatedEncoderProfilesProxy implements EncoderProfilesProxy {
+
+    /** Creates a VideoValidatedEncoderProfilesProxy instance from {@link EncoderProfilesProxy}. */
+    @NonNull
+    public static VideoValidatedEncoderProfilesProxy from(@NonNull EncoderProfilesProxy profiles) {
+        return create(
+                profiles.getDefaultDurationSeconds(),
+                profiles.getRecommendedFileFormat(),
+                profiles.getAudioProfiles(),
+                profiles.getVideoProfiles()
+        );
+    }
+
+    /** Creates a VideoValidatedEncoderProfilesProxy instance. */
+    @NonNull
+    public static VideoValidatedEncoderProfilesProxy create(
+            int defaultDurationSeconds,
+            int recommendedFileFormat,
+            @NonNull List<AudioProfileProxy> audioProfiles,
+            @NonNull List<VideoProfileProxy> videoProfiles) {
+        Preconditions.checkArgument(!videoProfiles.isEmpty(),
+                "Should contain at least one VideoProfile.");
+        VideoProfileProxy defaultVideoProfile = videoProfiles.get(0);
+
+        AudioProfileProxy defaultAudioProfile = null;
+        if (!audioProfiles.isEmpty()) {
+            defaultAudioProfile = audioProfiles.get(0);
+        }
+
+        return new AutoValue_VideoValidatedEncoderProfilesProxy(
+                defaultDurationSeconds,
+                recommendedFileFormat,
+                unmodifiableList(new ArrayList<>(audioProfiles)),
+                unmodifiableList(new ArrayList<>(videoProfiles)),
+                defaultAudioProfile,
+                defaultVideoProfile
+        );
+    }
+
+    /** Returns the default {@link AudioProfileProxy} or null if not existed. */
+    @Nullable
+    public abstract AudioProfileProxy getDefaultAudioProfile();
+
+    /** Returns the default {@link VideoProfileProxy}. */
+    @NonNull
+    public abstract VideoProfileProxy getDefaultVideoProfile();
+}
diff --git a/camera/camera-video/src/test/java/androidx/camera/video/internal/VideoValidatedEncoderProfilesProxyTest.kt b/camera/camera-video/src/test/java/androidx/camera/video/internal/VideoValidatedEncoderProfilesProxyTest.kt
new file mode 100644
index 0000000..b853ba7
--- /dev/null
+++ b/camera/camera-video/src/test/java/androidx/camera/video/internal/VideoValidatedEncoderProfilesProxyTest.kt
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.video.internal
+
+import android.os.Build
+import androidx.camera.core.impl.EncoderProfilesProxy.VideoProfileProxy
+import androidx.camera.testing.EncoderProfilesUtil
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.RobolectricTestRunner
+import org.robolectric.annotation.Config
+import org.robolectric.annotation.internal.DoNotInstrument
+
+private const val DEFAULT_WIDTH = 1920
+private const val DEFAULT_HEIGHT = 1080
+private val DEFAULT_VIDEO_PROFILE = VideoProfileProxy.create(
+    EncoderProfilesUtil.DEFAULT_VIDEO_CODEC,
+    EncoderProfilesUtil.DEFAULT_VIDEO_MEDIA_TYPE,
+    EncoderProfilesUtil.DEFAULT_VIDEO_BITRATE,
+    EncoderProfilesUtil.DEFAULT_VIDEO_FRAME_RATE,
+    DEFAULT_WIDTH,
+    DEFAULT_HEIGHT,
+    EncoderProfilesUtil.DEFAULT_VIDEO_PROFILE,
+    EncoderProfilesUtil.DEFAULT_VIDEO_BIT_DEPTH,
+    EncoderProfilesUtil.DEFAULT_VIDEO_CHROMA_SUBSAMPLING,
+    EncoderProfilesUtil.DEFAULT_VIDEO_HDR_FORMAT
+)
+
+@RunWith(RobolectricTestRunner::class)
+@DoNotInstrument
+@Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
+class VideoValidatedEncoderProfilesProxyTest {
+
+    @Test
+    fun createFromEncoderProfilesProxy() {
+        val profiles = EncoderProfilesUtil.PROFILES_1080P
+        val validatedProfiles = VideoValidatedEncoderProfilesProxy.from(profiles)
+
+        assertThat(validatedProfiles.recommendedFileFormat)
+            .isEqualTo(profiles.recommendedFileFormat)
+        assertThat(validatedProfiles.defaultDurationSeconds)
+            .isEqualTo(profiles.defaultDurationSeconds)
+        assertThat(validatedProfiles.audioProfiles.size).isEqualTo(profiles.audioProfiles.size)
+        assertThat(validatedProfiles.videoProfiles.size).isEqualTo(profiles.videoProfiles.size)
+        assertThat(validatedProfiles.defaultAudioProfile).isNotNull()
+        assertThat(validatedProfiles.defaultVideoProfile).isNotNull()
+        assertThat(validatedProfiles.audioProfiles[0].codec)
+            .isEqualTo(profiles.audioProfiles[0].codec)
+        assertThat(validatedProfiles.audioProfiles[0].mediaType)
+            .isEqualTo(profiles.audioProfiles[0].mediaType)
+        assertThat(validatedProfiles.audioProfiles[0].bitrate)
+            .isEqualTo(profiles.audioProfiles[0].bitrate)
+        assertThat(validatedProfiles.audioProfiles[0].sampleRate)
+            .isEqualTo(profiles.audioProfiles[0].sampleRate)
+        assertThat(validatedProfiles.audioProfiles[0].channels)
+            .isEqualTo(profiles.audioProfiles[0].channels)
+        assertThat(validatedProfiles.audioProfiles[0].profile)
+            .isEqualTo(profiles.audioProfiles[0].profile)
+        assertThat(validatedProfiles.videoProfiles[0].codec)
+            .isEqualTo(profiles.videoProfiles[0].codec)
+        assertThat(validatedProfiles.videoProfiles[0].mediaType)
+            .isEqualTo(profiles.videoProfiles[0].mediaType)
+        assertThat(validatedProfiles.videoProfiles[0].bitrate)
+            .isEqualTo(profiles.videoProfiles[0].bitrate)
+        assertThat(validatedProfiles.videoProfiles[0].frameRate)
+            .isEqualTo(profiles.videoProfiles[0].frameRate)
+        assertThat(validatedProfiles.videoProfiles[0].width)
+            .isEqualTo(profiles.videoProfiles[0].width)
+        assertThat(validatedProfiles.videoProfiles[0].height)
+            .isEqualTo(profiles.videoProfiles[0].height)
+        assertThat(validatedProfiles.videoProfiles[0].profile)
+            .isEqualTo(profiles.videoProfiles[0].profile)
+        assertThat(validatedProfiles.videoProfiles[0].bitDepth)
+            .isEqualTo(profiles.videoProfiles[0].bitDepth)
+        assertThat(validatedProfiles.videoProfiles[0].chromaSubsampling)
+            .isEqualTo(profiles.videoProfiles[0].chromaSubsampling)
+        assertThat(validatedProfiles.videoProfiles[0].hdrFormat)
+            .isEqualTo(profiles.videoProfiles[0].hdrFormat)
+    }
+
+    @Test(expected = IllegalArgumentException::class)
+    fun create_throwsException_whenVideoProfilesIsEmpty() {
+        VideoValidatedEncoderProfilesProxy.create(
+            EncoderProfilesUtil.DEFAULT_DURATION,
+            EncoderProfilesUtil.DEFAULT_OUTPUT_FORMAT,
+            emptyList(),
+            emptyList()
+        )
+    }
+
+    @Test
+    fun create_withEmptyAudioProfiles() {
+        val validatedProfiles = VideoValidatedEncoderProfilesProxy.create(
+            EncoderProfilesUtil.DEFAULT_DURATION,
+            EncoderProfilesUtil.DEFAULT_OUTPUT_FORMAT,
+            emptyList(),
+            listOf(DEFAULT_VIDEO_PROFILE)
+        )
+        assertThat(validatedProfiles.defaultAudioProfile).isNull()
+    }
+}
\ No newline at end of file