[go: nahoru, domu]

Merge "Fix the recorded video contains inconsistent video/audio timestamp" into androidx-main
diff --git a/camera/camera-video/src/androidTest/java/androidx/camera/video/OutputOptionsTest.kt b/camera/camera-video/src/androidTest/java/androidx/camera/video/OutputOptionsTest.kt
index 93c3a8e..3b4791e 100644
--- a/camera/camera-video/src/androidTest/java/androidx/camera/video/OutputOptionsTest.kt
+++ b/camera/camera-video/src/androidTest/java/androidx/camera/video/OutputOptionsTest.kt
@@ -42,8 +42,7 @@
         val savedFile = File.createTempFile("CameraX", ".tmp")
         savedFile.deleteOnExit()
 
-        val fileOutputOptions = FileOutputOptions.builder()
-            .setFile(savedFile)
+        val fileOutputOptions = FileOutputOptions.Builder(savedFile)
             .setFileSizeLimit(FILE_SIZE_LIMIT)
             .build()
 
@@ -65,12 +64,10 @@
             put(MediaStore.Video.Media.DISPLAY_NAME, fileName)
         }
 
-        val mediaStoreOutputOptions = MediaStoreOutputOptions.builder()
-            .setContentResolver(contentResolver)
-            .setFileSizeLimit(FILE_SIZE_LIMIT)
-            .setCollection(MediaStore.Video.Media.EXTERNAL_CONTENT_URI)
-            .setContentValues(contentValues)
-            .build()
+        val mediaStoreOutputOptions = MediaStoreOutputOptions.Builder(
+            contentResolver,
+            MediaStore.Video.Media.EXTERNAL_CONTENT_URI
+        ).setContentValues(contentValues).setFileSizeLimit(FILE_SIZE_LIMIT).build()
 
         assertThat(mediaStoreOutputOptions.contentResolver).isEqualTo(contentResolver)
         assertThat(mediaStoreOutputOptions.collection).isEqualTo(
@@ -89,8 +86,7 @@
             savedFile,
             ParcelFileDescriptor.MODE_READ_WRITE
         ).use { pfd ->
-            val fdOutputOptions = FileDescriptorOutputOptions.builder()
-                .setParcelFileDescriptor(pfd)
+            val fdOutputOptions = FileDescriptorOutputOptions.Builder(pfd)
                 .setFileSizeLimit(FILE_SIZE_LIMIT)
                 .build()
 
@@ -106,9 +102,7 @@
     fun file_builderContainsCorrectDefaults() {
         val savedFile = File.createTempFile("CameraX", ".tmp")
         savedFile.deleteOnExit()
-        val fileOutputOptions = FileOutputOptions.builder()
-            .setFile(savedFile)
-            .build()
+        val fileOutputOptions = FileOutputOptions.Builder(savedFile).build()
 
         assertThat(fileOutputOptions.fileSizeLimit).isEqualTo(OutputOptions.FILE_SIZE_UNLIMITED)
         savedFile.delete()
@@ -118,19 +112,14 @@
     fun mediaStore_builderContainsCorrectDefaults() {
         val context: Context = ApplicationProvider.getApplicationContext()
         val contentResolver: ContentResolver = context.contentResolver
-        val fileName = "OutputOptionTest"
-        val contentValues = ContentValues().apply {
-            put(MediaStore.MediaColumns.MIME_TYPE, "video/mp4")
-            put(MediaStore.Video.Media.TITLE, fileName)
-            put(MediaStore.Video.Media.DISPLAY_NAME, fileName)
-        }
 
-        val mediaStoreOutputOptions = MediaStoreOutputOptions.builder()
-            .setContentResolver(contentResolver)
-            .setCollection(MediaStore.Video.Media.EXTERNAL_CONTENT_URI)
-            .setContentValues(contentValues)
-            .build()
+        val mediaStoreOutputOptions = MediaStoreOutputOptions.Builder(
+            contentResolver,
+            MediaStore.Video.Media.EXTERNAL_CONTENT_URI
+        ).build()
 
+        assertThat(mediaStoreOutputOptions.contentValues)
+            .isEqualTo(MediaStoreOutputOptions.EMPTY_CONTENT_VALUES)
         assertThat(mediaStoreOutputOptions.fileSizeLimit)
             .isEqualTo(OutputOptions.FILE_SIZE_UNLIMITED)
     }
@@ -142,9 +131,7 @@
             savedFile,
             ParcelFileDescriptor.MODE_READ_WRITE
         ).use { pfd ->
-            val fdOutputOptions = FileDescriptorOutputOptions.builder()
-                .setParcelFileDescriptor(pfd)
-                .build()
+            val fdOutputOptions = FileDescriptorOutputOptions.Builder(pfd).build()
 
             assertThat(fdOutputOptions.fileSizeLimit).isEqualTo(OutputOptions.FILE_SIZE_UNLIMITED)
         }
diff --git a/camera/camera-video/src/androidTest/java/androidx/camera/video/RecorderTest.kt b/camera/camera-video/src/androidTest/java/androidx/camera/video/RecorderTest.kt
index d946668..c8cd073 100644
--- a/camera/camera-video/src/androidTest/java/androidx/camera/video/RecorderTest.kt
+++ b/camera/camera-video/src/androidTest/java/androidx/camera/video/RecorderTest.kt
@@ -179,9 +179,8 @@
         clearInvocations(videoRecordEventListener)
         invokeSurfaceRequest()
         val file = File.createTempFile("CameraX", ".tmp").apply { deleteOnExit() }
-        val outputOptions = FileOutputOptions.builder().setFile(file).build()
 
-        val activeRecording = recorder.prepareRecording(outputOptions)
+        val activeRecording = recorder.prepareRecording(FileOutputOptions.Builder(file).build())
             .withEventListener(CameraXExecutors.directExecutor(), videoRecordEventListener)
             .withAudioEnabled()
             .start()
@@ -220,11 +219,10 @@
             put(MediaStore.MediaColumns.MIME_TYPE, "video/mp4")
         }
 
-        val outputOptions = MediaStoreOutputOptions.builder()
-            .setContentResolver(contentResolver)
-            .setCollection(MediaStore.Video.Media.EXTERNAL_CONTENT_URI)
-            .setContentValues(contentValues)
-            .build()
+        val outputOptions = MediaStoreOutputOptions.Builder(
+            contentResolver,
+            MediaStore.Video.Media.EXTERNAL_CONTENT_URI
+        ).setContentValues(contentValues).build()
 
         var uri: Uri = Uri.EMPTY
         val activeRecording = recorder.prepareRecording(outputOptions)
@@ -264,11 +262,8 @@
             file,
             ParcelFileDescriptor.MODE_READ_WRITE
         ).use { pfd ->
-            val outputOptions = FileDescriptorOutputOptions.builder()
-                .setParcelFileDescriptor(pfd)
-                .build()
-
-            val activeRecording = recorder.prepareRecording(outputOptions)
+            val activeRecording = recorder
+                .prepareRecording(FileDescriptorOutputOptions.Builder(pfd).build())
                 .withEventListener(CameraXExecutors.directExecutor(), videoRecordEventListener)
                 .withAudioEnabled()
                 .start()
@@ -299,12 +294,8 @@
             file,
             ParcelFileDescriptor.MODE_READ_WRITE
         ).use { pfd ->
-            val outputOptions = FileDescriptorOutputOptions.builder()
-                .setParcelFileDescriptor(pfd)
-                .build()
-
             assertThrows(IllegalStateException::class.java) {
-                recorder.prepareRecording(outputOptions)
+                recorder.prepareRecording(FileDescriptorOutputOptions.Builder(pfd).build())
             }
         }
 
@@ -317,9 +308,8 @@
         invokeSurfaceRequest()
 
         val file = File.createTempFile("CameraX", ".tmp").apply { deleteOnExit() }
-        val outputOptions = FileOutputOptions.builder().setFile(file).build()
 
-        val activeRecording = recorder.prepareRecording(outputOptions)
+        val activeRecording = recorder.prepareRecording(FileOutputOptions.Builder(file).build())
             .withEventListener(CameraXExecutors.directExecutor(), videoRecordEventListener)
             .withAudioEnabled()
             .start()
@@ -354,9 +344,8 @@
         clearInvocations(videoRecordEventListener)
 
         val file = File.createTempFile("CameraX", ".tmp").apply { deleteOnExit() }
-        val outputOptions = FileOutputOptions.builder().setFile(file).build()
 
-        val activeRecording = recorder.prepareRecording(outputOptions)
+        val activeRecording = recorder.prepareRecording(FileOutputOptions.Builder(file).build())
             .withEventListener(CameraXExecutors.directExecutor(), videoRecordEventListener)
             .withAudioEnabled()
             .start()
@@ -387,14 +376,11 @@
         invokeSurfaceRequest()
 
         val file1 = File.createTempFile("CameraX1", ".tmp").apply { deleteOnExit() }
-        val outputOptions1 = FileOutputOptions.builder().setFile(file1).build()
-
         val file2 = File.createTempFile("CameraX2", ".tmp").apply { deleteOnExit() }
-        val outputOptions2 = FileOutputOptions.builder().setFile(file2).build()
 
         // Start and stop a recording to ensure recorder is idling
         val inOrder = inOrder(videoRecordEventListener)
-        val activeRecording1 = recorder.prepareRecording(outputOptions1)
+        val activeRecording1 = recorder.prepareRecording(FileOutputOptions.Builder(file1).build())
             .withEventListener(CameraXExecutors.directExecutor(), videoRecordEventListener)
             .withAudioEnabled()
             .start()
@@ -411,7 +397,7 @@
             .accept(any(VideoRecordEvent.Finalize::class.java))
 
         // First recording is now finalized. Try starting second recording paused.
-        val activeRecording2 = recorder.prepareRecording(outputOptions2)
+        val activeRecording2 = recorder.prepareRecording(FileOutputOptions.Builder(file2).build())
             .withEventListener(CameraXExecutors.directExecutor(), videoRecordEventListener)
             .withAudioEnabled()
             .start()
@@ -436,11 +422,10 @@
         invokeSurfaceRequest()
 
         val file = File.createTempFile("CameraX", ".tmp").apply { deleteOnExit() }
-        val outputOptions = FileOutputOptions.builder().setFile(file).build()
 
         val inOrder = inOrder(videoRecordEventListener)
         // Start
-        val activeRecording = recorder.prepareRecording(outputOptions)
+        val activeRecording = recorder.prepareRecording(FileOutputOptions.Builder(file).build())
             .withEventListener(CameraXExecutors.directExecutor(), videoRecordEventListener)
             .withAudioEnabled()
             .start()
@@ -524,7 +509,6 @@
         clearInvocations(videoRecordEventListener)
         invokeSurfaceRequest()
         val file = File.createTempFile("CameraX", ".tmp").apply { deleteOnExit() }
-        val outputOptions = FileOutputOptions.builder().setFile(file).build()
 
         @Suppress("UNCHECKED_CAST")
         val streamStateObserver =
@@ -538,7 +522,7 @@
         )
 
         // Start
-        val activeRecording = recorder.prepareRecording(outputOptions)
+        val activeRecording = recorder.prepareRecording(FileOutputOptions.Builder(file).build())
             .withEventListener(CameraXExecutors.directExecutor(), videoRecordEventListener)
             .withAudioEnabled()
             .start()
@@ -561,7 +545,7 @@
     fun start_throwsExceptionWhenActive() {
         invokeSurfaceRequest()
         val file = File.createTempFile("CameraX", ".tmp").apply { deleteOnExit() }
-        val outputOptions = FileOutputOptions.builder().setFile(file).build()
+        val outputOptions = FileOutputOptions.Builder(file).build()
 
         val activeRecording = recorder.prepareRecording(outputOptions).start()
 
@@ -578,9 +562,8 @@
     fun start_beforeSurfaceRequested() {
         clearInvocations(videoRecordEventListener)
         val file = File.createTempFile("CameraX", ".tmp").apply { deleteOnExit() }
-        val outputOptions = FileOutputOptions.builder().setFile(file).build()
 
-        val activeRecording = recorder.prepareRecording(outputOptions)
+        val activeRecording = recorder.prepareRecording(FileOutputOptions.Builder(file).build())
             .withEventListener(CameraXExecutors.directExecutor(), videoRecordEventListener)
             .withAudioEnabled()
             .start()
@@ -605,11 +588,10 @@
         clearInvocations(videoRecordEventListener)
         invokeSurfaceRequest()
         val file = File.createTempFile("CameraX", ".tmp").apply { deleteOnExit() }
-        val outputOptions = FileOutputOptions.builder().setFile(file).build()
 
         recorder.onSourceStateChanged(VideoOutput.SourceState.INACTIVE)
 
-        val activeRecording = recorder.prepareRecording(outputOptions)
+        val activeRecording = recorder.prepareRecording(FileOutputOptions.Builder(file).build())
             .withEventListener(CameraXExecutors.directExecutor(), videoRecordEventListener)
             .withAudioEnabled()
             .start()
@@ -630,9 +612,8 @@
     fun pause_beforeSurfaceRequested() {
         clearInvocations(videoRecordEventListener)
         val file = File.createTempFile("CameraX", ".tmp").apply { deleteOnExit() }
-        val outputOptions = FileOutputOptions.builder().setFile(file).build()
 
-        val activeRecording = recorder.prepareRecording(outputOptions)
+        val activeRecording = recorder.prepareRecording(FileOutputOptions.Builder(file).build())
             .withEventListener(CameraXExecutors.directExecutor(), videoRecordEventListener)
             .withAudioEnabled()
             .start()
@@ -659,9 +640,8 @@
         clearInvocations(videoRecordEventListener)
         invokeSurfaceRequest()
         val file = File.createTempFile("CameraX", ".tmp").apply { deleteOnExit() }
-        val outputOptions = FileOutputOptions.builder().setFile(file).build()
 
-        val activeRecording = recorder.prepareRecording(outputOptions)
+        val activeRecording = recorder.prepareRecording(FileOutputOptions.Builder(file).build())
             .withEventListener(CameraXExecutors.directExecutor(), videoRecordEventListener)
             .withAudioEnabled()
             .start()
@@ -697,9 +677,8 @@
         clearInvocations(videoRecordEventListener)
         invokeSurfaceRequest()
         val file = File.createTempFile("CameraX", ".tmp").apply { deleteOnExit() }
-        val outputOptions = FileOutputOptions.builder().setFile(file).build()
 
-        val activeRecording = recorder.prepareRecording(outputOptions)
+        val activeRecording = recorder.prepareRecording(FileOutputOptions.Builder(file).build())
             .withEventListener(CameraXExecutors.directExecutor(), videoRecordEventListener)
             .withAudioEnabled()
             .start()
@@ -723,9 +702,8 @@
         clearInvocations(videoRecordEventListener)
         invokeSurfaceRequest()
         val file = File.createTempFile("CameraX", ".tmp").apply { deleteOnExit() }
-        val outputOptions = FileOutputOptions.builder().setFile(file).build()
 
-        val activeRecording = recorder.prepareRecording(outputOptions)
+        val activeRecording = recorder.prepareRecording(FileOutputOptions.Builder(file).build())
             .withEventListener(CameraXExecutors.directExecutor(), videoRecordEventListener)
             .withAudioEnabled()
             .start()
@@ -754,9 +732,8 @@
         clearInvocations(videoRecordEventListener)
         invokeSurfaceRequest()
         val file = File.createTempFile("CameraX", ".tmp").apply { deleteOnExit() }
-        val outputOptions = FileOutputOptions.builder().setFile(file).build()
 
-        val activeRecording = recorder.prepareRecording(outputOptions)
+        val activeRecording = recorder.prepareRecording(FileOutputOptions.Builder(file).build())
             .withEventListener(CameraXExecutors.directExecutor(), videoRecordEventListener)
             .withAudioEnabled()
             .start()
@@ -779,9 +756,8 @@
     fun stop_beforeSurfaceRequested() {
         clearInvocations(videoRecordEventListener)
         val file = File.createTempFile("CameraX", ".tmp").apply { deleteOnExit() }
-        val outputOptions = FileOutputOptions.builder().setFile(file).build()
 
-        val activeRecording = recorder.prepareRecording(outputOptions)
+        val activeRecording = recorder.prepareRecording(FileOutputOptions.Builder(file).build())
             .withEventListener(CameraXExecutors.directExecutor(), videoRecordEventListener)
             .withAudioEnabled()
             .start()
@@ -801,11 +777,10 @@
     fun stop_fromAutoCloseable() {
         clearInvocations(videoRecordEventListener)
         val file = File.createTempFile("CameraX", ".tmp").apply { deleteOnExit() }
-        val outputOptions = FileOutputOptions.builder().setFile(file).build()
 
         val inOrder = inOrder(videoRecordEventListener)
         // Recording will be stopped by AutoCloseable.close() upon exiting use{} block
-        val pendingRecording = recorder.prepareRecording(outputOptions)
+        val pendingRecording = recorder.prepareRecording(FileOutputOptions.Builder(file).build())
         pendingRecording.withEventListener(
             CameraXExecutors.directExecutor(),
             videoRecordEventListener
@@ -828,10 +803,8 @@
         clearInvocations(videoRecordEventListener)
         invokeSurfaceRequest()
         val file = File.createTempFile("CameraX", ".tmp").apply { deleteOnExit() }
-        val outputOptions = FileOutputOptions.builder().setFile(file).build()
 
-        val activeRecording = recorder
-            .prepareRecording(outputOptions)
+        val activeRecording = recorder.prepareRecording(FileOutputOptions.Builder(file).build())
             .withEventListener(CameraXExecutors.directExecutor(), videoRecordEventListener)
             .withAudioEnabled()
             .start()
@@ -858,10 +831,10 @@
     fun stop_whenActiveRecordingIsGarbageCollected() {
         clearInvocations(videoRecordEventListener)
         val file = File.createTempFile("CameraX", ".tmp").apply { deleteOnExit() }
-        val outputOptions = FileOutputOptions.builder().setFile(file).build()
 
         val inOrder = inOrder(videoRecordEventListener)
-        var activeRecording: ActiveRecording? = recorder.prepareRecording(outputOptions)
+        var activeRecording: ActiveRecording? = recorder
+            .prepareRecording(FileOutputOptions.Builder(file).build())
             .withEventListener(CameraXExecutors.directExecutor(), videoRecordEventListener)
             .withAudioEnabled()
             .start()
@@ -893,9 +866,8 @@
         clearInvocations(videoRecordEventListener)
         invokeSurfaceRequest()
         val file = File.createTempFile("CameraX", ".tmp").apply { deleteOnExit() }
-        val outputOptions = FileOutputOptions.builder().setFile(file).build()
 
-        val activeRecording = recorder.prepareRecording(outputOptions)
+        val activeRecording = recorder.prepareRecording(FileOutputOptions.Builder(file).build())
             .withEventListener(CameraXExecutors.directExecutor(), videoRecordEventListener)
             .withAudioEnabled()
             .start()
@@ -948,9 +920,8 @@
         clearInvocations(videoRecordEventListener)
         invokeSurfaceRequest()
         val file = File.createTempFile("CameraX", ".tmp").apply { deleteOnExit() }
-        val outputOptions = FileOutputOptions.builder().setFile(file).build()
 
-        val activeRecording = recorder.prepareRecording(outputOptions)
+        val activeRecording = recorder.prepareRecording(FileOutputOptions.Builder(file).build())
             .withEventListener(CameraXExecutors.directExecutor(), videoRecordEventListener)
             .start()
 
@@ -982,14 +953,12 @@
     @Test
     fun cannotStartMultiplePendingRecordingsWhileInitializing() {
         val file1 = File.createTempFile("CameraX1", ".tmp").apply { deleteOnExit() }
-        val outputOptions1 = FileOutputOptions.builder().setFile(file1).build()
         val file2 = File.createTempFile("CameraX2", ".tmp").apply { deleteOnExit() }
-        val outputOptions2 = FileOutputOptions.builder().setFile(file2).build()
         try {
             // We explicitly do not invoke the surface request so the recorder is initializing.
-            recorder.prepareRecording(outputOptions1).start().use {
+            recorder.prepareRecording(FileOutputOptions.Builder(file1).build()).start().use {
                 assertThrows<IllegalStateException> {
-                    recorder.prepareRecording(outputOptions2).start()
+                    recorder.prepareRecording(FileOutputOptions.Builder(file2).build()).start()
                 }
             }
         } finally {
@@ -1005,13 +974,11 @@
         clearInvocations(videoRecordEventListener)
         invokeSurfaceRequest()
         val file1 = File.createTempFile("CameraX1", ".tmp").apply { deleteOnExit() }
-        val outputOptions1 = FileOutputOptions.builder().setFile(file1).build()
         val file2 = File.createTempFile("CameraX2", ".tmp").apply { deleteOnExit() }
-        val outputOptions2 = FileOutputOptions.builder().setFile(file2).build()
 
         val inOrder = inOrder(videoRecordEventListener)
         try {
-            recorder.prepareRecording(outputOptions1)
+            recorder.prepareRecording(FileOutputOptions.Builder(file1).build())
                 .withEventListener(
                     CameraXExecutors.directExecutor(),
                     videoRecordEventListener
@@ -1022,7 +989,7 @@
                         .accept(any(VideoRecordEvent.Status::class.java))
                 }
 
-            recorder.prepareRecording(outputOptions2)
+            recorder.prepareRecording(FileOutputOptions.Builder(file2).build())
                 .withEventListener(
                     CameraXExecutors.directExecutor(),
                     videoRecordEventListener
@@ -1108,8 +1075,7 @@
     private fun runFileSizeLimitTest(fileSizeLimit: Long) {
         invokeSurfaceRequest()
         val file = File.createTempFile("CameraX", ".tmp").apply { deleteOnExit() }
-        val outputOptions = FileOutputOptions.builder()
-            .setFile(file)
+        val outputOptions = FileOutputOptions.Builder(file)
             .setFileSizeLimit(fileSizeLimit)
             .build()
 
diff --git a/camera/camera-video/src/androidTest/java/androidx/camera/video/VideoRecordingTest.kt b/camera/camera-video/src/androidTest/java/androidx/camera/video/VideoRecordingTest.kt
index a172cc5..7ee8a9c 100644
--- a/camera/camera-video/src/androidTest/java/androidx/camera/video/VideoRecordingTest.kt
+++ b/camera/camera-video/src/androidTest/java/androidx/camera/video/VideoRecordingTest.kt
@@ -297,7 +297,6 @@
         // Arrange.
         val videoCapture = VideoCapture.withOutput(Recorder.Builder().build())
         val file = File.createTempFile("CameraX", ".tmp").apply { deleteOnExit() }
-        val outputOptions = FileOutputOptions.builder().setFile(file).build()
         @Suppress("UNCHECKED_CAST")
         val mockListener = mock(Consumer::class.java) as Consumer<VideoRecordEvent>
         instrumentation.runOnMainSync {
@@ -307,7 +306,7 @@
 
         // Act.
         videoCapture.output
-            .prepareRecording(outputOptions)
+            .prepareRecording(FileOutputOptions.Builder(file).build())
             .withEventListener(
                 CameraXExecutors.directExecutor(),
                 mockListener
@@ -403,10 +402,8 @@
 
     private fun startVideoRecording(videoCapture: VideoCapture<Recorder>, file: File):
         ActiveRecording {
-            val outputOptions = FileOutputOptions.builder().setFile(file).build()
-
             val activeRecording = videoCapture.output
-                .prepareRecording(outputOptions)
+                .prepareRecording(FileOutputOptions.Builder(file).build())
                 .withEventListener(
                     CameraXExecutors.directExecutor(),
                     videoRecordEventListener
diff --git a/camera/camera-video/src/main/java/androidx/camera/video/FileDescriptorOutputOptions.java b/camera/camera-video/src/main/java/androidx/camera/video/FileDescriptorOutputOptions.java
index 55f022f..b293823 100644
--- a/camera/camera-video/src/main/java/androidx/camera/video/FileDescriptorOutputOptions.java
+++ b/camera/camera-video/src/main/java/androidx/camera/video/FileDescriptorOutputOptions.java
@@ -19,6 +19,7 @@
 import android.os.ParcelFileDescriptor;
 
 import androidx.annotation.NonNull;
+import androidx.core.util.Preconditions;
 
 import com.google.auto.value.AutoValue;
 
@@ -31,49 +32,71 @@
  * <p>To use a {@link java.io.File} as an output destination instead of a file descriptor, use
  * {@link FileOutputOptions}.
  */
-@AutoValue
-public abstract class FileDescriptorOutputOptions extends OutputOptions {
+public final class FileDescriptorOutputOptions extends OutputOptions {
 
-    FileDescriptorOutputOptions() {
+    private final FileDescriptorOutputOptionsInternal mFileDescriptorOutputOptionsInternal;
+
+    FileDescriptorOutputOptions(
+            @NonNull FileDescriptorOutputOptionsInternal fileDescriptorOutputOptionsInternal) {
         super(OPTIONS_TYPE_FILE_DESCRIPTOR);
+        Preconditions.checkNotNull(fileDescriptorOutputOptionsInternal,
+                "FileDescriptorOutputOptionsInternal can't be null.");
+        mFileDescriptorOutputOptionsInternal = fileDescriptorOutputOptionsInternal;
     }
 
-    /** Returns a builder for this FileDescriptorOutputOptions. */
+    /**
+     * Gets the file descriptor instance.
+     *
+     * @return the file descriptor used as the output destination.
+     */
     @NonNull
-    public static Builder builder() {
-        return new AutoValue_FileDescriptorOutputOptions.Builder()
-                .setFileSizeLimit(FILE_SIZE_UNLIMITED);
+    public ParcelFileDescriptor getParcelFileDescriptor() {
+        return mFileDescriptorOutputOptionsInternal.getParcelFileDescriptor();
     }
 
     /**
      * Gets the limit for the file length in bytes.
      */
     @Override
-    public abstract long getFileSizeLimit();
+    public long getFileSizeLimit() {
+        return mFileDescriptorOutputOptionsInternal.getFileSizeLimit();
+    }
 
-    /**
-     * Gets the file descriptor instance.
-     * @return the file descriptor used as the output destination.
-     */
+    @Override
     @NonNull
-    public abstract ParcelFileDescriptor getParcelFileDescriptor();
+    public String toString() {
+        return mFileDescriptorOutputOptionsInternal.toString().replaceFirst(
+                mFileDescriptorOutputOptionsInternal.getClass().getSuperclass().getSimpleName(),
+                getClass().getSimpleName());
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        return mFileDescriptorOutputOptionsInternal.equals(o);
+    }
+
+    @Override
+    public int hashCode() {
+        return mFileDescriptorOutputOptionsInternal.hashCode();
+    }
 
     /** The builder of the {@link FileDescriptorOutputOptions}. */
-    @AutoValue.Builder
-    @SuppressWarnings("StaticFinalBuilder")
-    public abstract static class Builder implements
+    public static final class Builder implements
             OutputOptions.Builder<FileDescriptorOutputOptions, Builder> {
-        Builder() {
-        }
+        private final FileDescriptorOutputOptionsInternal.Builder mInternalBuilder =
+                new AutoValue_FileDescriptorOutputOptions_FileDescriptorOutputOptionsInternal
+                        .Builder()
+                        .setFileSizeLimit(FILE_SIZE_UNLIMITED);
 
         /**
-         * Defines the file descriptor used to store the result.
+         * Creates a builder of the {@link FileDescriptorOutputOptions} with a file descriptor.
          *
          * @param fileDescriptor the file descriptor to use as the output destination.
          */
-        @NonNull
-        public abstract Builder setParcelFileDescriptor(
-                @NonNull ParcelFileDescriptor fileDescriptor);
+        public Builder(@NonNull ParcelFileDescriptor fileDescriptor) {
+            Preconditions.checkNotNull(fileDescriptor, "File descriptor can't be null.");
+            mInternalBuilder.setParcelFileDescriptor(fileDescriptor);
+        }
 
         /**
          * Sets the limit for the file length in bytes. Zero or negative values are considered
@@ -89,11 +112,34 @@
          */
         @Override
         @NonNull
-        public abstract Builder setFileSizeLimit(long bytes);
+        public Builder setFileSizeLimit(long fileSizeLimitBytes) {
+            mInternalBuilder.setFileSizeLimit(fileSizeLimitBytes);
+            return this;
+        }
 
         /** Builds the {@link FileDescriptorOutputOptions} instance. */
         @Override
         @NonNull
-        public abstract FileDescriptorOutputOptions build();
+        public FileDescriptorOutputOptions build() {
+            return new FileDescriptorOutputOptions(mInternalBuilder.build());
+        }
+    }
+
+    @AutoValue
+    abstract static class FileDescriptorOutputOptionsInternal {
+        @NonNull
+        abstract ParcelFileDescriptor getParcelFileDescriptor();
+        abstract long getFileSizeLimit();
+
+        @AutoValue.Builder
+        abstract static class Builder {
+            @NonNull
+            abstract Builder setParcelFileDescriptor(
+                    @NonNull ParcelFileDescriptor parcelFileDescriptor);
+            @NonNull
+            abstract Builder setFileSizeLimit(long fileSizeLimitBytes);
+            @NonNull
+            abstract FileDescriptorOutputOptionsInternal build();
+        }
     }
 }
diff --git a/camera/camera-video/src/main/java/androidx/camera/video/FileOutputOptions.java b/camera/camera-video/src/main/java/androidx/camera/video/FileOutputOptions.java
index 0268b38..610f235 100644
--- a/camera/camera-video/src/main/java/androidx/camera/video/FileOutputOptions.java
+++ b/camera/camera-video/src/main/java/androidx/camera/video/FileOutputOptions.java
@@ -17,6 +17,7 @@
 package androidx.camera.video;
 
 import androidx.annotation.NonNull;
+import androidx.core.util.Preconditions;
 
 import com.google.auto.value.AutoValue;
 
@@ -30,42 +31,68 @@
  * <p>To use a {@link android.os.ParcelFileDescriptor} as an output desination instead of a
  * {@link File}, use {@link FileDescriptorOutputOptions}.
  */
-@AutoValue
-public abstract class FileOutputOptions extends OutputOptions {
+public final class FileOutputOptions extends OutputOptions {
 
-    FileOutputOptions() {
+    private final FileOutputOptionsInternal mFileOutputOptionsInternal;
+
+    FileOutputOptions(@NonNull FileOutputOptionsInternal fileOutputOptionsInternal) {
         super(OPTIONS_TYPE_FILE);
+        Preconditions.checkNotNull(fileOutputOptionsInternal,
+                "FileOutputOptionsInternal can't be null.");
+        mFileOutputOptionsInternal = fileOutputOptionsInternal;
     }
 
-    /** Returns a builder for this FileOutputOptions. */
+    /** Gets the File instance */
     @NonNull
-    public static Builder builder() {
-        return new AutoValue_FileOutputOptions.Builder()
-                .setFileSizeLimit(FILE_SIZE_UNLIMITED);
+    public File getFile() {
+        return mFileOutputOptionsInternal.getFile();
     }
 
     /**
      * Gets the limit for the file length in bytes.
      */
     @Override
-    public abstract long getFileSizeLimit();
+    public long getFileSizeLimit() {
+        return mFileOutputOptionsInternal.getFileSizeLimit();
+    }
 
-    /** Gets the File instance */
+    @Override
     @NonNull
-    public abstract File getFile();
+    public String toString() {
+        return mFileOutputOptionsInternal.toString().replaceFirst(
+                mFileOutputOptionsInternal.getClass().getSuperclass().getSimpleName(),
+                getClass().getSimpleName());
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        return mFileOutputOptionsInternal.equals(o);
+    }
+
+    @Override
+    public int hashCode() {
+        return mFileOutputOptionsInternal.hashCode();
+    }
 
     /** The builder of the {@link FileOutputOptions}. */
-    @AutoValue.Builder
-    @SuppressWarnings("StaticFinalBuilder")
-    public abstract static class Builder implements
-            OutputOptions.Builder<FileOutputOptions, Builder> {
-        Builder() {
-        }
+    public static final class Builder implements OutputOptions.Builder<FileOutputOptions, Builder> {
+        private final FileOutputOptionsInternal.Builder mInternalBuilder =
+                new AutoValue_FileOutputOptions_FileOutputOptionsInternal.Builder()
+                        .setFileSizeLimit(OutputOptions.FILE_SIZE_UNLIMITED);
 
-        /** Defines the file used to store the result. */
-        @SuppressWarnings("StreamFiles") // FileDescriptor API is in FileDescriptorOutputOptions
-        @NonNull
-        public abstract Builder setFile(@NonNull File file);
+        /**
+         * Creates a builder of the {@link FileOutputOptions} with a file object.
+         *
+         * <p>The file object can be created with a path using the {@link File} APIs. The path
+         * must be seekable and writable.
+         *
+         * @param file the file object.
+         * @see File
+         */
+        public Builder(@NonNull File file) {
+            Preconditions.checkNotNull(file, "File can't be null.");
+            mInternalBuilder.setFile(file);
+        }
 
         /**
          * Sets the limit for the file length in bytes. Zero or negative values are considered
@@ -81,11 +108,36 @@
          */
         @Override
         @NonNull
-        public abstract Builder setFileSizeLimit(long bytes);
+        public Builder setFileSizeLimit(long fileSizeLimitBytes) {
+            mInternalBuilder.setFileSizeLimit(fileSizeLimitBytes);
+            return this;
+        }
 
         /** Builds the {@link FileOutputOptions} instance. */
         @Override
         @NonNull
-        public abstract FileOutputOptions build();
+        public FileOutputOptions build() {
+            return new FileOutputOptions(mInternalBuilder.build());
+        }
+    }
+
+    @AutoValue
+    abstract static class FileOutputOptionsInternal {
+        @NonNull
+        abstract File getFile();
+
+        abstract long getFileSizeLimit();
+
+        @AutoValue.Builder
+        abstract static class Builder {
+            @NonNull
+            abstract Builder setFile(@NonNull File file);
+
+            @NonNull
+            abstract Builder setFileSizeLimit(long fileSizeLimitBytes);
+
+            @NonNull
+            abstract FileOutputOptionsInternal build();
+        }
     }
 }
diff --git a/camera/camera-video/src/main/java/androidx/camera/video/MediaStoreOutputOptions.java b/camera/camera-video/src/main/java/androidx/camera/video/MediaStoreOutputOptions.java
index 1248749..e2dda51 100644
--- a/camera/camera-video/src/main/java/androidx/camera/video/MediaStoreOutputOptions.java
+++ b/camera/camera-video/src/main/java/androidx/camera/video/MediaStoreOutputOptions.java
@@ -21,6 +21,7 @@
 import android.net.Uri;
 
 import androidx.annotation.NonNull;
+import androidx.core.util.Preconditions;
 
 import com.google.auto.value.AutoValue;
 
@@ -46,65 +47,106 @@
  *
  * }</pre>
  */
-@AutoValue
-public abstract class MediaStoreOutputOptions extends OutputOptions {
+public final class MediaStoreOutputOptions extends OutputOptions {
 
-    MediaStoreOutputOptions() {
+    /**
+     * An empty {@link ContentValues}.
+     */
+    public static final ContentValues EMPTY_CONTENT_VALUES = new ContentValues();
+
+    private final MediaStoreOutputOptionsInternal mMediaStoreOutputOptionsInternal;
+
+    MediaStoreOutputOptions(
+            @NonNull MediaStoreOutputOptionsInternal mediaStoreOutputOptionsInternal) {
         super(OPTIONS_TYPE_MEDIA_STORE);
+        Preconditions.checkNotNull(mediaStoreOutputOptionsInternal,
+                "MediaStoreOutputOptionsInternal can't be null.");
+        mMediaStoreOutputOptionsInternal = mediaStoreOutputOptionsInternal;
     }
 
     /**
-     * Returns a builder for this MediaStoreOutputOptions.
+     * Gets the ContentResolver instance in order to convert URI to a file path.
      */
     @NonNull
-    public static Builder builder() {
-        return new AutoValue_MediaStoreOutputOptions.Builder()
-                .setFileSizeLimit(FILE_SIZE_UNLIMITED);
+    public ContentResolver getContentResolver() {
+        return mMediaStoreOutputOptionsInternal.getContentResolver();
     }
 
     /**
-     * Gets the ContentResolver instance in order to convert Uri to a file path.
-     */
-    @NonNull
-    public abstract ContentResolver getContentResolver();
-
-    /**
      * Gets the URL of the table to insert into.
      */
     @NonNull
-    public abstract Uri getCollection();
+    public Uri getCollection() {
+        return mMediaStoreOutputOptionsInternal.getCollection();
+    }
 
     /**
      * Gets the content values to be included in the created file.
      */
     @NonNull
-    public abstract ContentValues getContentValues();
+    public ContentValues getContentValues() {
+        return mMediaStoreOutputOptionsInternal.getContentValues();
+    }
 
     /**
      * Gets the limit for the file length in bytes.
      */
     @Override
-    public abstract long getFileSizeLimit();
+    public long getFileSizeLimit() {
+        return mMediaStoreOutputOptionsInternal.getFileSizeLimit();
+    }
+
+    @Override
+    @NonNull
+    public String toString() {
+        return mMediaStoreOutputOptionsInternal.toString().replaceFirst(
+                mMediaStoreOutputOptionsInternal.getClass().getSuperclass().getSimpleName(),
+                getClass().getSimpleName());
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        return mMediaStoreOutputOptionsInternal.equals(o);
+    }
+
+    @Override
+    public int hashCode() {
+        return mMediaStoreOutputOptionsInternal.hashCode();
+    }
 
     /** The builder of the {@link MediaStoreOutputOptions}. */
-    @AutoValue.Builder
-    @SuppressWarnings("StaticFinalBuilder")
-    public abstract static class Builder implements
+    public static final class Builder implements
             OutputOptions.Builder<MediaStoreOutputOptions, Builder> {
-        Builder() {
+        private final MediaStoreOutputOptionsInternal.Builder mInternalBuilder =
+                new AutoValue_MediaStoreOutputOptions_MediaStoreOutputOptionsInternal.Builder()
+                        .setContentValues(EMPTY_CONTENT_VALUES)
+                        .setFileSizeLimit(FILE_SIZE_UNLIMITED);
+
+        /**
+         * Creates a builder of the {@link MediaStoreOutputOptions} with media store options.
+         *
+         * @param contentResolver the content resolver instance.
+         * @param collectionUri the URI of the table to insert into.
+         */
+        public Builder(@NonNull ContentResolver contentResolver, @NonNull Uri collectionUri) {
+            Preconditions.checkNotNull(contentResolver, "Content resolver can't be null.");
+            Preconditions.checkNotNull(collectionUri, "Collection Uri can't be null.");
+            mInternalBuilder.setContentResolver(contentResolver).setCollection(collectionUri);
         }
 
-        /** Sets the ContentResolver instance. */
+        /**
+         * Sets the content values to be included in the created file.
+         *
+         * <p>If not set, defaults to {@link #EMPTY_CONTENT_VALUES}.
+         *
+         * @param contentValues the content values to be inserted.
+         */
         @NonNull
-        public abstract Builder setContentResolver(@NonNull ContentResolver contentResolver);
-
-        /** Sets the URL of the table to insert into. */
-        @NonNull
-        public abstract Builder setCollection(@NonNull Uri collectionUri);
-
-        /** Sets the content values to be included in the created file. */
-        @NonNull
-        public abstract Builder setContentValues(@NonNull ContentValues contentValues);
+        public Builder setContentValues(@NonNull ContentValues contentValues) {
+            Preconditions.checkNotNull(contentValues, "Content values can't be null.");
+            mInternalBuilder.setContentValues(contentValues);
+            return this;
+        }
 
         /**
          * Sets the limit for the file length in bytes. Zero or negative values are considered
@@ -120,11 +162,41 @@
          */
         @Override
         @NonNull
-        public abstract Builder setFileSizeLimit(long bytes);
+        public Builder setFileSizeLimit(long fileSizeLimitBytes) {
+            mInternalBuilder.setFileSizeLimit(fileSizeLimitBytes);
+            return this;
+        }
 
         /** Builds the {@link MediaStoreOutputOptions} instance. */
         @Override
         @NonNull
-        public abstract MediaStoreOutputOptions build();
+        public MediaStoreOutputOptions build() {
+            return new MediaStoreOutputOptions(mInternalBuilder.build());
+        }
+    }
+
+    @AutoValue
+    abstract static class MediaStoreOutputOptionsInternal {
+        @NonNull
+        abstract ContentResolver getContentResolver();
+        @NonNull
+        abstract Uri getCollection();
+        @NonNull
+        abstract ContentValues getContentValues();
+        abstract long getFileSizeLimit();
+
+        @AutoValue.Builder
+        abstract static class Builder {
+            @NonNull
+            abstract Builder setContentResolver(@NonNull ContentResolver contentResolver);
+            @NonNull
+            abstract Builder setCollection(@NonNull Uri collectionUri);
+            @NonNull
+            abstract Builder setContentValues(@NonNull ContentValues contentValues);
+            @NonNull
+            abstract Builder setFileSizeLimit(long fileSizeLimitBytes);
+            @NonNull
+            abstract MediaStoreOutputOptionsInternal build();
+        }
     }
 }
diff --git a/camera/camera-video/src/test/java/androidx/camera/video/VideoRecordEventTest.kt b/camera/camera-video/src/test/java/androidx/camera/video/VideoRecordEventTest.kt
index 749e4f0..71c4d17 100644
--- a/camera/camera-video/src/test/java/androidx/camera/video/VideoRecordEventTest.kt
+++ b/camera/camera-video/src/test/java/androidx/camera/video/VideoRecordEventTest.kt
@@ -30,8 +30,7 @@
 import java.io.File
 
 private const val INVALID_FILE_PATH = "/invalid/file/path"
-private val TEST_OUTPUT_OPTION =
-    FileOutputOptions.builder().setFile(File(INVALID_FILE_PATH)).build()
+private val TEST_OUTPUT_OPTION = FileOutputOptions.Builder(File(INVALID_FILE_PATH)).build()
 private val TEST_RECORDING_STATE =
     RecordingStats.of(0, 0, AudioStats.of(AudioStats.AUDIO_STATE_ACTIVE, null))
 private val TEST_OUTPUT_RESULT = OutputResults.of(Uri.EMPTY)
diff --git a/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraXActivity.java b/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraXActivity.java
index d69c8f8..25d0175 100644
--- a/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraXActivity.java
+++ b/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraXActivity.java
@@ -546,9 +546,8 @@
         contentValues.put(MediaStore.Video.Media.DISPLAY_NAME, videoFileName);
         contentValues.put(MediaStore.Video.Media.DATE_ADDED, System.currentTimeMillis() / 1000);
         contentValues.put(MediaStore.Video.Media.DATE_TAKEN, System.currentTimeMillis());
-        return MediaStoreOutputOptions.builder()
-                .setContentResolver(getContentResolver())
-                .setCollection(MediaStore.Video.Media.EXTERNAL_CONTENT_URI)
+        return new MediaStoreOutputOptions.Builder(getContentResolver(),
+                MediaStore.Video.Media.EXTERNAL_CONTENT_URI)
                 .setContentValues(contentValues)
                 .build();
     }
@@ -560,7 +559,7 @@
                 Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES),
                 videoFileName + ".mp4");
         Log.d(TAG, "VideoOutputFileOptions file: " + videoFile.getAbsolutePath());
-        return FileOutputOptions.builder().setFile(videoFile).build();
+        return new FileOutputOptions.Builder(videoFile).build();
     }
 
     @NonNull
diff --git a/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/CoroutineBroadcastReceiverTest.kt b/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/CoroutineBroadcastReceiverTest.kt
index d1d5003..3085e20 100644
--- a/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/CoroutineBroadcastReceiverTest.kt
+++ b/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/CoroutineBroadcastReceiverTest.kt
@@ -21,8 +21,10 @@
 import android.content.Intent
 import android.content.IntentFilter
 import android.os.Looper.getMainLooper
+import android.util.Log
 import androidx.glance.GlanceInternalApi
 import androidx.test.core.app.ApplicationProvider
+import androidx.test.filters.FlakyTest
 import androidx.test.filters.MediumTest
 import com.google.common.truth.Truth.assertThat
 import com.google.common.truth.Truth.assertWithMessage
@@ -55,10 +57,12 @@
                     try {
                         awaitCancellation()
                     } catch (ex: CancellationException) {
+                        Log.i("CoroutineBRTest", "Scope cancelled")
                         scopeCancelled.countDown()
                         throw ex
                     }
                 }
+                Log.i("CoroutineBRTest", "Broadcast executed")
                 broadcastExecuted.countDown()
             }
         }
@@ -66,6 +70,7 @@
 
     @MediumTest
     @Test
+    @FlakyTest
     fun onReceive() {
         val broadcastReceiver = TestBroadcast()
         context.registerReceiver(