Merge "Add limit to dynamic expression nodes" into androidx-main
diff --git a/camera/camera-core/api/current.txt b/camera/camera-core/api/current.txt
index 729c27e..a768062 100644
--- a/camera/camera-core/api/current.txt
+++ b/camera/camera-core/api/current.txt
@@ -139,6 +139,7 @@
     method public androidx.camera.core.CameraSelector? getAvailableCamerasLimiter(androidx.camera.core.CameraSelector?);
     method public java.util.concurrent.Executor? getCameraExecutor(java.util.concurrent.Executor?);
     method public long getCameraOpenRetryMaxTimeoutInMillisWhileResuming();
+    method @SuppressCompatibility @androidx.camera.core.ExperimentalRetryPolicy public androidx.camera.core.RetryPolicy getCameraProviderInitRetryPolicy();
     method public int getMinimumLoggingLevel();
     method public android.os.Handler? getSchedulerHandler(android.os.Handler?);
   }
@@ -149,6 +150,7 @@
     method public androidx.camera.core.CameraXConfig.Builder setAvailableCamerasLimiter(androidx.camera.core.CameraSelector);
     method public androidx.camera.core.CameraXConfig.Builder setCameraExecutor(java.util.concurrent.Executor);
     method public androidx.camera.core.CameraXConfig.Builder setCameraOpenRetryMaxTimeoutInMillisWhileResuming(long);
+    method @SuppressCompatibility @androidx.camera.core.ExperimentalRetryPolicy public androidx.camera.core.CameraXConfig.Builder setCameraProviderInitRetryPolicy(androidx.camera.core.RetryPolicy);
     method public androidx.camera.core.CameraXConfig.Builder setMinimumLoggingLevel(@IntRange(from=android.util.Log.DEBUG, to=android.util.Log.ERROR) int);
     method public androidx.camera.core.CameraXConfig.Builder setSchedulerHandler(android.os.Handler);
   }
@@ -203,6 +205,9 @@
   @SuppressCompatibility @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalLensFacing {
   }
 
+  @SuppressCompatibility @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalRetryPolicy {
+  }
+
   @SuppressCompatibility @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalUseCaseApi {
   }
 
@@ -512,6 +517,47 @@
     method public int getRotationDegrees();
   }
 
+  @SuppressCompatibility @RequiresApi(21) @androidx.camera.core.ExperimentalRetryPolicy public interface RetryPolicy {
+    method public static long getDefaultRetryTimeoutInMillis();
+    method public default long getTimeoutInMillis();
+    method public androidx.camera.core.RetryPolicy.RetryResponse shouldRetry(androidx.camera.core.RetryPolicy.ExecutionState);
+    field public static final androidx.camera.core.RetryPolicy DEFAULT;
+    field public static final androidx.camera.core.RetryPolicy NEVER;
+    field public static final androidx.camera.core.RetryPolicy RETRY_UNAVAILABLE_CAMERA;
+  }
+
+  @SuppressCompatibility @androidx.camera.core.ExperimentalRetryPolicy public static final class RetryPolicy.Builder {
+    ctor public RetryPolicy.Builder(androidx.camera.core.RetryPolicy);
+    method public androidx.camera.core.RetryPolicy build();
+    method public androidx.camera.core.RetryPolicy.Builder setTimeoutInMillis(long);
+  }
+
+  @SuppressCompatibility @androidx.camera.core.ExperimentalRetryPolicy public static interface RetryPolicy.ExecutionState {
+    method public Throwable? getCause();
+    method public long getExecutedTimeInMillis();
+    method public int getNumOfAttempts();
+    method public int getStatus();
+    field public static final int STATUS_CAMERA_UNAVAILABLE = 2; // 0x2
+    field public static final int STATUS_CONFIGURATION_FAIL = 1; // 0x1
+    field public static final int STATUS_UNKNOWN_ERROR = 0; // 0x0
+  }
+
+  @SuppressCompatibility @androidx.camera.core.ExperimentalRetryPolicy public static final class RetryPolicy.RetryResponse {
+    method public static long getDefaultRetryDelayInMillis();
+    method public long getRetryDelayInMillis();
+    method public boolean shouldRetry();
+    field public static final androidx.camera.core.RetryPolicy.RetryResponse DEFAULT_DELAY_RETRY;
+    field public static final androidx.camera.core.RetryPolicy.RetryResponse MINI_DELAY_RETRY;
+    field public static final androidx.camera.core.RetryPolicy.RetryResponse NOT_RETRY;
+  }
+
+  @SuppressCompatibility @androidx.camera.core.ExperimentalRetryPolicy public static final class RetryPolicy.RetryResponse.Builder {
+    ctor public RetryPolicy.RetryResponse.Builder();
+    method public androidx.camera.core.RetryPolicy.RetryResponse build();
+    method public androidx.camera.core.RetryPolicy.RetryResponse.Builder setRetryDelayInMillis(@IntRange(from=100, to=2000) long);
+    method public androidx.camera.core.RetryPolicy.RetryResponse.Builder setShouldRetry(boolean);
+  }
+
   @RequiresApi(21) public class SurfaceOrientedMeteringPointFactory extends androidx.camera.core.MeteringPointFactory {
     ctor public SurfaceOrientedMeteringPointFactory(float, float);
     ctor public SurfaceOrientedMeteringPointFactory(float, float, androidx.camera.core.UseCase);
diff --git a/camera/camera-core/api/restricted_current.txt b/camera/camera-core/api/restricted_current.txt
index 729c27e..a768062 100644
--- a/camera/camera-core/api/restricted_current.txt
+++ b/camera/camera-core/api/restricted_current.txt
@@ -139,6 +139,7 @@
     method public androidx.camera.core.CameraSelector? getAvailableCamerasLimiter(androidx.camera.core.CameraSelector?);
     method public java.util.concurrent.Executor? getCameraExecutor(java.util.concurrent.Executor?);
     method public long getCameraOpenRetryMaxTimeoutInMillisWhileResuming();
+    method @SuppressCompatibility @androidx.camera.core.ExperimentalRetryPolicy public androidx.camera.core.RetryPolicy getCameraProviderInitRetryPolicy();
     method public int getMinimumLoggingLevel();
     method public android.os.Handler? getSchedulerHandler(android.os.Handler?);
   }
@@ -149,6 +150,7 @@
     method public androidx.camera.core.CameraXConfig.Builder setAvailableCamerasLimiter(androidx.camera.core.CameraSelector);
     method public androidx.camera.core.CameraXConfig.Builder setCameraExecutor(java.util.concurrent.Executor);
     method public androidx.camera.core.CameraXConfig.Builder setCameraOpenRetryMaxTimeoutInMillisWhileResuming(long);
+    method @SuppressCompatibility @androidx.camera.core.ExperimentalRetryPolicy public androidx.camera.core.CameraXConfig.Builder setCameraProviderInitRetryPolicy(androidx.camera.core.RetryPolicy);
     method public androidx.camera.core.CameraXConfig.Builder setMinimumLoggingLevel(@IntRange(from=android.util.Log.DEBUG, to=android.util.Log.ERROR) int);
     method public androidx.camera.core.CameraXConfig.Builder setSchedulerHandler(android.os.Handler);
   }
@@ -203,6 +205,9 @@
   @SuppressCompatibility @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalLensFacing {
   }
 
+  @SuppressCompatibility @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalRetryPolicy {
+  }
+
   @SuppressCompatibility @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalUseCaseApi {
   }
 
@@ -512,6 +517,47 @@
     method public int getRotationDegrees();
   }
 
+  @SuppressCompatibility @RequiresApi(21) @androidx.camera.core.ExperimentalRetryPolicy public interface RetryPolicy {
+    method public static long getDefaultRetryTimeoutInMillis();
+    method public default long getTimeoutInMillis();
+    method public androidx.camera.core.RetryPolicy.RetryResponse shouldRetry(androidx.camera.core.RetryPolicy.ExecutionState);
+    field public static final androidx.camera.core.RetryPolicy DEFAULT;
+    field public static final androidx.camera.core.RetryPolicy NEVER;
+    field public static final androidx.camera.core.RetryPolicy RETRY_UNAVAILABLE_CAMERA;
+  }
+
+  @SuppressCompatibility @androidx.camera.core.ExperimentalRetryPolicy public static final class RetryPolicy.Builder {
+    ctor public RetryPolicy.Builder(androidx.camera.core.RetryPolicy);
+    method public androidx.camera.core.RetryPolicy build();
+    method public androidx.camera.core.RetryPolicy.Builder setTimeoutInMillis(long);
+  }
+
+  @SuppressCompatibility @androidx.camera.core.ExperimentalRetryPolicy public static interface RetryPolicy.ExecutionState {
+    method public Throwable? getCause();
+    method public long getExecutedTimeInMillis();
+    method public int getNumOfAttempts();
+    method public int getStatus();
+    field public static final int STATUS_CAMERA_UNAVAILABLE = 2; // 0x2
+    field public static final int STATUS_CONFIGURATION_FAIL = 1; // 0x1
+    field public static final int STATUS_UNKNOWN_ERROR = 0; // 0x0
+  }
+
+  @SuppressCompatibility @androidx.camera.core.ExperimentalRetryPolicy public static final class RetryPolicy.RetryResponse {
+    method public static long getDefaultRetryDelayInMillis();
+    method public long getRetryDelayInMillis();
+    method public boolean shouldRetry();
+    field public static final androidx.camera.core.RetryPolicy.RetryResponse DEFAULT_DELAY_RETRY;
+    field public static final androidx.camera.core.RetryPolicy.RetryResponse MINI_DELAY_RETRY;
+    field public static final androidx.camera.core.RetryPolicy.RetryResponse NOT_RETRY;
+  }
+
+  @SuppressCompatibility @androidx.camera.core.ExperimentalRetryPolicy public static final class RetryPolicy.RetryResponse.Builder {
+    ctor public RetryPolicy.RetryResponse.Builder();
+    method public androidx.camera.core.RetryPolicy.RetryResponse build();
+    method public androidx.camera.core.RetryPolicy.RetryResponse.Builder setRetryDelayInMillis(@IntRange(from=100, to=2000) long);
+    method public androidx.camera.core.RetryPolicy.RetryResponse.Builder setShouldRetry(boolean);
+  }
+
   @RequiresApi(21) public class SurfaceOrientedMeteringPointFactory extends androidx.camera.core.MeteringPointFactory {
     ctor public SurfaceOrientedMeteringPointFactory(float, float);
     ctor public SurfaceOrientedMeteringPointFactory(float, float, androidx.camera.core.UseCase);
diff --git a/camera/camera-core/build.gradle b/camera/camera-core/build.gradle
index 520f97c..28fa725 100644
--- a/camera/camera-core/build.gradle
+++ b/camera/camera-core/build.gradle
@@ -46,6 +46,7 @@
     annotationProcessor(libs.autoValue)
 
     testImplementation(libs.kotlinCoroutinesAndroid)
+    testImplementation(libs.kotlinCoroutinesTest)
     testImplementation(libs.kotlinStdlib)
     testImplementation(libs.testCore)
     testImplementation(libs.testRunner)
diff --git a/camera/camera-core/src/androidTest/java/androidx/camera/core/CameraXTest.java b/camera/camera-core/src/androidTest/java/androidx/camera/core/CameraXTest.java
index 08c4315..adb3f0e 100644
--- a/camera/camera-core/src/androidTest/java/androidx/camera/core/CameraXTest.java
+++ b/camera/camera-core/src/androidTest/java/androidx/camera/core/CameraXTest.java
@@ -26,6 +26,8 @@
 import androidx.camera.core.impl.CameraFactory;
 import androidx.camera.core.impl.utils.executor.CameraXExecutors;
 import androidx.camera.testing.fakes.FakeAppConfig;
+import androidx.camera.testing.fakes.FakeCamera;
+import androidx.camera.testing.fakes.FakeCameraInfoInternal;
 import androidx.camera.testing.impl.fakes.FakeCameraFactory;
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -47,6 +49,8 @@
 @SdkSuppress(minSdkVersion = 21)
 public final class CameraXTest {
 
+    private static final String CAMERA_ID_0 = "0";
+    private static final String CAMERA_ID_1 = "1";
     private Context mContext;
     private CameraXConfig.Builder mConfigBuilder;
 
@@ -126,10 +130,10 @@
 
     @Test
     public void init_withDifferentCameraXConfig() throws ExecutionException, InterruptedException {
-        CameraFactory cameraFactory0 = new FakeCameraFactory();
+        CameraFactory cameraFactory0 = createFakeCameraFactory();
         CameraFactory.Provider cameraFactoryProvider0 =
                 (ignored0, ignored1, ignored2, ignored3) -> cameraFactory0;
-        CameraFactory cameraFactory1 = new FakeCameraFactory();
+        CameraFactory cameraFactory1 = createFakeCameraFactory();
         CameraFactory.Provider cameraFactoryProvider1 =
                 (ignored0, ignored1, ignored2, ignored3) -> cameraFactory1;
 
@@ -227,4 +231,17 @@
         // Waits for the CameraX instance being shutdown successfully.
         cameraX.shutdown().get(10000, TimeUnit.MILLISECONDS);
     }
-}
+
+    private CameraFactory createFakeCameraFactory() {
+        FakeCameraFactory cameraFactory = new FakeCameraFactory();
+        cameraFactory.insertCamera(CameraSelector.LENS_FACING_BACK, CAMERA_ID_0,
+                () -> new FakeCamera(CAMERA_ID_0, null,
+                        new FakeCameraInfoInternal(CAMERA_ID_0, 0,
+                                CameraSelector.LENS_FACING_BACK)));
+        cameraFactory.insertCamera(CameraSelector.LENS_FACING_FRONT, CAMERA_ID_1,
+                () -> new FakeCamera(CAMERA_ID_1, null,
+                        new FakeCameraInfoInternal(CAMERA_ID_1, 0,
+                                CameraSelector.LENS_FACING_FRONT)));
+        return cameraFactory;
+    }
+}
\ No newline at end of file
diff --git a/camera/camera-core/src/androidTest/java/androidx/camera/core/imagecapture/Bitmap2JpegBytesTest.kt b/camera/camera-core/src/androidTest/java/androidx/camera/core/imagecapture/Bitmap2JpegBytesTest.kt
index 837f3fd..793802f 100644
--- a/camera/camera-core/src/androidTest/java/androidx/camera/core/imagecapture/Bitmap2JpegBytesTest.kt
+++ b/camera/camera-core/src/androidTest/java/androidx/camera/core/imagecapture/Bitmap2JpegBytesTest.kt
@@ -16,16 +16,21 @@
 
 package androidx.camera.core.imagecapture
 
+import android.graphics.Bitmap
 import android.graphics.BitmapFactory.decodeByteArray
+import android.graphics.ImageFormat
 import android.graphics.Matrix
 import android.graphics.Rect
+import android.os.Build
 import androidx.camera.core.imagecapture.Utils.CAMERA_CAPTURE_RESULT
 import androidx.camera.core.imagecapture.Utils.HEIGHT
 import androidx.camera.core.imagecapture.Utils.WIDTH
 import androidx.camera.core.processing.Packet
 import androidx.camera.testing.impl.ExifUtil.createExif
 import androidx.camera.testing.impl.TestImageUtil.createBitmap
+import androidx.camera.testing.impl.TestImageUtil.createGainmap
 import androidx.camera.testing.impl.TestImageUtil.createJpegBytes
+import androidx.camera.testing.impl.TestImageUtil.createJpegrBytes
 import androidx.camera.testing.impl.TestImageUtil.getAverageDiff
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SdkSuppress
@@ -43,6 +48,7 @@
 class Bitmap2JpegBytesTest {
 
     private val operation = Bitmap2JpegBytes()
+
     @Test
     fun process_verifyOutput() {
         // Arrange.
@@ -61,8 +67,49 @@
         val output = operation.apply(input)
 
         // Assert
-        val restoredBitmap = decodeByteArray(output.data, 0, output.data.size)
-        assertThat(getAverageDiff(bitmap, restoredBitmap)).isEqualTo(0)
+        assertThat(output.format).isEqualTo(ImageFormat.JPEG)
+        verifyOutputData(output, bitmap)
+    }
+
+    @SdkSuppress(minSdkVersion = 34)
+    @Test
+    fun process_withJpegR_verifyOutput() {
+        // Arrange.
+        val bitmap = createBitmap(WIDTH, HEIGHT).apply {
+            this.gainmap = createGainmap(WIDTH, HEIGHT)
+        }
+        assertThat(bitmap.hasGainmap()).isTrue()
+        val inputPacket = Packet.of(
+            bitmap,
+            createExif(createJpegrBytes(WIDTH, HEIGHT)),
+            Rect(0, 0, WIDTH, HEIGHT),
+            90,
+            Matrix(),
+            CAMERA_CAPTURE_RESULT
+        )
+        val input = Bitmap2JpegBytes.In.of(inputPacket, 100)
+
+        // Act.
+        val output = operation.apply(input)
+
+        // Assert
+        assertThat(output.format).isEqualTo(ImageFormat.JPEG_R)
+        verifyOutputData(output, bitmap)
+    }
+
+    private fun verifyOutputData(output: Packet<ByteArray>, expectedBitmap: Bitmap) {
         assertThat(output.cameraCaptureResult).isEqualTo(CAMERA_CAPTURE_RESULT)
+
+        // Verify bitmap content.
+        val restoredBitmap = decodeByteArray(output.data, 0, output.data.size)
+        assertThat(getAverageDiff(expectedBitmap, restoredBitmap)).isEqualTo(0)
+
+        // Verify gainmap content.
+        if (Build.VERSION.SDK_INT >= 34 && expectedBitmap.hasGainmap()) {
+            assertThat(restoredBitmap.hasGainmap()).isEqualTo(true)
+            val sourceGainmap = expectedBitmap.gainmap!!.gainmapContents
+            val restoredGainmap = restoredBitmap.gainmap!!.gainmapContents
+            assertThat(getAverageDiff(restoredGainmap, sourceGainmap)).isEqualTo(0)
+        }
     }
 }
diff --git a/camera/camera-core/src/androidTest/java/androidx/camera/core/imagecapture/ProcessingNodeDeviceTest.kt b/camera/camera-core/src/androidTest/java/androidx/camera/core/imagecapture/ProcessingNodeDeviceTest.kt
index 641fda4..88ffc8d 100644
--- a/camera/camera-core/src/androidTest/java/androidx/camera/core/imagecapture/ProcessingNodeDeviceTest.kt
+++ b/camera/camera-core/src/androidTest/java/androidx/camera/core/imagecapture/ProcessingNodeDeviceTest.kt
@@ -22,11 +22,13 @@
 import android.graphics.ImageFormat
 import android.graphics.Rect
 import android.os.Build
+import androidx.annotation.RequiresApi
 import androidx.camera.core.ImageCapture.OutputFileOptions
 import androidx.camera.core.ImageProxy
 import androidx.camera.core.imagecapture.Utils.CAMERA_CAPTURE_RESULT
 import androidx.camera.core.imagecapture.Utils.CROP_RECT
 import androidx.camera.core.imagecapture.Utils.EXIF_DESCRIPTION
+import androidx.camera.core.imagecapture.Utils.EXIF_GAINMAP_PATTERNS
 import androidx.camera.core.imagecapture.Utils.HEIGHT
 import androidx.camera.core.imagecapture.Utils.OUTPUT_FILE_OPTIONS
 import androidx.camera.core.imagecapture.Utils.SENSOR_TO_BUFFER
@@ -42,10 +44,15 @@
 import androidx.camera.core.processing.InternalImageProcessor
 import androidx.camera.testing.impl.AndroidUtil
 import androidx.camera.testing.impl.ExifUtil
+import androidx.camera.testing.impl.TestImageUtil.COLOR_GRAY
+import androidx.camera.testing.impl.TestImageUtil.COLOR_WHITE
 import androidx.camera.testing.impl.TestImageUtil.createA24ProblematicJpegByteArray
 import androidx.camera.testing.impl.TestImageUtil.createBitmap
+import androidx.camera.testing.impl.TestImageUtil.createBitmapWithGainmap
 import androidx.camera.testing.impl.TestImageUtil.createJpegBytes
 import androidx.camera.testing.impl.TestImageUtil.createJpegFakeImageProxy
+import androidx.camera.testing.impl.TestImageUtil.createJpegrBytes
+import androidx.camera.testing.impl.TestImageUtil.createJpegrFakeImageProxy
 import androidx.camera.testing.impl.TestImageUtil.createYuvFakeImageProxy
 import androidx.camera.testing.impl.TestImageUtil.getAverageDiff
 import androidx.camera.testing.impl.fakes.GrayscaleImageEffect
@@ -107,16 +114,34 @@
         cropRectEqualsImageRect_croppingNotInvoked(OUTPUT_FILE_OPTIONS)
     }
 
+    @SdkSuppress(minSdkVersion = 34)
+    @Test
+    fun processImageEqualsCropSize_croppingNotInvoked_whenFormatIsJpegr() = runBlocking {
+        cropRectEqualsImageRect_croppingNotInvoked_whenFormatIsJpegr(OUTPUT_FILE_OPTIONS)
+    }
+
     @Test
     fun processInMemoryInputPacket_callbackInvoked() = runBlocking {
         inMemoryInputPacket_callbackInvoked(null)
     }
 
+    @SdkSuppress(minSdkVersion = 34)
+    @Test
+    fun processInMemoryInputPacket_callbackInvoked_whenFormatIsJpegr() = runBlocking {
+        inMemoryInputPacket_callbackInvoked_withJpegrFormat(null)
+    }
+
     @Test
     fun processSaveJpegOnDisk_verifyOutput() = runBlocking {
         saveJpegOnDisk_verifyOutput(OUTPUT_FILE_OPTIONS)
     }
 
+    @SdkSuppress(minSdkVersion = 34)
+    @Test
+    fun processSaveJpegOnDisk_verifyOutput_whenFormatIsJpegr() = runBlocking {
+        saveJpegrOnDisk_verifyOutput(OUTPUT_FILE_OPTIONS)
+    }
+
     private suspend fun processYuvAndVerifyOutputSize(outputFileOptions: OutputFileOptions?) {
         // Arrange: create node with JPEG input and grayscale effect.
         val node = ProcessingNode(mainThreadExecutor())
@@ -228,6 +253,57 @@
         ).isAtMost(avgDiffTolerance)
     }
 
+    @RequiresApi(api = 34)
+    private suspend fun cropRectEqualsImageRect_croppingNotInvoked_whenFormatIsJpegr(
+        outputFileOptions: OutputFileOptions?
+    ) {
+        // Arrange: create a request with no cropping
+        val format = ImageFormat.JPEG_R
+        val node = ProcessingNode(mainThreadExecutor())
+        val nodeIn = ProcessingNode.In.of(format, format)
+        node.transform(nodeIn)
+        val takePictureCallback = FakeTakePictureCallback()
+
+        val processingRequest = ProcessingRequest(
+            { listOf() },
+            outputFileOptions,
+            Rect(0, 0, WIDTH, HEIGHT),
+            0,
+            /*jpegQuality=*/100,
+            SENSOR_TO_BUFFER,
+            takePictureCallback,
+            Futures.immediateFuture(null)
+        )
+        val imageIn = createJpegrFakeImageProxy(
+            CameraCaptureResultImageInfo(CAMERA_CAPTURE_RESULT),
+            createJpegrBytes(WIDTH, HEIGHT)
+        )
+
+        // Act.
+        val input = ProcessingNode.InputPacket.of(processingRequest, imageIn)
+        nodeIn.edge.accept(input)
+        val filePath = takePictureCallback.getOnDiskResult().savedUri!!.path!!
+
+        // Assert: restored image is not cropped.
+        val restoredBitmap = BitmapFactory.decodeFile(filePath)
+        assertThat(
+            getAverageDiff(
+                createBitmap(WIDTH, HEIGHT),
+                restoredBitmap
+            )
+        ).isAtMost(avgDiffTolerance)
+
+        // Assert: JPEG/R related info when format is JPEG/R.
+        assertThat(restoredBitmap.hasGainmap()).isTrue()
+        val gainmapContents = restoredBitmap.gainmap!!.gainmapContents
+        assertThat(
+            getAverageDiff(
+                createBitmapWithGainmap(WIDTH, HEIGHT).gainmap!!.gainmapContents,
+                gainmapContents
+            )
+        ).isAtMost(avgDiffTolerance)
+    }
+
     private suspend fun inMemoryInputPacket_callbackInvoked(outputFileOptions: OutputFileOptions?) {
         // Arrange.
         val node = ProcessingNode(mainThreadExecutor())
@@ -263,6 +339,47 @@
         assertThat(imageOut.imageInfo.timestamp).isEqualTo(TIMESTAMP)
     }
 
+    @RequiresApi(api = 34)
+    private suspend fun inMemoryInputPacket_callbackInvoked_withJpegrFormat(
+        outputFileOptions: OutputFileOptions?
+    ) {
+        // Arrange.
+        val format = ImageFormat.JPEG_R
+        val node = ProcessingNode(mainThreadExecutor())
+        val nodeIn = ProcessingNode.In.of(format, format)
+        node.transform(nodeIn)
+        val takePictureCallback = FakeTakePictureCallback()
+
+        val processingRequest = ProcessingRequest(
+            { listOf() },
+            outputFileOptions,
+            Rect(0, 0, WIDTH, HEIGHT),
+            0,
+            /*jpegQuality=*/100,
+            SENSOR_TO_BUFFER,
+            takePictureCallback,
+            Futures.immediateFuture(null)
+        )
+        val imageIn = createJpegrFakeImageProxy(
+            CameraCaptureResultImageInfo(CAMERA_CAPTURE_RESULT),
+            createJpegrBytes(WIDTH, HEIGHT)
+        )
+
+        // Act.
+        val input = ProcessingNode.InputPacket.of(processingRequest, imageIn)
+        nodeIn.edge.accept(input)
+
+        // Assert: the output image is identical to the input.
+        val imageOut = takePictureCallback.getInMemoryResult()
+        assertThat(imageOut.format).isEqualTo(ImageFormat.JPEG_R)
+        val restoredJpegByteArray = jpegImageToJpegByteArray(imageOut)
+        val expectedJpegByteArray = createJpegrBytes(WIDTH, HEIGHT)
+        assertThat(getAverageDiff(expectedJpegByteArray, restoredJpegByteArray))
+            .isAtMost(avgDiffTolerance)
+        assertThat(expectedJpegByteArray.size).isEqualTo(restoredJpegByteArray.size)
+        assertThat(imageOut.imageInfo.timestamp).isEqualTo(TIMESTAMP)
+    }
+
     private suspend fun saveJpegOnDisk_verifyOutput(outputFileOptions: OutputFileOptions?) {
         // Arrange: create a on-disk processing request.
         val node = ProcessingNode(mainThreadExecutor())
@@ -294,7 +411,6 @@
 
         // Assert: image content is cropped correctly
         val bitmap = BitmapFactory.decodeFile(filePath)
-
         assertThat(getAverageDiff(bitmap, Rect(0, 0, 320, 240), Color.BLUE)).isAtMost(
             avgDiffTolerance
         )
@@ -306,6 +422,62 @@
         assertThat(exif.description).isEqualTo(EXIF_DESCRIPTION)
     }
 
+    @RequiresApi(api = 34)
+    private suspend fun saveJpegrOnDisk_verifyOutput(outputFileOptions: OutputFileOptions?) {
+        // Arrange: create a on-disk processing request.
+        val format = ImageFormat.JPEG_R
+        val node = ProcessingNode(mainThreadExecutor())
+        val nodeIn = ProcessingNode.In.of(format, format)
+        node.transform(nodeIn)
+        val takePictureCallback = FakeTakePictureCallback()
+        val jpegBytes = ExifUtil.updateExif(createJpegrBytes(640, 480)) {
+            it.description = EXIF_DESCRIPTION
+        }
+        val imageIn = createJpegrFakeImageProxy(
+            CameraCaptureResultImageInfo(CAMERA_CAPTURE_RESULT),
+            jpegBytes
+        )
+        val processingRequest = ProcessingRequest(
+            { listOf() },
+            outputFileOptions,
+            CROP_RECT,
+            0,
+            /*jpegQuality=*/100,
+            SENSOR_TO_BUFFER,
+            takePictureCallback,
+            Futures.immediateFuture(null)
+        )
+
+        // Act: send input to the edge and wait for the saved URI
+        val input = ProcessingNode.InputPacket.of(processingRequest, imageIn)
+        nodeIn.edge.accept(input)
+        val filePath = takePictureCallback.getOnDiskResult().savedUri!!.path!!
+
+        // Assert: image content is cropped correctly
+        val bitmap = BitmapFactory.decodeFile(filePath)
+        assertThat(getAverageDiff(bitmap, Rect(0, 0, 320, 240), Color.BLUE))
+            .isAtMost(avgDiffTolerance)
+        assertThat(getAverageDiff(bitmap, Rect(321, 0, WIDTH, 240), Color.YELLOW))
+            .isAtMost(avgDiffTolerance)
+
+        // Assert: JPEG/R related info when format is JPEG/R.
+        assertThat(bitmap.hasGainmap()).isTrue()
+        val gainmapContents = bitmap.gainmap!!.gainmapContents
+        assertThat(getAverageDiff(gainmapContents, Rect(0, 0, WIDTH, 120), COLOR_GRAY))
+            .isAtMost(avgDiffTolerance)
+        assertThat(getAverageDiff(gainmapContents, Rect(0, 121, WIDTH, 240), COLOR_WHITE))
+            .isAtMost(avgDiffTolerance)
+
+        // Assert: Exif info is saved correctly.
+        val exif = Exif.createFromFileString(filePath)
+        assertThat(exif.description).isEqualTo(EXIF_DESCRIPTION)
+        val exifMetadata = exif.metadata
+        assertThat(exifMetadata).isNotNull()
+        for (pattern in EXIF_GAINMAP_PATTERNS) {
+            assertThat(exifMetadata).contains(pattern)
+        }
+    }
+
     @Test
     fun canFixIncorrectJpegMetadataForA24Device(): Unit = runBlocking {
         // Arrange.
diff --git a/camera/camera-core/src/androidTest/java/androidx/camera/core/imagecapture/Utils.kt b/camera/camera-core/src/androidTest/java/androidx/camera/core/imagecapture/Utils.kt
index d48e68b..8138f46 100644
--- a/camera/camera-core/src/androidTest/java/androidx/camera/core/imagecapture/Utils.kt
+++ b/camera/camera-core/src/androidTest/java/androidx/camera/core/imagecapture/Utils.kt
@@ -47,4 +47,9 @@
         .also {
         it.timestamp = TIMESTAMP
     }
+    val EXIF_GAINMAP_PATTERNS = listOf(
+        "xmlns:hdrgm=\"http://ns.adobe.com/hdr-gain-map/",
+        "hdrgm:Version=",
+        "Item:Semantic=\"GainMap\"",
+    )
 }
diff --git a/camera/camera-core/src/androidTest/java/androidx/camera/core/internal/utils/ImageUtilDeviceTest.kt b/camera/camera-core/src/androidTest/java/androidx/camera/core/internal/utils/ImageUtilDeviceTest.kt
index 6b85356..b18075b 100644
--- a/camera/camera-core/src/androidTest/java/androidx/camera/core/internal/utils/ImageUtilDeviceTest.kt
+++ b/camera/camera-core/src/androidTest/java/androidx/camera/core/internal/utils/ImageUtilDeviceTest.kt
@@ -17,16 +17,20 @@
 package androidx.camera.core.internal.utils
 
 import android.graphics.Bitmap
+import android.graphics.BitmapFactory
 import android.graphics.ImageFormat
 import android.graphics.Matrix
 import android.graphics.PixelFormat
+import android.graphics.Rect
 import androidx.camera.core.ImageProcessingUtil
 import androidx.camera.core.ImageProxy
 import androidx.camera.core.ImageReaderProxys
 import androidx.camera.core.ImmutableImageInfo
 import androidx.camera.core.SafeCloseImageReaderProxy
 import androidx.camera.core.impl.TagBundle
+import androidx.camera.core.internal.utils.ImageUtil.CodecFailedException
 import androidx.camera.testing.impl.TestImageUtil
+import androidx.camera.testing.impl.TestImageUtil.getAverageDiff
 import androidx.camera.testing.impl.fakes.FakeImageProxy
 import androidx.camera.testing.impl.fakes.FakeJpegPlaneProxy
 import androidx.camera.testing.impl.fakes.FakePlaneProxy
@@ -34,13 +38,18 @@
 import androidx.test.filters.SdkSuppress
 import androidx.test.filters.SmallTest
 import androidx.testutils.assertThrows
-import com.google.common.truth.Truth
 import com.google.common.truth.Truth.assertThat
 import java.io.ByteArrayOutputStream
 import java.nio.ByteBuffer
 import org.junit.Test
 import org.junit.runner.RunWith
 
+private const val WIDTH = 160
+private const val HEIGHT = 120
+private const val CROP_WIDTH = 100
+private const val CROP_HEIGHT = 100
+private const val DEFAULT_JPEG_QUALITY = 100
+
 /**
  * Unit tests for {@link ImageUtil}.
  */
@@ -49,9 +58,6 @@
 @SdkSuppress(minSdkVersion = 21)
 class ImageUtilDeviceTest {
 
-    private val WIDTH = 160
-    private val HEIGHT = 120
-
     @Test(expected = IllegalArgumentException::class)
     fun createBitmapWithWrongRowStride_throwsException() {
         // Arrange.
@@ -116,7 +122,7 @@
             HEIGHT
         )
         // Assert.
-        Truth.assertThat(TestImageUtil.getAverageDiff(original, restored)).isEqualTo(0)
+        assertThat(getAverageDiff(original, restored)).isEqualTo(0)
     }
 
     @Test(expected = java.lang.IllegalArgumentException::class)
@@ -176,27 +182,46 @@
         assertThat(fakeRgbaImageProxy).isNotNull()
 
         val bitmap = ImageUtil.createBitmapFromImageProxy(fakeRgbaImageProxy!!)
-
-        assertThat(bitmap.width).isEqualTo(WIDTH)
-        assertThat(bitmap.height).isEqualTo(HEIGHT)
-        assertThat(bitmap.byteCount).isEqualTo(76800)
+        verifyBitmapContents(bitmap)
     }
 
     @Test
     fun createBitmapFromImageProxy_jpeg() {
+        // Arrange.
         val jpegBytes = TestImageUtil.createJpegBytes(WIDTH, HEIGHT)
         val fakeJpegImageProxy = TestImageUtil.createJpegFakeImageProxy(jpegBytes)
 
+        // Assert: bitmap contents are matched.
         val bitmap = ImageUtil.createBitmapFromImageProxy(fakeJpegImageProxy)
+        verifyBitmapContents(bitmap)
 
-        assertThat(bitmap.width).isEqualTo(WIDTH)
-        assertThat(bitmap.height).isEqualTo(HEIGHT)
-        assertThat(bitmap.byteCount).isEqualTo(76800)
+        // Assert: jpeg bytes are matched.
+        val stream = ByteArrayOutputStream()
+        bitmap.compress(Bitmap.CompressFormat.JPEG, 100, stream)
+        val byteArray = stream.toByteArray()
+        assertThat(getAverageDiff(jpegBytes, byteArray)).isEqualTo(0)
+    }
+
+    @SdkSuppress(minSdkVersion = 34)
+    @Test
+    fun createBitmapFromImageProxy_jpegr() {
+        // Arrange.
+        val jpegBytes = TestImageUtil.createJpegrBytes(WIDTH, HEIGHT)
+        val fakeJpegrImageProxy = TestImageUtil.createJpegrFakeImageProxy(jpegBytes)
+        assertThat(fakeJpegrImageProxy.format).isEqualTo(ImageFormat.JPEG_R)
+
+        // Assert: bitmap contents are matched.
+        val bitmap = ImageUtil.createBitmapFromImageProxy(fakeJpegrImageProxy)
+        verifyBitmapContents(bitmap)
+
+        // Assert: gainmap contents are matched.
+        assertThat(bitmap.hasGainmap()).isTrue()
+        verifyBitmapContents(bitmap.gainmap!!.gainmapContents)
 
         val stream = ByteArrayOutputStream()
         bitmap.compress(Bitmap.CompressFormat.JPEG, 100, stream)
         val byteArray = stream.toByteArray()
-        assertThat(TestImageUtil.getAverageDiff(jpegBytes, byteArray)).isEqualTo(0)
+        assertThat(getAverageDiff(jpegBytes, byteArray)).isEqualTo(0)
     }
 
     @Test
@@ -230,4 +255,42 @@
             ImageUtil.createBitmapFromImageProxy(image)
         }
     }
+
+    @SdkSuppress(minSdkVersion = 34)
+    @Test
+    @Throws(CodecFailedException::class)
+    fun canCropJpegByteArrayOfJpegr() {
+        // Since Robolectric does not support JPEG/R related bitmap operations, uses real device
+        // for testing.
+
+        // Arrange.
+        val jpegBytes = TestImageUtil.createJpegrBytes(WIDTH, HEIGHT)
+        val fakeJpegrImageProxy = TestImageUtil.createJpegrFakeImageProxy(jpegBytes)
+        assertThat(fakeJpegrImageProxy.format).isEqualTo(ImageFormat.JPEG_R)
+
+        // Act.
+        val byteArray = ImageUtil.jpegImageToJpegByteArray(
+            fakeJpegrImageProxy,
+            Rect(0, 0, CROP_WIDTH, CROP_HEIGHT),
+            DEFAULT_JPEG_QUALITY
+        )
+
+        // Assert.
+        val bitmap = BitmapFactory.decodeByteArray(byteArray, 0, byteArray.size)
+        verifyBitmapContents(bitmap, CROP_WIDTH, CROP_HEIGHT, CROP_WIDTH * CROP_HEIGHT * 4)
+        assertThat(bitmap.hasGainmap()).isTrue()
+        val gainmapContents = bitmap.gainmap!!.gainmapContents
+        verifyBitmapContents(gainmapContents, CROP_WIDTH, CROP_HEIGHT, CROP_WIDTH * CROP_HEIGHT * 4)
+    }
+
+    private fun verifyBitmapContents(
+        bitmap: Bitmap,
+        expectedWidth: Int = WIDTH,
+        expectedHeight: Int = HEIGHT,
+        expectedByteCount: Int = 76800
+    ) {
+        assertThat(bitmap.width).isEqualTo(expectedWidth)
+        assertThat(bitmap.height).isEqualTo(expectedHeight)
+        assertThat(bitmap.byteCount).isEqualTo(expectedByteCount)
+    }
 }
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/CameraX.java b/camera/camera-core/src/main/java/androidx/camera/core/CameraX.java
index 4f52d28..9401862 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/CameraX.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/CameraX.java
@@ -16,6 +16,10 @@
 
 package androidx.camera.core;
 
+import static androidx.camera.core.CameraUnavailableException.CAMERA_ERROR;
+import static androidx.camera.core.impl.CameraValidator.CameraIdListIncorrectException;
+import static androidx.camera.core.impl.CameraValidator.validateCameras;
+
 import android.app.Application;
 import android.content.ComponentName;
 import android.content.Context;
@@ -32,14 +36,15 @@
 import androidx.annotation.MainThread;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.annotation.OptIn;
 import androidx.annotation.RequiresApi;
 import androidx.annotation.RestrictTo;
 import androidx.annotation.RestrictTo.Scope;
 import androidx.camera.core.impl.CameraDeviceSurfaceManager;
 import androidx.camera.core.impl.CameraFactory;
+import androidx.camera.core.impl.CameraProviderExecutionState;
 import androidx.camera.core.impl.CameraRepository;
 import androidx.camera.core.impl.CameraThreadConfig;
-import androidx.camera.core.impl.CameraValidator;
 import androidx.camera.core.impl.MetadataHolderService;
 import androidx.camera.core.impl.UseCaseConfigFactory;
 import androidx.camera.core.impl.utils.ContextUtil;
@@ -60,13 +65,12 @@
  *
  */
 @RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
+@OptIn(markerClass = ExperimentalRetryPolicy.class)
 @MainThread
 @RestrictTo(Scope.LIBRARY_GROUP)
 public final class CameraX {
     private static final String TAG = "CameraX";
     private static final String RETRY_TOKEN = "retry_token";
-    private static final long WAIT_INITIALIZED_TIMEOUT_MILLIS = 3000L;
-    private static final long RETRY_SLEEP_MILLIS = 500L;
 
     final CameraRepository mCameraRepository = new CameraRepository();
     private final Object mInitializeLock = new Object();
@@ -80,6 +84,7 @@
     private CameraFactory mCameraFactory;
     private CameraDeviceSurfaceManager mSurfaceManager;
     private UseCaseConfigFactory mDefaultConfigFactory;
+    private RetryPolicy mRetryPolicy;
     // TODO(b/161302102): Remove the stored context. Only make use of the context within the
     //  called method.
     private Context mAppContext;
@@ -129,6 +134,8 @@
         mMinLogLevel = mCameraXConfig.retrieveOption(CameraXConfig.OPTION_MIN_LOGGING_LEVEL, null);
         increaseMinLogLevelReference(mMinLogLevel);
 
+        mRetryPolicy = new RetryPolicy.Builder(
+                mCameraXConfig.getCameraProviderInitRetryPolicy()).build();
         mInitInternalFuture = initInternal(context);
     }
 
@@ -265,7 +272,7 @@
             mInitState = InternalInitState.INITIALIZING;
             return CallbackToFutureAdapter.getFuture(
                     completer -> {
-                        initAndRetryRecursively(mCameraExecutor, SystemClock.elapsedRealtime(),
+                        initAndRetryRecursively(mCameraExecutor, SystemClock.elapsedRealtime(), 1,
                                 context, completer);
                         return "CameraX initInternal";
                     });
@@ -278,6 +285,7 @@
     private void initAndRetryRecursively(
             @NonNull Executor cameraExecutor,
             long startMs,
+            int attemptCount,
             @NonNull Context context,
             @NonNull CallbackToFutureAdapter.Completer<Void> completer) {
         cameraExecutor.execute(() -> {
@@ -335,33 +343,41 @@
                 mCameraRepository.init(mCameraFactory);
 
                 // Please ensure only validate the camera at the last of the initialization.
-                CameraValidator.validateCameras(mAppContext, mCameraRepository,
-                        availableCamerasLimiter);
+                validateCameras(mAppContext, mCameraRepository, availableCamerasLimiter);
 
                 // Set completer to null if the init was successful.
                 setStateToInitialized();
                 completer.set(null);
-            } catch (CameraValidator.CameraIdListIncorrectException | InitializationException
-                    | RuntimeException e) {
-                if (SystemClock.elapsedRealtime() - startMs
-                        < WAIT_INITIALIZED_TIMEOUT_MILLIS - RETRY_SLEEP_MILLIS) {
+            } catch (CameraIdListIncorrectException | InitializationException
+                     | RuntimeException e) {
+                RetryPolicy.RetryResponse response = mRetryPolicy.shouldRetry(
+                        new CameraProviderExecutionState(startMs, attemptCount, e));
+                if (response.shouldRetry() && attemptCount < Integer.MAX_VALUE) {
                     Logger.w(TAG, "Retry init. Start time " + startMs + " current time "
                             + SystemClock.elapsedRealtime(), e);
                     HandlerCompat.postDelayed(mSchedulerHandler, () -> initAndRetryRecursively(
-                            cameraExecutor, startMs, mAppContext, completer), RETRY_TOKEN,
-                            RETRY_SLEEP_MILLIS);
+                            cameraExecutor, startMs, attemptCount + 1, mAppContext,
+                            completer), RETRY_TOKEN, response.getRetryDelayInMillis());
 
                 } else {
                     synchronized (mInitializeLock) {
                         mInitState = InternalInitState.INITIALIZING_ERROR;
                     }
-                    if (e instanceof CameraValidator.CameraIdListIncorrectException) {
-                        // Ignore the camera validation failure if it reaches the maximum retry
-                        // time. Set complete.
-                        Logger.e(TAG, "The device might underreport the amount of the cameras. "
-                                + "Finish the initialize task since we are already reaching the "
-                                + "maximum number of retries.");
+                    if (response.shouldCompleteWithoutFailure()) {
+                        // Ignoring camera failure for compatibility reasons. Initialization will
+                        // be marked as complete, but some camera features might be unavailable.
+                        setStateToInitialized();
                         completer.set(null);
+                    } else if (e instanceof CameraIdListIncorrectException) {
+                        String message = "Device reporting less cameras than anticipated. On real"
+                                + " devices: Retrying initialization might resolve temporary "
+                                + "camera errors. On emulators: Ensure virtual camera "
+                                + "configuration matches supported camera features as reported by"
+                                + " PackageManager#hasSystemFeature. Available cameras: "
+                                + ((CameraIdListIncorrectException) e).getAvailableCameraCount();
+                        Logger.e(TAG, message, e);
+                        completer.setException(new InitializationException(
+                                new CameraUnavailableException(CAMERA_ERROR, message)));
                     } else if (e instanceof InitializationException) {
                         completer.setException(e);
                     } else {
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/CameraXConfig.java b/camera/camera-core/src/main/java/androidx/camera/core/CameraXConfig.java
index 3a49499..fe25379 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/CameraXConfig.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/CameraXConfig.java
@@ -23,6 +23,7 @@
 import androidx.annotation.IntRange;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.annotation.OptIn;
 import androidx.annotation.RequiresApi;
 import androidx.annotation.RestrictTo;
 import androidx.annotation.RestrictTo.Scope;
@@ -35,6 +36,7 @@
 import androidx.camera.core.impl.UseCaseConfigFactory;
 import androidx.camera.core.internal.TargetConfig;
 
+import java.util.Objects;
 import java.util.UUID;
 import java.util.concurrent.Executor;
 
@@ -114,6 +116,12 @@
                     "camerax.core.appConfig.cameraOpenRetryMaxTimeoutInMillisWhileResuming",
                     long.class);
 
+    @OptIn(markerClass = ExperimentalRetryPolicy.class)
+    static final Option<RetryPolicy> OPTION_CAMERA_PROVIDER_INIT_RETRY_POLICY =
+            Option.create(
+                    "camerax.core.appConfig.cameraProviderInitRetryPolicy",
+                    RetryPolicy.class);
+
     static final long DEFAULT_OPTION_CAMERA_OPEN_RETRY_MAX_TIMEOUT_IN_MILLIS_WHILE_RESUMING = -1L;
 
     // *********************************************************************************************
@@ -210,6 +218,23 @@
                 DEFAULT_OPTION_CAMERA_OPEN_RETRY_MAX_TIMEOUT_IN_MILLIS_WHILE_RESUMING);
     }
 
+    /**
+     * Retrieves the {@link RetryPolicy} for the CameraProvider initialization. This policy
+     * determines whether to retry the CameraProvider initialization if it fails.
+     *
+     * @return The {@link RetryPolicy} to be used for the CameraProvider initialization. If not
+     * explicitly set, it defaults to {@link RetryPolicy#DEFAULT}.
+     *
+     * @see Builder#setCameraProviderInitRetryPolicy(RetryPolicy)
+     */
+    @NonNull
+    @ExperimentalRetryPolicy
+    public RetryPolicy getCameraProviderInitRetryPolicy() {
+        return Objects.requireNonNull(
+                mConfig.retrieveOption(OPTION_CAMERA_PROVIDER_INIT_RETRY_POLICY,
+                        RetryPolicy.DEFAULT));
+    }
+
     @RestrictTo(Scope.LIBRARY_GROUP)
     @NonNull
     @Override
@@ -413,6 +438,25 @@
             return this;
         }
 
+        /**
+         * Sets the {@link RetryPolicy} for the CameraProvider initialization. This policy
+         * determines whether to retry the CameraProvider initialization if it fails.
+         *
+         * <p>If not set, a default retry policy {@link RetryPolicy#DEFAULT} will be applied.
+         *
+         * @param retryPolicy The {@link RetryPolicy} to use for retrying the CameraProvider
+         *                    initialization.
+         * @return this builder.
+         */
+        @NonNull
+        @ExperimentalRetryPolicy
+        public Builder setCameraProviderInitRetryPolicy(@NonNull RetryPolicy retryPolicy) {
+            getMutableConfig().insertOption(
+                    OPTION_CAMERA_PROVIDER_INIT_RETRY_POLICY,
+                    retryPolicy);
+            return this;
+        }
+
         @NonNull
         private MutableConfig getMutableConfig() {
             return mMutableConfig;
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/ExperimentalRetryPolicy.java b/camera/camera-core/src/main/java/androidx/camera/core/ExperimentalRetryPolicy.java
new file mode 100644
index 0000000..20834c0
--- /dev/null
+++ b/camera/camera-core/src/main/java/androidx/camera/core/ExperimentalRetryPolicy.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2024 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.core;
+
+import static java.lang.annotation.RetentionPolicy.CLASS;
+
+import androidx.annotation.RequiresOptIn;
+
+import java.lang.annotation.Retention;
+
+/**
+ * Indicates that the annotated API uses the experimental {@link RetryPolicy} feature for CameraX
+ * initialization. This feature helps improve stability in scenarios where the device might
+ * temporarily underreport available cameras.
+ */
+@Retention(CLASS)
+@RequiresOptIn
+public @interface ExperimentalRetryPolicy {
+}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/ImageCapture.java b/camera/camera-core/src/main/java/androidx/camera/core/ImageCapture.java
index 400babe..2517c8a 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/ImageCapture.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/ImageCapture.java
@@ -16,7 +16,10 @@
 
 package androidx.camera.core;
 
+import static android.graphics.ImageFormat.JPEG_R;
+
 import static androidx.camera.core.CameraEffect.IMAGE_CAPTURE;
+import static androidx.camera.core.DynamicRange.HDR_UNSPECIFIED_10_BIT;
 import static androidx.camera.core.impl.ImageCaptureConfig.OPTION_BUFFER_FORMAT;
 import static androidx.camera.core.impl.ImageCaptureConfig.OPTION_CAPTURE_CONFIG_UNPACKER;
 import static androidx.camera.core.impl.ImageCaptureConfig.OPTION_DEFAULT_CAPTURE_CONFIG;
@@ -28,6 +31,7 @@
 import static androidx.camera.core.impl.ImageCaptureConfig.OPTION_IO_EXECUTOR;
 import static androidx.camera.core.impl.ImageCaptureConfig.OPTION_JPEG_COMPRESSION_QUALITY;
 import static androidx.camera.core.impl.ImageCaptureConfig.OPTION_MAX_RESOLUTION;
+import static androidx.camera.core.impl.ImageCaptureConfig.OPTION_OUTPUT_FORMAT;
 import static androidx.camera.core.impl.ImageCaptureConfig.OPTION_POSTVIEW_ENABLED;
 import static androidx.camera.core.impl.ImageCaptureConfig.OPTION_POSTVIEW_RESOLUTION_SELECTOR;
 import static androidx.camera.core.impl.ImageCaptureConfig.OPTION_SCREEN_FLASH;
@@ -288,6 +292,20 @@
     public static final int FLASH_TYPE_USE_TORCH_AS_FLASH = 1;
 
     /**
+     * Captures SDR image using the {@link ImageFormat#JPEG} image format.
+     */
+    @RestrictTo(Scope.LIBRARY_GROUP)
+    public static final int OUTPUT_FORMAT_JPEG = 0;
+
+    /**
+     * Captures Ultra HDR compressed images using the {@link ImageFormat#JPEG_R} image format.
+     * This format is backward compatible with SDR JPEG images and supports HDR rendering of
+     * content.
+     */
+    @RestrictTo(Scope.LIBRARY_GROUP)
+    public static final int OUTPUT_FORMAT_ULTRA_HDR = 1;
+
+    /**
      * Provides a static configuration with implementation-agnostic options.
      */
     @RestrictTo(Scope.LIBRARY_GROUP)
@@ -444,7 +462,11 @@
             builder.getMutableConfig().insertOption(OPTION_INPUT_FORMAT,
                     useSoftwareJpeg ? ImageFormat.YUV_420_888 : bufferFormat);
         } else {
-            if (useSoftwareJpeg) {
+            if (isOutputFormatUltraHdr(builder.getMutableConfig())) {
+                builder.getMutableConfig().insertOption(OPTION_INPUT_FORMAT, JPEG_R);
+                builder.getMutableConfig().insertOption(OPTION_INPUT_DYNAMIC_RANGE,
+                        HDR_UNSPECIFIED_10_BIT);
+            } else if (useSoftwareJpeg) {
                 builder.getMutableConfig().insertOption(OPTION_INPUT_FORMAT,
                         ImageFormat.YUV_420_888);
             } else {
@@ -481,6 +503,11 @@
         return false;
     }
 
+    private static boolean isOutputFormatUltraHdr(@NonNull MutableConfig config) {
+        return Objects.equals(config.retrieveOption(OPTION_OUTPUT_FORMAT, null),
+                OUTPUT_FORMAT_ULTRA_HDR);
+    }
+
     /**
      * Configures flash mode to CameraControlInternal once it is ready.
      */
@@ -812,6 +839,25 @@
     }
 
     /**
+     * Returns the output format setting.
+     *
+     * <p>If the output format was not provided to
+     * {@link ImageCapture.Builder#setOutputFormat(int)}, this will return the default of
+     * {@link #OUTPUT_FORMAT_JPEG}.
+     *
+     * @return the output format set for this {@code ImageCapture} use case.
+     *
+     * @see ImageCapture.Builder#setOutputFormat(int)
+     */
+    @RestrictTo(Scope.LIBRARY_GROUP)
+    @OutputFormat
+    @NonNull
+    public Integer getOutputFormat() {
+        return checkNotNull(getCurrentConfig().retrieveOption(OPTION_OUTPUT_FORMAT,
+                Defaults.DEFAULT_OUTPUT_FORMAT));
+    }
+
+    /**
      * Captures a new still image for in memory access.
      *
      * <p>The callback will be called only once for every invocation of this method. The listener
@@ -919,6 +965,28 @@
             }
             return false;
         }
+
+        @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+        @NonNull
+        @Override
+        public Set<Integer> getSupportedOutputFormats() {
+            Set<Integer> formats = new HashSet<>();
+            formats.add(OUTPUT_FORMAT_JPEG);
+            if (isUltraHdrSupported()) {
+                formats.add(OUTPUT_FORMAT_ULTRA_HDR);
+            }
+
+            return formats;
+        }
+
+        private boolean isUltraHdrSupported() {
+            if (mCameraInfo instanceof CameraInfoInternal) {
+                CameraInfoInternal cameraInfoInternal = (CameraInfoInternal) mCameraInfo;
+                return cameraInfoInternal.getSupportedOutputFormats().contains(JPEG_R);
+            }
+
+            return false;
+        }
     }
 
     @NonNull
@@ -1506,6 +1574,15 @@
     public @interface FlashType {
     }
 
+    /**
+     * The output format of the captured image.
+     */
+    @IntDef({OUTPUT_FORMAT_JPEG, OUTPUT_FORMAT_ULTRA_HDR})
+    @Retention(RetentionPolicy.SOURCE)
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    public @interface OutputFormat {
+    }
+
     /** Listener containing callbacks for image file I/O events. */
     public interface OnImageSavedCallback {
         /**
@@ -1595,8 +1672,8 @@
          * <p>The application is responsible for calling {@link ImageProxy#close()} to close the
          * image.
          *
-         * <p>The image is of format {@link ImageFormat#JPEG}, queryable via
-         * {@link ImageProxy#getFormat()}.
+         * <p>The image is of format {@link ImageFormat#JPEG} or {@link ImageFormat#JPEG_R},
+         * queryable via {@link ImageProxy#getFormat()}.
          *
          * <p>The image is provided as captured by the underlying {@link ImageReader} without
          * rotation applied. The value in {@code image.getImageInfo().getRotationDegrees()}
@@ -1691,7 +1768,6 @@
         void onCompleted();
     }
 
-
     /**
      * Interface to do the application changes required for screen flash operations.
      *
@@ -1768,6 +1844,7 @@
             implements ConfigProvider<ImageCaptureConfig> {
         private static final int DEFAULT_SURFACE_OCCUPANCY_PRIORITY = 4;
         private static final int DEFAULT_ASPECT_RATIO = AspectRatio.RATIO_4_3;
+        private static final int DEFAULT_OUTPUT_FORMAT = OUTPUT_FORMAT_JPEG;
 
         private static final ResolutionSelector DEFAULT_RESOLUTION_SELECTOR =
                 new ResolutionSelector.Builder().setAspectRatioStrategy(
@@ -1784,6 +1861,7 @@
                     .setSurfaceOccupancyPriority(DEFAULT_SURFACE_OCCUPANCY_PRIORITY)
                     .setTargetAspectRatio(DEFAULT_ASPECT_RATIO)
                     .setResolutionSelector(DEFAULT_RESOLUTION_SELECTOR)
+                    .setOutputFormat(DEFAULT_OUTPUT_FORMAT)
                     .setDynamicRange(DEFAULT_DYNAMIC_RANGE);
 
             DEFAULT_CONFIG = builder.getUseCaseConfig();
@@ -2220,7 +2298,13 @@
             if (bufferFormat != null) {
                 getMutableConfig().insertOption(OPTION_INPUT_FORMAT, bufferFormat);
             } else {
-                getMutableConfig().insertOption(OPTION_INPUT_FORMAT, ImageFormat.JPEG);
+                if (isOutputFormatUltraHdr(getMutableConfig())) {
+                    getMutableConfig().insertOption(OPTION_INPUT_FORMAT, JPEG_R);
+                    getMutableConfig().insertOption(OPTION_INPUT_DYNAMIC_RANGE,
+                            HDR_UNSPECIFIED_10_BIT);
+                } else {
+                    getMutableConfig().insertOption(OPTION_INPUT_FORMAT, ImageFormat.JPEG);
+                }
             }
 
             ImageCaptureConfig imageCaptureConfig = getUseCaseConfig();
@@ -2714,6 +2798,32 @@
             return this;
         }
 
+        /**
+         * Sets the output format of the captured image.
+         *
+         * <p>The supported output formats for capturing image depend on the capabilities of the
+         * camera. The supported output formats of the camera can be queried using
+         * {@link ImageCaptureCapabilities#getSupportedOutputFormats()}.
+         *
+         * <p>If not set, the output format will default to {@link #OUTPUT_FORMAT_JPEG}.
+         *
+         * <p>If an Ultra HDR output format is used, a {@link DynamicRange#HDR_UNSPECIFIED_10_BIT}
+         * will be used as the dynamic range of this use case.
+         *
+         * @param outputFormat The output image format. Value is {@link #OUTPUT_FORMAT_JPEG} or
+         *                     {@link #OUTPUT_FORMAT_ULTRA_HDR}.
+         * @return The current Builder.
+         *
+         * @see OutputFormat
+         * @see ImageCaptureCapabilities#getSupportedOutputFormats()
+         */
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @NonNull
+        public Builder setOutputFormat(@OutputFormat int outputFormat) {
+            getMutableConfig().insertOption(OPTION_OUTPUT_FORMAT, outputFormat);
+            return this;
+        }
+
         @RestrictTo(Scope.LIBRARY_GROUP)
         @Override
         @NonNull
@@ -2804,10 +2914,6 @@
         @NonNull
         @Override
         public Builder setDynamicRange(@NonNull DynamicRange dynamicRange) {
-            // TODO(b/280893255): ImageCapture currently does not support HDR.
-            if (!Objects.equals(DynamicRange.SDR, dynamicRange)) {
-                throw new UnsupportedOperationException("ImageCapture currently only supports SDR");
-            }
             getMutableConfig().insertOption(OPTION_INPUT_DYNAMIC_RANGE, dynamicRange);
             return this;
         }
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/ImageCaptureCapabilities.java b/camera/camera-core/src/main/java/androidx/camera/core/ImageCaptureCapabilities.java
index 9c05020..0469165 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/ImageCaptureCapabilities.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/ImageCaptureCapabilities.java
@@ -16,6 +16,11 @@
 
 package androidx.camera.core;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
+
+import java.util.Set;
+
 /**
  * ImageCaptureCapabilities is used to query {@link ImageCapture} use case capabilities on the
  * device.
@@ -41,4 +46,15 @@
      * the apps.
      */
     boolean isCaptureProcessProgressSupported();
+
+    /**
+     * Gets the supported {@link ImageCapture.OutputFormat} set.
+     *
+     * @return a set of supported output formats.
+     *
+     * @see ImageCapture.Builder#setOutputFormat(int)
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    @NonNull
+    Set<Integer> getSupportedOutputFormats();
 }
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/RetryPolicy.java b/camera/camera-core/src/main/java/androidx/camera/core/RetryPolicy.java
new file mode 100644
index 0000000..2fb6980
--- /dev/null
+++ b/camera/camera-core/src/main/java/androidx/camera/core/RetryPolicy.java
@@ -0,0 +1,521 @@
+/*
+ * 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.core;
+
+import androidx.annotation.IntDef;
+import androidx.annotation.IntRange;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
+import androidx.annotation.RestrictTo;
+import androidx.camera.core.impl.CameraProviderInitRetryPolicy;
+import androidx.camera.core.impl.RetryPolicyInternal;
+import androidx.camera.core.impl.TimeoutRetryPolicy;
+import androidx.core.util.Preconditions;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Defines a strategy for retrying upon initialization failures of the {@link CameraProvider}.
+ * When the initialization task is interrupted by an error or exception during execution, the
+ * task will determine whether to be rescheduled based on the specified {@link RetryPolicy}.
+ *
+ * <p>Several predefined retry policies are available:
+ * <ul>
+ * <li>{@link RetryPolicy#NEVER}: Never retries the initialization.</li>
+ * <li>{@link RetryPolicy#DEFAULT}: The default retry policy, which retries initialization up to
+ *   a maximum timeout of {@link #getDefaultRetryTimeoutInMillis()}, providing a general-purpose
+ *   approach for handling most errors.</li>
+ * <li>{@link RetryPolicy#RETRY_UNAVAILABLE_CAMERA}: The retry policy automatically retries upon
+ *   encountering errors like the {@link #DEFAULT} policy, and specifically designed to handle
+ *   potential device inconsistencies in reporting available camera instances. In cases where the
+ *   initial camera configuration fails due to the device underreporting (i.e., not accurately
+ *   disclosing all available camera instances) the policy proactively triggers a
+ *   reinitialization attempt. If the cameras are not successfully configured within
+ *   {@link #getDefaultRetryTimeoutInMillis()}, the initialization is considered a failure.</li>
+ * </ul>
+ * <p>If no {@link RetryPolicy} is specified, {@link RetryPolicy#DEFAULT} will be used as
+ * the default.
+ *
+ * <p>Examples of configuring the {@link RetryPolicy}:
+ * <pre>{@code
+ * // Configuring {@link RetryPolicy#RETRY_UNAVAILABLE_CAMERA} to the CameraXConfig
+ * ProcessCameraProvider.configureInstance(
+ *    CameraXConfig.Builder.fromConfig(Camera2Config.defaultConfig())
+ *      .setCameraProviderInitRetryPolicy(RetryPolicy.RETRY_UNAVAILABLE_CAMERA)
+ *      .build()
+ * );
+ * ...
+ * }</pre>
+ *
+ * <p>Configuring a customized {@link RetryPolicy}:
+ * <pre>{@code
+ * ProcessCameraProvider.configureInstance(CameraXConfig.Builder.fromConfig(
+ *         Camera2Config.defaultConfig()).setCameraProviderInitRetryPolicy(
+ *         executionState -> {
+ *             if (executionState.getExecutedTimeInMillis() > 10000L
+ *                     || executionState.getNumOfAttempts() > 10
+ *                     || executionState.getStatus() == ExecutionState.STATUS_CONFIGURATION_FAIL) {
+ *                 return RetryResponse.NOT_RETRY;
+ *             } else if (executionState.getStatus() == ExecutionState.STATUS_CAMERA_UNAVAILABLE) {
+ *                 return RetryResponse.DEFAULT_DELAY_RETRY;
+ *             } else {
+ *                 Log.d("CameraX", "Unknown error occur: " + executionState.getCause());
+ *                 return RetryResponse.MINI_DELAY_RETRY;
+ *             }
+ *         }).build());
+ * ...
+ * }</pre>
+ * In the second example, the custom retry policy retries the initialization up to 10 times or
+ * for a maximum of 10 seconds. If an unknown error occurs, the retry policy delays the next
+ * retry after a delay defined by {@link RetryResponse#MINI_DELAY_RETRY}. The retry process
+ * stops if the status is {@link ExecutionState#STATUS_CONFIGURATION_FAIL}. For
+ * {@link ExecutionState#STATUS_CAMERA_UNAVAILABLE}, the retry policy applies
+ * {@link RetryResponse#DEFAULT_DELAY_RETRY}.
+ */
+@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
+@ExperimentalRetryPolicy
+public interface RetryPolicy {
+
+    /**
+     * Default timeout in milliseconds of the initialization.
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    long DEFAULT_RETRY_TIMEOUT_IN_MILLIS = 6000L;
+
+    /**
+     * A retry policy that prevents any retry attempts and
+     * immediately halts the initialization upon encountering an error.
+     */
+    @NonNull
+    RetryPolicy NEVER = executionState -> RetryResponse.NOT_RETRY;
+
+    /**
+     * This retry policy increases initialization success by automatically retrying upon
+     * encountering errors.
+     *
+     * <p>By default, it continues retrying for a maximum of
+     * {@link #getDefaultRetryTimeoutInMillis()} milliseconds. To adjust the timeout duration to
+     * specific requirements, utilize {@link RetryPolicy.Builder}.
+     *
+     * <p>Example: Create a policy based on {@link #DEFAULT} with a 10-second timeout:
+     * <pre>{@code
+     * RetryPolicy customTimeoutPolicy =
+     *     new RetryPolicy.Builder(RetryPolicy.DEFAULT).setTimeoutInMillis(10000L).build();
+     * }</pre>
+     */
+    @NonNull
+    RetryPolicy DEFAULT = new CameraProviderInitRetryPolicy.Legacy(
+            getDefaultRetryTimeoutInMillis());
+
+    /**
+     * This retry policy automatically retries upon encountering errors and specifically
+     * designed to handle potential device inconsistencies in reporting available camera
+     * instances. If the initial camera configuration fails due to underreporting, the policy
+     * automatically attempts reinitialization.
+     * <p>By default, it perseveres for a maximum of {@link #getDefaultRetryTimeoutInMillis()}
+     * milliseconds before conceding initialization as unsuccessful. For finer control over the
+     * timeout duration, utilize {@link RetryPolicy.Builder}.
+     * <p>Example: Create a policy based on {@link #RETRY_UNAVAILABLE_CAMERA} with a 10-second
+     * timeout:
+     * <pre>{@code
+     * RetryPolicy customTimeoutPolicy = new RetryPolicy.Builder(
+     *     RetryPolicy.RETRY_UNAVAILABLE_CAMERA).setTimeoutInMillis(10000L).build();
+     * }</pre>
+     */
+    @NonNull
+    RetryPolicy RETRY_UNAVAILABLE_CAMERA =
+            new CameraProviderInitRetryPolicy(getDefaultRetryTimeoutInMillis());
+
+    /**
+     * Retrieves the default timeout value, in milliseconds, used for initialization retries.
+     *
+     * @return The default timeout duration, expressed in milliseconds. This value determines the
+     * maximum time to wait for a successful initialization before considering the process as
+     * failed.
+     */
+    static long getDefaultRetryTimeoutInMillis() {
+        return DEFAULT_RETRY_TIMEOUT_IN_MILLIS;
+    }
+
+    /**
+     * Determines whether to retry the initialization.
+     *
+     * @param executionState The information about the execution state of the camera initialization.
+     * @return A RetryResponse indicating whether to retry the initialization.
+     */
+    @NonNull
+    RetryResponse shouldRetry(@NonNull ExecutionState executionState);
+
+    /**
+     * Returns the maximum allowed retry duration in milliseconds. Initialization will
+     * be terminated if retries take longer than this timeout. A value of 0 indicates
+     * no timeout is enforced.
+     *
+     * <p>The default value is 0 (no timeout).
+     *
+     * @return The retry timeout in milliseconds.
+     */
+    default long getTimeoutInMillis() {
+        return 0;
+    }
+
+    /**
+     * A builder class for customizing RetryPolicy behavior.
+     *
+     * <p>Use the {@link #Builder(RetryPolicy)} to modify existing RetryPolicy instances,
+     * typically starting with the predefined options like {@link RetryPolicy#DEFAULT} or
+     * {@link RetryPolicy#RETRY_UNAVAILABLE_CAMERA}. The most common use is to set a custom timeout.
+     *
+     * <p>Example: Create a policy based on {@link RetryPolicy#DEFAULT} with a 10-second timeout:
+     * <pre>{@code
+     * new RetryPolicy.Builder(RetryPolicy.DEFAULT).setTimeoutInMillis(10000L).build()
+     * }</pre>
+     */
+    @ExperimentalRetryPolicy
+    final class Builder {
+
+        private final RetryPolicy mBasePolicy;
+        private long mTimeoutInMillis;
+
+        /**
+         * Creates a builder based on an existing {@link RetryPolicy}.
+         *
+         * <p>This allows you to start with a predefined policy and add further customizations.
+         * For example, set a timeout to prevent retries from continuing endlessly.
+         *
+         * @param basePolicy The RetryPolicy to use as a starting point.
+         */
+        public Builder(@NonNull RetryPolicy basePolicy) {
+            mBasePolicy = basePolicy;
+            mTimeoutInMillis = basePolicy.getTimeoutInMillis();
+        }
+
+        /**
+         * Sets a timeout in milliseconds. If retries exceed this duration, they will be
+         * terminated with {@link RetryPolicy.RetryResponse#NOT_RETRY}.
+         *
+         * @param timeoutInMillis The maximum duration for retries in milliseconds. A value of 0
+         *                        indicates no timeout.
+         * @return {@code this} for method chaining.
+         */
+        @NonNull
+        public Builder setTimeoutInMillis(long timeoutInMillis) {
+            mTimeoutInMillis = timeoutInMillis;
+            return this;
+        }
+
+        /**
+         * Creates the customized {@link RetryPolicy} instance.
+         *
+         * @return The new {@link RetryPolicy}.
+         */
+        @NonNull
+        public RetryPolicy build() {
+            if (mBasePolicy instanceof RetryPolicyInternal) {
+                return ((RetryPolicyInternal) mBasePolicy).copy(mTimeoutInMillis);
+            }
+            return new TimeoutRetryPolicy(mTimeoutInMillis, mBasePolicy);
+        }
+    }
+
+    /**
+     * Provides insights into the current execution state of the camera initialization process.
+     *
+     * <p>The ExecutionState empowers app developers to make informed decisions about retry
+     * strategies and fine-tune timeout settings. It also facilitates comprehensive app-level
+     * logging for tracking initialization success/failure rates and overall camera performance.
+     *
+     * <p>Key use cases:
+     * <ul>
+     *   <li>Determining whether to retry initialization based on specific error conditions.</li>
+     *   <li>Logging detailed execution information for debugging and analysis.</li>
+     *   <li>Monitoring retry success/failure rates to identify potential issues.</li>
+     * </ul>
+     */
+    @ExperimentalRetryPolicy
+    interface ExecutionState {
+
+        /**
+         * Defines the status codes for the {@link ExecutionState}.
+         */
+        @IntDef({STATUS_UNKNOWN_ERROR, STATUS_CONFIGURATION_FAIL, STATUS_CAMERA_UNAVAILABLE})
+        @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+        @Retention(RetentionPolicy.SOURCE)
+        @interface Status {
+        }
+
+        /**
+         * Indicates that the initialization encountered an unknown error. This error may be
+         * transient, and retrying may resolve the issue.
+         */
+        int STATUS_UNKNOWN_ERROR = 0;
+
+        /**
+         * Indicates that initialization of CameraX failed due to invalid customization options
+         * within the provided {@link CameraXConfig}. This error typically indicates a problem
+         * with the configuration settings and may require developer attention to resolve.
+         *
+         * <p>Possible Causes:
+         * <ul>
+         *  <li>Incorrect or incompatible settings within the {@link CameraXConfig}.</li>
+         *  <li>Conflicting options that prevent successful initialization.</li>
+         *  <li>Missing or invalid values for essential configuration parameters.</li>
+         * </ul>
+         *
+         * <p>To troubleshoot:
+         * Please see {@code getCause().getMessage()} for details, and review configuration of
+         * the {@link CameraXConfig} to identify potential errors or inconsistencies in the
+         * customization options.
+         *
+         * @see androidx.camera.lifecycle.ProcessCameraProvider#configureInstance(CameraXConfig)
+         * @see CameraXConfig.Builder
+         */
+        int STATUS_CONFIGURATION_FAIL = 1;
+
+        /**
+         * Indicates that the {@link CameraProvider} failed to initialize due to an unavailable
+         * camera.
+         * This error suggests a temporary issue with the device's cameras, and retrying may
+         * resolve the issue.
+         */
+        int STATUS_CAMERA_UNAVAILABLE = 2;
+
+        /**
+         * Retrieves the status of the most recently completed initialization attempt.
+         *
+         * @return The status code representing the outcome of the initialization.
+         */
+        @Status
+        int getStatus();
+
+        /**
+         * Gets the cause that occurred during the task execution, if any.
+         *
+         * @return The cause that occurred during the task execution, or null if there was no error.
+         */
+        @Nullable
+        Throwable getCause();
+
+        /**
+         * Gets the total execution time of the initialization task in milliseconds.
+         *
+         * @return The total execution time of the initialization task in milliseconds.
+         */
+        long getExecutedTimeInMillis();
+
+        /**
+         * Indicates the total number of attempts made to initialize the camera, including the
+         * current attempt. The count starts from 1.
+         *
+         * @return The total number of attempts made to initialize the camera.
+         */
+        int getNumOfAttempts();
+    }
+
+    /**
+     * Represents the outcome of a {@link RetryPolicy} decision.
+     */
+    @ExperimentalRetryPolicy
+    final class RetryResponse {
+
+        private static final long MINI_DELAY_MILLIS = 100L;
+        private static final long DEFAULT_DELAY_MILLIS = 500L;
+
+        /** A RetryResponse indicating that no further retries should be attempted. */
+        @NonNull
+        public static final RetryResponse NOT_RETRY = new RetryResponse(false, 0L);
+
+        /**
+         * A RetryResponse indicating that the initialization should be retried after the default
+         * delay (determined by {@link #getDefaultRetryDelayInMillis()}). This delay provides
+         * sufficient time for typical device recovery processes, balancing retry efficiency
+         * and minimizing user wait time.
+         */
+        @NonNull
+        public static final RetryResponse DEFAULT_DELAY_RETRY = new RetryResponse(true);
+
+        /**
+         * A RetryResponse indicating that the initialization should be retried after a minimum
+         * delay of 100 milliseconds.
+         *
+         * This short delay serves two purposes:
+         * <ul>
+         * <li>Reduced Latency: Minimizes the wait time for the camera to become available,
+         * improving user experience.
+         * <li>Camera Self-Recovery: Provides a brief window for the camera to potentially
+         * recover from temporary issues.
+         * </ul>
+         * This approach balances quick retries with potential self-recovery, aiming for the
+         * fastest possible camera restoration.
+         */
+        @NonNull
+        public static final RetryResponse MINI_DELAY_RETRY = new RetryResponse(true,
+                MINI_DELAY_MILLIS);
+
+        /**
+         * A RetryResponse indicating that the initialization should be considered complete
+         * without retrying. This response is intended for internal use and is not intended to
+         * trigger further retries. It represents the legacy behavior of not failing the
+         * initialization task for minor issues.
+         */
+        @RestrictTo(RestrictTo.Scope.LIBRARY)
+        @NonNull
+        public static RetryResponse COMPLETE_WITHOUT_FAILURE = new RetryResponse(false, 0, true);
+
+        /**
+         * Returns the recommended default delay to optimize retry attempts and camera recovery.
+         *
+         * @return The default delay value, carefully calibrated based on extensive lab testing to:
+         * <ul>
+         *  <li>Provide sufficient time for typical device recovery processes in common bad
+         *      camera states.</li>
+         *  <li>Strike an optimal balance between minimizing latency and avoiding excessive retries,
+         *      ensuring efficient camera initialization without unnecessary overhead.</li>
+         * </ul>
+         * This value represents the generally recommended delay for most scenarios, striking a
+         * balance between providing adequate time for camera recovery and maintaining a smooth
+         * user experience.
+         */
+        public static long getDefaultRetryDelayInMillis() {
+            return DEFAULT_DELAY_MILLIS;
+        }
+
+        private final long mDelayInMillis;
+        private final boolean mShouldRetry;
+        private final boolean mCompleteWithoutFailure;
+
+        private RetryResponse(boolean shouldRetry) {
+            this(shouldRetry, RetryResponse.getDefaultRetryDelayInMillis());
+        }
+
+        private RetryResponse(boolean shouldRetry, long delayInMillis) {
+            this(shouldRetry, delayInMillis, false);
+        }
+
+        /**
+         * Constructor for determining whether to retry the initialization.
+         *
+         * @param shouldRetry            Whether to retry the initialization.
+         * @param delayInMillis          The delay time in milliseconds before starting the next
+         *                               retry.
+         * @param completeWithoutFailure Indicates whether to skip retries and not fail the
+         *                               initialization. This is a flag for legacy behavior to
+         *                               avoid failing the initialization task for minor issues.
+         *                               When this flag is set to true, `shouldRetry` must be
+         *                               false.
+         */
+        private RetryResponse(boolean shouldRetry, long delayInMillis,
+                boolean completeWithoutFailure) {
+            mShouldRetry = shouldRetry;
+            mDelayInMillis = delayInMillis;
+            if (completeWithoutFailure) {
+                Preconditions.checkArgument(!shouldRetry,
+                        "shouldRetry must be false when completeWithoutFailure is set to true");
+            }
+            mCompleteWithoutFailure = completeWithoutFailure;
+        }
+
+        /**
+         * Determines whether the initialization should be retried.
+         *
+         * @return true if the initialization should be retried, false otherwise.
+         */
+        public boolean shouldRetry() {
+            return mShouldRetry;
+        }
+
+        /**
+         * Gets the delay time in milliseconds before the next retry attempt should be made.
+         *
+         * @return The delay time in milliseconds.
+         */
+        public long getRetryDelayInMillis() {
+            return mDelayInMillis;
+        }
+
+        /**
+         * Signals to treat initialization errors as successful for legacy behavior compatibility.
+         *
+         * <p>This response is intended for internal use and is not intended to trigger further
+         * retries.
+         *
+         * @return true if initialization should be deemed complete without additional retries,
+         * despite any errors encountered. Otherwise, returns false.
+         */
+        @RestrictTo(RestrictTo.Scope.LIBRARY)
+        public boolean shouldCompleteWithoutFailure() {
+            return mCompleteWithoutFailure;
+        }
+
+        /**
+         * A builder class for creating and customizing {@link RetryResponse} objects.
+         *
+         * <p>While predefined responses like {@link RetryResponse#DEFAULT_DELAY_RETRY} are
+         * recommended for typical recovery scenarios, this builder allows for fine-tuned control
+         * when specific requirements necessitate a different approach.
+         */
+        @ExperimentalRetryPolicy
+        public static final class Builder {
+
+            private boolean mShouldRetry = true;
+            private long mTimeoutInMillis = RetryResponse.getDefaultRetryDelayInMillis();
+
+            /**
+             * Specifies whether a retry should be attempted.
+             *
+             * @param shouldRetry  If true (the default), initialization will be retried.
+             *                     If false, initialization will not be retried.
+             * @return {@code this} for method chaining.
+             */
+            @NonNull
+            public Builder setShouldRetry(boolean shouldRetry) {
+                mShouldRetry = shouldRetry;
+                return this;
+            }
+
+            /**
+             * Sets the retry delay in milliseconds.
+             *
+             * <p>If set, the initialization will be retried after the specified delay. For optimal
+             * results, the delay should be within the range of 100 to 2000 milliseconds. This
+             * aligns with lab testing, which suggests this range provides sufficient recovery
+             * time for most common camera issues while minimizing latency.
+             *
+             * @param timeoutInMillis The delay in milliseconds.
+             * @return {@code this} for method chaining.
+             */
+            @NonNull
+            public Builder setRetryDelayInMillis(
+                    @IntRange(from = 100, to = 2000) long timeoutInMillis) {
+                mTimeoutInMillis = timeoutInMillis;
+                return this;
+            }
+
+            /**
+             * Builds the customized {@link RetryResponse} object.
+             *
+             * @return The configured RetryResponse.
+             */
+            @NonNull
+            public RetryResponse build() {
+                return new RetryResponse(mShouldRetry, mTimeoutInMillis);
+            }
+        }
+    }
+}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/Bitmap2JpegBytes.java b/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/Bitmap2JpegBytes.java
index 6602c12..776c1f4 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/Bitmap2JpegBytes.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/Bitmap2JpegBytes.java
@@ -22,6 +22,7 @@
 import android.graphics.ImageFormat;
 import android.os.Build;
 
+import androidx.annotation.DoNotInline;
 import androidx.annotation.NonNull;
 import androidx.annotation.RequiresApi;
 import androidx.camera.core.ImageCaptureException;
@@ -49,7 +50,7 @@
         //packet.getData().recycle();
         return Packet.of(outputStream.toByteArray(),
                 requireNonNull(packet.getExif()),
-                ImageFormat.JPEG,
+                getOutputFormat(packet.getData()),
                 packet.getSize(),
                 packet.getCropRect(),
                 packet.getRotationDegrees(),
@@ -57,6 +58,26 @@
                 packet.getCameraCaptureResult());
     }
 
+    private static int getOutputFormat(@NonNull Bitmap bitmap) {
+        if (Build.VERSION.SDK_INT >= 34 && Api34Impl.hasGainmap(bitmap)) {
+            return ImageFormat.JPEG_R;
+        } else {
+            return ImageFormat.JPEG;
+        }
+    }
+
+    @RequiresApi(34)
+    private static class Api34Impl {
+        @DoNotInline
+        static boolean hasGainmap(@NonNull Bitmap bitmap) {
+            return bitmap.hasGainmap();
+        }
+
+        // This class is not instantiable.
+        private Api34Impl() {
+        }
+    }
+
     /**
      * Input of {@link Bitmap2JpegBytes} processor.
      */
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/CaptureNode.java b/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/CaptureNode.java
index 43bc378..1046b45 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/CaptureNode.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/CaptureNode.java
@@ -386,8 +386,8 @@
         /**
          * The output format of the pipeline.
          *
-         * <p> For public users, only {@link ImageFormat#JPEG} is supported. Other formats are
-         * only used by in-memory capture in tests.
+         * <p> For public users, only {@link ImageFormat#JPEG} and {@link ImageFormat#JPEG_R} are
+         * supported. Other formats are only used by in-memory capture in tests.
          */
         abstract int getOutputFormat();
 
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/Image2JpegBytes.java b/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/Image2JpegBytes.java
index 6c1555d..7102adf 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/Image2JpegBytes.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/Image2JpegBytes.java
@@ -17,6 +17,7 @@
 package androidx.camera.core.imagecapture;
 
 import static android.graphics.ImageFormat.JPEG;
+import static android.graphics.ImageFormat.JPEG_R;
 import static android.graphics.ImageFormat.YUV_420_888;
 
 import static androidx.camera.core.ImageCapture.ERROR_UNKNOWN;
@@ -64,7 +65,9 @@
             int imageFormat = input.getPacket().getFormat();
             switch (imageFormat) {
                 case JPEG:
-                    return processJpegImage(input);
+                    // fall through
+                case JPEG_R:
+                    return processJpegImage(input, imageFormat);
                 case YUV_420_888:
                     return processYuvImage(input);
                 default:
@@ -75,12 +78,12 @@
         }
     }
 
-    private Packet<byte[]> processJpegImage(@NonNull Image2JpegBytes.In input) {
+    private Packet<byte[]> processJpegImage(@NonNull Image2JpegBytes.In input, int imageFormat) {
         Packet<ImageProxy> packet = input.getPacket();
         return Packet.of(
                 mJpegMetadataCorrector.jpegImageToJpegByteArray(packet.getData()),
                 requireNonNull(packet.getExif()),
-                JPEG,
+                imageFormat,
                 packet.getSize(),
                 packet.getCropRect(),
                 packet.getRotationDegrees(),
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/ImagePipeline.java b/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/ImagePipeline.java
index e23e151..1eb1698 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/ImagePipeline.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/ImagePipeline.java
@@ -18,8 +18,10 @@
 
 import static androidx.camera.core.CaptureBundles.singleDefaultCaptureBundle;
 import static androidx.camera.core.impl.ImageCaptureConfig.OPTION_BUFFER_FORMAT;
+import static androidx.camera.core.impl.ImageInputConfig.OPTION_INPUT_FORMAT;
 import static androidx.camera.core.impl.utils.Threads.checkMainThread;
 import static androidx.camera.core.impl.utils.TransformUtils.hasCropping;
+import static androidx.camera.core.internal.utils.ImageUtil.isJpegFormats;
 
 import static java.util.Objects.requireNonNull;
 
@@ -242,6 +244,12 @@
         if (bufferFormat != null) {
             return bufferFormat;
         }
+
+        Integer inputFormat = mUseCaseConfig.retrieveOption(OPTION_INPUT_FORMAT, null);
+        if (inputFormat != null && inputFormat == ImageFormat.JPEG_R) {
+            return ImageFormat.JPEG_R;
+        }
+
         // By default, use JPEG format.
         return ImageFormat.JPEG;
     }
@@ -298,9 +306,9 @@
             builder.addSurface(mPipelineIn.getSurface());
             builder.setPostviewEnabled(shouldEnablePostview());
 
-            // Only sets the JPEG rotation and quality for JPEG format. Some devices do not
+            // Only sets the JPEG rotation and quality for JPEG formats. Some devices do not
             // handle these configs for non-JPEG images. See b/204375890.
-            if (mPipelineIn.getInputFormat() == ImageFormat.JPEG) {
+            if (isJpegFormats(mPipelineIn.getInputFormat())) {
                 if (EXIF_ROTATION_AVAILABILITY.isRotationOptionSupported()) {
                     builder.addImplementationOption(CaptureConfig.OPTION_ROTATION,
                             takePictureRequest.getRotationDegrees());
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/ProcessingInput2Packet.java b/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/ProcessingInput2Packet.java
index 43f140a..cca52df 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/ProcessingInput2Packet.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/ProcessingInput2Packet.java
@@ -16,14 +16,13 @@
 
 package androidx.camera.core.imagecapture;
 
-import static android.graphics.ImageFormat.JPEG;
-
 import static androidx.camera.core.ImageCapture.ERROR_FILE_IO;
 import static androidx.camera.core.imagecapture.ImagePipeline.EXIF_ROTATION_AVAILABILITY;
 import static androidx.camera.core.impl.utils.Exif.createFromImageProxy;
 import static androidx.camera.core.impl.utils.TransformUtils.getRectToRect;
 import static androidx.camera.core.impl.utils.TransformUtils.is90or270;
 import static androidx.camera.core.impl.utils.TransformUtils.within360;
+import static androidx.camera.core.internal.utils.ImageUtil.isJpegFormats;
 import static androidx.core.util.Preconditions.checkNotNull;
 
 import android.graphics.Matrix;
@@ -65,7 +64,7 @@
 
         // Extracts Exif data from JPEG.
         Exif exif = null;
-        if (image.getFormat() == JPEG) {
+        if (isJpegFormats(image.getFormat())) {
             try {
                 exif = createFromImageProxy(image);
                 // Rewind the buffer after reading.
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/ProcessingNode.java b/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/ProcessingNode.java
index ce77781..f6d8d27 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/ProcessingNode.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/ProcessingNode.java
@@ -21,6 +21,7 @@
 
 import static androidx.camera.core.ImageCapture.ERROR_UNKNOWN;
 import static androidx.camera.core.impl.utils.executor.CameraXExecutors.mainThreadExecutor;
+import static androidx.camera.core.internal.utils.ImageUtil.isJpegFormats;
 import static androidx.core.util.Preconditions.checkArgument;
 import static androidx.core.util.Preconditions.checkState;
 
@@ -196,6 +197,10 @@
 
     @WorkerThread
     void processPostviewInputPacket(@NonNull InputPacket inputPacket) {
+        int format = mInputEdge.getOutputFormat();
+        checkArgument(format == YUV_420_888 || format == JPEG,
+                String.format("Postview only support YUV and JPEG output formats. "
+                        + "Output format: %s", format));
         ProcessingRequest request = inputPacket.getProcessingRequest();
         try {
             Packet<ImageProxy> image = mInput2Packet.apply(inputPacket);
@@ -211,9 +216,9 @@
     @WorkerThread
     ImageCapture.OutputFileResults processOnDiskCapture(@NonNull InputPacket inputPacket)
             throws ImageCaptureException {
-        checkArgument(mInputEdge.getOutputFormat() == JPEG,
-                String.format("On-disk capture only support JPEG output format. Output format: %s",
-                        mInputEdge.getOutputFormat()));
+        int format = mInputEdge.getOutputFormat();
+        checkArgument(isJpegFormats(format), String.format("On-disk capture only support JPEG and"
+                + " JPEG/R output formats. Output format: %s", format));
         ProcessingRequest request = inputPacket.getProcessingRequest();
         Packet<ImageProxy> originalImage = mInput2Packet.apply(inputPacket);
         Packet<byte[]> jpegBytes = mImage2JpegBytes.apply(
@@ -231,6 +236,8 @@
             throws ImageCaptureException {
         ProcessingRequest request = inputPacket.getProcessingRequest();
         Packet<ImageProxy> image = mInput2Packet.apply(inputPacket);
+        // TODO(b/322311893): Update to handle JPEG/R as output format in the if-statement when YUV
+        //  to JPEG/R and effect with JPEG/R are supported.
         if ((image.getFormat() == YUV_420_888 || mBitmapEffect != null
                 || mHasIncorrectJpegMetadataQuirk)
                 && mInputEdge.getOutputFormat() == JPEG) {
@@ -249,7 +256,7 @@
      */
     private Packet<byte[]> cropAndMaybeApplyEffect(Packet<byte[]> jpegPacket, int jpegQuality)
             throws ImageCaptureException {
-        checkState(jpegPacket.getFormat() == JPEG);
+        checkState(isJpegFormats(jpegPacket.getFormat()));
         Packet<Bitmap> bitmapPacket = mJpegBytes2CroppedBitmap.apply(jpegPacket);
         if (mBitmapEffect != null) {
             // Apply effect if present.
@@ -310,8 +317,8 @@
         /**
          * The output format of the pipeline.
          *
-         * <p> For public users, only {@link ImageFormat#JPEG} is supported. Other formats are
-         * only used by in-memory capture in tests.
+         * <p> For public users, only {@link ImageFormat#JPEG} and {@link ImageFormat#JPEG_R} are
+         * supported. Other formats are only used by in-memory capture in tests.
          */
         abstract int getOutputFormat();
 
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/impl/CameraProviderExecutionState.java b/camera/camera-core/src/main/java/androidx/camera/core/impl/CameraProviderExecutionState.java
new file mode 100644
index 0000000..3bdaa42
--- /dev/null
+++ b/camera/camera-core/src/main/java/androidx/camera/core/impl/CameraProviderExecutionState.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2024 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.core.impl;
+
+import android.os.SystemClock;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
+import androidx.camera.core.CameraUnavailableException;
+import androidx.camera.core.ExperimentalRetryPolicy;
+import androidx.camera.core.InitializationException;
+import androidx.camera.core.RetryPolicy;
+
+/**
+ * This class acts as a container for information about the execution state of the camera
+ * initialization process.
+ */
+@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
+@ExperimentalRetryPolicy
+public final class CameraProviderExecutionState implements RetryPolicy.ExecutionState {
+
+    private final int mStatus;
+    private final int mAttemptCount;
+    private final long mTaskExecutedTimeInMillis;
+    @Nullable
+    private final Throwable mCause;
+
+    /**
+     * Constructs a {@link CameraProviderExecutionState} object.
+     *
+     * @param taskStartTimeInMillis The start time of the task in milliseconds.
+     * @param attemptCount          The number of times the task has been attempted.
+     * @param throwable             The error that occurred during the task execution, or null if
+     *                              there was no error.
+     */
+    public CameraProviderExecutionState(
+            long taskStartTimeInMillis,
+            int attemptCount,
+            @Nullable Throwable throwable) {
+        mTaskExecutedTimeInMillis = SystemClock.elapsedRealtime() - taskStartTimeInMillis;
+        mAttemptCount = attemptCount;
+        if (throwable instanceof CameraValidator.CameraIdListIncorrectException) {
+            mStatus = STATUS_CAMERA_UNAVAILABLE;
+            mCause = throwable;
+        } else if (throwable instanceof InitializationException) {
+            Throwable cause = throwable.getCause();
+            mCause = cause != null ? cause : throwable;
+            if (mCause instanceof CameraUnavailableException) {
+                mStatus = STATUS_CAMERA_UNAVAILABLE;
+            } else if (mCause instanceof IllegalArgumentException) {
+                mStatus = STATUS_CONFIGURATION_FAIL;
+            } else {
+                mStatus = STATUS_UNKNOWN_ERROR;
+            }
+        } else {
+            mStatus = STATUS_UNKNOWN_ERROR;
+            mCause = throwable;
+        }
+    }
+
+    /**
+     * @return The status of the ExecutionState.
+     */
+    @Status
+    @Override
+    public int getStatus() {
+        return mStatus;
+    }
+
+    /**
+     * @return The cause that occurred during the task execution, or null if there was no error.
+     */
+    @Nullable
+    @Override
+    public Throwable getCause() {
+        return mCause;
+    }
+
+    /**
+     * @return The time in milliseconds that the task has been executing.
+     */
+    @Override
+    public long getExecutedTimeInMillis() {
+        return mTaskExecutedTimeInMillis;
+    }
+
+    /**
+     * @return The number of times the task has been attempted.
+     */
+    @Override
+    public int getNumOfAttempts() {
+        return mAttemptCount;
+    }
+}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/impl/CameraProviderInitRetryPolicy.java b/camera/camera-core/src/main/java/androidx/camera/core/impl/CameraProviderInitRetryPolicy.java
new file mode 100644
index 0000000..1efbe47
--- /dev/null
+++ b/camera/camera-core/src/main/java/androidx/camera/core/impl/CameraProviderInitRetryPolicy.java
@@ -0,0 +1,132 @@
+/*
+ * 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.core.impl;
+
+import static androidx.camera.core.impl.CameraValidator.CameraIdListIncorrectException;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+import androidx.camera.core.ExperimentalRetryPolicy;
+import androidx.camera.core.Logger;
+import androidx.camera.core.RetryPolicy;
+
+/**
+ * Basic retry policy that automatically retries most failures with a standard delay.
+ *
+ * <p>This policy will initiate a retry with the
+ * {@link RetryResponse#DEFAULT_DELAY_RETRY} delay for any failure status except
+ * {@link ExecutionState#STATUS_CONFIGURATION_FAIL}.
+ */
+@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
+@ExperimentalRetryPolicy
+public final class CameraProviderInitRetryPolicy implements RetryPolicyInternal {
+
+    private final RetryPolicy mDelegatePolicy;
+
+    public CameraProviderInitRetryPolicy(long timeoutInMillis) {
+        mDelegatePolicy = new TimeoutRetryPolicy(timeoutInMillis, new RetryPolicy() {
+            @NonNull
+            @Override
+            public RetryResponse shouldRetry(@NonNull ExecutionState executionState) {
+                if (executionState.getStatus() == ExecutionState.STATUS_CONFIGURATION_FAIL) {
+                    return RetryResponse.NOT_RETRY;
+                }
+
+                return RetryResponse.DEFAULT_DELAY_RETRY;
+            }
+
+            @Override
+            public long getTimeoutInMillis() {
+                return timeoutInMillis;
+            }
+        });
+    }
+
+    @NonNull
+    @Override
+    public RetryResponse shouldRetry(@NonNull ExecutionState executionState) {
+        return mDelegatePolicy.shouldRetry(executionState);
+    }
+
+    @Override
+    public long getTimeoutInMillis() {
+        return mDelegatePolicy.getTimeoutInMillis();
+    }
+
+    @NonNull
+    @Override
+    public RetryPolicy copy(long timeoutInMillis) {
+        return new CameraProviderInitRetryPolicy(timeoutInMillis);
+    }
+
+    /**
+     * A legacy implementation of {@link CameraProviderInitRetryPolicy} that treats
+     * {@link CameraIdListIncorrectException} as a special case.
+     *
+     * <p>In older versions of the CameraProviderInitRetryPolicy, there's a special rule for
+     * handling CameraValidator.CameraIdListIncorrectException errors if:
+     * <ul>
+     *   <li>The camera initialization task takes longer than the allowed timeout.
+     *   <li>The error is CameraValidator.CameraIdListIncorrectException.
+     *   <li>There's more than one camera available.
+     * </ul>
+     * Then:
+     * <ul>
+     *   <li>The task is considered complete and won't be retried.
+     * </ul>
+     */
+    public static final class Legacy implements RetryPolicyInternal {
+
+        private final RetryPolicy mBasePolicy;
+
+        public Legacy(long timeoutInMillis) {
+            mBasePolicy = new CameraProviderInitRetryPolicy(timeoutInMillis);
+        }
+
+        @NonNull
+        @Override
+        public RetryResponse shouldRetry(@NonNull ExecutionState executionState) {
+            if (!mBasePolicy.shouldRetry(executionState).shouldRetry()) {
+                Throwable cause = executionState.getCause();
+                if (cause instanceof CameraIdListIncorrectException) {
+                    Logger.e("CameraX", "The device might underreport the amount of the "
+                            + "cameras. Finish the initialize task since we are already "
+                            + "reaching the maximum number of retries.");
+                    if (((CameraIdListIncorrectException) cause).getAvailableCameraCount() > 0) {
+                        // If the initialization task execution time exceeds the timeout
+                        // threshold and the error type is CameraIdListIncorrectException,
+                        // consider the initialization complete without retrying.
+                        return RetryResponse.COMPLETE_WITHOUT_FAILURE;
+                    }
+                }
+                return RetryResponse.NOT_RETRY;
+            }
+            return RetryResponse.DEFAULT_DELAY_RETRY;
+        }
+
+        @Override
+        public long getTimeoutInMillis() {
+            return mBasePolicy.getTimeoutInMillis();
+        }
+
+        @NonNull
+        @Override
+        public RetryPolicy copy(long timeoutInMillis) {
+            return new Legacy(timeoutInMillis);
+        }
+    }
+}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/impl/CameraValidator.java b/camera/camera-core/src/main/java/androidx/camera/core/impl/CameraValidator.java
index 9ec589f..4af8c7b 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/impl/CameraValidator.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/impl/CameraValidator.java
@@ -74,6 +74,8 @@
                         + lensFacing);
 
         PackageManager pm = context.getPackageManager();
+        Throwable exception = null;
+        int availableCameraCount = 0;
         try {
             if (pm.hasSystemFeature(PackageManager.FEATURE_CAMERA)) {
                 if (availableCamerasSelector == null
@@ -81,27 +83,49 @@
                     // Only verify the main camera if it is NOT specifying the available lens
                     // facing or it required the LENS_FACING_BACK camera.
                     CameraSelector.DEFAULT_BACK_CAMERA.select(cameraRepository.getCameras());
+                    availableCameraCount++;
                 }
             }
+        } catch (IllegalArgumentException e) {
+            Logger.w(TAG, "Camera LENS_FACING_BACK verification failed", e);
+            exception = e;
+        }
+        try {
             if (pm.hasSystemFeature(PackageManager.FEATURE_CAMERA_FRONT)) {
                 if (availableCamerasSelector == null
                         || lensFacing.intValue() == CameraSelector.LENS_FACING_FRONT) {
                     // Only verify the front camera if it is NOT specifying the available lens
                     // facing or it required the LENS_FACING_FRONT camera.
                     CameraSelector.DEFAULT_FRONT_CAMERA.select(cameraRepository.getCameras());
+                    availableCameraCount++;
                 }
             }
         } catch (IllegalArgumentException e) {
+            Logger.w(TAG, "Camera LENS_FACING_FRONT verification failed", e);
+            exception = e;
+        }
+
+        if (exception != null) {
             Logger.e(TAG, "Camera LensFacing verification failed, existing cameras: "
                     + cameraRepository.getCameras());
-            throw new CameraIdListIncorrectException("Expected camera missing from device.", e);
+            throw new CameraIdListIncorrectException(
+                    "Expected camera missing from device.", availableCameraCount, exception);
         }
     }
 
     /** The exception for the b/167201193: incorrect camera id list. */
     public static class CameraIdListIncorrectException extends Exception {
-        public CameraIdListIncorrectException(@Nullable String message, @Nullable Throwable cause) {
+
+        private int mAvailableCameraCount;
+
+        public CameraIdListIncorrectException(@Nullable String message,
+                int availableCameraCount, @Nullable Throwable cause) {
             super(message, cause);
+            mAvailableCameraCount = availableCameraCount;
+        }
+
+        public int getAvailableCameraCount() {
+            return mAvailableCameraCount;
         }
     }
 }
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/impl/ImageCaptureConfig.java b/camera/camera-core/src/main/java/androidx/camera/core/impl/ImageCaptureConfig.java
index e4a6cef..0ea98d6 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/impl/ImageCaptureConfig.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/impl/ImageCaptureConfig.java
@@ -42,14 +42,15 @@
     // *********************************************************************************************
 
     public static final Option<Integer> OPTION_IMAGE_CAPTURE_MODE =
-            Option.create(
-                    "camerax.core.imageCapture.captureMode", int.class);
+            Option.create("camerax.core.imageCapture.captureMode", int.class);
     public static final Option<Integer> OPTION_FLASH_MODE =
             Option.create("camerax.core.imageCapture.flashMode", int.class);
     public static final Option<CaptureBundle> OPTION_CAPTURE_BUNDLE =
             Option.create("camerax.core.imageCapture.captureBundle", CaptureBundle.class);
     public static final Option<Integer> OPTION_BUFFER_FORMAT =
             Option.create("camerax.core.imageCapture.bufferFormat", Integer.class);
+    public static final Option<Integer> OPTION_OUTPUT_FORMAT =
+            Option.create("camerax.core.imageCapture.outputFormat", Integer.class);
     public static final Option<Integer> OPTION_MAX_CAPTURE_STAGES =
             Option.create("camerax.core.imageCapture.maxCaptureStages", Integer.class);
     public static final Option<ImageReaderProxyProvider> OPTION_IMAGE_READER_PROXY_PROVIDER =
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/impl/RetryPolicyInternal.java b/camera/camera-core/src/main/java/androidx/camera/core/impl/RetryPolicyInternal.java
new file mode 100644
index 0000000..b6c7bad
--- /dev/null
+++ b/camera/camera-core/src/main/java/androidx/camera/core/impl/RetryPolicyInternal.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2024 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.core.impl;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+import androidx.camera.core.ExperimentalRetryPolicy;
+import androidx.camera.core.RetryPolicy;
+
+/**
+ * Internal interface for constructing tailored RetryPolicies.
+ * @see RetryPolicy
+ */
+@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
+@ExperimentalRetryPolicy
+public interface RetryPolicyInternal extends RetryPolicy {
+
+    /**
+     * Creates a RetryPolicy that mirrors retry logic but enforces a new timeout.
+     *
+     * @param timeoutInMillis The maximum duration for retries in milliseconds.
+     * @return A RetryPolicy that seamlessly integrates the inherited retry logic with the
+     * newly specified timeout.
+     */
+    @NonNull
+    RetryPolicy copy(long timeoutInMillis);
+}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/impl/TimeoutRetryPolicy.java b/camera/camera-core/src/main/java/androidx/camera/core/impl/TimeoutRetryPolicy.java
new file mode 100644
index 0000000..139ba82
--- /dev/null
+++ b/camera/camera-core/src/main/java/androidx/camera/core/impl/TimeoutRetryPolicy.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2024 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.core.impl;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+import androidx.camera.core.ExperimentalRetryPolicy;
+import androidx.camera.core.RetryPolicy;
+import androidx.core.util.Preconditions;
+
+/**
+ * Automatically halts retries if execution time surpasses a specified timeout.
+ *
+ * <p>This retry policy monitors the total execution time of a task. If the time surpasses the
+ * configured timeout threshold, it immediately stops any further retries by returning
+ * {@link RetryPolicy.RetryResponse#NOT_RETRY}.
+ *
+ * <p>If the task total execution within the timeout, this policy delegates the retry decision to
+ * the underlying {@link RetryPolicy}, allowing for normal retry behavior based on other factors.
+ *
+ * <p>A timeout of 0 means the execution will never be halted.
+ */
+@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
+@ExperimentalRetryPolicy
+public final class TimeoutRetryPolicy implements RetryPolicy {
+
+    private final long mTimeoutInMillis;
+    private final RetryPolicy mDelegatePolicy;
+
+    /**
+     * Constructor.
+     *
+     * @param timeoutInMillis  The maximum allowed execution time in milliseconds. Must be
+     *                         0 or greater. 0 means the execution will never be halted.
+     * @param delegatePolicy   The RetryPolicy used to decide retries within the timeout.
+     */
+    public TimeoutRetryPolicy(long timeoutInMillis, @NonNull RetryPolicy delegatePolicy) {
+        Preconditions.checkArgument(timeoutInMillis >= 0, "Timeout must be non-negative.");
+        mTimeoutInMillis = timeoutInMillis;
+        mDelegatePolicy = delegatePolicy;
+    }
+
+    @NonNull
+    @Override
+    public RetryResponse shouldRetry(@NonNull ExecutionState executionState) {
+        RetryResponse response = mDelegatePolicy.shouldRetry(executionState);
+        return getTimeoutInMillis() > 0 && executionState.getExecutedTimeInMillis()
+                >= getTimeoutInMillis() - response.getRetryDelayInMillis() ? RetryResponse.NOT_RETRY
+                : response;
+    }
+
+    @Override
+    public long getTimeoutInMillis() {
+        return mTimeoutInMillis;
+    }
+}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/impl/utils/Exif.java b/camera/camera-core/src/main/java/androidx/camera/core/impl/utils/Exif.java
index 729fa09..98c8e16 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/impl/utils/Exif.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/impl/utils/Exif.java
@@ -608,6 +608,12 @@
     }
 
     @VisibleForTesting
+    @Nullable
+    public String getMetadata() {
+        return mExifInterface.getAttribute(ExifInterface.TAG_XMP);
+    }
+
+    @VisibleForTesting
     @NonNull
     public ExifInterface getExifInterface() {
         return mExifInterface;
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/internal/CameraUseCaseAdapter.java b/camera/camera-core/src/main/java/androidx/camera/core/internal/CameraUseCaseAdapter.java
index 784151f..dc7d470 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/internal/CameraUseCaseAdapter.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/internal/CameraUseCaseAdapter.java
@@ -22,12 +22,15 @@
 import static androidx.camera.core.DynamicRange.BIT_DEPTH_10_BIT;
 import static androidx.camera.core.DynamicRange.ENCODING_SDR;
 import static androidx.camera.core.DynamicRange.ENCODING_UNSPECIFIED;
+import static androidx.camera.core.ImageCapture.OUTPUT_FORMAT_ULTRA_HDR;
+import static androidx.camera.core.impl.ImageCaptureConfig.OPTION_OUTPUT_FORMAT;
 import static androidx.camera.core.impl.UseCaseConfig.OPTION_CAPTURE_TYPE;
 import static androidx.camera.core.impl.utils.TransformUtils.rectToSize;
 import static androidx.camera.core.processing.TargetUtils.getNumberOfTargets;
 import static androidx.camera.core.streamsharing.StreamSharing.getCaptureTypes;
 import static androidx.camera.core.streamsharing.StreamSharing.isStreamSharing;
 import static androidx.core.util.Preconditions.checkArgument;
+import static androidx.core.util.Preconditions.checkNotNull;
 import static androidx.core.util.Preconditions.checkState;
 
 import static java.util.Collections.emptyList;
@@ -921,6 +924,15 @@
             throw new IllegalArgumentException("Extensions are only supported for use with "
                     + "standard dynamic range.");
         }
+
+        // TODO(b/322311893): throw exception to block feature combination of effect with Ultra
+        //  HDR, until ImageProcessor and SurfaceProcessor can support JPEG/R format.
+        synchronized (mLock) {
+            if (!mEffects.isEmpty() && hasUltraHdrImageCapture(useCases)) {
+                throw new IllegalArgumentException("Ultra HDR image capture does not support for "
+                        + "use with CameraEffect.");
+            }
+        }
     }
 
     private static boolean hasNonSdrConfig(@NonNull Collection<UseCase> useCases) {
@@ -941,6 +953,22 @@
         return is10Bit || isHdr;
     }
 
+    private static boolean hasUltraHdrImageCapture(@NonNull Collection<UseCase> useCases) {
+        for (UseCase useCase : useCases) {
+            if (!isImageCapture(useCase)) {
+                continue;
+            }
+
+            UseCaseConfig<?> config = useCase.getCurrentConfig();
+            if (config.containsOption(OPTION_OUTPUT_FORMAT) && checkNotNull(
+                    config.retrieveOption(OPTION_OUTPUT_FORMAT)) == OUTPUT_FORMAT_ULTRA_HDR) {
+                return true;
+            }
+
+        }
+        return false;
+    }
+
     /**
      * An identifier for a {@link CameraUseCaseAdapter}.
      *
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/internal/compat/workaround/ExifRotationAvailability.java b/camera/camera-core/src/main/java/androidx/camera/core/internal/compat/workaround/ExifRotationAvailability.java
index 0d64495..76210bc 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/internal/compat/workaround/ExifRotationAvailability.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/internal/compat/workaround/ExifRotationAvailability.java
@@ -16,7 +16,7 @@
 
 package androidx.camera.core.internal.compat.workaround;
 
-import android.graphics.ImageFormat;
+import static androidx.camera.core.internal.utils.ImageUtil.isJpegFormats;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.RequiresApi;
@@ -53,6 +53,6 @@
      * @param image The captured image object.
      */
     public boolean shouldUseExifOrientation(@NonNull ImageProxy image) {
-        return isRotationOptionSupported() && image.getFormat() == ImageFormat.JPEG;
+        return isRotationOptionSupported() && isJpegFormats(image.getFormat());
     }
 }
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/internal/utils/ImageUtil.java b/camera/camera-core/src/main/java/androidx/camera/core/internal/utils/ImageUtil.java
index 3ca31bf..c67e4de 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/internal/utils/ImageUtil.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/internal/utils/ImageUtil.java
@@ -65,10 +65,10 @@
     /**
      * Creates {@link Bitmap} from {@link ImageProxy}.
      *
-     * <p> Currently only {@link ImageFormat#YUV_420_888}, {@link ImageFormat#JPEG} and
-     * {@link PixelFormat#RGBA_8888} are supported. If the format is invalid, an
-     * {@link IllegalArgumentException} will be thrown. If the conversion to bimap failed, an
-     * {@link UnsupportedOperationException} will be thrown.
+     * <p> Currently only {@link ImageFormat#YUV_420_888}, {@link ImageFormat#JPEG},
+     * {@link ImageFormat#JPEG_R} and {@link PixelFormat#RGBA_8888} are supported. If the format
+     * is invalid, an {@link IllegalArgumentException} will be thrown. If the conversion to bimap
+     * failed, an {@link UnsupportedOperationException} will be thrown.
      *
      * @param imageProxy The input {@link ImageProxy} instance.
      * @return {@link Bitmap} instance.
@@ -79,6 +79,7 @@
             case ImageFormat.YUV_420_888:
                 return ImageProcessingUtil.convertYUVToBitmap(imageProxy);
             case ImageFormat.JPEG:
+            case ImageFormat.JPEG_R:
                 return createBitmapFromJpegImage(imageProxy);
             case PixelFormat.RGBA_8888:
                 return createBitmapFromRgbaImage(imageProxy);
@@ -168,11 +169,11 @@
     }
 
     /**
-     * Converts JPEG {@link ImageProxy} to JPEG byte array.
+     * Converts JPEG or JPEG_R {@link ImageProxy} to JPEG byte array.
      */
     @NonNull
     public static byte[] jpegImageToJpegByteArray(@NonNull ImageProxy image) {
-        if (image.getFormat() != ImageFormat.JPEG) {
+        if (!isJpegFormats(image.getFormat())) {
             throw new IllegalArgumentException(
                     "Incorrect image format of the input image proxy: " + image.getFormat());
         }
@@ -194,7 +195,7 @@
     public static byte[] jpegImageToJpegByteArray(@NonNull ImageProxy image,
             @NonNull Rect cropRect, @IntRange(from = 1, to = 100) int jpegQuality)
             throws CodecFailedException {
-        if (image.getFormat() != ImageFormat.JPEG) {
+        if (!isJpegFormats(image.getFormat())) {
             throw new IllegalArgumentException(
                     "Incorrect image format of the input image proxy: " + image.getFormat());
         }
@@ -296,7 +297,7 @@
         return nv21;
     }
 
-    /** Crops JPEG byte array with given {@link android.graphics.Rect}. */
+    /** Crops JPEG or JPEG_R byte array with given {@link android.graphics.Rect}. */
     @NonNull
     @SuppressWarnings("deprecation")
     private static byte[] cropJpegByteArray(@NonNull byte[] data, @NonNull Rect cropRect,
@@ -336,6 +337,11 @@
         return aspectRatio != null && aspectRatio.floatValue() > 0 && !aspectRatio.isNaN();
     }
 
+    /** True if the given image format is JPEG or JPEG/R. */
+    public static boolean isJpegFormats(int imageFormat) {
+        return imageFormat == ImageFormat.JPEG || imageFormat == ImageFormat.JPEG_R;
+    }
+
     /** True if the given aspect ratio is meaningful and has effect on the given size. */
     public static boolean isAspectRatioValid(@NonNull Size sourceSize,
             @Nullable Rational aspectRatio) {
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/processing/Packet.java b/camera/camera-core/src/main/java/androidx/camera/core/processing/Packet.java
index 1136ee9..e52183e 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/processing/Packet.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/processing/Packet.java
@@ -16,8 +16,7 @@
 
 package androidx.camera.core.processing;
 
-import static android.graphics.ImageFormat.JPEG;
-
+import static androidx.camera.core.internal.utils.ImageUtil.isJpegFormats;
 import static androidx.core.util.Preconditions.checkNotNull;
 
 import android.graphics.Bitmap;
@@ -89,7 +88,8 @@
      *
      * <p>This value must match the format of the image in {@link #getData()}.
      *
-     * <p>For {@link Bitmap} type, the value is {@link ImageFormat#FLEX_RGBA_8888}.
+     * <p>For {@link Bitmap} type, the value is {@link ImageFormat#FLEX_RGBA_8888}. If the Bitmap
+     * has a gainmap, it can be converted to JPEG_R format on API level 34+
      */
     public abstract int getFormat();
 
@@ -174,7 +174,7 @@
             @NonNull Size size, @NonNull Rect cropRect, int rotationDegrees,
             @NonNull Matrix sensorToBufferTransform,
             @NonNull CameraCaptureResult cameraCaptureResult) {
-        if (data.getFormat() == JPEG) {
+        if (isJpegFormats(data.getFormat())) {
             checkNotNull(exif, "JPEG image must have Exif.");
         }
         return new AutoValue_Packet<>(data, exif, data.getFormat(), size, cropRect, rotationDegrees,
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/CameraXConfigTest.java b/camera/camera-core/src/test/java/androidx/camera/core/CameraXConfigTest.java
index 13b888d..5991c00 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/CameraXConfigTest.java
+++ b/camera/camera-core/src/test/java/androidx/camera/core/CameraXConfigTest.java
@@ -115,4 +115,14 @@
         assertThat(cameraXConfig.getCameraOpenRetryMaxTimeoutInMillisWhileResuming())
                 .isEqualTo(1000L);
     }
+
+    @Test
+    public void canGetInitRetryPolicy() {
+        CameraXConfig cameraXConfig = new CameraXConfig.Builder()
+                .setCameraProviderInitRetryPolicy(RetryPolicy.RETRY_UNAVAILABLE_CAMERA)
+                .build();
+
+        assertThat(cameraXConfig.getCameraProviderInitRetryPolicy())
+                .isEqualTo(RetryPolicy.RETRY_UNAVAILABLE_CAMERA);
+    }
 }
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/CameraXInitRetryTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/CameraXInitRetryTest.kt
new file mode 100644
index 0000000..15b652d
--- /dev/null
+++ b/camera/camera-core/src/test/java/androidx/camera/core/CameraXInitRetryTest.kt
@@ -0,0 +1,665 @@
+/*
+ * Copyright 2024 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.core
+
+import android.content.Context
+import android.content.pm.PackageManager
+import android.os.Build
+import android.os.Handler
+import android.os.Looper
+import android.os.SystemClock
+import androidx.camera.core.RetryPolicy.DEFAULT
+import androidx.camera.core.RetryPolicy.DEFAULT_RETRY_TIMEOUT_IN_MILLIS
+import androidx.camera.core.RetryPolicy.ExecutionState
+import androidx.camera.core.RetryPolicy.NEVER
+import androidx.camera.core.RetryPolicy.RETRY_UNAVAILABLE_CAMERA
+import androidx.camera.core.RetryPolicy.RetryResponse
+import androidx.camera.core.concurrent.CameraCoordinator
+import androidx.camera.core.impl.CameraDeviceSurfaceManager
+import androidx.camera.core.impl.CameraFactory
+import androidx.camera.core.impl.CameraFactory.Provider
+import androidx.camera.core.impl.CameraInternal
+import androidx.camera.core.impl.CameraThreadConfig
+import androidx.camera.core.impl.UseCaseConfigFactory
+import androidx.camera.testing.fakes.FakeCamera
+import androidx.camera.testing.fakes.FakeCameraInfoInternal
+import androidx.camera.testing.impl.fakes.FakeCameraCoordinator
+import androidx.camera.testing.impl.fakes.FakeCameraDeviceSurfaceManager
+import androidx.camera.testing.impl.fakes.FakeCameraFactory
+import androidx.camera.testing.impl.fakes.FakeUseCaseConfigFactory
+import androidx.concurrent.futures.await
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.core.internal.os.HandlerExecutor
+import androidx.testutils.assertThrows
+import com.google.common.truth.Truth.assertThat
+import java.util.concurrent.TimeUnit
+import kotlin.math.abs
+import kotlinx.coroutines.Deferred
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.async
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.currentTime
+import kotlinx.coroutines.test.runTest
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.RobolectricTestRunner
+import org.robolectric.Shadows.shadowOf
+import org.robolectric.annotation.Config
+import org.robolectric.annotation.internal.DoNotInstrument
+import org.robolectric.shadows.ShadowPackageManager
+import org.robolectric.shadows.ShadowSystemClock
+
+@RunWith(RobolectricTestRunner::class)
+@DoNotInstrument
+@Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
+@OptIn(ExperimentalCoroutinesApi::class)
+class CameraXInitRetryTest {
+    private val context = ApplicationProvider.getApplicationContext<Context>()
+    private val handler = Handler(Looper.getMainLooper()) // Same to the looper of TestScope
+    private val handlerExecutor = HandlerExecutor(handler)
+    private lateinit var shadowPackageManager: ShadowPackageManager
+    private var repeatingJob: Deferred<Unit>? = null
+
+    @Before
+    fun setUp() {
+        // This test asserts both the type of the exception thrown, and the type of the cause of the
+        // exception thrown in many cases. The Kotlin stacktrace recovery feature is useful for
+        // debugging, but it inserts exceptions into the `cause` chain and interferes with this
+        // test.
+        System.setProperty("kotlinx.coroutines.stacktrace.recovery", false.toString())
+        shadowPackageManager = shadowOf(context.packageManager)
+        shadowPackageManager.setSystemFeature(PackageManager.FEATURE_CAMERA, true)
+        shadowPackageManager.setSystemFeature(PackageManager.FEATURE_CAMERA_FRONT, true)
+    }
+
+    @After
+    fun tearDown() {
+        repeatingJob?.cancel()
+    }
+
+    @Test
+    fun initializationSucceedsOnValidEnvironment() = runTest {
+        // Arrange.
+        val executionStateMutableList = mutableListOf<ExecutionState>()
+        val policy = RetryPolicy { executionState: ExecutionState ->
+            executionStateMutableList.add(executionState)
+            return@RetryPolicy DEFAULT.shouldRetry(executionState)
+        }
+        val configBuilder: CameraXConfig.Builder = CameraXConfig.Builder.fromConfig(
+            createCameraXConfig(
+                cameraFactory = createFakeCameraFactory(
+                    frontCamera = true,
+                    backCamera = true
+                )
+            )
+        ).apply {
+            setCameraExecutor(handlerExecutor)
+            setSchedulerHandler(handler)
+            setCameraProviderInitRetryPolicy(policy)
+        }
+
+        // Simulate the system time increases.
+        repeatingJob = simulateSystemTimeIncrease()
+
+        // Act.
+        val cameraX = CameraX(context) { configBuilder.build() }
+        cameraX.initializeFuture.await()
+
+        // Assert.
+        assertThat(cameraX.isInitialized).isTrue()
+        assertThat(executionStateMutableList).isEmpty()
+    }
+
+    @Test
+    fun verifyInitSucceedsUsingDefaultRetryPolicy_OneCameraScenario() = runTest {
+        // Arrange.
+        val configBuilder: CameraXConfig.Builder = CameraXConfig.Builder.fromConfig(
+            createCameraXConfig(
+                cameraFactory = createFakeCameraFactory(
+                    frontCamera = true,
+                    backCamera = false
+                )
+            )
+        ).setSchedulerHandler(handler).setCameraExecutor(handlerExecutor)
+
+        // Simulate the system time increases.
+        repeatingJob = simulateSystemTimeIncrease()
+
+        // Act.
+        val cameraX = CameraX(context) { configBuilder.build() }
+        cameraX.initializeFuture.await()
+
+        // Assert.
+        assertThat(cameraX.isInitialized).isTrue()
+        cameraX.shutdown().get()
+        assertThat(cameraX.isInitialized).isFalse()
+    }
+
+    @Test
+    fun verifyInitFailureUsingDefaultRetryPolicy_NoCameraScenario() = runTest {
+        // Arrange. Set up a simulated environment that no accessible cameras.
+        val configBuilder: CameraXConfig.Builder = CameraXConfig.Builder.fromConfig(
+            createCameraXConfig(
+                cameraFactory = createFakeCameraFactory(
+                    frontCamera = false,
+                    backCamera = false
+                )
+            )
+        ).setSchedulerHandler(handler).setCameraExecutor(handlerExecutor)
+
+        // Simulate the system time increases.
+        repeatingJob = simulateSystemTimeIncrease()
+
+        // Act.
+        val cameraX = CameraX(context) { configBuilder.build() }
+        val throwableSubject = assertThrows<InitializationException> {
+            cameraX.initializeFuture.await()
+        }
+
+        // Assert.
+        throwableSubject.hasCauseThat().isInstanceOf(CameraUnavailableException::class.java)
+        assertThat(cameraX.isInitialized).isFalse()
+        cameraX.shutdown().get()
+    }
+
+    @Test
+    fun verifyImmediateFailureWithOptionNEVER() = runTest {
+        // Arrange. Set up a simulated environment that no accessible cameras.
+        var callCount = 0
+        val configBuilder: CameraXConfig.Builder = CameraXConfig.Builder.fromConfig(
+            createCameraXConfig(
+                cameraFactory = createFakeCameraFactory(
+                    frontCamera = false,
+                    backCamera = false
+                )
+            )
+        ).apply {
+            setCameraExecutor(handlerExecutor)
+            setSchedulerHandler(handler)
+            setCameraProviderInitRetryPolicy { executionState ->
+                callCount++
+                NEVER.shouldRetry(executionState)
+            }
+        }
+
+        // Simulate the system time increases.
+        repeatingJob = simulateSystemTimeIncrease()
+
+        // Act.
+        val cameraX = CameraX(context) { configBuilder.build() }
+        assertThrows<InitializationException> {
+            cameraX.initializeFuture.await()
+        }
+
+        // Assert.
+        assertThat(cameraX.isInitialized).isFalse()
+        assertThat(callCount).isAtMost(1)
+    }
+
+    @Test
+    fun verifyImmediateFailureWithOptionResponseNotRetry() = runTest {
+        // Arrange. Set up a simulated environment that no accessible cameras.
+        var callCount = 0
+        val configBuilder: CameraXConfig.Builder = CameraXConfig.Builder.fromConfig(
+            createCameraXConfig(
+                cameraFactory = createFakeCameraFactory(
+                    frontCamera = false,
+                    backCamera = false
+                )
+            )
+        ).apply {
+            setCameraExecutor(handlerExecutor)
+            setSchedulerHandler(handler)
+            setCameraProviderInitRetryPolicy {
+                callCount++
+                RetryResponse.Builder().setShouldRetry(false).build()
+            }
+        }
+
+        // Simulate the system time increases.
+        repeatingJob = simulateSystemTimeIncrease()
+
+        // Act.
+        val cameraX = CameraX(context) { configBuilder.build() }
+        assertThrows<InitializationException> {
+            cameraX.initializeFuture.await()
+        }
+
+        // Assert.
+        assertThat(cameraX.isInitialized).isFalse()
+        assertThat(callCount).isAtMost(1)
+    }
+
+    @Test
+    fun verifyInitFails_RetryCameraUnavailableMode_NoCameraScenario() = runTest {
+        // Arrange. Set up a simulated environment that no accessible cameras.
+        val configBuilder: CameraXConfig.Builder = CameraXConfig.Builder.fromConfig(
+            createCameraXConfig()
+        ).apply {
+            setCameraExecutor(handlerExecutor)
+            setSchedulerHandler(handler)
+            setCameraProviderInitRetryPolicy(RETRY_UNAVAILABLE_CAMERA)
+        }
+
+        // Simulate the system time increases.
+        repeatingJob = simulateSystemTimeIncrease()
+
+        // Act.
+        val cameraX = CameraX(context) { configBuilder.build() }
+        val throwableSubject = assertThrows<InitializationException> {
+            cameraX.initializeFuture.await()
+        }
+
+        // Assert.
+        throwableSubject.hasCauseThat().isInstanceOf(CameraUnavailableException::class.java)
+        assertThat(cameraX.isInitialized).isFalse()
+    }
+
+    @Test
+    fun verifyExecTimeNotExceedTimeout_RetryCamUnavailable_NoCameraScenario() = runTest {
+        // Arrange. Set up a simulated environment that no accessible cameras.
+        var executedTime = 0L
+        val configBuilder: CameraXConfig.Builder = CameraXConfig.Builder.fromConfig(
+            createCameraXConfig()
+        ).apply {
+            setCameraExecutor(handlerExecutor)
+            setSchedulerHandler(handler)
+            setCameraProviderInitRetryPolicy { executionState: ExecutionState ->
+                RETRY_UNAVAILABLE_CAMERA.shouldRetry(executionState).also {
+                    executedTime = executionState.executedTimeInMillis
+                }
+            }
+        }
+
+        // Simulate the system time increases.
+        repeatingJob = simulateSystemTimeIncrease()
+
+        // Act.
+        val cameraX = CameraX(context) { configBuilder.build() }
+        val throwableSubject = assertThrows<InitializationException> {
+            cameraX.initializeFuture.await()
+        }
+
+        // Assert.
+        throwableSubject.hasCauseThat().isInstanceOf(CameraUnavailableException::class.java)
+        assertThat(cameraX.isInitialized).isFalse()
+        assertThat(abs(DEFAULT_RETRY_TIMEOUT_IN_MILLIS - executedTime)).isLessThan(
+            RetryResponse.DEFAULT_DELAY_RETRY.retryDelayInMillis + 100
+            // Allow the tolerance for retry delay + 100ms potential processing time variations.
+        )
+    }
+
+    @Test
+    fun testTimeoutAdjustment_RetryCameraUnavailableMode() = runTest {
+        testTimeoutAdjustment(RETRY_UNAVAILABLE_CAMERA)
+    }
+
+    @Test
+    fun testTimeoutAdjustment_DefaultMode() = runTest {
+        testTimeoutAdjustment(DEFAULT)
+    }
+
+    @Test
+    fun testTimeoutAdjustment_CustomRetryPolicyMode() = runTest {
+        // Arrange. Set up a RetryPolicy that persistently retries initialization attempts.
+        val customAlwaysRetryPolicy = RetryPolicy { RetryResponse.MINI_DELAY_RETRY }
+
+        // Act. & Assert. Confirm that retries cease if the total execution time surpasses the
+        // defined timeout, preventing indefinite loops.
+        testTimeoutAdjustment(customAlwaysRetryPolicy)
+    }
+
+    private suspend fun TestScope.testTimeoutAdjustment(policy: RetryPolicy) = coroutineScope {
+        // Arrange. Set up a simulated environment that no accessible cameras.
+        var executedTime = 0L
+        val testCustomTimeout = 10000L
+        val customTimeoutPolicy =
+            RetryPolicy.Builder(policy).setTimeoutInMillis(testCustomTimeout).build()
+        val configBuilder: CameraXConfig.Builder = CameraXConfig.Builder.fromConfig(
+            createCameraXConfig()
+        ).apply {
+            setCameraExecutor(handlerExecutor)
+            setSchedulerHandler(handler)
+            setCameraProviderInitRetryPolicy { executionState ->
+                customTimeoutPolicy.shouldRetry(executionState).also {
+                    executedTime = executionState.executedTimeInMillis
+                }
+            }
+        }
+
+        // Simulate the system time increases.
+        repeatingJob = simulateSystemTimeIncrease()
+
+        // Act.
+        val cameraX = CameraX(context) { configBuilder.build() }
+        assertThrows<InitializationException> {
+            cameraX.initializeFuture.await()
+        }
+
+        // Assert. Verify that initialization persists with retries until the total execution
+        // time exhausts the allotted timeout.
+        assertThat(abs(testCustomTimeout - executedTime)).isLessThan(
+            RetryResponse.DEFAULT_DELAY_RETRY.retryDelayInMillis + 100
+            // Allow the tolerance for retry delay + 100ms potential processing time variations.
+        )
+    }
+
+    @Test
+    fun verifyRetryDelayCustomization() = runTest {
+        val desiredDelayTime = 900L
+
+        assertThat(
+            RetryResponse.Builder().setRetryDelayInMillis(desiredDelayTime)
+                .build().retryDelayInMillis
+        ).isEqualTo(
+            desiredDelayTime
+        )
+    }
+
+    @Test
+    fun verifyExecTimeNotExceedTimeout_CustomizedRetryPolicyOverrideGetTimeout_NoCameraScenario() =
+        runTest {
+        // Arrange. Set up a simulated environment that no accessible cameras.
+        val timeoutInMs = 10000L
+        val executionStateMutableList = mutableListOf<ExecutionState>()
+        val policy = object : RetryPolicy {
+            override fun shouldRetry(executionState: ExecutionState): RetryResponse {
+                if (executionState.getExecutedTimeInMillis() < timeoutInMillis) {
+                    executionStateMutableList.add(executionState)
+                }
+
+                return RetryResponse.DEFAULT_DELAY_RETRY
+            }
+
+            override fun getTimeoutInMillis(): Long {
+                return timeoutInMs
+            }
+        }
+        val configBuilder: CameraXConfig.Builder = CameraXConfig.Builder.fromConfig(
+            createCameraXConfig()
+        ).apply {
+            setCameraExecutor(handlerExecutor)
+            setSchedulerHandler(handler)
+            setCameraProviderInitRetryPolicy(policy)
+        }
+
+        // Simulate the system time increases.
+        repeatingJob = simulateSystemTimeIncrease()
+
+        // Act.
+        val cameraX = CameraX(context) { configBuilder.build() }
+        assertThrows<InitializationException> {
+            cameraX.initializeFuture.await()
+        }
+
+        // Assert. Confirm that initialization did not succeed.
+        assertThat(cameraX.isInitialized).isFalse()
+
+        // Assert. Verify that retry attempts occurred in sequential order.
+        val numAttemptList =
+            executionStateMutableList.map { executionState -> executionState.numOfAttempts }
+        assertThat(numAttemptList).isInOrder()
+
+        // Assert. Ensure all errors encountered were specifically due to camera unavailability.
+        val statusList =
+            executionStateMutableList.map { executionState -> executionState.status }.toSet()
+        assertThat(statusList).containsExactlyElementsIn(
+            listOf(ExecutionState.STATUS_CAMERA_UNAVAILABLE)
+        )
+
+        // Assert. Verify that the total execution time did not surpass the timeout limit.
+        assertThat(executionStateMutableList.last().executedTimeInMillis).isLessThan(timeoutInMs)
+    }
+
+    @Test
+    fun verifyExecTimeNotExceedTimeout_CustomizedRetryPolicy_NoCameraScenario() = runTest {
+        // Arrange. Set up a simulated environment that no accessible cameras.
+        val timeoutInMs = 10000L
+        val executionStateMutableList = mutableListOf<ExecutionState>()
+        val policy = RetryPolicy { executionState ->
+            if (executionState.getExecutedTimeInMillis() < timeoutInMs) {
+                executionStateMutableList.add(executionState)
+                return@RetryPolicy RetryResponse.DEFAULT_DELAY_RETRY;
+            }
+
+            return@RetryPolicy RetryResponse.NOT_RETRY
+        }
+        val configBuilder: CameraXConfig.Builder = CameraXConfig.Builder.fromConfig(
+            createCameraXConfig()
+        ).apply {
+            setCameraExecutor(handlerExecutor)
+            setSchedulerHandler(handler)
+            setCameraProviderInitRetryPolicy(policy)
+        }
+
+        // Simulate the system time increases.
+        repeatingJob = simulateSystemTimeIncrease()
+
+        // Act.
+        val cameraX = CameraX(context) { configBuilder.build() }
+        assertThrows<InitializationException> {
+            cameraX.initializeFuture.await()
+        }
+
+        // Assert. Confirm that initialization did not succeed.
+        assertThat(cameraX.isInitialized).isFalse()
+
+        // Assert. Verify that retry attempts occurred in sequential order.
+        val numAttemptList =
+            executionStateMutableList.map { executionState -> executionState.numOfAttempts }
+        assertThat(numAttemptList).isInOrder()
+
+        // Assert. Ensure all errors encountered were specifically due to camera unavailability.
+        val statusList =
+            executionStateMutableList.map { executionState -> executionState.status }.toSet()
+        assertThat(statusList).containsExactlyElementsIn(
+            listOf(ExecutionState.STATUS_CAMERA_UNAVAILABLE)
+        )
+
+        // Assert. Verify that the total execution time did not surpass the timeout limit.
+        assertThat(executionStateMutableList.last().executedTimeInMillis).isLessThan(timeoutInMs)
+    }
+
+    @Test
+    fun verifyMaxAttemptsAdhereToStopCriteria_CustomRetryPolicy() = runTest {
+        // Arrange. Set up a simulated environment that no accessible cameras.
+        val maxAttempts = 5
+        val executionStateMutableList = mutableListOf<ExecutionState>()
+        val policy = RetryPolicy { executionState: ExecutionState ->
+            executionStateMutableList.add(executionState)
+            if (executionState.numOfAttempts < maxAttempts) {
+                return@RetryPolicy RetryResponse.DEFAULT_DELAY_RETRY;
+            }
+
+            return@RetryPolicy RetryResponse.NOT_RETRY
+        }
+        val configBuilder: CameraXConfig.Builder = CameraXConfig.Builder.fromConfig(
+            createCameraXConfig()
+        ).apply {
+            setCameraExecutor(handlerExecutor)
+            setSchedulerHandler(handler)
+            setCameraProviderInitRetryPolicy(policy)
+        }
+
+        // Simulate the system time increases.
+        repeatingJob = simulateSystemTimeIncrease()
+
+        // Act.
+        val cameraX = CameraX(context) { configBuilder.build() }
+        assertThrows<InitializationException> {
+            cameraX.initializeFuture.await()
+        }
+
+        // Assert. Confirm that initialization did not succeed.
+        assertThat(cameraX.isInitialized).isFalse()
+        // Assert. Confirm the maximum number of retry attempts was reached.
+        assertThat(executionStateMutableList.last().numOfAttempts).isEqualTo(maxAttempts)
+    }
+
+    @Test
+    fun testInitializationFailsPromptlyWithConfigurationError() = runTest {
+        // Arrange.
+        val resultList = mutableListOf<ExecutionState>()
+        val policy = RetryPolicy { executionState: ExecutionState ->
+            resultList.add(executionState)
+            RETRY_UNAVAILABLE_CAMERA.shouldRetry(executionState)
+        }
+        val configBuilder: CameraXConfig.Builder = CameraXConfig.Builder.fromConfig(
+            createCameraXConfig(surfaceManager = null, useCaseConfigFactory = null)
+        ).apply {
+            setCameraExecutor(handlerExecutor)
+            setSchedulerHandler(handler)
+            setCameraProviderInitRetryPolicy(policy)
+        }
+
+        // Simulate the system time increases.
+        repeatingJob = simulateSystemTimeIncrease()
+
+        // Act.
+        val cameraX = CameraX(context) { configBuilder.build() }
+        assertThrows<InitializationException> {
+            cameraX.initializeFuture.await()
+        }
+
+        // Assert.
+        assertThat(resultList.size).isEqualTo(1)
+        assertThat(resultList.last().status).isEqualTo(ExecutionState.STATUS_CONFIGURATION_FAIL)
+    }
+
+    @Test
+    fun testInitializationFailsWithUnknownErrorDueToRuntimeException() = runTest {
+        // Arrange. Create a CameraFactory simulation that throws RuntimeException on all API usage.
+        val testException = RuntimeException("test")
+        val executionStateMutableList = mutableListOf<ExecutionState>()
+        val configBuilder: CameraXConfig.Builder = CameraXConfig.Builder.fromConfig(
+            createCameraXConfig(
+                cameraFactory = object : CameraFactory {
+                    override fun getCamera(cameraId: String): CameraInternal {
+                        throw testException
+                    }
+
+                    override fun getAvailableCameraIds(): MutableSet<String> {
+                        throw testException
+                    }
+
+                    override fun getCameraCoordinator(): CameraCoordinator {
+                        throw testException
+                    }
+
+                    override fun getCameraManager(): Any? {
+                        throw testException
+                    }
+                })
+        ).apply {
+            setCameraExecutor(handlerExecutor)
+            setSchedulerHandler(handler)
+            setCameraProviderInitRetryPolicy { executionState: ExecutionState ->
+                executionStateMutableList.add(executionState)
+                RetryResponse.NOT_RETRY
+            }
+        }
+
+        // Simulate the system time increases.
+        repeatingJob = simulateSystemTimeIncrease()
+
+        // Act.
+        val cameraX = CameraX(context) { configBuilder.build() }
+        assertThrows<InitializationException> {
+            cameraX.initializeFuture.await()
+        }
+
+        // Assert.
+        assertThat(executionStateMutableList.size).isEqualTo(1)
+        assertThat(executionStateMutableList.last().status).isEqualTo(
+            ExecutionState.STATUS_UNKNOWN_ERROR
+        )
+    }
+
+    private fun createCameraXConfig(
+        cameraFactory: CameraFactory = createFakeCameraFactory(),
+        surfaceManager: CameraDeviceSurfaceManager? = FakeCameraDeviceSurfaceManager(),
+        useCaseConfigFactory: UseCaseConfigFactory? = FakeUseCaseConfigFactory()
+    ): CameraXConfig {
+        val cameraFactoryProvider =
+            Provider { _: Context?, _: CameraThreadConfig?, _: CameraSelector?, _: Long ->
+                cameraFactory
+            }
+        return CameraXConfig.Builder().setCameraFactoryProvider(cameraFactoryProvider).apply {
+            surfaceManager?.let {
+                setDeviceSurfaceManagerProvider { _: Context?, _: Any?, _: Set<String?>? -> it }
+            }
+            useCaseConfigFactory?.let {
+                setUseCaseConfigFactoryProvider { _: Context? -> it }
+            }
+        }.build()
+    }
+
+    private fun createFakeCameraFactory(
+        frontCamera: Boolean = false,
+        backCamera: Boolean = false,
+    ): CameraFactory =
+        FakeCameraFactory(null).also { cameraFactory ->
+            if (backCamera) {
+                cameraFactory.insertCamera(CameraSelector.LENS_FACING_BACK, CAMERA_ID_0) {
+                    FakeCamera(
+                        CAMERA_ID_0, null,
+                        FakeCameraInfoInternal(
+                            CAMERA_ID_0, 0,
+                            CameraSelector.LENS_FACING_BACK
+                        )
+                    )
+                }
+            }
+            if (frontCamera) {
+                cameraFactory.insertCamera(CameraSelector.LENS_FACING_FRONT, CAMERA_ID_1) {
+                    FakeCamera(
+                        CAMERA_ID_1,
+                        null,
+                        FakeCameraInfoInternal(
+                            CAMERA_ID_1, 0,
+                            CameraSelector.LENS_FACING_FRONT
+                        )
+                    )
+                }
+            }
+            cameraFactory.cameraCoordinator = FakeCameraCoordinator()
+        }
+
+    private fun TestScope.simulateSystemTimeIncrease() = async {
+        val startTimeMs = SystemClock.elapsedRealtime()
+        while (SystemClock.elapsedRealtime() - startTimeMs < 20000L) {
+            shadowOf(handler.looper).idle()
+            if (SystemClock.elapsedRealtime() < currentTime) {
+                ShadowSystemClock.advanceBy(
+                    currentTime - SystemClock.elapsedRealtime(),
+                    TimeUnit.MILLISECONDS
+                )
+            }
+            delay(FAKE_INIT_PROCESS_TIME_MS)
+        }
+    }
+
+    companion object {
+        private const val CAMERA_ID_0 = "0"
+        private const val CAMERA_ID_1 = "1"
+        private const val FAKE_INIT_PROCESS_TIME_MS = 33L
+    }
+}
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/ImageCaptureTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/ImageCaptureTest.kt
index 30155a3..b2005fb 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/ImageCaptureTest.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/ImageCaptureTest.kt
@@ -31,11 +31,14 @@
 import androidx.camera.core.CameraEffect.IMAGE_CAPTURE
 import androidx.camera.core.CameraEffect.PREVIEW
 import androidx.camera.core.CameraEffect.VIDEO_CAPTURE
+import androidx.camera.core.ImageCapture.OUTPUT_FORMAT_JPEG
+import androidx.camera.core.ImageCapture.OUTPUT_FORMAT_ULTRA_HDR
 import androidx.camera.core.MirrorMode.MIRROR_MODE_OFF
 import androidx.camera.core.MirrorMode.MIRROR_MODE_ON_FRONT_ONLY
 import androidx.camera.core.impl.CameraFactory
 import androidx.camera.core.impl.CaptureConfig
 import androidx.camera.core.impl.ImageCaptureConfig
+import androidx.camera.core.impl.ImageFormatConstants.INTERNAL_DEFINED_IMAGE_FORMAT_PRIVATE
 import androidx.camera.core.impl.MutableOptionsBundle
 import androidx.camera.core.impl.SessionConfig
 import androidx.camera.core.impl.SessionProcessor
@@ -56,9 +59,12 @@
 import androidx.camera.testing.impl.CameraUtil
 import androidx.camera.testing.impl.CameraXUtil
 import androidx.camera.testing.impl.fakes.FakeCameraConfig
+import androidx.camera.testing.impl.fakes.FakeCameraCoordinator
+import androidx.camera.testing.impl.fakes.FakeCameraDeviceSurfaceManager
 import androidx.camera.testing.impl.fakes.FakeCameraFactory
 import androidx.camera.testing.impl.fakes.FakeImageReaderProxy
 import androidx.camera.testing.impl.fakes.FakeSessionProcessor
+import androidx.camera.testing.impl.fakes.FakeUseCaseConfigFactory
 import androidx.camera.testing.impl.mocks.MockScreenFlash
 import androidx.camera.testing.impl.mocks.MockScreenFlashListener
 import androidx.test.core.app.ApplicationProvider
@@ -384,6 +390,88 @@
             .isEqualTo(ImageFormat.YUV_420_888)
     }
 
+    @Test
+    fun canGetSupportedOutputFormats_whenCameraDoNotSupportUltraHdr() {
+        val cameraInfo = FakeCameraInfoInternal()
+        cameraInfo.setSupportedResolutions(ImageFormat.JPEG, listOf())
+
+        // Verify.
+        val capabilities = ImageCapture.getImageCaptureCapabilities(cameraInfo)
+        assertThat(capabilities.supportedOutputFormats).containsExactlyElementsIn(
+            listOf(OUTPUT_FORMAT_JPEG)
+        )
+    }
+
+    @Config(minSdk = 34)
+    @Test
+    fun canGetSupportedOutputFormats_whenCameraSupportsUltraHdr() {
+        val cameraInfo = FakeCameraInfoInternal()
+        cameraInfo.setSupportedResolutions(ImageFormat.JPEG, listOf())
+        cameraInfo.setSupportedResolutions(ImageFormat.JPEG_R, listOf())
+
+        // Verify.
+        val capabilities = ImageCapture.getImageCaptureCapabilities(cameraInfo)
+        assertThat(capabilities.supportedOutputFormats).containsExactlyElementsIn(
+            listOf(OUTPUT_FORMAT_JPEG, OUTPUT_FORMAT_ULTRA_HDR)
+        )
+    }
+
+    @Test
+    fun outputFormat_isDefaultAsJpeg_whenNotSet() {
+        val imageCapture = ImageCapture.Builder().build()
+
+        // Verify.
+        assertThat(imageCapture.outputFormat).isEqualTo(OUTPUT_FORMAT_JPEG)
+    }
+
+    @Test
+    fun canSetOutputFormatAsJpeg() {
+        val imageCapture = ImageCapture.Builder()
+            .setOutputFormat(OUTPUT_FORMAT_JPEG)
+            .build()
+
+        // Verify.
+        assertThat(imageCapture.outputFormat).isEqualTo(OUTPUT_FORMAT_JPEG)
+    }
+
+    @Config(minSdk = 34)
+    @Test
+    fun canSetOutputFormatAsUltraHdr() {
+        val imageCapture = ImageCapture.Builder()
+            .setOutputFormat(OUTPUT_FORMAT_ULTRA_HDR)
+            .build()
+
+        // Verify.
+        assertThat(imageCapture.outputFormat).isEqualTo(OUTPUT_FORMAT_ULTRA_HDR)
+    }
+
+    @Config(minSdk = 34)
+    @Test
+    fun sessionConfigSurfaceFormat_isJpegR_whenOutputFormatIsSetAsUltraHdr() {
+        // Arrange.
+        val imageCapture = ImageCapture.Builder()
+            .setOutputFormat(OUTPUT_FORMAT_ULTRA_HDR)
+            .build()
+
+        // Act.
+        val cameraId = "fakeCameraId"
+        val fakeManager = FakeCameraDeviceSurfaceManager()
+        fakeManager.setValidSurfaceCombos(
+            setOf(listOf(INTERNAL_DEFINED_IMAGE_FORMAT_PRIVATE, ImageFormat.JPEG_R))
+        )
+        val adapter = CameraUseCaseAdapter(
+            FakeCamera(cameraId),
+            FakeCameraCoordinator(),
+            fakeManager,
+            FakeUseCaseConfigFactory()
+        )
+        adapter.addUseCases(listOf(imageCapture))
+
+        // Verify.
+        assertThat(imageCapture.sessionConfig.surfaces[0].prescribedStreamFormat)
+            .isEqualTo(ImageFormat.JPEG_R)
+    }
+
     @Config(maxSdk = 22)
     @Test
     fun bindImageCaptureWithZslUnsupportedSdkVersion_notAddZslConfig() {
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/Image2JpegBytesTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/Image2JpegBytesTest.kt
index 13253c9..e8824e7 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/Image2JpegBytesTest.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/Image2JpegBytesTest.kt
@@ -17,6 +17,7 @@
 package androidx.camera.core.imagecapture
 
 import android.graphics.ImageFormat.JPEG
+import android.graphics.ImageFormat.JPEG_R
 import android.os.Build
 import android.util.Size
 import androidx.camera.core.imagecapture.Utils.CAMERA_CAPTURE_RESULT
@@ -30,6 +31,8 @@
 import androidx.camera.testing.impl.ExifUtil.createExif
 import androidx.camera.testing.impl.TestImageUtil.createJpegBytes
 import androidx.camera.testing.impl.TestImageUtil.createJpegFakeImageProxy
+import androidx.camera.testing.impl.TestImageUtil.createJpegrBytes
+import androidx.camera.testing.impl.TestImageUtil.createJpegrFakeImageProxy
 import androidx.camera.testing.impl.TestImageUtil.getAverageDiff
 import com.google.common.truth.Truth.assertThat
 import org.junit.Test
@@ -80,4 +83,38 @@
         assertThat(output.sensorToBufferTransform).isEqualTo(SENSOR_TO_BUFFER)
         assertThat(output.cameraCaptureResult).isEqualTo(CAMERA_CAPTURE_RESULT)
     }
+
+    @Config(minSdk = 34)
+    @Test
+    fun processJpegrImage_assertOutput() {
+        // Arrange: create input
+        val jpegBytes = createJpegrBytes(WIDTH, HEIGHT)
+        val exif = createExif(jpegBytes)
+        val image = createJpegrFakeImageProxy(jpegBytes)
+        val input = Packet.of(
+            image,
+            exif,
+            CROP_RECT,
+            ROTATION_DEGREES,
+            SENSOR_TO_BUFFER,
+            CAMERA_CAPTURE_RESULT
+        )
+
+        // Act.
+        val output = operation.apply(Image2JpegBytes.In.of(input, 100))
+
+        // Assert: the image is the same.
+        assertThat(getAverageDiff(jpegBytes, output.data)).isEqualTo(0)
+        // Assert: the Exif is extracted correctly.
+        assertThat(output.exif).isEqualTo(exif)
+        // Assert: the image is closed.
+        assertThat(image.isClosed).isTrue()
+        // Assert: metadata is correct.
+        assertThat(output.cropRect).isEqualTo(CROP_RECT)
+        assertThat(output.rotationDegrees).isEqualTo(ROTATION_DEGREES)
+        assertThat(output.format).isEqualTo(JPEG_R)
+        assertThat(output.size).isEqualTo(Size(WIDTH, HEIGHT))
+        assertThat(output.sensorToBufferTransform).isEqualTo(SENSOR_TO_BUFFER)
+        assertThat(output.cameraCaptureResult).isEqualTo(CAMERA_CAPTURE_RESULT)
+    }
 }
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/ImagePipelineTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/ImagePipelineTest.kt
index 8da5e69..7d93b7b 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/ImagePipelineTest.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/ImagePipelineTest.kt
@@ -22,6 +22,7 @@
 import android.os.Build
 import android.os.Looper.getMainLooper
 import android.util.Size
+import androidx.camera.core.DynamicRange.HLG_10_BIT
 import androidx.camera.core.ImageCapture
 import androidx.camera.core.ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY
 import androidx.camera.core.ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY
@@ -45,20 +46,24 @@
 import androidx.camera.core.imagecapture.Utils.WIDTH
 import androidx.camera.core.imagecapture.Utils.createCameraCaptureResultImageInfo
 import androidx.camera.core.imagecapture.Utils.injectRotationOptionQuirk
-import androidx.camera.core.impl.CaptureConfig
+import androidx.camera.core.impl.CaptureConfig.OPTION_JPEG_QUALITY
 import androidx.camera.core.impl.CaptureConfig.OPTION_ROTATION
 import androidx.camera.core.impl.ImageCaptureConfig
 import androidx.camera.core.impl.ImageCaptureConfig.OPTION_BUFFER_FORMAT
 import androidx.camera.core.impl.ImageInputConfig
+import androidx.camera.core.impl.ImageInputConfig.OPTION_INPUT_DYNAMIC_RANGE
 import androidx.camera.core.impl.utils.executor.CameraXExecutors.mainThreadExecutor
 import androidx.camera.core.impl.utils.futures.Futures
 import androidx.camera.core.internal.IoConfig.OPTION_IO_EXECUTOR
 import androidx.camera.testing.impl.TestImageUtil.createJpegBytes
 import androidx.camera.testing.impl.TestImageUtil.createJpegFakeImageProxy
+import androidx.camera.testing.impl.TestImageUtil.createJpegrBytes
+import androidx.camera.testing.impl.TestImageUtil.createJpegrFakeImageProxy
 import androidx.camera.testing.impl.TestImageUtil.createYuvFakeImageProxy
 import androidx.camera.testing.impl.fakes.FakeImageInfo
 import androidx.camera.testing.impl.fakes.FakeImageReaderProxy
 import androidx.camera.testing.impl.fakes.GrayscaleImageEffect
+import androidx.core.util.Pair
 import com.google.common.truth.Truth.assertThat
 import org.junit.After
 import org.junit.Before
@@ -90,15 +95,7 @@
 
     @Before
     fun setUp() {
-        // Create ImageCaptureConfig.
-        val builder = ImageCapture.Builder()
-            .setCaptureOptionUnpacker { _, builder ->
-                builder.templateType = TEMPLATE_TYPE
-            }
-        builder.mutableConfig.insertOption(OPTION_IO_EXECUTOR, mainThreadExecutor())
-        builder.mutableConfig.insertOption(ImageInputConfig.OPTION_INPUT_FORMAT, ImageFormat.JPEG)
-        builder.setSessionOptionUnpacker { _, _, _ -> }
-        imageCaptureConfig = builder.useCaseConfig
+        imageCaptureConfig = createImageCaptureConfig()
         imagePipeline = ImagePipeline(imageCaptureConfig, SIZE)
     }
 
@@ -169,24 +166,25 @@
         // Act: create requests
         val result =
             imagePipeline.createRequests(IN_MEMORY_REQUEST, CALLBACK, Futures.immediateFuture(null))
-        // Assert: CameraRequest is constructed correctly.
-        val cameraRequest = result.first!!
-        val captureConfig = cameraRequest.captureConfigs.single()
-        assertThat(captureConfig.cameraCaptureCallbacks)
-            .containsExactly(captureInput.cameraCaptureCallback)
-        assertThat(captureConfig.surfaces).containsExactly(captureInput.surface)
-        assertThat(captureConfig.templateType).isEqualTo(TEMPLATE_TYPE)
-        val jpegQuality = captureConfig.implementationOptions
-            .retrieveOption(CaptureConfig.OPTION_JPEG_QUALITY)
-        assertThat(jpegQuality).isEqualTo(JPEG_QUALITY)
-        assertThat(captureConfig.implementationOptions.retrieveOption(OPTION_ROTATION))
-            .isEqualTo(ROTATION_DEGREES)
 
-        // Act: fail the processing request.
-        val processingRequest = result.second!!
-        processingRequest.onCaptureFailure(FAILURE)
-        // Assert: The failure is propagated.
-        assertThat(CALLBACK.captureFailure).isEqualTo(FAILURE)
+        // Assert: CameraRequest is constructed correctly.
+        verifyCaptureRequest(captureInput, result)
+    }
+
+    @Config(minSdk = 34)
+    @Test
+    fun createRequests_verifyCameraRequest_whenFormatIsJpegr() {
+        // Arrange.
+        imageCaptureConfig = createImageCaptureConfig(inputFormat = ImageFormat.JPEG_R)
+        imagePipeline = ImagePipeline(imageCaptureConfig, SIZE)
+        val captureInput = imagePipeline.captureNode.inputEdge
+
+        // Act: create requests
+        val result =
+            imagePipeline.createRequests(IN_MEMORY_REQUEST, CALLBACK, Futures.immediateFuture(null))
+
+        // Assert: CameraRequest is constructed correctly.
+        verifyCaptureRequest(captureInput, result)
     }
 
     @Test
@@ -341,9 +339,7 @@
         // Get JPEG quality and return.
         val cameraRequest = result.first!!
         val captureConfig = cameraRequest.captureConfigs.single()
-        return captureConfig.implementationOptions.retrieveOption(
-            CaptureConfig.OPTION_JPEG_QUALITY
-        )!!
+        return captureConfig.implementationOptions.retrieveOption(OPTION_JPEG_QUALITY)!!
     }
 
     @Test
@@ -370,20 +366,8 @@
         builder.mutableConfig.insertOption(ImageInputConfig.OPTION_INPUT_FORMAT, ImageFormat.JPEG)
         val pipeline = ImagePipeline(builder.useCaseConfig, SIZE)
 
-        // Arrange.
-        val processingRequest = imagePipeline.createRequests(
-            IN_MEMORY_REQUEST, CALLBACK, Futures.immediateFuture(null)
-        ).second!!
-        val imageInfo = createCameraCaptureResultImageInfo(
-            processingRequest.tagBundleKey,
-            processingRequest.stageIds.single()
-        )
-        val image = createYuvFakeImageProxy(imageInfo, WIDTH, HEIGHT)
-
-        // Act: send processing request and the image.
-        pipeline.submitProcessingRequest(processingRequest)
-        pipeline.captureNode.onImageProxyAvailable(image)
-        shadowOf(getMainLooper()).idle()
+        // Arrange & act.
+        sendInMemoryRequest(pipeline, ImageFormat.YUV_420_888)
 
         assertThat(CALLBACK.inMemoryResult!!.format).isEqualTo(ImageFormat.YUV_420_888)
     }
@@ -394,23 +378,58 @@
         val image = sendInMemoryRequest(imagePipeline)
 
         // Assert: the image is received by TakePictureCallback.
+        assertThat(image.format).isEqualTo(ImageFormat.JPEG)
+        assertThat(CALLBACK.inMemoryResult!!.format).isEqualTo(ImageFormat.JPEG)
+        assertThat(CALLBACK.inMemoryResult!!.planes).isEqualTo(image.planes)
+    }
+
+    @Config(minSdk = 34)
+    @Test
+    fun sendInMemoryRequest_receivesImageProxy_whenFormatIsJpegr() {
+        // Arrange & act.
+        imageCaptureConfig = createImageCaptureConfig(inputFormat = ImageFormat.JPEG_R)
+        imagePipeline = ImagePipeline(imageCaptureConfig, SIZE)
+        val image = sendInMemoryRequest(imagePipeline, ImageFormat.JPEG_R)
+
+        // Assert: the image is received by TakePictureCallback.
+        assertThat(image.format).isEqualTo(ImageFormat.JPEG_R)
+        assertThat(CALLBACK.inMemoryResult!!.format).isEqualTo(ImageFormat.JPEG_R)
         assertThat(CALLBACK.inMemoryResult!!.planes).isEqualTo(image.planes)
     }
 
     /**
      * Creates a ImageProxy and sends it to the pipeline.
      */
-    private fun sendInMemoryRequest(pipeline: ImagePipeline): ImageProxy {
+    private fun sendInMemoryRequest(
+        pipeline: ImagePipeline,
+        format: Int = ImageFormat.JPEG
+    ): ImageProxy {
         // Arrange.
         val processingRequest = imagePipeline.createRequests(
             IN_MEMORY_REQUEST, CALLBACK, Futures.immediateFuture(null)
         ).second!!
-        val jpegBytes = createJpegBytes(WIDTH, HEIGHT)
         val imageInfo = createCameraCaptureResultImageInfo(
             processingRequest.tagBundleKey,
             processingRequest.stageIds.single()
         )
-        val image = createJpegFakeImageProxy(imageInfo, jpegBytes)
+        val image = when (format) {
+            ImageFormat.YUV_420_888 -> {
+                createYuvFakeImageProxy(imageInfo, WIDTH, HEIGHT)
+            }
+
+            ImageFormat.JPEG_R -> {
+                if (Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
+                    throw IllegalArgumentException("JPEG_R is supported after API 34.")
+                }
+                val jpegBytes = createJpegrBytes(WIDTH, HEIGHT)
+                createJpegrFakeImageProxy(imageInfo, jpegBytes)
+            }
+
+            else -> {
+                val jpegBytes = createJpegBytes(WIDTH, HEIGHT)
+                createJpegFakeImageProxy(imageInfo, jpegBytes)
+            }
+        }
 
         // Act: send processing request and the image.
         pipeline.submitProcessingRequest(processingRequest)
@@ -457,4 +476,42 @@
         // Assert: The failure is propagated.
         assertThat(CALLBACK.captureFailure).isEqualTo(FAILURE)
     }
+
+    private fun createImageCaptureConfig(
+        templateType: Int = TEMPLATE_TYPE,
+        inputFormat: Int = ImageFormat.JPEG,
+    ): ImageCaptureConfig {
+        val builder = ImageCapture.Builder().setCaptureOptionUnpacker { _, builder ->
+            builder.templateType = templateType
+        }
+        builder.mutableConfig.insertOption(OPTION_IO_EXECUTOR, mainThreadExecutor())
+        builder.mutableConfig.insertOption(ImageInputConfig.OPTION_INPUT_FORMAT, inputFormat)
+        if (Build.VERSION.SDK_INT >= 34 && inputFormat == ImageFormat.JPEG_R) {
+            builder.mutableConfig.insertOption(OPTION_INPUT_DYNAMIC_RANGE, HLG_10_BIT)
+        }
+        builder.setSessionOptionUnpacker { _, _, _ -> }
+        return builder.useCaseConfig
+    }
+
+    private fun verifyCaptureRequest(
+        captureInput: CaptureNode.In,
+        result: Pair<CameraRequest, ProcessingRequest>
+    ) {
+        val cameraRequest = result.first!!
+        val captureConfig = cameraRequest.captureConfigs.single()
+        assertThat(captureConfig.cameraCaptureCallbacks)
+            .containsExactly(captureInput.cameraCaptureCallback)
+        assertThat(captureConfig.surfaces).containsExactly(captureInput.surface)
+        assertThat(captureConfig.templateType).isEqualTo(TEMPLATE_TYPE)
+        val jpegQuality = captureConfig.implementationOptions.retrieveOption(OPTION_JPEG_QUALITY)
+        assertThat(jpegQuality).isEqualTo(JPEG_QUALITY)
+        assertThat(captureConfig.implementationOptions.retrieveOption(OPTION_ROTATION))
+            .isEqualTo(ROTATION_DEGREES)
+
+        // Act: fail the processing request.
+        val processingRequest = result.second!!
+        processingRequest.onCaptureFailure(FAILURE)
+        // Assert: The failure is propagated.
+        assertThat(CALLBACK.captureFailure).isEqualTo(FAILURE)
+    }
 }
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/ProcessingInput2PacketTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/ProcessingInput2PacketTest.kt
index 96c98a4..bfad712 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/ProcessingInput2PacketTest.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/ProcessingInput2PacketTest.kt
@@ -37,6 +37,8 @@
 import androidx.camera.testing.impl.ExifUtil.updateExif
 import androidx.camera.testing.impl.TestImageUtil.createJpegBytes
 import androidx.camera.testing.impl.TestImageUtil.createJpegFakeImageProxy
+import androidx.camera.testing.impl.TestImageUtil.createJpegrBytes
+import androidx.camera.testing.impl.TestImageUtil.createJpegrFakeImageProxy
 import androidx.camera.testing.impl.TestImageUtil.createYuvFakeImageProxy
 import androidx.camera.testing.impl.TestImageUtil.getAverageDiff
 import com.google.common.truth.Truth.assertThat
@@ -106,6 +108,32 @@
         assertThat(output.exif!!.description).isEqualTo(EXIF_DESCRIPTION)
     }
 
+    @Config(minSdk = 34)
+    @Test
+    fun processInput_assertImageAndNonTransformationExif_whenOutputFormatIsJpegr() {
+        // Arrange: create input
+        val jpegBytes = updateExif(createJpegrBytes(640, 480)) {
+            it.description = EXIF_DESCRIPTION
+        }
+        val image = createJpegrFakeImageProxy(jpegBytes)
+        val processingRequest = createProcessingRequest()
+        val input = ProcessingNode.InputPacket.of(processingRequest, image)
+
+        // Act.
+        val output = operation.apply(input)
+
+        // Assert.
+        assertThat(output.format).isEqualTo(ImageFormat.JPEG_R)
+        // Assert: buffer is rewound after reading Exif data.
+        val buffer = output.data.planes[0].buffer
+        assertThat(buffer.position()).isEqualTo(0)
+        // Assert: image is the same.
+        val restoredJpeg = jpegImageToJpegByteArray(output.data)
+        assertThat(getAverageDiff(jpegBytes, restoredJpeg)).isEqualTo(0)
+        // Assert: the Exif is extracted correctly.
+        assertThat(output.exif!!.description).isEqualTo(EXIF_DESCRIPTION)
+    }
+
     @Test
     fun withoutQuirk_outputMetadataIsBasedOnJpegExif() {
         // Arrange: assume the rotation is 90 and it's applied by the HAL.
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/ProcessingNodeTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/ProcessingNodeTest.kt
index 3c85deb..fe00f5f 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/ProcessingNodeTest.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/ProcessingNodeTest.kt
@@ -35,6 +35,8 @@
 import androidx.camera.testing.impl.TestImageUtil.createA24ProblematicJpegByteArray
 import androidx.camera.testing.impl.TestImageUtil.createJpegBytes
 import androidx.camera.testing.impl.TestImageUtil.createJpegFakeImageProxy
+import androidx.camera.testing.impl.TestImageUtil.createJpegrBytes
+import androidx.camera.testing.impl.TestImageUtil.createJpegrFakeImageProxy
 import androidx.camera.testing.impl.fakes.FakeImageInfo
 import androidx.camera.testing.impl.fakes.FakeImageProxy
 import com.google.common.truth.Truth.assertThat
@@ -90,6 +92,32 @@
         assertThat(callback.onDiskResult).isNotNull()
     }
 
+    @Config(minSdk = 34)
+    @Test
+    fun processRequest_hasDiskResult_whenFormatIsJpegr() {
+        // Arrange: create a request with callback.
+        val callback = FakeTakePictureCallback()
+        val request = ProcessingRequest(
+            { listOf() },
+            OUTPUT_FILE_OPTIONS,
+            Rect(0, 0, WIDTH, HEIGHT),
+            ROTATION_DEGREES,
+            /*jpegQuality=*/100,
+            SENSOR_TO_BUFFER,
+            callback,
+            Futures.immediateFuture(null)
+        )
+
+        // Act: process the request.
+        val jpegBytes = createJpegrBytes(WIDTH, HEIGHT)
+        val image = createJpegrFakeImageProxy(jpegBytes)
+        processingNodeIn.edge.accept(ProcessingNode.InputPacket.of(request, image))
+        shadowOf(getMainLooper()).idle()
+
+        // Assert: the image is saved.
+        assertThat(callback.onDiskResult).isNotNull()
+    }
+
     @Test
     fun processAbortedRequest_noOps() {
         // Arrange: create a request with aborted callback.
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/internal/CameraUseCaseAdapterTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/internal/CameraUseCaseAdapterTest.kt
index 3d2b8d3..9293479 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/internal/CameraUseCaseAdapterTest.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/internal/CameraUseCaseAdapterTest.kt
@@ -17,6 +17,7 @@
 package androidx.camera.core.internal
 
 import android.graphics.ImageFormat.JPEG
+import android.graphics.ImageFormat.JPEG_R
 import android.graphics.Matrix
 import android.graphics.Rect
 import android.os.Build
@@ -47,6 +48,7 @@
 import androidx.camera.core.impl.CameraInternal
 import androidx.camera.core.impl.Config
 import androidx.camera.core.impl.Identifier
+import androidx.camera.core.impl.ImageFormatConstants.INTERNAL_DEFINED_IMAGE_FORMAT_PRIVATE
 import androidx.camera.core.impl.MutableOptionsBundle
 import androidx.camera.core.impl.OptionsBundle
 import androidx.camera.core.impl.RestrictedCameraControl
@@ -273,6 +275,56 @@
         adapter.addUseCases(setOf(hdrUseCase))
     }
 
+    @RequiresApi(34) // Ultra HDR only supported on API 34+
+    @Test(expected = CameraException::class)
+    fun useUltraHdrWithExtensions_throwsException() {
+        // Arrange: enable extensions.
+        val extensionsConfig = createCoexistingRequiredRuleCameraConfig(FakeSessionProcessor())
+        val cameraId = "fakeCameraId"
+        val fakeManager = FakeCameraDeviceSurfaceManager()
+        fakeManager.setValidSurfaceCombos(
+            setOf(listOf(INTERNAL_DEFINED_IMAGE_FORMAT_PRIVATE, JPEG_R))
+        )
+        val fakeCamera = FakeCamera(cameraId)
+        val adapter = CameraUseCaseAdapter(
+            fakeCamera,
+            RestrictedCameraInfo(fakeCamera.cameraInfoInternal, extensionsConfig),
+            FakeCameraCoordinator(),
+            fakeManager,
+            FakeUseCaseConfigFactory(),
+        )
+
+        // Act: add ImageCapture that sets Ultra HDR.
+        val imageCapture = ImageCapture.Builder()
+            .setOutputFormat(ImageCapture.OUTPUT_FORMAT_ULTRA_HDR)
+            .build()
+        adapter.addUseCases(setOf(imageCapture))
+    }
+
+    @RequiresApi(34) // Ultra HDR only supported on API 34+
+    @Test(expected = CameraException::class)
+    fun useUltraHdrWithCameraEffect_throwsException() {
+        // Arrange: add an image effect.
+        val cameraId = "fakeCameraId"
+        val fakeManager = FakeCameraDeviceSurfaceManager()
+        fakeManager.setValidSurfaceCombos(
+            setOf(listOf(INTERNAL_DEFINED_IMAGE_FORMAT_PRIVATE, JPEG_R))
+        )
+        val adapter = CameraUseCaseAdapter(
+            FakeCamera(cameraId),
+            FakeCameraCoordinator(),
+            fakeManager,
+            FakeUseCaseConfigFactory(),
+        )
+        adapter.setEffects(listOf(imageEffect))
+
+        // Act: add ImageCapture that sets Ultra HDR.
+        val imageCapture = ImageCapture.Builder()
+            .setOutputFormat(ImageCapture.OUTPUT_FORMAT_ULTRA_HDR)
+            .build()
+        adapter.addUseCases(setOf(imageCapture))
+    }
+
     @Test(expected = CameraException::class)
     fun addStreamSharing_throwsException() {
         val streamSharing = StreamSharing(fakeCamera, setOf(preview, video), useCaseConfigFactory)
diff --git a/camera/camera-testing/src/androidTest/java/androidx/camera/testing/ExifUtilDeviceTest.kt b/camera/camera-testing/src/androidTest/java/androidx/camera/testing/ExifUtilDeviceTest.kt
new file mode 100644
index 0000000..8bbcf8b
--- /dev/null
+++ b/camera/camera-testing/src/androidTest/java/androidx/camera/testing/ExifUtilDeviceTest.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2024 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.testing
+
+import androidx.camera.testing.impl.ExifUtil
+import androidx.camera.testing.impl.TestImageUtil
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SdkSuppress
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@SdkSuppress(minSdkVersion = 21)
+class ExifUtilDeviceTest {
+
+    companion object {
+        private const val WIDTH = 640
+        private const val HEIGHT = 480
+        private val EXIF_GAINMAP_PATTERNS = listOf(
+            "xmlns:hdrgm=\"http://ns.adobe.com/hdr-gain-map/",
+            "hdrgm:Version=",
+            "Item:Semantic=\"GainMap\"",
+        )
+    }
+
+    @SdkSuppress(minSdkVersion = 34)
+    @Test
+    fun createJpegrWithExif_verifyExif() {
+        // Arrange.
+        val jpegBytes = TestImageUtil.createJpegrBytes(WIDTH, HEIGHT)
+
+        // Act.
+        val exif = ExifUtil.createExif(jpegBytes)
+
+        // Assert.
+        val exifMetadata = exif.metadata
+        assertThat(exifMetadata).isNotNull()
+        for (pattern in EXIF_GAINMAP_PATTERNS) {
+            assertThat(exifMetadata).contains(pattern)
+        }
+    }
+}
diff --git a/camera/camera-testing/src/androidTest/java/androidx/camera/testing/TestImageUtilDeviceTest.kt b/camera/camera-testing/src/androidTest/java/androidx/camera/testing/TestImageUtilDeviceTest.kt
index 450f4cd..97f39b5 100644
--- a/camera/camera-testing/src/androidTest/java/androidx/camera/testing/TestImageUtilDeviceTest.kt
+++ b/camera/camera-testing/src/androidTest/java/androidx/camera/testing/TestImageUtilDeviceTest.kt
@@ -21,11 +21,19 @@
 import android.graphics.Color.GREEN
 import android.graphics.Color.RED
 import android.graphics.Color.YELLOW
+import android.graphics.ImageFormat
 import android.graphics.Rect
 import androidx.camera.core.internal.utils.ImageUtil.jpegImageToJpegByteArray
+import androidx.camera.testing.impl.TestImageUtil.COLOR_BLACK
+import androidx.camera.testing.impl.TestImageUtil.COLOR_DARK_GRAY
+import androidx.camera.testing.impl.TestImageUtil.COLOR_GRAY
+import androidx.camera.testing.impl.TestImageUtil.COLOR_WHITE
 import androidx.camera.testing.impl.TestImageUtil.createBitmap
+import androidx.camera.testing.impl.TestImageUtil.createGainmap
 import androidx.camera.testing.impl.TestImageUtil.createJpegBytes
 import androidx.camera.testing.impl.TestImageUtil.createJpegFakeImageProxy
+import androidx.camera.testing.impl.TestImageUtil.createJpegrBytes
+import androidx.camera.testing.impl.TestImageUtil.createJpegrFakeImageProxy
 import androidx.camera.testing.impl.TestImageUtil.getAverageDiff
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SdkSuppress
@@ -60,6 +68,36 @@
         assertThat(getAverageDiff(bitmap, Rect(0, 241, 320, 480), BLUE)).isEqualTo(0)
     }
 
+    @SdkSuppress(minSdkVersion = 34)
+    @Test
+    fun createTestJpegr_verifyColorAndGainmapContent() {
+        // Act.
+        val jpegBytes = createJpegrBytes(WIDTH, HEIGHT)
+        val bitmap = decodeByteArray(jpegBytes, 0, jpegBytes.size)
+        assertThat(bitmap.hasGainmap()).isTrue()
+        val contents = bitmap.gainmap!!.gainmapContents
+        // Assert.
+        assertThat(getAverageDiff(bitmap, Rect(0, 0, 320, 240), RED)).isEqualTo(0)
+        assertThat(getAverageDiff(bitmap, Rect(321, 0, 640, 240), GREEN)).isEqualTo(0)
+        assertThat(getAverageDiff(bitmap, Rect(321, 241, 640, 480), YELLOW)).isEqualTo(0)
+        assertThat(getAverageDiff(bitmap, Rect(0, 241, 320, 480), BLUE)).isEqualTo(0)
+        assertThat(getAverageDiff(contents, Rect(0, 0, 640, 120), COLOR_BLACK)).isEqualTo(0)
+        assertThat(getAverageDiff(contents, Rect(0, 121, 640, 240), COLOR_DARK_GRAY)).isEqualTo(0)
+        assertThat(getAverageDiff(contents, Rect(0, 241, 640, 360), COLOR_GRAY)).isEqualTo(0)
+        assertThat(getAverageDiff(contents, Rect(0, 361, 640, 480), COLOR_WHITE)).isEqualTo(0)
+    }
+
+    @Test
+    fun createBitmap_verifyColor() {
+        // Act.
+        val bitmap = createBitmap(WIDTH, HEIGHT)
+        // Assert.
+        assertThat(getAverageDiff(bitmap, Rect(0, 0, 320, 240), RED)).isEqualTo(0)
+        assertThat(getAverageDiff(bitmap, Rect(321, 0, 640, 240), GREEN)).isEqualTo(0)
+        assertThat(getAverageDiff(bitmap, Rect(321, 241, 640, 480), YELLOW)).isEqualTo(0)
+        assertThat(getAverageDiff(bitmap, Rect(0, 241, 320, 480), BLUE)).isEqualTo(0)
+    }
+
     @Test
     fun createBitmap_verifyWithIncorrectColor() {
         // The color is supposed to be RED.
@@ -72,6 +110,18 @@
         ).isEqualTo(255)
     }
 
+    @SdkSuppress(minSdkVersion = 34)
+    @Test
+    fun createGainmap_verifyContents() {
+        // Act.
+        val contents = createGainmap(WIDTH, HEIGHT).gainmapContents
+        // Assert.
+        assertThat(getAverageDiff(contents, Rect(0, 0, 640, 120), COLOR_BLACK)).isEqualTo(0)
+        assertThat(getAverageDiff(contents, Rect(0, 121, 640, 240), COLOR_DARK_GRAY)).isEqualTo(0)
+        assertThat(getAverageDiff(contents, Rect(0, 241, 640, 360), COLOR_GRAY)).isEqualTo(0)
+        assertThat(getAverageDiff(contents, Rect(0, 361, 640, 480), COLOR_WHITE)).isEqualTo(0)
+    }
+
     @Test
     fun createJpegImageProxy_verifyContent() {
         // Arrange: create JPEG bytes.
@@ -85,5 +135,23 @@
             decodeByteArray(restoredJpegBytes, 0, restoredJpegBytes.size)
         )
         assertThat(diff).isEqualTo(0)
+        assertThat(image.format).isEqualTo(ImageFormat.JPEG)
+    }
+
+    @SdkSuppress(minSdkVersion = 34)
+    @Test
+    fun createJpegrImageProxy_verifyContent() {
+        // Arrange: create JPEG bytes of JPEG/R.
+        val jpegBytes = createJpegrBytes(WIDTH, HEIGHT)
+        // Act: create ImageProxy output the JPEG bytes.
+        val image = createJpegrFakeImageProxy(jpegBytes)
+        // Act: get the image out of the ImageProxy and verify its content.
+        val restoredJpegBytes = jpegImageToJpegByteArray(image)
+        val diff = getAverageDiff(
+            decodeByteArray(jpegBytes, 0, jpegBytes.size),
+            decodeByteArray(restoredJpegBytes, 0, restoredJpegBytes.size)
+        )
+        assertThat(diff).isEqualTo(0)
+        assertThat(image.format).isEqualTo(ImageFormat.JPEG_R)
     }
 }
diff --git a/camera/camera-testing/src/main/java/androidx/camera/testing/impl/CameraUtil.java b/camera/camera-testing/src/main/java/androidx/camera/testing/impl/CameraUtil.java
index 9d9a543..5c0d04e 100644
--- a/camera/camera-testing/src/main/java/androidx/camera/testing/impl/CameraUtil.java
+++ b/camera/camera-testing/src/main/java/androidx/camera/testing/impl/CameraUtil.java
@@ -47,13 +47,16 @@
 import androidx.annotation.IntDef;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.annotation.OptIn;
 import androidx.annotation.RequiresApi;
 import androidx.annotation.RequiresPermission;
 import androidx.annotation.VisibleForTesting;
 import androidx.camera.core.CameraSelector;
 import androidx.camera.core.CameraX;
 import androidx.camera.core.CameraXConfig;
+import androidx.camera.core.ExperimentalRetryPolicy;
 import androidx.camera.core.Logger;
+import androidx.camera.core.RetryPolicy;
 import androidx.camera.core.UseCase;
 import androidx.camera.core.concurrent.CameraCoordinator;
 import androidx.camera.core.impl.CameraConfig;
@@ -618,6 +621,8 @@
      * @param cameraCoordinator The camera coordinator for concurrent cameras.
      * @param cameraSelector The selector to select cameras with.
      */
+    @SuppressLint("NullAnnotationGroup")
+    @OptIn(markerClass = ExperimentalRetryPolicy.class)
     @VisibleForTesting
     @NonNull
     public static CameraUseCaseAdapter createCameraUseCaseAdapter(
@@ -626,8 +631,8 @@
             @NonNull CameraSelector cameraSelector,
             @NonNull CameraConfig cameraConfig) {
         try {
-            CameraX cameraX = CameraXUtil.getOrCreateInstance(context, null).get(5000,
-                    TimeUnit.MILLISECONDS);
+            CameraX cameraX = CameraXUtil.getOrCreateInstance(context, null).get(
+                    RetryPolicy.getDefaultRetryTimeoutInMillis() + 2000, TimeUnit.MILLISECONDS);
             CameraInternal camera =
                     cameraSelector.select(cameraX.getCameraRepository().getCameras());
             return new CameraUseCaseAdapter(camera,
diff --git a/camera/camera-testing/src/main/java/androidx/camera/testing/impl/TestImageUtil.java b/camera/camera-testing/src/main/java/androidx/camera/testing/impl/TestImageUtil.java
index 43219e3..e6339d8 100644
--- a/camera/camera-testing/src/main/java/androidx/camera/testing/impl/TestImageUtil.java
+++ b/camera/camera-testing/src/main/java/androidx/camera/testing/impl/TestImageUtil.java
@@ -18,6 +18,7 @@
 
 import static android.graphics.BitmapFactory.decodeByteArray;
 import static android.graphics.ImageFormat.JPEG;
+import static android.graphics.ImageFormat.JPEG_R;
 import static android.graphics.ImageFormat.YUV_420_888;
 
 import static androidx.camera.testing.impl.ImageProxyUtil.createYUV420ImagePlanes;
@@ -26,13 +27,16 @@
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Color;
+import android.graphics.Gainmap;
 import android.graphics.Matrix;
 import android.graphics.Paint;
 import android.graphics.Rect;
 import android.os.Build;
 
+import androidx.annotation.DoNotInline;
 import androidx.annotation.NonNull;
 import androidx.annotation.RequiresApi;
+import androidx.annotation.VisibleForTesting;
 import androidx.camera.core.ImageInfo;
 import androidx.camera.core.internal.CameraCaptureResultImageInfo;
 import androidx.camera.testing.impl.fakes.FakeCameraCaptureResult;
@@ -54,10 +58,34 @@
  * |  blue | yellow |
  * ------------------
  * </pre>
+ *
+ * <p> The gain map generated by this class contains 4 gray scale blocks follows the pattern below.
+ * Each block have the same size and covers 1/4 of the image.
+ *
+ * <pre>
+ * ------------------
+ * |      black     |
+ * ------------------
+ * |    dark gray   |
+ * ------------------
+ * |      gray      |
+ * ------------------
+ * |      white     |
+ * ------------------
+ * </pre>
  */
 @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
 public class TestImageUtil {
 
+    @VisibleForTesting
+    public static final int COLOR_BLACK = 0xFF000000;
+    @VisibleForTesting
+    public static final int COLOR_DARK_GRAY = 0xFF404040;
+    @VisibleForTesting
+    public static final int COLOR_GRAY = 0xFF808080;
+    @VisibleForTesting
+    public static final int COLOR_WHITE = 0xFFFFFFFF;
+
     private TestImageUtil() {
     }
 
@@ -90,6 +118,17 @@
     }
 
     /**
+     * Creates a {@link FakeImageProxy} from JPEG bytes of JPEG/R.
+     */
+    @NonNull
+    public static FakeImageProxy createJpegrFakeImageProxy(@NonNull ImageInfo imageInfo,
+            @NonNull byte[] jpegBytes) {
+        Bitmap bitmap = decodeByteArray(jpegBytes, 0, jpegBytes.length);
+        return createJpegrFakeImageProxy(imageInfo, jpegBytes, bitmap.getWidth(),
+                bitmap.getHeight());
+    }
+
+    /**
      * Creates a {@link FakeImageProxy} from JPEG bytes.
      */
     @NonNull
@@ -104,6 +143,20 @@
     }
 
     /**
+     * Creates a {@link FakeImageProxy} from JPEG bytes of JPEG/R.
+     */
+    @NonNull
+    public static FakeImageProxy createJpegrFakeImageProxy(@NonNull ImageInfo imageInfo,
+            @NonNull byte[] jpegBytes, int width, int height) {
+        FakeImageProxy image = new FakeImageProxy(imageInfo);
+        image.setFormat(JPEG_R);
+        image.setPlanes(new FakeJpegPlaneProxy[]{new FakeJpegPlaneProxy(jpegBytes)});
+        image.setWidth(width);
+        image.setHeight(height);
+        return image;
+    }
+
+    /**
      * Creates a {@link FakeImageProxy} from JPEG bytes.
      */
     @NonNull
@@ -113,6 +166,15 @@
     }
 
     /**
+     * Creates a {@link FakeImageProxy} from JPEG bytes of JPEG/R.
+     */
+    @NonNull
+    public static FakeImageProxy createJpegrFakeImageProxy(@NonNull byte[] jpegBytes) {
+        return createJpegrFakeImageProxy(
+                new CameraCaptureResultImageInfo(new FakeCameraCaptureResult()), jpegBytes);
+    }
+
+    /**
      * Generates a JPEG image.
      */
     @NonNull
@@ -124,6 +186,18 @@
     }
 
     /**
+     * Generates a JPEG/R image.
+     */
+    @RequiresApi(api = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    @NonNull
+    public static byte[] createJpegrBytes(int width, int height) {
+        Bitmap bitmap = createBitmapWithGainmap(width, height);
+        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        bitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream);
+        return outputStream.toByteArray();
+    }
+
+    /**
      * Generates a A24 problematic JPEG image.
      */
     @NonNull
@@ -156,6 +230,36 @@
     }
 
     /**
+     * Generates a {@link Bitmap} image (contains 4 color blocks) with gain map (contains 4 gray
+     * scale blocks) set.
+     */
+    @RequiresApi(api = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    @NonNull
+    public static Bitmap createBitmapWithGainmap(int width, int height) {
+        Bitmap bitmap = createBitmap(width, height);
+        Api34Impl.setGainmap(bitmap, createGainmap(width, height));
+        return bitmap;
+    }
+
+    /**
+     * Generates a {@link Gainmap} image and paints it with 4 gray scale blocks.
+     */
+    @RequiresApi(api = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    @NonNull
+    public static Gainmap createGainmap(int width, int height) {
+        Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+        int oneFourthY = height / 4;
+        int twoFourthsY = oneFourthY * 2;
+        int threeFourthsY = oneFourthY * 3;
+        Canvas canvas = new Canvas(bitmap);
+        canvas.drawRect(0, 0, width, oneFourthY, createPaint(COLOR_BLACK));
+        canvas.drawRect(0, oneFourthY, width, twoFourthsY, createPaint(COLOR_DARK_GRAY));
+        canvas.drawRect(0, twoFourthsY, width, threeFourthsY, createPaint(COLOR_GRAY));
+        canvas.drawRect(0, threeFourthsY, width, height, createPaint(COLOR_WHITE));
+        return Api34Impl.createGainmap(bitmap);
+    }
+
+    /**
      * Rotates the bitmap clockwise by the given degrees.
      */
     @NonNull
@@ -228,4 +332,21 @@
         paint.setColor(color);
         return paint;
     }
+
+    @RequiresApi(34)
+    private static class Api34Impl {
+        @DoNotInline
+        static Gainmap createGainmap(@NonNull Bitmap bitmap) {
+            return new Gainmap(bitmap);
+        }
+
+        @DoNotInline
+        static void setGainmap(@NonNull Bitmap bitmap, @NonNull Gainmap gainmap) {
+            bitmap.setGainmap(gainmap);
+        }
+
+        // This class is not instantiable.
+        private Api34Impl() {
+        }
+    }
 }
diff --git a/camera/camera-testing/src/main/java/androidx/camera/testing/impl/fakes/FakeImageCaptureCallback.kt b/camera/camera-testing/src/main/java/androidx/camera/testing/impl/fakes/FakeImageCaptureCallback.kt
index 4041fb9..a04d99b 100644
--- a/camera/camera-testing/src/main/java/androidx/camera/testing/impl/fakes/FakeImageCaptureCallback.kt
+++ b/camera/camera-testing/src/main/java/androidx/camera/testing/impl/fakes/FakeImageCaptureCallback.kt
@@ -71,7 +71,7 @@
     }
 
     private fun getExif(image: ImageProxy): Exif? {
-        if (image.format == ImageFormat.JPEG) {
+        if (image.format == ImageFormat.JPEG || image.format == ImageFormat.JPEG_R) {
             val planes = image.planes
             val buffer = planes[0].buffer
             val data = ByteArray(buffer.capacity())
diff --git a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/ImageCaptureTest.kt b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/ImageCaptureTest.kt
index f6dac7d..2d7267b 100644
--- a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/ImageCaptureTest.kt
+++ b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/ImageCaptureTest.kt
@@ -34,6 +34,7 @@
 import android.util.Rational
 import android.util.Size
 import android.view.Surface
+import androidx.annotation.MainThread
 import androidx.annotation.OptIn
 import androidx.camera.camera2.Camera2Config
 import androidx.camera.camera2.interop.ExperimentalCamera2Interop
@@ -46,6 +47,8 @@
 import androidx.camera.core.CameraXConfig
 import androidx.camera.core.ImageAnalysis
 import androidx.camera.core.ImageCapture
+import androidx.camera.core.ImageCapture.OUTPUT_FORMAT_JPEG
+import androidx.camera.core.ImageCapture.OUTPUT_FORMAT_ULTRA_HDR
 import androidx.camera.core.ImageCaptureException
 import androidx.camera.core.ImageProxy
 import androidx.camera.core.Preview
@@ -122,6 +125,11 @@
 private const val BACK_LENS_FACING = CameraSelector.LENS_FACING_BACK
 private const val CAPTURE_TIMEOUT = 15_000.toLong() //  15 seconds
 private const val TOLERANCE = 1e-3f
+private val EXIF_GAINMAP_PATTERNS = listOf(
+    "xmlns:hdrgm=\"http://ns.adobe.com/hdr-gain-map/",
+    "hdrgm:Version=",
+    "Item:Semantic=\"GainMap\"",
+)
 
 @LargeTest
 @RunWith(Parameterized::class)
@@ -185,21 +193,43 @@
         }
     }
 
-    @Suppress("DEPRECATION") // test for legacy resolution API
     @Test
-    fun capturedImageHasCorrectSize() = runBlocking {
-        val useCase = ImageCapture.Builder()
+    fun capturedImageHasCorrectSize() {
+        takeImageAndVerifySize()
+    }
+
+    @SdkSuppress(minSdkVersion = 34)
+    @Test
+    fun capturedImageHasCorrectSize_whenOutputFormatIsUltraHdr() {
+        takeImageAndVerifySize(outputFormat = OUTPUT_FORMAT_ULTRA_HDR)
+    }
+
+    @Suppress("DEPRECATION") // test for legacy resolution API
+    private fun takeImageAndVerifySize(
+        cameraSelector: CameraSelector = BACK_SELECTOR,
+        @ImageCapture.OutputFormat outputFormat: Int = OUTPUT_FORMAT_JPEG,
+    ): Unit = runBlocking {
+        // Arrange.
+        val useCaseBuilder = ImageCapture.Builder()
             .setTargetResolution(DEFAULT_RESOLUTION)
             .setTargetRotation(Surface.ROTATION_0)
-            .build()
 
-        withContext(Dispatchers.Main) {
-            cameraProvider.bindToLifecycle(fakeLifecycleOwner, BACK_SELECTOR, useCase)
+        // Only test Ultra HDR on supported devices.
+        if (outputFormat == OUTPUT_FORMAT_ULTRA_HDR) {
+            assumeUltraHdrSupported(cameraSelector)
+            useCaseBuilder.setOutputFormat(OUTPUT_FORMAT_ULTRA_HDR)
         }
 
+        val useCase = useCaseBuilder.build()
+        withContext(Dispatchers.Main) {
+            cameraProvider.bindToLifecycle(fakeLifecycleOwner, cameraSelector, useCase)
+        }
+
+        // Act.
         val callback = FakeImageCaptureCallback(captureCount = 1)
         useCase.takePicture(mainExecutor, callback)
 
+        // Assert.
         // Wait for the signal that the image has been captured.
         callback.awaitCapturesAndAssert(capturedImagesCount = 1)
 
@@ -223,11 +253,28 @@
         }
     }
 
+    @MainThread
+    private suspend fun assumeUltraHdrSupported(cameraSelector: CameraSelector) {
+        withContext(Dispatchers.Main) {
+            val camera = cameraProvider.bindToLifecycle(fakeLifecycleOwner, cameraSelector)
+            val capabilities = ImageCapture.getImageCaptureCapabilities(camera.cameraInfo)
+            assumeTrue(capabilities.supportedOutputFormats.contains(OUTPUT_FORMAT_ULTRA_HDR))
+        }
+    }
+
     @Test
     fun canCaptureMultipleImages() {
         canTakeImages(defaultBuilder, numImages = 5)
     }
 
+    @SdkSuppress(minSdkVersion = 34)
+    @Test
+    fun canCaptureMultipleImages_whenOutputFormatIsUltraHdr() {
+        canTakeImages(defaultBuilder.setOutputFormat(OUTPUT_FORMAT_ULTRA_HDR), numImages = 5) {
+            assumeUltraHdrSupported(BACK_SELECTOR)
+        }
+    }
+
     @Test
     fun canCaptureMultipleImagesWithMaxQuality() {
         canTakeImages(
@@ -236,6 +283,17 @@
         )
     }
 
+    @SdkSuppress(minSdkVersion = 34)
+    @Test
+    fun canCaptureMultipleImagesWithMaxQuality_whenOutputFormatIsUltraHdr() {
+        val builder = ImageCapture.Builder()
+            .setCaptureMode(ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY)
+            .setOutputFormat(OUTPUT_FORMAT_ULTRA_HDR)
+        canTakeImages(builder, numImages = 5) {
+            assumeUltraHdrSupported(BACK_SELECTOR)
+        }
+    }
+
     @Test
     fun canCaptureMultipleImagesWithZsl() = runBlocking {
         val useCase = ImageCapture.Builder()
@@ -332,17 +390,23 @@
         builder: ImageCapture.Builder,
         cameraSelector: CameraSelector = BACK_SELECTOR,
         numImages: Int = 1,
+        runAtStart: suspend () -> Unit = {},
     ): Unit = runBlocking {
+        runAtStart()
+
+        // Arrange.
         val useCase = builder.build()
         withContext(Dispatchers.Main) {
             cameraProvider.bindToLifecycle(fakeLifecycleOwner, cameraSelector, useCase)
         }
 
+        // Act.
         val callback = FakeImageCaptureCallback(captureCount = numImages)
         repeat(numImages) {
             useCase.takePicture(mainExecutor, callback)
         }
 
+        // Assert.
         callback.awaitCapturesAndAssert(
             timeout = numImages * CAPTURE_TIMEOUT,
             capturedImagesCount = numImages
@@ -398,12 +462,65 @@
         callback.awaitCapturesAndAssert(savedImagesCount = 1)
     }
 
+    @SdkSuppress(minSdkVersion = 34)
     @Test
-    fun saveToUri(): Unit = runBlocking {
+    fun canSaveToFile_withGainmapInfoInMetadata_whenOutputFormatIsUltraHdr(): Unit = runBlocking {
+        val cameraSelector = BACK_SELECTOR
+        assumeUltraHdrSupported(cameraSelector)
+
         // Arrange.
+        val useCase = ImageCapture.Builder()
+            .setOutputFormat(OUTPUT_FORMAT_ULTRA_HDR)
+            .build()
+        withContext(Dispatchers.Main) {
+            cameraProvider.bindToLifecycle(fakeLifecycleOwner, cameraSelector, useCase)
+        }
+
+        // Act.
+        val saveLocation = temporaryFolder.newFile("test.jpg")
+        val outputFileOptions = ImageCapture.OutputFileOptions.Builder(saveLocation).build()
+        val callback = FakeImageSavedCallback(capturesCount = 1)
+        useCase.takePicture(outputFileOptions, mainExecutor, callback)
+
+        // Assert.
+        // Wait for the signal that the image has been saved.
+        callback.awaitCapturesAndAssert(savedImagesCount = 1)
+
+        // Retrieve the exif from the image and assert.
+        val exifMetadata = Exif.createFromFile(saveLocation).metadata
+        assertThat(exifMetadata).isNotNull()
+        for (pattern in EXIF_GAINMAP_PATTERNS) {
+            assertThat(exifMetadata).contains(pattern)
+        }
+    }
+
+    @Test
+    fun canSaveToUri() {
+        saveToUri()
+    }
+
+    @SdkSuppress(minSdkVersion = 34)
+    @Test
+    fun canSaveToUri_whenOutputFormatIsUltraHdr() {
+        saveToUri(outputFormat = OUTPUT_FORMAT_ULTRA_HDR)
+    }
+
+    private fun saveToUri(
+        cameraSelector: CameraSelector = BACK_SELECTOR,
+        @ImageCapture.OutputFormat outputFormat: Int = OUTPUT_FORMAT_JPEG,
+    ): Unit = runBlocking {
+        // Arrange.
+        val useCaseBuilder = defaultBuilder
+
+        // Only test Ultra HDR on supported devices.
+        if (outputFormat == OUTPUT_FORMAT_ULTRA_HDR) {
+            assumeUltraHdrSupported(cameraSelector)
+            useCaseBuilder.setOutputFormat(OUTPUT_FORMAT_ULTRA_HDR)
+        }
+
         val useCase = defaultBuilder.build()
         withContext(Dispatchers.Main) {
-            cameraProvider.bindToLifecycle(fakeLifecycleOwner, BACK_SELECTOR, useCase)
+            cameraProvider.bindToLifecycle(fakeLifecycleOwner, cameraSelector, useCase)
         }
 
         val contentValues = ContentValues()
@@ -431,11 +548,32 @@
     }
 
     @Test
-    fun saveToOutputStream() = runBlocking {
+    fun canSaveToOutputStream() {
+        saveToOutputStream()
+    }
+
+    @SdkSuppress(minSdkVersion = 34)
+    @Test
+    fun canSaveToOutputStream_whenOutputFormatIsUltraHdr() {
+        saveToOutputStream(outputFormat = OUTPUT_FORMAT_ULTRA_HDR)
+    }
+
+    private fun saveToOutputStream(
+        cameraSelector: CameraSelector = BACK_SELECTOR,
+        @ImageCapture.OutputFormat outputFormat: Int = OUTPUT_FORMAT_JPEG,
+    ) = runBlocking {
         // Arrange.
+        val useCaseBuilder = defaultBuilder
+
+        // Only test Ultra HDR on supported devices.
+        if (outputFormat == OUTPUT_FORMAT_ULTRA_HDR) {
+            assumeUltraHdrSupported(cameraSelector)
+            useCaseBuilder.setOutputFormat(OUTPUT_FORMAT_ULTRA_HDR)
+        }
+
         val useCase = defaultBuilder.build()
         withContext(Dispatchers.Main) {
-            cameraProvider.bindToLifecycle(fakeLifecycleOwner, BACK_SELECTOR, useCase)
+            cameraProvider.bindToLifecycle(fakeLifecycleOwner, cameraSelector, useCase)
         }
 
         val saveLocation = temporaryFolder.newFile("test.jpg")
diff --git a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/camera2/SurfaceOrientedMeteringPointFactoryTest.kt b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/camera2/SurfaceOrientedMeteringPointFactoryTest.kt
index 7a7052a..f9f26d5 100644
--- a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/camera2/SurfaceOrientedMeteringPointFactoryTest.kt
+++ b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/camera2/SurfaceOrientedMeteringPointFactoryTest.kt
@@ -36,6 +36,7 @@
 import org.junit.After
 import org.junit.Assume
 import org.junit.Before
+import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.Parameterized
@@ -47,7 +48,12 @@
     private val implName: String,
     private val cameraConfig: CameraXConfig
 ) {
-    var pointFactory: SurfaceOrientedMeteringPointFactory? = null
+    @get:Rule
+    val cameraRule = CameraUtil.grantCameraPermissionAndPreTest(
+        CameraUtil.PreTestCameraIdList(cameraConfig)
+    )
+
+    private var pointFactory: SurfaceOrientedMeteringPointFactory? = null
     private var context: Context? = null
     @Before
     fun setUp() {
diff --git a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/carousel/CarouselTest.kt b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/carousel/CarouselTest.kt
index 124a0db..83811d5 100644
--- a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/carousel/CarouselTest.kt
+++ b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/carousel/CarouselTest.kt
@@ -165,6 +165,7 @@
                         carouselMainAxisSize = availableSpace,
                         preferredItemSize = with(density) { 186.dp.toPx() },
                         itemSpacing = 0f,
+                        itemCount = itemCount.invoke(),
                     )
                 },
                 modifier = modifier.testTag(CarouselTestTag),
diff --git a/compose/material3/material3/src/androidUnitTest/kotlin/androidx/compose/material3/carousel/MultiBrowseTest.kt b/compose/material3/material3/src/androidUnitTest/kotlin/androidx/compose/material3/carousel/MultiBrowseTest.kt
index af8d1d2..9cd40a4 100644
--- a/compose/material3/material3/src/androidUnitTest/kotlin/androidx/compose/material3/carousel/MultiBrowseTest.kt
+++ b/compose/material3/material3/src/androidUnitTest/kotlin/androidx/compose/material3/carousel/MultiBrowseTest.kt
@@ -34,7 +34,8 @@
             density = Density,
             carouselMainAxisSize = 500f,
             preferredItemSize = itemSize,
-            itemSpacing = 0f
+            itemSpacing = 0f,
+            itemCount = 10,
         )!!
         val strategy = Strategy { keylineList }.apply(500f)
 
@@ -48,8 +49,9 @@
             density = Density,
             carouselMainAxisSize = 100f,
             preferredItemSize = itemSize,
-            itemSpacing = 0f
-        )!!
+            itemSpacing = 0f,
+            itemCount = 10,
+            )!!
         val strategy = Strategy { keylineList }.apply(100f)
         val minSmallItemSize: Float = with(Density) { StrategyDefaults.MinSmallSize.toPx() }
         val keylines = strategy.defaultKeylines
@@ -71,8 +73,9 @@
             density = Density,
             carouselMainAxisSize = minSmallItemSize,
             preferredItemSize = 200f,
-            itemSpacing = 0f
-        )!!
+            itemSpacing = 0f,
+            itemCount = 10,
+            )!!
         val strategy = Strategy { keylineList }.apply(minSmallItemSize)
         val keylines = strategy.defaultKeylines
 
@@ -87,8 +90,9 @@
             density = Density,
             carouselMainAxisSize = 0f,
             preferredItemSize = 200f,
-            itemSpacing = 0f
-        )
+            itemSpacing = 0f,
+            itemCount = 10,
+            )
         assertThat(keylineList).isNull()
     }
 
@@ -101,8 +105,9 @@
             density = Density,
             carouselMainAxisSize = carouselSize,
             preferredItemSize = preferredItemSize,
-            itemSpacing = 0f
-        )!!
+            itemSpacing = 0f,
+            itemCount = 10,
+            )!!
         val strategy = Strategy { keylineList }.apply(carouselSize)
         val keylines = strategy.defaultKeylines
 
@@ -115,4 +120,27 @@
         assertThat(keylines[3].size).isLessThan(keylines[2].size)
         assertThat(keylines[4].size).isLessThan(keylines[3].size)
     }
+
+    @Test
+    fun testMultiBrowse_withLessItemsThanKeylines() {
+        val maxSmallItemSize: Float = with(Density) { StrategyDefaults.MaxSmallSize.toPx() }
+        val preferredItemSize = 200f
+        val carouselSize = preferredItemSize * 2 + maxSmallItemSize * 2
+        val keylineList = multiBrowseKeylineList(
+            density = Density,
+            carouselMainAxisSize = carouselSize,
+            preferredItemSize = preferredItemSize,
+            itemSpacing = 0f,
+            itemCount = 3,
+        )!!
+        val strategy = Strategy { keylineList }.apply(carouselSize)
+        val keylines = strategy.defaultKeylines
+
+        // We originally expect a keyline list of [xSmall-Large-Large-Medium-Small-xSmall], but with
+        // a maximum keyline restriction of 3, we now expect [xSmall-Large-Large-Small-xSmall]
+        assertThat(keylines).hasSize(5)
+        assertThat(keylines[1].isFocal).isTrue()
+        assertThat(keylines[2].isFocal).isTrue()
+        assertThat(keylines[3].size).isLessThan(keylines[2].size)
+    }
 }
diff --git a/compose/material3/material3/src/androidUnitTest/kotlin/androidx/compose/material3/carousel/StrategyTest.kt b/compose/material3/material3/src/androidUnitTest/kotlin/androidx/compose/material3/carousel/StrategyTest.kt
index afdc6d9..b19c365 100644
--- a/compose/material3/material3/src/androidUnitTest/kotlin/androidx/compose/material3/carousel/StrategyTest.kt
+++ b/compose/material3/material3/src/androidUnitTest/kotlin/androidx/compose/material3/carousel/StrategyTest.kt
@@ -302,11 +302,12 @@
     fun testStrategy_sameAvailableSpaceCreatesEqualObjects() {
         val itemSize = large
         val itemSpacing = 0f
+        val itemCount = 10
         val strategy1 = Strategy { availableSpace ->
-            multiBrowseKeylineList(Density, availableSpace, itemSize, itemSpacing)
+            multiBrowseKeylineList(Density, availableSpace, itemSize, itemSpacing, itemCount)
         }
         val strategy2 = Strategy { availableSpace ->
-            multiBrowseKeylineList(Density, availableSpace, itemSize, itemSpacing)
+            multiBrowseKeylineList(Density, availableSpace, itemSize, itemSpacing, itemCount)
         }
         strategy1.apply(500f)
         strategy2.apply(500f)
@@ -319,11 +320,12 @@
     fun testStrategy_differentAvailableSpaceCreatesUnequalObjects() {
         val itemSize = large
         val itemSpacing = 0f
+        val itemCount = 10
         val strategy1 = Strategy { availableSpace ->
-            multiBrowseKeylineList(Density, availableSpace, itemSize, itemSpacing)
+            multiBrowseKeylineList(Density, availableSpace, itemSize, itemSpacing, itemCount)
         }
         val strategy2 = Strategy { availableSpace ->
-            multiBrowseKeylineList(Density, availableSpace, itemSize, itemSpacing)
+            multiBrowseKeylineList(Density, availableSpace, itemSize, itemSpacing, itemCount)
         }
         strategy1.apply(500f)
         strategy2.apply(500f + 1f)
@@ -336,11 +338,12 @@
     fun testStrategy_invalidObjectDoesNotEqualValidObject() {
         val itemSize = large
         val itemSpacing = 0f
+        val itemCount = 10
         val strategy1 = Strategy { availableSpace ->
-            multiBrowseKeylineList(Density, availableSpace, itemSize, itemSpacing)
+            multiBrowseKeylineList(Density, availableSpace, itemSize, itemSpacing, itemCount)
         }
         val strategy2 = Strategy { availableSpace ->
-            multiBrowseKeylineList(Density, availableSpace, itemSize, itemSpacing)
+            multiBrowseKeylineList(Density, availableSpace, itemSize, itemSpacing, itemCount)
         }
         strategy1.apply(500f)
 
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/Arrangement.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/Arrangement.kt
index 5a46dae..d410938 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/Arrangement.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/Arrangement.kt
@@ -61,6 +61,13 @@
         return abs(targetLargeSize - largeSize) * priority
     }
 
+    /**
+     * Returns number of items (keylines) in the arrangement
+     */
+    fun itemCount(): Int {
+        return largeCount + mediumCount + smallCount
+    }
+
     companion object {
         // Specifies a percentage of a medium item's size by which it can be increased or decreased
         // to help fit an arrangement into the carousel's available space.
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/Carousel.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/Carousel.kt
index d8461aa..7dfdb25 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/Carousel.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/Carousel.kt
@@ -93,8 +93,9 @@
                     carouselMainAxisSize = availableSpace,
                     preferredItemSize = preferredItemSize.toPx(),
                     itemSpacing = itemSpacing.toPx(),
+                    itemCount = state.itemCountState.value.invoke(),
                     minSmallSize = minSmallSize.toPx(),
-                    maxSmallSize = maxSmallSize.toPx()
+                    maxSmallSize = maxSmallSize.toPx(),
                 )
             }
         },
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/Keylines.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/Keylines.kt
index 506af37..0ddfab3 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/Keylines.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/Keylines.kt
@@ -38,6 +38,7 @@
  * @param carouselMainAxisSize The carousel container's pixel size in the main scrolling axis
  * @param preferredItemSize the desired size of large items, in pixels, in the main scrolling axis
  * @param itemSpacing the spacing between items in pixels
+ * @param itemCount the number of items in the carousel
  * @param minSmallSize the minimum allowable size of small items in pixels
  * @param maxSmallSize the maximum allowable size of small items in pixels
  */
@@ -46,6 +47,7 @@
     carouselMainAxisSize: Float,
     preferredItemSize: Float,
     itemSpacing: Float,
+    itemCount: Int,
     minSmallSize: Float = with(density) { StrategyDefaults.MinSmallSize.toPx() },
     maxSmallSize: Float = with(density) { StrategyDefaults.MaxSmallSize.toPx() },
 ): KeylineList? {
@@ -85,7 +87,7 @@
 
     val largeCounts = IntArray(maxLargeCount - minLargeCount + 1) { maxLargeCount - it }
     val anchorSize = with(density) { StrategyDefaults.AnchorSize.toPx() }
-    val arrangement = Arrangement.findLowestCostArrangement(
+    var arrangement = Arrangement.findLowestCostArrangement(
         availableSpace = carouselMainAxisSize,
         targetSmallSize = targetSmallSize,
         minSmallSize = minSmallSize,
@@ -97,16 +99,45 @@
         largeCounts = largeCounts,
     )
 
-    return if (arrangement == null) {
-        null
-    } else {
-        createLeftAlignedKeylineList(
-            carouselMainAxisSize = carouselMainAxisSize,
-            leftAnchorSize = anchorSize,
-            rightAnchorSize = anchorSize,
-            arrangement = arrangement
+    if (arrangement != null && arrangement.itemCount() > itemCount) {
+        var keylineSurplus = arrangement.itemCount() - itemCount
+        var smallCount = arrangement.smallCount
+        var mediumCount = arrangement.mediumCount
+        while (keylineSurplus > 0) {
+            if (smallCount > 0) {
+                smallCount -= 1;
+            } else if (mediumCount > 1) {
+                // Keep at least 1 medium so the large items don't fill the entire carousel in new
+                // strategy.
+                mediumCount -= 1;
+            }
+            // large items don't need to be removed even if they are a surplus because large items
+            // are already fully unmasked.
+            keylineSurplus -= 1;
+        }
+        arrangement = Arrangement.findLowestCostArrangement(
+            availableSpace = carouselMainAxisSize,
+            targetSmallSize = targetSmallSize,
+            minSmallSize = minSmallSize,
+            maxSmallSize = maxSmallSize,
+            smallCounts = intArrayOf(smallCount),
+            targetMediumSize = targetMediumSize,
+            mediumCounts = intArrayOf(mediumCount),
+            targetLargeSize = targetLargeSize,
+            largeCounts = largeCounts,
         )
     }
+
+    if (arrangement == null) {
+        return null
+    }
+
+    return createLeftAlignedKeylineList(
+        carouselMainAxisSize = carouselMainAxisSize,
+        rightAnchorSize = anchorSize,
+        leftAnchorSize = anchorSize,
+        arrangement = arrangement
+    )
 }
 
 internal fun createLeftAlignedKeylineList(
diff --git a/core/core-animation/src/main/java/androidx/core/animation/AnimatorInflater.java b/core/core-animation/src/main/java/androidx/core/animation/AnimatorInflater.java
index 6185835..fa5e929 100644
--- a/core/core-animation/src/main/java/androidx/core/animation/AnimatorInflater.java
+++ b/core/core-animation/src/main/java/androidx/core/animation/AnimatorInflater.java
@@ -143,11 +143,7 @@
                 // This path buffer has to have the same size and structure as the morphing path.
                 mPathData = PathParser.deepCopyNodes(endPathData);
             }
-            if (!PathParser.interpolatePathDataNodes(
-                    mPathData, startPathData, endPathData, fraction)) {
-                throw new IllegalArgumentException("Can't interpolate between"
-                        + " two incompatible pathData");
-            }
+            PathParser.interpolatePathDataNodes(mPathData, fraction, startPathData, endPathData);
             return mPathData;
         }
     }
diff --git a/core/core/api/1.13.0-beta01.txt b/core/core/api/1.13.0-beta01.txt
index c87a9ce..6344463 100644
--- a/core/core/api/1.13.0-beta01.txt
+++ b/core/core/api/1.13.0-beta01.txt
@@ -1516,12 +1516,13 @@
     method public static boolean setBlendMode(android.graphics.Paint, androidx.core.graphics.BlendModeCompat?);
   }
 
-  public class PathParser {
+  public final class PathParser {
     method public static boolean canMorph(androidx.core.graphics.PathParser.PathDataNode![]?, androidx.core.graphics.PathParser.PathDataNode![]?);
     method public static androidx.core.graphics.PathParser.PathDataNode![] createNodesFromPathData(String);
     method public static android.graphics.Path createPathFromPathData(String);
     method public static androidx.core.graphics.PathParser.PathDataNode![] deepCopyNodes(androidx.core.graphics.PathParser.PathDataNode![]);
-    method public static boolean interpolatePathDataNodes(androidx.core.graphics.PathParser.PathDataNode![], androidx.core.graphics.PathParser.PathDataNode![], androidx.core.graphics.PathParser.PathDataNode![], float);
+    method public static void interpolatePathDataNodes(androidx.core.graphics.PathParser.PathDataNode![], float, androidx.core.graphics.PathParser.PathDataNode![], androidx.core.graphics.PathParser.PathDataNode![]);
+    method public static void nodesToPath(androidx.core.graphics.PathParser.PathDataNode![], android.graphics.Path);
     method public static void updateNodes(androidx.core.graphics.PathParser.PathDataNode![], androidx.core.graphics.PathParser.PathDataNode![]);
   }
 
@@ -1529,7 +1530,6 @@
     method public float[] getParams();
     method public char getType();
     method public void interpolatePathDataNode(androidx.core.graphics.PathParser.PathDataNode, androidx.core.graphics.PathParser.PathDataNode, float);
-    method public static void nodesToPath(androidx.core.graphics.PathParser.PathDataNode![], android.graphics.Path);
   }
 
   public final class PathSegment {
diff --git a/core/core/api/api_lint.ignore b/core/core/api/api_lint.ignore
index 9e413b7..c28cad6 100644
--- a/core/core/api/api_lint.ignore
+++ b/core/core/api/api_lint.ignore
@@ -117,28 +117,6 @@
     Method parameter should be Collection<Intent> (or subclass) instead of raw array; was `android.content.Intent[]`
 ArrayReturn: androidx.core.content.pm.ShortcutInfoCompat.Builder#setPersons(androidx.core.app.Person[]) parameter #0:
     Method parameter should be Collection<Person> (or subclass) instead of raw array; was `androidx.core.app.Person[]`
-ArrayReturn: androidx.core.graphics.PathParser#canMorph(androidx.core.graphics.PathParser.PathDataNode[], androidx.core.graphics.PathParser.PathDataNode[]) parameter #0:
-    Method parameter should be Collection<PathDataNode> (or subclass) instead of raw array; was `androidx.core.graphics.PathParser.PathDataNode[]`
-ArrayReturn: androidx.core.graphics.PathParser#canMorph(androidx.core.graphics.PathParser.PathDataNode[], androidx.core.graphics.PathParser.PathDataNode[]) parameter #1:
-    Method parameter should be Collection<PathDataNode> (or subclass) instead of raw array; was `androidx.core.graphics.PathParser.PathDataNode[]`
-ArrayReturn: androidx.core.graphics.PathParser#createNodesFromPathData(String):
-    Method should return Collection<PathDataNode> (or subclass) instead of raw array; was `androidx.core.graphics.PathParser.PathDataNode[]`
-ArrayReturn: androidx.core.graphics.PathParser#deepCopyNodes(androidx.core.graphics.PathParser.PathDataNode[]):
-    Method should return Collection<PathDataNode> (or subclass) instead of raw array; was `androidx.core.graphics.PathParser.PathDataNode[]`
-ArrayReturn: androidx.core.graphics.PathParser#deepCopyNodes(androidx.core.graphics.PathParser.PathDataNode[]) parameter #0:
-    Method parameter should be Collection<PathDataNode> (or subclass) instead of raw array; was `androidx.core.graphics.PathParser.PathDataNode[]`
-ArrayReturn: androidx.core.graphics.PathParser#interpolatePathDataNodes(androidx.core.graphics.PathParser.PathDataNode[], androidx.core.graphics.PathParser.PathDataNode[], androidx.core.graphics.PathParser.PathDataNode[], float) parameter #0:
-    Method parameter should be Collection<PathDataNode> (or subclass) instead of raw array; was `androidx.core.graphics.PathParser.PathDataNode[]`
-ArrayReturn: androidx.core.graphics.PathParser#interpolatePathDataNodes(androidx.core.graphics.PathParser.PathDataNode[], androidx.core.graphics.PathParser.PathDataNode[], androidx.core.graphics.PathParser.PathDataNode[], float) parameter #1:
-    Method parameter should be Collection<PathDataNode> (or subclass) instead of raw array; was `androidx.core.graphics.PathParser.PathDataNode[]`
-ArrayReturn: androidx.core.graphics.PathParser#interpolatePathDataNodes(androidx.core.graphics.PathParser.PathDataNode[], androidx.core.graphics.PathParser.PathDataNode[], androidx.core.graphics.PathParser.PathDataNode[], float) parameter #2:
-    Method parameter should be Collection<PathDataNode> (or subclass) instead of raw array; was `androidx.core.graphics.PathParser.PathDataNode[]`
-ArrayReturn: androidx.core.graphics.PathParser#updateNodes(androidx.core.graphics.PathParser.PathDataNode[], androidx.core.graphics.PathParser.PathDataNode[]) parameter #0:
-    Method parameter should be Collection<PathDataNode> (or subclass) instead of raw array; was `androidx.core.graphics.PathParser.PathDataNode[]`
-ArrayReturn: androidx.core.graphics.PathParser#updateNodes(androidx.core.graphics.PathParser.PathDataNode[], androidx.core.graphics.PathParser.PathDataNode[]) parameter #1:
-    Method parameter should be Collection<PathDataNode> (or subclass) instead of raw array; was `androidx.core.graphics.PathParser.PathDataNode[]`
-ArrayReturn: androidx.core.graphics.PathParser.PathDataNode#nodesToPath(androidx.core.graphics.PathParser.PathDataNode[], android.graphics.Path) parameter #0:
-    Method parameter should be Collection<PathDataNode> (or subclass) instead of raw array; was `androidx.core.graphics.PathParser.PathDataNode[]`
 ArrayReturn: androidx.core.hardware.display.DisplayManagerCompat#getDisplays():
     Method should return Collection<Display> (or subclass) instead of raw array; was `android.view.Display[]`
 ArrayReturn: androidx.core.hardware.display.DisplayManagerCompat#getDisplays(String):
diff --git a/core/core/api/current.txt b/core/core/api/current.txt
index c87a9ce..6344463 100644
--- a/core/core/api/current.txt
+++ b/core/core/api/current.txt
@@ -1516,12 +1516,13 @@
     method public static boolean setBlendMode(android.graphics.Paint, androidx.core.graphics.BlendModeCompat?);
   }
 
-  public class PathParser {
+  public final class PathParser {
     method public static boolean canMorph(androidx.core.graphics.PathParser.PathDataNode![]?, androidx.core.graphics.PathParser.PathDataNode![]?);
     method public static androidx.core.graphics.PathParser.PathDataNode![] createNodesFromPathData(String);
     method public static android.graphics.Path createPathFromPathData(String);
     method public static androidx.core.graphics.PathParser.PathDataNode![] deepCopyNodes(androidx.core.graphics.PathParser.PathDataNode![]);
-    method public static boolean interpolatePathDataNodes(androidx.core.graphics.PathParser.PathDataNode![], androidx.core.graphics.PathParser.PathDataNode![], androidx.core.graphics.PathParser.PathDataNode![], float);
+    method public static void interpolatePathDataNodes(androidx.core.graphics.PathParser.PathDataNode![], float, androidx.core.graphics.PathParser.PathDataNode![], androidx.core.graphics.PathParser.PathDataNode![]);
+    method public static void nodesToPath(androidx.core.graphics.PathParser.PathDataNode![], android.graphics.Path);
     method public static void updateNodes(androidx.core.graphics.PathParser.PathDataNode![], androidx.core.graphics.PathParser.PathDataNode![]);
   }
 
@@ -1529,7 +1530,6 @@
     method public float[] getParams();
     method public char getType();
     method public void interpolatePathDataNode(androidx.core.graphics.PathParser.PathDataNode, androidx.core.graphics.PathParser.PathDataNode, float);
-    method public static void nodesToPath(androidx.core.graphics.PathParser.PathDataNode![], android.graphics.Path);
   }
 
   public final class PathSegment {
diff --git a/core/core/api/restricted_1.13.0-beta01.txt b/core/core/api/restricted_1.13.0-beta01.txt
index ade44fa..eb69423 100644
--- a/core/core/api/restricted_1.13.0-beta01.txt
+++ b/core/core/api/restricted_1.13.0-beta01.txt
@@ -1759,12 +1759,14 @@
     method public static boolean setBlendMode(android.graphics.Paint, androidx.core.graphics.BlendModeCompat?);
   }
 
-  public class PathParser {
+  public final class PathParser {
     method public static boolean canMorph(androidx.core.graphics.PathParser.PathDataNode![]?, androidx.core.graphics.PathParser.PathDataNode![]?);
     method public static androidx.core.graphics.PathParser.PathDataNode![] createNodesFromPathData(String);
     method public static android.graphics.Path createPathFromPathData(String);
     method public static androidx.core.graphics.PathParser.PathDataNode![] deepCopyNodes(androidx.core.graphics.PathParser.PathDataNode![]);
-    method public static boolean interpolatePathDataNodes(androidx.core.graphics.PathParser.PathDataNode![], androidx.core.graphics.PathParser.PathDataNode![], androidx.core.graphics.PathParser.PathDataNode![], float);
+    method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static boolean interpolatePathDataNodes(androidx.core.graphics.PathParser.PathDataNode![], androidx.core.graphics.PathParser.PathDataNode![], androidx.core.graphics.PathParser.PathDataNode![], float);
+    method public static void interpolatePathDataNodes(androidx.core.graphics.PathParser.PathDataNode![], float, androidx.core.graphics.PathParser.PathDataNode![], androidx.core.graphics.PathParser.PathDataNode![]);
+    method public static void nodesToPath(androidx.core.graphics.PathParser.PathDataNode![], android.graphics.Path);
     method public static void updateNodes(androidx.core.graphics.PathParser.PathDataNode![], androidx.core.graphics.PathParser.PathDataNode![]);
   }
 
@@ -1772,7 +1774,7 @@
     method public float[] getParams();
     method public char getType();
     method public void interpolatePathDataNode(androidx.core.graphics.PathParser.PathDataNode, androidx.core.graphics.PathParser.PathDataNode, float);
-    method public static void nodesToPath(androidx.core.graphics.PathParser.PathDataNode![], android.graphics.Path);
+    method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static void nodesToPath(androidx.core.graphics.PathParser.PathDataNode![], android.graphics.Path);
   }
 
   public final class PathSegment {
diff --git a/core/core/api/restricted_current.txt b/core/core/api/restricted_current.txt
index ade44fa..eb69423 100644
--- a/core/core/api/restricted_current.txt
+++ b/core/core/api/restricted_current.txt
@@ -1759,12 +1759,14 @@
     method public static boolean setBlendMode(android.graphics.Paint, androidx.core.graphics.BlendModeCompat?);
   }
 
-  public class PathParser {
+  public final class PathParser {
     method public static boolean canMorph(androidx.core.graphics.PathParser.PathDataNode![]?, androidx.core.graphics.PathParser.PathDataNode![]?);
     method public static androidx.core.graphics.PathParser.PathDataNode![] createNodesFromPathData(String);
     method public static android.graphics.Path createPathFromPathData(String);
     method public static androidx.core.graphics.PathParser.PathDataNode![] deepCopyNodes(androidx.core.graphics.PathParser.PathDataNode![]);
-    method public static boolean interpolatePathDataNodes(androidx.core.graphics.PathParser.PathDataNode![], androidx.core.graphics.PathParser.PathDataNode![], androidx.core.graphics.PathParser.PathDataNode![], float);
+    method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static boolean interpolatePathDataNodes(androidx.core.graphics.PathParser.PathDataNode![], androidx.core.graphics.PathParser.PathDataNode![], androidx.core.graphics.PathParser.PathDataNode![], float);
+    method public static void interpolatePathDataNodes(androidx.core.graphics.PathParser.PathDataNode![], float, androidx.core.graphics.PathParser.PathDataNode![], androidx.core.graphics.PathParser.PathDataNode![]);
+    method public static void nodesToPath(androidx.core.graphics.PathParser.PathDataNode![], android.graphics.Path);
     method public static void updateNodes(androidx.core.graphics.PathParser.PathDataNode![], androidx.core.graphics.PathParser.PathDataNode![]);
   }
 
@@ -1772,7 +1774,7 @@
     method public float[] getParams();
     method public char getType();
     method public void interpolatePathDataNode(androidx.core.graphics.PathParser.PathDataNode, androidx.core.graphics.PathParser.PathDataNode, float);
-    method public static void nodesToPath(androidx.core.graphics.PathParser.PathDataNode![], android.graphics.Path);
+    method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static void nodesToPath(androidx.core.graphics.PathParser.PathDataNode![], android.graphics.Path);
   }
 
   public final class PathSegment {
diff --git a/core/core/src/main/java/androidx/core/graphics/PathParser.java b/core/core/src/main/java/androidx/core/graphics/PathParser.java
index 8788fad..9f77d78 100644
--- a/core/core/src/main/java/androidx/core/graphics/PathParser.java
+++ b/core/core/src/main/java/androidx/core/graphics/PathParser.java
@@ -17,11 +17,14 @@
 package androidx.core.graphics;
 
 
+import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX;
+
 import android.graphics.Path;
 import android.util.Log;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
 
 import java.util.ArrayList;
 
@@ -30,7 +33,7 @@
 /**
  * Parses SVG path strings.
  */
-public class PathParser {
+public final class PathParser {
     private static final String LOGTAG = "PathParser";
 
     // Copy from Arrays.copyOfRange() which is only available from API level 9.
@@ -86,6 +89,7 @@
      * @param pathData The string representing a path, the same as "d" string in svg file.
      * @return an array of the PathDataNode.
      */
+    @SuppressWarnings("ArrayReturn")
     @NonNull
     public static PathDataNode[] createNodesFromPathData(@NonNull String pathData) {
         int start = 0;
@@ -113,8 +117,11 @@
      * @param source The array of PathDataNode to be duplicated.
      * @return a deep copy of the <code>source</code>.
      */
+    @SuppressWarnings("ArrayReturn")
     @NonNull
-    public static PathDataNode[] deepCopyNodes(@NonNull PathDataNode[] source) {
+    public static PathDataNode[] deepCopyNodes(
+            @SuppressWarnings("ArrayReturn") @NonNull PathDataNode[] source
+    ) {
         PathDataNode[] copy = new PathParser.PathDataNode[source.length];
         for (int i = 0; i < source.length; i++) {
             copy[i] = new PathDataNode(source[i]);
@@ -127,8 +134,11 @@
      * @param nodesTo   The target path represented in an array of PathDataNode
      * @return whether the <code>nodesFrom</code> can morph into <code>nodesTo</code>
      */
-    public static boolean canMorph(@Nullable PathDataNode[] nodesFrom,
-            @Nullable PathDataNode[] nodesTo) {
+    @SuppressWarnings("ArrayReturn")
+    public static boolean canMorph(
+            @SuppressWarnings("ArrayReturn") @Nullable PathDataNode[] nodesFrom,
+            @SuppressWarnings("ArrayReturn") @Nullable PathDataNode[] nodesTo
+    ) {
         if (nodesFrom == null || nodesTo == null) {
             return false;
         }
@@ -153,7 +163,10 @@
      * @param target The target path represented in an array of PathDataNode
      * @param source The source path represented in an array of PathDataNode
      */
-    public static void updateNodes(@NonNull PathDataNode[] target, @NonNull PathDataNode[] source) {
+    public static void updateNodes(
+            @SuppressWarnings("ArrayReturn") @NonNull PathDataNode[] target,
+            @SuppressWarnings("ArrayReturn") @NonNull PathDataNode[] source
+    ) {
         for (int i = 0; i < source.length; i++) {
             target[i].mType = source[i].mType;
             for (int j = 0; j < source[i].mParams.length; j++) {
@@ -297,15 +310,48 @@
      * Interpolate between two arrays of PathDataNodes with the given fraction, and store the
      * results in the first parameter.
      *
-     * @param target The resulting array of {@link PathDataNode} for the interpolation
-     * @param from The array of {@link PathDataNode} when fraction is 0
-     * @param to The array of {@link PathDataNode} when the fraction is 1
+     * @param target   The resulting array of {@link PathDataNode} for the interpolation
      * @param fraction A float fraction value in the range of 0 to 1
-     * @return whether it's possible to interpolate between the two arrays of PathDataNodes
+     * @param from     The array of {@link PathDataNode} when fraction is 0
+     * @param to       The array of {@link PathDataNode} when the fraction is 1
+     * @throws IllegalArgumentException When the arrays of nodes are incompatible for interpolation.
      * @see #canMorph(PathDataNode[], PathDataNode[])
      */
-    public static boolean interpolatePathDataNodes(@NonNull PathDataNode[] target,
-            @NonNull PathDataNode[] from, @NonNull PathDataNode[] to, float fraction) {
+    public static void interpolatePathDataNodes(
+            @SuppressWarnings("ArrayReturn") @NonNull PathDataNode[] target,
+            float fraction,
+            @SuppressWarnings("ArrayReturn") @NonNull PathDataNode[] from,
+            @SuppressWarnings("ArrayReturn") @NonNull PathDataNode[] to
+    ) {
+        if (!interpolatePathDataNodes(target, from, to, fraction)) {
+            throw new IllegalArgumentException(
+                    "Can't interpolate between two incompatible pathData"
+            );
+        }
+    }
+
+    /**
+     * Interpolate between two arrays of PathDataNodes with the given fraction, and store the
+     * results in the first parameter.
+     *
+     * @param target   The resulting array of {@link PathDataNode} for the interpolation
+     * @param from     The array of {@link PathDataNode} when fraction is 0
+     * @param to       The array of {@link PathDataNode} when the fraction is 1
+     * @param fraction A float fraction value in the range of 0 to 1
+     * @throws IllegalArgumentException When the arrays of nodes are incompatible for interpolation.
+     * @see #canMorph(PathDataNode[], PathDataNode[])
+     * @deprecated Use
+     * {@link #interpolatePathDataNodes(PathDataNode[], float, PathDataNode[], PathDataNode[])}
+     * instead.
+     */
+    @Deprecated
+    @RestrictTo(LIBRARY_GROUP_PREFIX)
+    public static boolean interpolatePathDataNodes(
+            @SuppressWarnings("ArrayReturn") @NonNull PathDataNode[] target,
+            @SuppressWarnings("ArrayReturn") @NonNull PathDataNode[] from,
+            @SuppressWarnings("ArrayReturn") @NonNull PathDataNode[] to,
+            float fraction
+    ) {
         if (target.length != from.length || from.length != to.length) {
             throw new IllegalArgumentException("The nodes to be interpolated and resulting nodes"
                     + " must have the same length");
@@ -322,6 +368,26 @@
     }
 
     /**
+     * Convert an array of PathDataNode to Path.
+     *
+     * @param node The source array of PathDataNode.
+     * @param path The target Path object.
+     */
+    @SuppressWarnings("ArrayReturn")
+    public static void nodesToPath(
+            @SuppressWarnings("ArrayReturn") @NonNull PathDataNode[] node,
+            @NonNull Path path
+    ) {
+        float[] current = new float[6];
+        char previousCommand = 'm';
+        for (PathDataNode pathDataNode : node) {
+            PathDataNode.addCommand(path, current, previousCommand, pathDataNode.mType,
+                    pathDataNode.mParams);
+            previousCommand = pathDataNode.mType;
+        }
+    }
+
+    /**
      * Each PathDataNode represents one command in the "d" attribute of the svg
      * file.
      * An array of PathDataNode can represent the whole "d" attribute.
@@ -360,14 +426,16 @@
          *
          * @param node The source array of PathDataNode.
          * @param path The target Path object.
+         * @deprecated Use {@link PathParser#nodesToPath(PathDataNode[], Path)} instead.
          */
-        public static void nodesToPath(@NonNull PathDataNode[] node, @NonNull Path path) {
-            float[] current = new float[6];
-            char previousCommand = 'm';
-            for (int i = 0; i < node.length; i++) {
-                addCommand(path, current, previousCommand, node[i].mType, node[i].mParams);
-                previousCommand = node[i].mType;
-            }
+        @Deprecated
+        @RestrictTo(LIBRARY_GROUP_PREFIX)
+        @SuppressWarnings("ArrayReturn")
+        public static void nodesToPath(
+                @SuppressWarnings("ArrayReturn") @NonNull PathDataNode[] node,
+                @NonNull Path path
+        ) {
+            PathParser.nodesToPath(node, path);
         }
 
         /**
diff --git a/lifecycle/lifecycle-viewmodel/api/current.txt b/lifecycle/lifecycle-viewmodel/api/current.txt
index 440b09e..f4bf3aa 100644
--- a/lifecycle/lifecycle-viewmodel/api/current.txt
+++ b/lifecycle/lifecycle-viewmodel/api/current.txt
@@ -17,6 +17,8 @@
     ctor public ViewModel();
     ctor @Deprecated public ViewModel(java.io.Closeable... closeables);
     ctor public ViewModel(java.lang.AutoCloseable... closeables);
+    ctor public ViewModel(kotlinx.coroutines.CoroutineScope viewModelScope);
+    ctor public ViewModel(kotlinx.coroutines.CoroutineScope viewModelScope, java.lang.AutoCloseable... closeables);
     method @Deprecated public void addCloseable(java.io.Closeable closeable);
     method public void addCloseable(AutoCloseable closeable);
     method public final void addCloseable(String key, AutoCloseable closeable);
diff --git a/lifecycle/lifecycle-viewmodel/api/restricted_current.txt b/lifecycle/lifecycle-viewmodel/api/restricted_current.txt
index 440b09e..f4bf3aa 100644
--- a/lifecycle/lifecycle-viewmodel/api/restricted_current.txt
+++ b/lifecycle/lifecycle-viewmodel/api/restricted_current.txt
@@ -17,6 +17,8 @@
     ctor public ViewModel();
     ctor @Deprecated public ViewModel(java.io.Closeable... closeables);
     ctor public ViewModel(java.lang.AutoCloseable... closeables);
+    ctor public ViewModel(kotlinx.coroutines.CoroutineScope viewModelScope);
+    ctor public ViewModel(kotlinx.coroutines.CoroutineScope viewModelScope, java.lang.AutoCloseable... closeables);
     method @Deprecated public void addCloseable(java.io.Closeable closeable);
     method public void addCloseable(AutoCloseable closeable);
     method public final void addCloseable(String key, AutoCloseable closeable);
diff --git a/lifecycle/lifecycle-viewmodel/src/androidInstrumentedTest/kotlin/androidx/lifecycle/ViewModelScopeTest.kt b/lifecycle/lifecycle-viewmodel/src/androidInstrumentedTest/kotlin/androidx/lifecycle/ViewModelScopeTest.kt
index be3a1c2..eee2cfa 100644
--- a/lifecycle/lifecycle-viewmodel/src/androidInstrumentedTest/kotlin/androidx/lifecycle/ViewModelScopeTest.kt
+++ b/lifecycle/lifecycle-viewmodel/src/androidInstrumentedTest/kotlin/androidx/lifecycle/ViewModelScopeTest.kt
@@ -19,11 +19,14 @@
 import androidx.kruth.assertThat
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import kotlin.time.Duration.Companion.seconds
+import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.async
 import kotlinx.coroutines.cancelAndJoin
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.launch
-import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
 import org.junit.Test
 import org.junit.runner.RunWith
 
@@ -31,49 +34,95 @@
 @SmallTest
 class ViewModelScopeTest {
 
+    private val testScope = TestScope()
+
+    //region viewModelScope with default scope
     @Test
-    fun testVmScope() {
-        val vm = object : ViewModel() {}
-        val job1 = vm.viewModelScope.launch { delay(1000) }
-        val job2 = vm.viewModelScope.launch { delay(1000) }
-        vm.clear()
+    fun viewModelScope_withDefaultScope_whenLaunch_cancelsOnClear() {
+        val viewModel = ViewModel1()
+
+        val job1 = viewModel.viewModelScope.launch { delay(1.seconds) }
+        val job2 = viewModel.viewModelScope.launch { delay(1.seconds) }
+        viewModel.clear()
+
         assertThat(job1.isCancelled).isTrue()
         assertThat(job2.isCancelled).isTrue()
     }
 
     @Test
-    fun testStartJobInClearedVM() {
-        val vm = object : ViewModel() {}
-        vm.clear()
-        val job1 = vm.viewModelScope.launch { delay(1000) }
+    fun viewModelScope_withDefaultScope_afterClear_launchesCancelledJob() {
+        val viewModel = ViewModel1()
+
+        viewModel.clear()
+        val job1 = viewModel.viewModelScope.launch { delay(1.seconds) }
+
         assertThat(job1.isCancelled).isTrue()
     }
 
     @Test
-    fun testSameScope() {
-        val vm = object : ViewModel() {}
-        val scope1 = vm.viewModelScope
-        val scope2 = vm.viewModelScope
-        assertThat(scope1).isSameInstanceAs(scope2)
-        vm.clear()
-        val scope3 = vm.viewModelScope
-        assertThat(scope3).isSameInstanceAs(scope2)
+    fun viewModelScope_withDefaultScope_afterClear_returnsSameScope() {
+        val viewModel = ViewModel1()
+
+        val scopeBeforeClear = viewModel.viewModelScope
+        viewModel.clear()
+        val scopeAfterClear = viewModel.viewModelScope
+
+        assertThat(scopeBeforeClear).isSameInstanceAs(scopeAfterClear)
     }
 
     @Test
-    fun testJobIsSuperVisor() {
-        val vm = object : ViewModel() {}
-        val scope = vm.viewModelScope
-        val delayingDeferred = scope.async { delay(Long.MAX_VALUE) }
-        val failingDeferred = scope.async { throw Error() }
+    fun viewModelScope_defaultScope_launchesSupervisedJobs() {
+        testScope.runTest {
+            val viewModel = ViewModel1()
 
-        runBlocking {
-            try {
-                failingDeferred.await()
-            } catch (e: Error) {
-            }
+            val delayingDeferred = viewModel.viewModelScope.async { delay(Long.MAX_VALUE) }
+            val failingDeferred = viewModel.viewModelScope.async { throw Error() }
+            runCatching { failingDeferred.await() }
+
             assertThat(delayingDeferred.isActive).isTrue()
             delayingDeferred.cancelAndJoin()
         }
     }
+    //endregion
+
+    //region viewModelScope with custom scope
+    @Test
+    fun viewModelScope_withCustomScope_whenLaunch_cancelsOnClear() {
+        val viewModel = ViewModel2(viewModelScope = testScope.backgroundScope)
+
+        val job1 = viewModel.viewModelScope.launch { delay(1.seconds) }
+        val job2 = viewModel.viewModelScope.launch { delay(1.seconds) }
+        viewModel.clear()
+
+        assertThat(job1.isCancelled).isTrue()
+        assertThat(job2.isCancelled).isTrue()
+    }
+
+    @Test
+    fun viewModelScope_withCustomScope_afterClear_launchesCancelledJob() {
+        val viewModel = ViewModel2(viewModelScope = testScope.backgroundScope)
+
+        viewModel.clear()
+        val job1 = viewModel.viewModelScope.launch { delay(1.seconds) }
+
+        assertThat(job1.isCancelled).isTrue()
+    }
+
+    @Test
+    fun viewModelScope_withCustomScope_afterClear_returnsSameScope() {
+        val viewModel = ViewModel2(viewModelScope = testScope.backgroundScope)
+
+        val scopeBeforeClear = viewModel.viewModelScope
+        viewModel.clear()
+        val scopeAfterClear = viewModel.viewModelScope
+
+        assertThat(scopeBeforeClear).isSameInstanceAs(scopeAfterClear)
+        assertThat(scopeAfterClear.coroutineContext)
+            .isSameInstanceAs(testScope.backgroundScope.coroutineContext)
+    }
+    //endregion
+
+    private class ViewModel1() : ViewModel()
+
+    private class ViewModel2(viewModelScope: CoroutineScope) : ViewModel(viewModelScope)
 }
diff --git a/lifecycle/lifecycle-viewmodel/src/commonMain/kotlin/androidx/lifecycle/ViewModel.kt b/lifecycle/lifecycle-viewmodel/src/commonMain/kotlin/androidx/lifecycle/ViewModel.kt
index 62ee01b..05b1880 100644
--- a/lifecycle/lifecycle-viewmodel/src/commonMain/kotlin/androidx/lifecycle/ViewModel.kt
+++ b/lifecycle/lifecycle-viewmodel/src/commonMain/kotlin/androidx/lifecycle/ViewModel.kt
@@ -18,11 +18,14 @@
 package androidx.lifecycle
 
 import androidx.annotation.MainThread
-import kotlin.coroutines.CoroutineContext
+import androidx.lifecycle.viewmodel.internal.CloseableCoroutineScope
+import androidx.lifecycle.viewmodel.internal.VIEW_MODEL_SCOPE_KEY
+import androidx.lifecycle.viewmodel.internal.createViewModelScope
+import kotlin.coroutines.EmptyCoroutineContext
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.MainCoroutineDispatcher
 import kotlinx.coroutines.SupervisorJob
-import kotlinx.coroutines.cancel
 
 /**
  * ViewModel is a class that is responsible for preparing and managing the data for
@@ -96,23 +99,45 @@
 public expect abstract class ViewModel {
 
     /**
-     * Construct a new ViewModel instance.
+     * Creates a new [ViewModel].
      *
-     * You should **never** manually construct a ViewModel outside of a
+     * You should **never** manually create a [ViewModel] outside of a
      * [ViewModelProvider.Factory].
      */
     public constructor()
 
     /**
-     * Construct a new ViewModel instance. Any [AutoCloseable] objects provided here
-     * will be closed directly before [ViewModel.onCleared] is called.
+     * Creates a new [ViewModel].
      *
-     * You should **never** manually construct a ViewModel outside of a
+     * You should **never** manually create a [ViewModel] outside of a
      * [ViewModelProvider.Factory].
+     *
+     * @param viewModelScope a [CoroutineScope] to be cancelled when the [ViewModel] is cleared.
+     */
+    public constructor(viewModelScope: CoroutineScope)
+
+    /**
+     * Creates a new [ViewModel].
+     *
+     * You should **never** manually create a [ViewModel] outside of a
+     * [ViewModelProvider.Factory].
+     *
+     * @param closeables the resources to be closed when the [ViewModel] is cleared.
      */
     public constructor(vararg closeables: AutoCloseable)
 
     /**
+     * Creates a new [ViewModel].
+     *
+     * You should **never** manually create a [ViewModel] outside of a
+     * [ViewModelProvider.Factory].
+     *
+     * @param viewModelScope a [CoroutineScope] to be cancelled when the [ViewModel] is cleared.
+     * @param closeables the resources to be closed when the [ViewModel] is cleared.
+     */
+    public constructor(viewModelScope: CoroutineScope, vararg closeables: AutoCloseable)
+
+    /**
      * This method will be called when this ViewModel is no longer used and will be destroyed.
      *
      * It is useful when ViewModel observes some data and you need to clear this subscription to
@@ -157,28 +182,28 @@
     public fun <T : AutoCloseable> getCloseable(key: String): T?
 }
 
-private const val JOB_KEY = "androidx.lifecycle.ViewModelCoroutineScope.JOB_KEY"
+internal const val VIEW_MODEL_SCOPE_KEY =
+    "androidx.lifecycle.viewmodel.internal.ViewModelCoroutineScope.JOB_KEY"
 
 /**
- * [CoroutineScope] tied to this [ViewModel].
- * This scope will be canceled when ViewModel will be cleared, i.e. [ViewModel.onCleared] is called
+ * The [CoroutineScope] associated with this [ViewModel].
  *
- * This scope is bound to
- * [Dispatchers.Main.immediate][kotlinx.coroutines.MainCoroutineDispatcher.immediate]
+ * The [CoroutineScope.coroutineContext] is configured with:
+ * - [SupervisorJob]: ensures children jobs can fail independently of each other.
+ * - [MainCoroutineDispatcher.immediate]: executes jobs immediately on the main (UI) thread. If
+ *  the [Dispatchers.Main] is not available on the current platform (e.g., Linux), we fallback
+ *  to an [EmptyCoroutineContext].
+ *
+ * This scope is automatically cancelled when the [ViewModel] is cleared, and can be replaced by
+ * using the [ViewModel] constructor overload that takes in a `viewModelScope: CoroutineScope`.
+ *
+ * For background execution, use [kotlinx.coroutines.withContext] to switch to appropriate
+ * dispatchers (e.g., [kotlinx.coroutines.IO]).
+ *
+ * @see ViewModel.onCleared
  */
 public val ViewModel.viewModelScope: CoroutineScope
     get() {
-        return getCloseable<CloseableCoroutineScope>(JOB_KEY) ?: CloseableCoroutineScope(
-            SupervisorJob() + Dispatchers.Main.immediate
-        ).also { newClosableScope ->
-            addCloseable(JOB_KEY, newClosableScope)
-        }
+        return getCloseable<CloseableCoroutineScope>(VIEW_MODEL_SCOPE_KEY)
+            ?: createViewModelScope().also { scope -> addCloseable(VIEW_MODEL_SCOPE_KEY, scope) }
     }
-
-private class CloseableCoroutineScope(context: CoroutineContext) : AutoCloseable, CoroutineScope {
-    override val coroutineContext: CoroutineContext = context
-
-    override fun close() {
-        coroutineContext.cancel()
-    }
-}
diff --git a/lifecycle/lifecycle-viewmodel/src/commonMain/kotlin/androidx/lifecycle/viewmodel/internal/CloseableCoroutineScope.kt b/lifecycle/lifecycle-viewmodel/src/commonMain/kotlin/androidx/lifecycle/viewmodel/internal/CloseableCoroutineScope.kt
new file mode 100644
index 0000000..6d7f2c9
--- /dev/null
+++ b/lifecycle/lifecycle-viewmodel/src/commonMain/kotlin/androidx/lifecycle/viewmodel/internal/CloseableCoroutineScope.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2024 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.
+ */
+
+@file:OptIn(ExperimentalStdlibApi::class)
+
+package androidx.lifecycle.viewmodel.internal
+
+import androidx.lifecycle.ViewModel
+import kotlin.coroutines.CoroutineContext
+import kotlin.coroutines.EmptyCoroutineContext
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.MainCoroutineDispatcher
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.cancel
+
+/**
+ * Key used to [ViewModel.addCloseable] the [CoroutineScope] associated with a [ViewModel].
+ *
+ * @see androidx.lifecycle.viewmodel.internal.ViewModelImpl
+ * @see androidx.lifecycle.viewModelScope
+ */
+internal const val VIEW_MODEL_SCOPE_KEY =
+    "androidx.lifecycle.viewmodel.internal.ViewModelCoroutineScope.JOB_KEY"
+
+/**
+ * Creates a [CloseableCoroutineScope] intended for [ViewModel] use.
+ *
+ * The [CoroutineScope.coroutineContext] is configured with:
+ * - [SupervisorJob]: ensures children jobs can fail independently of each other.
+ * - [MainCoroutineDispatcher.immediate]: executes jobs immediately on the main (UI) thread. If
+ *  the [Dispatchers.Main] is not available on the current platform (e.g., Linux), we fallback to
+ *  an [EmptyCoroutineContext].
+ *
+ * For background execution, use [kotlinx.coroutines.withContext] to switch to appropriate
+ * dispatchers (e.g., [kotlinx.coroutines.IO]).
+ */
+internal fun createViewModelScope(): CloseableCoroutineScope {
+    val dispatcher = try {
+        Dispatchers.Main.immediate
+    } catch (_: NotImplementedError) {
+        // In platforms where `Dispatchers.Main` is not available, Kotlin Multiplatform will throw
+        // a `NotImplementedError`. Since there's no direct functional alternative, we use
+        // `EmptyCoroutineContext` to ensure a `launch` will run in the same context as the caller.
+        EmptyCoroutineContext
+    }
+    return CloseableCoroutineScope(coroutineContext = dispatcher + SupervisorJob())
+}
+
+/** Represents this [CoroutineScope] as a [AutoCloseable]. */
+internal fun CoroutineScope.asCloseable() = CloseableCoroutineScope(coroutineScope = this)
+
+/**
+ * [CoroutineScope] that provides a method to [close] it, causing the rejection of any new tasks and
+ * cleanup of all underlying resources associated with the scope.
+ */
+internal class CloseableCoroutineScope(
+    override val coroutineContext: CoroutineContext,
+) : AutoCloseable, CoroutineScope {
+
+    constructor(coroutineScope: CoroutineScope) : this(coroutineScope.coroutineContext)
+
+    override fun close() = coroutineContext.cancel()
+}
diff --git a/lifecycle/lifecycle-viewmodel/src/commonMain/kotlin/androidx/lifecycle/viewmodel/internal/ViewModelImpl.kt b/lifecycle/lifecycle-viewmodel/src/commonMain/kotlin/androidx/lifecycle/viewmodel/internal/ViewModelImpl.kt
index ea3201a..b674d29 100644
--- a/lifecycle/lifecycle-viewmodel/src/commonMain/kotlin/androidx/lifecycle/viewmodel/internal/ViewModelImpl.kt
+++ b/lifecycle/lifecycle-viewmodel/src/commonMain/kotlin/androidx/lifecycle/viewmodel/internal/ViewModelImpl.kt
@@ -21,6 +21,7 @@
 import androidx.annotation.MainThread
 import androidx.lifecycle.ViewModel
 import kotlin.jvm.Volatile
+import kotlinx.coroutines.CoroutineScope
 
 /**
  * Internal implementation of the multiplatform [ViewModel].
@@ -61,25 +62,21 @@
     @Volatile
     private var isCleared = false
 
-    /**
-     * Construct a new [ViewModel] instance.
-     *
-     * You should **never** manually construct a [ViewModel] outside of a
-     * [androidx.lifecycle.ViewModelProvider.Factory].
-     */
     constructor()
 
-    /**
-     * Construct a new [ViewModel] instance. Any [AutoCloseable] objects provided here
-     * will be closed directly before [ViewModel.onCleared] is called.
-     *
-     * You should **never** manually construct a [ViewModel] outside of a
-     * [androidx.lifecycle.ViewModelProvider.Factory].
-     */
+    constructor(viewModelScope: CoroutineScope) {
+        addCloseable(VIEW_MODEL_SCOPE_KEY, viewModelScope.asCloseable())
+    }
+
     constructor(vararg closeables: AutoCloseable) {
         this.closeables += closeables
     }
 
+    constructor(viewModelScope: CoroutineScope, vararg closeables: AutoCloseable) {
+        addCloseable(VIEW_MODEL_SCOPE_KEY, viewModelScope.asCloseable())
+        this.closeables += closeables
+    }
+
     @MainThread
     fun clear() {
         isCleared = true
diff --git a/lifecycle/lifecycle-viewmodel/src/jvmMain/kotlin/androidx/lifecycle/ViewModel.jvm.kt b/lifecycle/lifecycle-viewmodel/src/jvmMain/kotlin/androidx/lifecycle/ViewModel.jvm.kt
index d26032b..8db771d 100644
--- a/lifecycle/lifecycle-viewmodel/src/jvmMain/kotlin/androidx/lifecycle/ViewModel.jvm.kt
+++ b/lifecycle/lifecycle-viewmodel/src/jvmMain/kotlin/androidx/lifecycle/ViewModel.jvm.kt
@@ -14,15 +14,16 @@
  * limitations under the License.
  */
 @file:JvmName("ViewModel")
+@file:OptIn(ExperimentalStdlibApi::class)
 
 package androidx.lifecycle
 
 import androidx.annotation.MainThread
 import androidx.lifecycle.viewmodel.internal.ViewModelImpl
 import java.io.Closeable
+import kotlinx.coroutines.CoroutineScope
 
 public actual abstract class ViewModel {
-
     /**
      * Internal implementation of the multiplatform [ViewModel].
      *
@@ -36,10 +37,18 @@
         impl = ViewModelImpl()
     }
 
+    public actual constructor(viewModelScope: CoroutineScope) {
+        impl = ViewModelImpl(viewModelScope)
+    }
+
     public actual constructor(vararg closeables: AutoCloseable) {
         impl = ViewModelImpl(*closeables)
     }
 
+    public actual constructor(viewModelScope: CoroutineScope, vararg closeables: AutoCloseable) {
+        impl = ViewModelImpl(viewModelScope, *closeables)
+    }
+
     /**
      * Construct a new ViewModel instance. Any [AutoCloseable] objects provided here
      * will be closed directly before [ViewModel.onCleared] is called.
diff --git a/lifecycle/lifecycle-viewmodel/src/nativeMain/kotlin/androidx/lifecycle/ViewModel.native.kt b/lifecycle/lifecycle-viewmodel/src/nativeMain/kotlin/androidx/lifecycle/ViewModel.native.kt
index 6106d62..2e43754 100644
--- a/lifecycle/lifecycle-viewmodel/src/nativeMain/kotlin/androidx/lifecycle/ViewModel.native.kt
+++ b/lifecycle/lifecycle-viewmodel/src/nativeMain/kotlin/androidx/lifecycle/ViewModel.native.kt
@@ -19,6 +19,7 @@
 
 import androidx.annotation.MainThread
 import androidx.lifecycle.viewmodel.internal.ViewModelImpl
+import kotlinx.coroutines.CoroutineScope
 
 public actual abstract class ViewModel {
 
@@ -28,10 +29,18 @@
         impl = ViewModelImpl()
     }
 
+    public actual constructor(viewModelScope: CoroutineScope) {
+        impl = ViewModelImpl(viewModelScope)
+    }
+
     public actual constructor(vararg closeables: AutoCloseable) {
         impl = ViewModelImpl(*closeables)
     }
 
+    public actual constructor(viewModelScope: CoroutineScope, vararg closeables: AutoCloseable) {
+        impl = ViewModelImpl(viewModelScope, *closeables)
+    }
+
     protected actual open fun onCleared() {}
 
     @MainThread
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/dao/BooksDao.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/dao/BooksDao.kt
index f780922..758773b 100644
--- a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/dao/BooksDao.kt
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/dao/BooksDao.kt
@@ -79,7 +79,7 @@
     fun addPublisherMaybe(publisher: Publisher): Maybe<Long>
 
     @Insert
-    fun addPublisherSuspend(publisher: Publisher)
+    fun addPublisher(publisher: Publisher)
 
     @Delete
     fun deletePublishers(vararg publishers: Publisher)
@@ -416,7 +416,7 @@
     @Transaction
     suspend fun addAuthorPublisherBooks(author: Author, publisher: Publisher, vararg books: Book) {
         addAuthorsSuspend(author)
-        addPublisherSuspend(publisher)
+        addPublisher(publisher)
         for (book in books) {
             insertBookSuspend(book)
         }
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/InvalidationTrackerFlowTest.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/InvalidationTrackerFlowTest.kt
index af57fe9..d72d5eb 100644
--- a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/InvalidationTrackerFlowTest.kt
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/InvalidationTrackerFlowTest.kt
@@ -94,7 +94,7 @@
         assertThat(channel.receive())
             .containsExactly("book")
 
-        booksDao.addPublisherSuspend(TestUtil.PUBLISHER2)
+        booksDao.addPublisher(TestUtil.PUBLISHER2)
         drain() // drain async invalidate
         yield()
 
diff --git a/room/integration-tests/multiplatformtestapp/src/commonTest/kotlin/androidx/room/integration/multiplatformtestapp/test/BaseSimpleQueryTest.kt b/room/integration-tests/multiplatformtestapp/src/commonTest/kotlin/androidx/room/integration/multiplatformtestapp/test/BaseSimpleQueryTest.kt
index 4cf7cb3..45d8d1b 100644
--- a/room/integration-tests/multiplatformtestapp/src/commonTest/kotlin/androidx/room/integration/multiplatformtestapp/test/BaseSimpleQueryTest.kt
+++ b/room/integration-tests/multiplatformtestapp/src/commonTest/kotlin/androidx/room/integration/multiplatformtestapp/test/BaseSimpleQueryTest.kt
@@ -77,4 +77,54 @@
         )
         assertThat(dao.getItemList().map { it.pk }).containsExactly(2L)
     }
+
+    @Test
+    fun simpleInsertAndDelete() = runTest {
+        val sampleEntity = SampleEntity(1, 1)
+        val dao = getRoomDatabase().dao()
+
+        dao.insert(sampleEntity)
+        assertThat(dao.getSingleItemWithColumn().pk).isEqualTo(1)
+
+        dao.delete(sampleEntity)
+        assertThrows<IllegalStateException> {
+            dao.getSingleItemWithColumn()
+        }.hasMessageThat().contains("The query result was empty")
+    }
+
+    @Test
+    fun simpleInsertAndUpdateAndDelete() = runTest {
+        val sampleEntity1 = SampleEntity(1, 1)
+        val sampleEntity2 = SampleEntity(1, 2)
+        val dao = getRoomDatabase().dao()
+
+        dao.insert(sampleEntity1)
+        assertThat(dao.getSingleItemWithColumn().data).isEqualTo(1)
+
+        dao.update(sampleEntity2)
+        assertThat(dao.getSingleItemWithColumn().data).isEqualTo(2)
+
+        dao.delete(sampleEntity2)
+        assertThrows<IllegalStateException> {
+            dao.getSingleItem()
+        }.hasMessageThat().contains("The query result was empty")
+    }
+
+    @Test
+    fun simpleInsertAndUpsertAndDelete() = runTest {
+        val sampleEntity1 = SampleEntity(1, 1)
+        val sampleEntity2 = SampleEntity(1, 2)
+        val dao = getRoomDatabase().dao()
+
+        dao.insert(sampleEntity1)
+        assertThat(dao.getSingleItemWithColumn().data).isEqualTo(1)
+
+        dao.upsert(sampleEntity2)
+        assertThat(dao.getSingleItemWithColumn().data).isEqualTo(2)
+
+        dao.delete(sampleEntity2)
+        assertThrows<IllegalStateException> {
+            dao.getSingleItem()
+        }.hasMessageThat().contains("The query result was empty")
+    }
 }
diff --git a/room/integration-tests/multiplatformtestapp/src/commonTest/kotlin/androidx/room/integration/multiplatformtestapp/test/SampleDatabase.kt b/room/integration-tests/multiplatformtestapp/src/commonTest/kotlin/androidx/room/integration/multiplatformtestapp/test/SampleDatabase.kt
index 0592e2c..30c89ce6b 100644
--- a/room/integration-tests/multiplatformtestapp/src/commonTest/kotlin/androidx/room/integration/multiplatformtestapp/test/SampleDatabase.kt
+++ b/room/integration-tests/multiplatformtestapp/src/commonTest/kotlin/androidx/room/integration/multiplatformtestapp/test/SampleDatabase.kt
@@ -16,18 +16,25 @@
 
 package androidx.room.integration.multiplatformtestapp.test
 
+import androidx.room.ColumnInfo
 import androidx.room.Dao
 import androidx.room.Database
+import androidx.room.Delete
 import androidx.room.Entity
+import androidx.room.Insert
 import androidx.room.PrimaryKey
 import androidx.room.Query
 import androidx.room.RoomDatabase
 import androidx.room.Transaction
+import androidx.room.Update
+import androidx.room.Upsert
 
 @Entity
 data class SampleEntity(
     @PrimaryKey
-    val pk: Long
+    val pk: Long,
+    @ColumnInfo(defaultValue = "0")
+    val data: Long
 )
 
 @Dao
@@ -50,6 +57,21 @@
         require(!withError)
         pks.forEach { deleteItem(it) }
     }
+
+    @Query("SELECT * FROM SampleEntity")
+    suspend fun getSingleItemWithColumn(): SampleEntity
+
+    @Insert
+    suspend fun insert(entity: SampleEntity)
+
+    @Upsert
+    suspend fun upsert(entity: SampleEntity)
+
+    @Delete
+    suspend fun delete(entity: SampleEntity)
+
+    @Update
+    suspend fun update(entity: SampleEntity)
 }
 
 @Database(
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/ext/xpoet_ext.kt b/room/room-compiler/src/main/kotlin/androidx/room/ext/xpoet_ext.kt
index c83a2c9..3ee723e 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/ext/xpoet_ext.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/ext/xpoet_ext.kt
@@ -55,9 +55,15 @@
     val ROOM_DB_KT = XClassName.get(ROOM_PACKAGE, "RoomDatabaseKt")
     val ROOM_DB_CALLBACK = XClassName.get(ROOM_PACKAGE, "RoomDatabase", "Callback")
     val ROOM_DB_CONFIG = XClassName.get(ROOM_PACKAGE, "DatabaseConfiguration")
-    val INSERT_ADAPTER = XClassName.get(ROOM_PACKAGE, "EntityInsertionAdapter")
-    val UPSERT_ADAPTER = XClassName.get(ROOM_PACKAGE, "EntityUpsertionAdapter")
-    val DELETE_OR_UPDATE_ADAPTER = XClassName.get(ROOM_PACKAGE, "EntityDeletionOrUpdateAdapter")
+    val INSERT_ADAPTER = XClassName.get(ROOM_PACKAGE, "EntityInsertAdapter")
+    val UPSERT_ADAPTER = XClassName.get(ROOM_PACKAGE, "EntityUpsertAdapter")
+    val DELETE_OR_UPDATE_ADAPTER = XClassName.get(ROOM_PACKAGE, "EntityDeleteOrUpdateAdapter")
+    val INSERT_ADAPTER_COMPAT = XClassName.get(ROOM_PACKAGE, "EntityInsertionAdapter")
+    val UPSERT_ADAPTER_COMPAT = XClassName.get(ROOM_PACKAGE, "EntityUpsertionAdapter")
+    val DELETE_OR_UPDATE_ADAPTER_COMPAT = XClassName.get(
+        ROOM_PACKAGE,
+        "EntityDeletionOrUpdateAdapter"
+    )
     val SHARED_SQLITE_STMT = XClassName.get(ROOM_PACKAGE, "SharedSQLiteStatement")
     val INVALIDATION_TRACKER = XClassName.get(ROOM_PACKAGE, "InvalidationTracker")
     val ROOM_SQL_QUERY = XClassName.get(ROOM_PACKAGE, "RoomSQLiteQuery")
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/processor/MethodProcessorDelegate.kt b/room/room-compiler/src/main/kotlin/androidx/room/processor/MethodProcessorDelegate.kt
index 4bfe5a2..7325825 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/processor/MethodProcessorDelegate.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/processor/MethodProcessorDelegate.kt
@@ -36,9 +36,9 @@
 import androidx.room.solver.prepared.binder.PreparedQueryResultBinder
 import androidx.room.solver.query.result.CoroutineResultBinder
 import androidx.room.solver.query.result.QueryResultBinder
-import androidx.room.solver.shortcut.binder.CallableDeleteOrUpdateMethodBinder.Companion.createDeleteOrUpdateBinder
-import androidx.room.solver.shortcut.binder.CallableInsertMethodBinder.Companion.createInsertBinder
-import androidx.room.solver.shortcut.binder.CallableUpsertMethodBinder.Companion.createUpsertBinder
+import androidx.room.solver.shortcut.binder.CoroutineDeleteOrUpdateMethodBinder
+import androidx.room.solver.shortcut.binder.CoroutineInsertMethodBinder
+import androidx.room.solver.shortcut.binder.CoroutineUpsertMethodBinder
 import androidx.room.solver.shortcut.binder.DeleteOrUpdateMethodBinder
 import androidx.room.solver.shortcut.binder.InsertOrUpsertMethodBinder
 import androidx.room.solver.transaction.binder.CoroutineTransactionMethodBinder
@@ -247,30 +247,27 @@
     override fun findInsertMethodBinder(
         returnType: XType,
         params: List<ShortcutQueryParameter>
-    ) = createInsertBinder(
+    ) = CoroutineInsertMethodBinder(
         typeArg = returnType,
-        adapter = context.typeAdapterStore.findInsertAdapter(returnType, params)
-    ) { callableImpl, dbProperty ->
-        addCoroutineExecuteStatement(callableImpl, dbProperty)
-    }
+        adapter = context.typeAdapterStore.findInsertAdapter(returnType, params),
+        continuationParamName = continuationParam.name
+    )
 
     override fun findUpsertMethodBinder(
         returnType: XType,
         params: List<ShortcutQueryParameter>
-    ) = createUpsertBinder(
+    ) = CoroutineUpsertMethodBinder(
         typeArg = returnType,
-        adapter = context.typeAdapterStore.findUpsertAdapter(returnType, params)
-    ) { callableImpl, dbProperty ->
-        addCoroutineExecuteStatement(callableImpl, dbProperty)
-    }
+        adapter = context.typeAdapterStore.findUpsertAdapter(returnType, params),
+        continuationParamName = continuationParam.name
+    )
 
     override fun findDeleteOrUpdateMethodBinder(returnType: XType) =
-        createDeleteOrUpdateBinder(
+        CoroutineDeleteOrUpdateMethodBinder(
             typeArg = returnType,
-            adapter = context.typeAdapterStore.findDeleteOrUpdateAdapter(returnType)
-        ) { callableImpl, dbProperty ->
-            addCoroutineExecuteStatement(callableImpl, dbProperty)
-        }
+            adapter = context.typeAdapterStore.findDeleteOrUpdateAdapter(returnType),
+            continuationParamName = continuationParam.name
+        )
 
     override fun findTransactionMethodBinder(callType: TransactionMethod.CallType) =
         CoroutineTransactionMethodBinder(
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/CallableDeleteOrUpdateMethodBinder.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/CallableDeleteOrUpdateMethodBinder.kt
index 7d3af7d..815d751 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/CallableDeleteOrUpdateMethodBinder.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/CallableDeleteOrUpdateMethodBinder.kt
@@ -55,9 +55,18 @@
         dbProperty: XPropertySpec,
         scope: CodeGenScope
     ) {
+      convertAndReturnCompat(parameters, adapters, dbProperty, scope)
+    }
+
+    override fun convertAndReturnCompat(
+        parameters: List<ShortcutQueryParameter>,
+        adapters: Map<String, Pair<XPropertySpec, XTypeSpec>>,
+        dbProperty: XPropertySpec,
+        scope: CodeGenScope
+    ) {
         val adapterScope = scope.fork()
         val callableImpl = CallableTypeSpecBuilder(scope.language, typeArg.asTypeName()) {
-            adapter?.createDeleteOrUpdateMethodBody(
+            adapter?.generateMethodBodyCompat(
                 parameters = parameters,
                 adapters = adapters,
                 dbProperty = dbProperty,
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/CallableInsertMethodBinder.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/CallableInsertMethodBinder.kt
index 8152905..5a624a6 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/CallableInsertMethodBinder.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/CallableInsertMethodBinder.kt
@@ -55,11 +55,20 @@
         dbProperty: XPropertySpec,
         scope: CodeGenScope
     ) {
+        convertAndReturnCompat(parameters, adapters, dbProperty, scope)
+    }
+
+    override fun convertAndReturnCompat(
+        parameters: List<ShortcutQueryParameter>,
+        adapters: Map<String, Pair<XPropertySpec, Any>>,
+        dbProperty: XPropertySpec,
+        scope: CodeGenScope
+    ) {
         val adapterScope = scope.fork()
         val callableImpl = CallableTypeSpecBuilder(scope.language, typeArg.asTypeName()) {
             addCode(
                 XCodeBlock.builder(language).apply {
-                    adapter?.createMethodBody(
+                    adapter?.generateMethodBodyCompat(
                         parameters = parameters,
                         adapters = adapters,
                         dbProperty = dbProperty,
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/CallableUpsertMethodBinder.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/CallableUpsertMethodBinder.kt
index ae0acfd..5078cf6 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/CallableUpsertMethodBinder.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/CallableUpsertMethodBinder.kt
@@ -58,11 +58,20 @@
         dbProperty: XPropertySpec,
         scope: CodeGenScope
     ) {
+        convertAndReturnCompat(parameters, adapters, dbProperty, scope)
+    }
+
+    override fun convertAndReturnCompat(
+        parameters: List<ShortcutQueryParameter>,
+        adapters: Map<String, Pair<XPropertySpec, Any>>,
+        dbProperty: XPropertySpec,
+        scope: CodeGenScope
+    ) {
         val adapterScope = scope.fork()
         val callableImpl = CallableTypeSpecBuilder(scope.language, typeArg.asTypeName()) {
             addCode(
                 XCodeBlock.builder(language).apply {
-                    adapter?.createMethodBody(
+                    adapter?.generateMethodBodyCompat(
                         parameters = parameters,
                         adapters = adapters,
                         dbProperty = dbProperty,
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/CoroutineDeleteOrUpdateMethodBinder.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/CoroutineDeleteOrUpdateMethodBinder.kt
new file mode 100644
index 0000000..6fd03bf
--- /dev/null
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/CoroutineDeleteOrUpdateMethodBinder.kt
@@ -0,0 +1,133 @@
+/*
+ * Copyright 2018 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.room.solver.shortcut.binder
+
+import androidx.room.compiler.codegen.CodeLanguage
+import androidx.room.compiler.codegen.XMemberName.Companion.packageMember
+import androidx.room.compiler.codegen.XPropertySpec
+import androidx.room.compiler.codegen.XTypeSpec
+import androidx.room.compiler.codegen.box
+import androidx.room.compiler.processing.XType
+import androidx.room.ext.Function1TypeSpec
+import androidx.room.ext.RoomTypeNames
+import androidx.room.ext.SQLiteDriverTypeNames
+import androidx.room.solver.CodeGenScope
+import androidx.room.solver.shortcut.result.DeleteOrUpdateMethodAdapter
+import androidx.room.vo.ShortcutQueryParameter
+
+/**
+ * Binder for suspending delete and update methods.
+ */
+class CoroutineDeleteOrUpdateMethodBinder(
+    val typeArg: XType,
+    adapter: DeleteOrUpdateMethodAdapter?,
+    private val continuationParamName: String
+) : DeleteOrUpdateMethodBinder(adapter) {
+
+    override fun convertAndReturn(
+        parameters: List<ShortcutQueryParameter>,
+        adapters: Map<String, Pair<XPropertySpec, XTypeSpec>>,
+        dbProperty: XPropertySpec,
+        scope: CodeGenScope
+    ) {
+        when (scope.language) {
+            CodeLanguage.JAVA -> convertAndReturnJava(
+                parameters, adapters, dbProperty, scope
+            )
+            CodeLanguage.KOTLIN -> convertAndReturnKotlin(
+                parameters, adapters, dbProperty, scope
+            )
+        }
+    }
+
+    private fun convertAndReturnJava(
+        parameters: List<ShortcutQueryParameter>,
+        adapters: Map<String, Pair<XPropertySpec, Any>>,
+        dbProperty: XPropertySpec,
+        scope: CodeGenScope
+    ) {
+        if (adapter == null) {
+            return
+        }
+        val connectionVar = scope.getTmpVar("_connection")
+        scope.builder.addStatement(
+            "return %M(%N, %L, %L, %L, %L)",
+            RoomTypeNames.DB_UTIL.packageMember("performSuspending"),
+            dbProperty,
+            false, // isReadOnly
+            true, // inTransaction
+            Function1TypeSpec(
+                language = scope.language,
+                parameterTypeName = SQLiteDriverTypeNames.CONNECTION,
+                parameterName = connectionVar,
+                returnTypeName = adapter.returnType.asTypeName().box()
+            ) {
+                val functionScope = scope.fork()
+                val functionCode = functionScope.builder.apply {
+                    adapter.generateMethodBody(
+                        scope = functionScope,
+                        parameters = parameters,
+                        adapters = adapters,
+                        connectionVar = connectionVar
+                    )
+                }.build()
+                this.addCode(functionCode)
+            },
+            continuationParamName
+        )
+    }
+
+    private fun convertAndReturnKotlin(
+        parameters: List<ShortcutQueryParameter>,
+        adapters: Map<String, Pair<XPropertySpec, Any>>,
+        dbProperty: XPropertySpec,
+        scope: CodeGenScope
+    ) {
+        if (adapter == null) {
+            return
+        }
+        val connectionVar = scope.getTmpVar("_connection")
+        scope.builder.apply {
+            beginControlFlow(
+                "return %M(%N, %L, %L) { %L ->",
+                RoomTypeNames.DB_UTIL.packageMember("performSuspending"),
+                dbProperty,
+                false, // isReadOnly
+                true, // inTransaction
+                connectionVar
+            ).apply {
+                adapter.generateMethodBody(
+                    scope = scope,
+                    parameters = parameters,
+                    adapters = adapters,
+                    connectionVar = connectionVar
+                )
+            }.endControlFlow()
+        }
+    }
+
+    override fun convertAndReturnCompat(
+        parameters: List<ShortcutQueryParameter>,
+        adapters: Map<String, Pair<XPropertySpec, XTypeSpec>>,
+        dbProperty: XPropertySpec,
+        scope: CodeGenScope
+    ) {
+        TODO("Will be removed")
+    }
+
+    override fun isMigratedToDriver(): Boolean = true
+}
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/CoroutineInsertMethodBinder.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/CoroutineInsertMethodBinder.kt
new file mode 100644
index 0000000..f1c92ca
--- /dev/null
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/CoroutineInsertMethodBinder.kt
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2018 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.room.solver.shortcut.binder
+
+import androidx.room.compiler.codegen.CodeLanguage
+import androidx.room.compiler.codegen.XMemberName.Companion.packageMember
+import androidx.room.compiler.codegen.XPropertySpec
+import androidx.room.compiler.codegen.box
+import androidx.room.compiler.processing.XType
+import androidx.room.ext.Function1TypeSpec
+import androidx.room.ext.RoomTypeNames
+import androidx.room.ext.SQLiteDriverTypeNames
+import androidx.room.solver.CodeGenScope
+import androidx.room.solver.shortcut.result.InsertOrUpsertMethodAdapter
+import androidx.room.vo.ShortcutQueryParameter
+
+/**
+ * Binder for suspending insert methods.
+ */
+class CoroutineInsertMethodBinder(
+    val typeArg: XType,
+    adapter: InsertOrUpsertMethodAdapter?,
+    private val continuationParamName: String
+) : InsertOrUpsertMethodBinder(adapter) {
+
+    override fun convertAndReturn(
+        parameters: List<ShortcutQueryParameter>,
+        adapters: Map<String, Pair<XPropertySpec, Any>>,
+        dbProperty: XPropertySpec,
+        scope: CodeGenScope
+    ) {
+        when (scope.language) {
+            CodeLanguage.JAVA -> convertAndReturnJava(
+                parameters, adapters, dbProperty, scope
+            )
+            CodeLanguage.KOTLIN -> convertAndReturnKotlin(
+                parameters, adapters, dbProperty, scope
+            )
+        }
+    }
+
+    private fun convertAndReturnJava(
+        parameters: List<ShortcutQueryParameter>,
+        adapters: Map<String, Pair<XPropertySpec, Any>>,
+        dbProperty: XPropertySpec,
+        scope: CodeGenScope
+    ) {
+        if (adapter == null) {
+            return
+        }
+        val connectionVar = scope.getTmpVar("_connection")
+        scope.builder.addStatement(
+            "return %M(%N, %L, %L, %L, %L)",
+            RoomTypeNames.DB_UTIL.packageMember("performSuspending"),
+            dbProperty,
+            false, // isReadOnly
+            true, // inTransaction
+            Function1TypeSpec(
+                language = scope.language,
+                parameterTypeName = SQLiteDriverTypeNames.CONNECTION,
+                parameterName = connectionVar,
+                returnTypeName = adapter.returnType.asTypeName().box()
+            ) {
+                val functionScope = scope.fork()
+                val functionCode = functionScope.builder.apply {
+                    adapter.generateMethodBody(
+                        scope = functionScope,
+                        connectionVar = connectionVar,
+                        parameters = parameters,
+                        adapters = adapters
+                    )
+                }.build()
+                this.addCode(functionCode)
+            },
+            continuationParamName
+        )
+    }
+
+    private fun convertAndReturnKotlin(
+        parameters: List<ShortcutQueryParameter>,
+        adapters: Map<String, Pair<XPropertySpec, Any>>,
+        dbProperty: XPropertySpec,
+        scope: CodeGenScope
+    ) {
+        if (adapter == null) {
+            return
+        }
+        val connectionVar = scope.getTmpVar("_connection")
+        scope.builder.apply {
+            beginControlFlow(
+                "return %M(%N, %L, %L) { %L ->",
+                RoomTypeNames.DB_UTIL.packageMember("performSuspending"),
+                dbProperty,
+                false, // isReadOnly
+                true, // inTransaction
+                connectionVar
+            ).apply {
+                adapter.generateMethodBody(
+                    scope = scope,
+                    connectionVar = connectionVar,
+                    parameters = parameters,
+                    adapters = adapters
+                )
+            }.endControlFlow()
+        }
+    }
+
+    override fun convertAndReturnCompat(
+        parameters: List<ShortcutQueryParameter>,
+        adapters: Map<String, Pair<XPropertySpec, Any>>,
+        dbProperty: XPropertySpec,
+        scope: CodeGenScope
+    ) {
+        error("Wrong convertAndReturn invoked")
+    }
+
+    override fun isMigratedToDriver(): Boolean = true
+}
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/CoroutineUpsertMethodBinder.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/CoroutineUpsertMethodBinder.kt
new file mode 100644
index 0000000..d820e4d
--- /dev/null
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/CoroutineUpsertMethodBinder.kt
@@ -0,0 +1,133 @@
+/*
+ * Copyright 2022 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.room.solver.shortcut.binder
+
+import androidx.room.compiler.codegen.CodeLanguage
+import androidx.room.compiler.codegen.XMemberName.Companion.packageMember
+import androidx.room.compiler.codegen.XPropertySpec
+import androidx.room.compiler.codegen.box
+import androidx.room.compiler.processing.XType
+import androidx.room.ext.Function1TypeSpec
+import androidx.room.ext.RoomTypeNames
+import androidx.room.ext.SQLiteDriverTypeNames
+import androidx.room.solver.CodeGenScope
+import androidx.room.solver.shortcut.result.InsertOrUpsertMethodAdapter
+import androidx.room.vo.ShortcutQueryParameter
+
+/**
+ * Binder for suspending upsert methods.
+ */
+class CoroutineUpsertMethodBinder(
+    val typeArg: XType,
+    adapter: InsertOrUpsertMethodAdapter?,
+    private val continuationParamName: String
+) : InsertOrUpsertMethodBinder(adapter) {
+
+    override fun convertAndReturn(
+        parameters: List<ShortcutQueryParameter>,
+        adapters: Map<String, Pair<XPropertySpec, Any>>,
+        dbProperty: XPropertySpec,
+        scope: CodeGenScope
+    ) {
+        when (scope.language) {
+            CodeLanguage.JAVA -> convertAndReturnJava(
+                parameters, adapters, dbProperty, scope
+            )
+            CodeLanguage.KOTLIN -> convertAndReturnKotlin(
+                parameters, adapters, dbProperty, scope
+            )
+        }
+    }
+
+    private fun convertAndReturnJava(
+        parameters: List<ShortcutQueryParameter>,
+        adapters: Map<String, Pair<XPropertySpec, Any>>,
+        dbProperty: XPropertySpec,
+        scope: CodeGenScope
+    ) {
+        if (adapter == null) {
+            return
+        }
+        val connectionVar = scope.getTmpVar("_connection")
+        scope.builder.addStatement(
+            "return %M(%N, %L, %L, %L, %L)",
+            RoomTypeNames.DB_UTIL.packageMember("performSuspending"),
+            dbProperty,
+            false, // isReadOnly
+            true, // inTransaction
+            Function1TypeSpec(
+                language = scope.language,
+                parameterTypeName = SQLiteDriverTypeNames.CONNECTION,
+                parameterName = connectionVar,
+                returnTypeName = adapter.returnType.asTypeName().box()
+            ) {
+                val functionScope = scope.fork()
+                val functionCode = functionScope.builder.apply {
+                    adapter.generateMethodBody(
+                        scope = functionScope,
+                        connectionVar = connectionVar,
+                        parameters = parameters,
+                        adapters = adapters
+                    )
+                }.build()
+                this.addCode(functionCode)
+            },
+            continuationParamName
+        )
+    }
+
+    private fun convertAndReturnKotlin(
+        parameters: List<ShortcutQueryParameter>,
+        adapters: Map<String, Pair<XPropertySpec, Any>>,
+        dbProperty: XPropertySpec,
+        scope: CodeGenScope
+    ) {
+        if (adapter == null) {
+            return
+        }
+        val connectionVar = scope.getTmpVar("_connection")
+
+        scope.builder.apply {
+            beginControlFlow(
+                "return %M(%N, %L, %L) { %L ->",
+                RoomTypeNames.DB_UTIL.packageMember("performSuspending"),
+                dbProperty,
+                false, // isReadOnly
+                true, // inTransaction
+                connectionVar
+            ).apply {
+                adapter.generateMethodBody(
+                    scope = scope,
+                    connectionVar = connectionVar,
+                    parameters = parameters,
+                    adapters = adapters
+                )
+            }.endControlFlow()
+        }
+    }
+
+    override fun convertAndReturnCompat(
+        parameters: List<ShortcutQueryParameter>,
+        adapters: Map<String, Pair<XPropertySpec, Any>>,
+        dbProperty: XPropertySpec,
+        scope: CodeGenScope
+    ) {
+        error("Wrong convertAndReturn invoked")
+    }
+
+    override fun isMigratedToDriver(): Boolean = true
+}
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/DeleteOrUpdateMethodBinder.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/DeleteOrUpdateMethodBinder.kt
index b1e85c7..32850a6 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/DeleteOrUpdateMethodBinder.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/DeleteOrUpdateMethodBinder.kt
@@ -35,23 +35,6 @@
     /**
      * Received the delete/update method parameters, the adapters and generates the code that
      * runs the delete/update and returns the result.
-     *
-     * For example, for the DAO method
-     * ```
-     * @Delete
-     * fun deletePublishers(vararg publishers: Publisher)
-     * ```
-     * The following code will be generated:
-     *
-     * ```
-     * __db.beginTransaction();
-     * try {
-     *   __deletionAdapterOfPublisher.handleMultiple(publishers);
-     *   __db.setTransactionSuccessful();
-     * } finally {
-     *   __db.endTransaction();
-     * }
-     * ```
      */
     abstract fun convertAndReturn(
         parameters: List<ShortcutQueryParameter>,
@@ -59,4 +42,14 @@
         dbProperty: XPropertySpec,
         scope: CodeGenScope
     )
+
+    abstract fun convertAndReturnCompat(
+        parameters: List<ShortcutQueryParameter>,
+        adapters: Map<String, Pair<XPropertySpec, XTypeSpec>>,
+        dbProperty: XPropertySpec,
+        scope: CodeGenScope
+    )
+
+    // TODO(b/319660042): Remove once migration to driver API is done.
+    open fun isMigratedToDriver(): Boolean = false
 }
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/InsertOrUpsertMethodBinder.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/InsertOrUpsertMethodBinder.kt
index addc576..7d71f31 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/InsertOrUpsertMethodBinder.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/InsertOrUpsertMethodBinder.kt
@@ -29,24 +29,6 @@
     /**
      * Received an insert or upsert method parameters, their adapters and generations the code that
      * runs the insert or upsert and returns the result.
-     *
-     * For example, for the DAO method:
-     * ```
-     * @Upsert
-     * fun addPublishers(vararg publishers: Publisher): List<Long>
-     * ```
-     * The following code will be generated:
-     *
-     * ```
-     * __db.beginTransaction();
-     * try {
-     *  List<Long> _result = __upsertionAdapterOfPublisher.upsertAndReturnIdsList(publishers);
-     *  __db.setTransactionSuccessful();
-     *  return _result;
-     * } finally {
-     *  __db.endTransaction();
-     * }
-     * ```
      */
     abstract fun convertAndReturn(
         parameters: List<ShortcutQueryParameter>,
@@ -54,4 +36,14 @@
         dbProperty: XPropertySpec,
         scope: CodeGenScope
     )
+
+    abstract fun convertAndReturnCompat(
+        parameters: List<ShortcutQueryParameter>,
+        adapters: Map<String, Pair<XPropertySpec, Any>>,
+        dbProperty: XPropertySpec,
+        scope: CodeGenScope
+    )
+
+    // TODO(b/319660042): Remove once migration to driver API is done.
+    open fun isMigratedToDriver(): Boolean = false
 }
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/InstantDeleteOrUpdateMethodBinder.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/InstantDeleteOrUpdateMethodBinder.kt
index 7b6580d..7ce2c3d 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/InstantDeleteOrUpdateMethodBinder.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/InstantDeleteOrUpdateMethodBinder.kt
@@ -16,8 +16,15 @@
 
 package androidx.room.solver.shortcut.binder
 
+import androidx.room.compiler.codegen.CodeLanguage
+import androidx.room.compiler.codegen.XMemberName.Companion.packageMember
 import androidx.room.compiler.codegen.XPropertySpec
 import androidx.room.compiler.codegen.XTypeSpec
+import androidx.room.compiler.codegen.box
+import androidx.room.ext.Function1TypeSpec
+import androidx.room.ext.RoomTypeNames
+import androidx.room.ext.SQLiteDriverTypeNames
+import androidx.room.ext.isNotVoid
 import androidx.room.solver.CodeGenScope
 import androidx.room.solver.shortcut.result.DeleteOrUpdateMethodAdapter
 import androidx.room.vo.ShortcutQueryParameter
@@ -35,14 +42,96 @@
         dbProperty: XPropertySpec,
         scope: CodeGenScope
     ) {
-        scope.builder.apply {
-            addStatement("%N.assertNotSuspendingTransaction()", dbProperty)
+        when (scope.language) {
+            CodeLanguage.JAVA -> convertAndReturnJava(
+                parameters, adapters, dbProperty, scope
+            )
+            CodeLanguage.KOTLIN -> convertAndReturnKotlin(
+                parameters, adapters, dbProperty, scope
+            )
         }
-        adapter?.createDeleteOrUpdateMethodBody(
+    }
+
+    private fun convertAndReturnJava(
+        parameters: List<ShortcutQueryParameter>,
+        adapters: Map<String, Pair<XPropertySpec, Any>>,
+        dbProperty: XPropertySpec,
+        scope: CodeGenScope
+    ) {
+        if (adapter == null) {
+            return
+        }
+
+        val returnPrefix = if (adapter.returnType.isNotVoid()) { "return " } else { "" }
+        val connectionVar = scope.getTmpVar("_connection")
+        scope.builder.addStatement(
+            "$returnPrefix%M(%N, %L, %L, %L)",
+            RoomTypeNames.DB_UTIL.packageMember("performBlocking"),
+            dbProperty,
+            false, // isReadOnly
+            true, // inTransaction
+            Function1TypeSpec(
+                language = scope.language,
+                parameterTypeName = SQLiteDriverTypeNames.CONNECTION,
+                parameterName = connectionVar,
+                returnTypeName = adapter.returnType.asTypeName().box()
+            ) {
+                val functionScope = scope.fork()
+                val functionCode = functionScope.builder.apply {
+                    adapter.generateMethodBody(
+                        scope = functionScope,
+                        parameters = parameters,
+                        adapters = adapters,
+                        connectionVar = connectionVar
+                    )
+                }.build()
+                this.addCode(functionCode)
+            }
+        )
+    }
+
+    private fun convertAndReturnKotlin(
+        parameters: List<ShortcutQueryParameter>,
+        adapters: Map<String, Pair<XPropertySpec, Any>>,
+        dbProperty: XPropertySpec,
+        scope: CodeGenScope
+    ) {
+        if (adapter == null) {
+            return
+        }
+        val connectionVar = scope.getTmpVar("_connection")
+        scope.builder.apply {
+            beginControlFlow(
+                "return %M(%N, %L, %L) { %L ->",
+                RoomTypeNames.DB_UTIL.packageMember("performBlocking"),
+                dbProperty,
+                false, // isReadOnly
+                true, // inTransaction
+                connectionVar
+            ).apply {
+                adapter.generateMethodBody(
+                    scope = scope,
+                    parameters = parameters,
+                    adapters = adapters,
+                    connectionVar = connectionVar
+                )
+            }.endControlFlow()
+        }
+    }
+
+    override fun convertAndReturnCompat(
+        parameters: List<ShortcutQueryParameter>,
+        adapters: Map<String, Pair<XPropertySpec, XTypeSpec>>,
+        dbProperty: XPropertySpec,
+        scope: CodeGenScope
+    ) {
+        adapter?.generateMethodBodyCompat(
             parameters = parameters,
             adapters = adapters,
             dbProperty = dbProperty,
             scope = scope
         )
     }
+
+    override fun isMigratedToDriver() = true
 }
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/InstantInsertMethodBinder.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/InstantInsertMethodBinder.kt
index 28d8e63..ca27b5f 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/InstantInsertMethodBinder.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/InstantInsertMethodBinder.kt
@@ -16,7 +16,14 @@
 
 package androidx.room.solver.shortcut.binder
 
+import androidx.room.compiler.codegen.CodeLanguage
+import androidx.room.compiler.codegen.XMemberName.Companion.packageMember
 import androidx.room.compiler.codegen.XPropertySpec
+import androidx.room.compiler.codegen.box
+import androidx.room.ext.Function1TypeSpec
+import androidx.room.ext.RoomTypeNames
+import androidx.room.ext.SQLiteDriverTypeNames
+import androidx.room.ext.isNotVoid
 import androidx.room.solver.CodeGenScope
 import androidx.room.solver.shortcut.result.InsertOrUpsertMethodAdapter
 import androidx.room.vo.ShortcutQueryParameter
@@ -33,14 +40,96 @@
         dbProperty: XPropertySpec,
         scope: CodeGenScope
     ) {
-        scope.builder.apply {
-            addStatement("%N.assertNotSuspendingTransaction()", dbProperty)
+        when (scope.language) {
+            CodeLanguage.JAVA -> convertAndReturnJava(
+                parameters, adapters, dbProperty, scope
+            )
+            CodeLanguage.KOTLIN -> convertAndReturnKotlin(
+                parameters, adapters, dbProperty, scope
+            )
         }
-        adapter?.createMethodBody(
+    }
+
+    private fun convertAndReturnJava(
+        parameters: List<ShortcutQueryParameter>,
+        adapters: Map<String, Pair<XPropertySpec, Any>>,
+        dbProperty: XPropertySpec,
+        scope: CodeGenScope
+    ) {
+        if (adapter == null) {
+            return
+        }
+        val returnPrefix = if (adapter.returnType.isNotVoid()) { "return " } else { "" }
+
+        val connectionVar = scope.getTmpVar("_connection")
+        scope.builder.addStatement(
+            "$returnPrefix%M(%N, %L, %L, %L)",
+            RoomTypeNames.DB_UTIL.packageMember("performBlocking"),
+            dbProperty,
+            false, // isReadOnly
+            true, // inTransaction
+            Function1TypeSpec(
+                language = scope.language,
+                parameterTypeName = SQLiteDriverTypeNames.CONNECTION,
+                parameterName = connectionVar,
+                returnTypeName = adapter.returnType.asTypeName().box()
+            ) {
+                val functionScope = scope.fork()
+                val functionCode = functionScope.builder.apply {
+                    adapter.generateMethodBody(
+                        scope = functionScope,
+                        connectionVar = connectionVar,
+                        parameters = parameters,
+                        adapters = adapters
+                    )
+                }.build()
+                this.addCode(functionCode)
+            }
+        )
+    }
+
+    private fun convertAndReturnKotlin(
+        parameters: List<ShortcutQueryParameter>,
+        adapters: Map<String, Pair<XPropertySpec, Any>>,
+        dbProperty: XPropertySpec,
+        scope: CodeGenScope
+    ) {
+        if (adapter == null) {
+            return
+        }
+        val connectionVar = scope.getTmpVar("_connection")
+        scope.builder.apply {
+            beginControlFlow(
+                "return %M(%N, %L, %L) { %L ->",
+                RoomTypeNames.DB_UTIL.packageMember("performBlocking"),
+                dbProperty,
+                false, // isReadOnly
+                true, // inTransaction
+                connectionVar
+            ).apply {
+                adapter.generateMethodBody(
+                    scope = scope,
+                    connectionVar = connectionVar,
+                    parameters = parameters,
+                    adapters = adapters
+                )
+            }.endControlFlow()
+        }
+    }
+
+    override fun convertAndReturnCompat(
+        parameters: List<ShortcutQueryParameter>,
+        adapters: Map<String, Pair<XPropertySpec, Any>>,
+        dbProperty: XPropertySpec,
+        scope: CodeGenScope
+    ) {
+        adapter?.generateMethodBodyCompat(
             parameters = parameters,
             adapters = adapters,
             dbProperty = dbProperty,
             scope = scope
         )
     }
+
+    override fun isMigratedToDriver() = true
 }
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/InstantUpsertMethodBinder.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/InstantUpsertMethodBinder.kt
index 46a42f8..244e6c7 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/InstantUpsertMethodBinder.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/InstantUpsertMethodBinder.kt
@@ -16,7 +16,14 @@
 
 package androidx.room.solver.shortcut.binder
 
+import androidx.room.compiler.codegen.CodeLanguage
+import androidx.room.compiler.codegen.XMemberName.Companion.packageMember
 import androidx.room.compiler.codegen.XPropertySpec
+import androidx.room.compiler.codegen.box
+import androidx.room.ext.Function1TypeSpec
+import androidx.room.ext.RoomTypeNames
+import androidx.room.ext.SQLiteDriverTypeNames
+import androidx.room.ext.isNotVoid
 import androidx.room.solver.CodeGenScope
 import androidx.room.solver.shortcut.result.InsertOrUpsertMethodAdapter
 import androidx.room.vo.ShortcutQueryParameter
@@ -33,14 +40,95 @@
         dbProperty: XPropertySpec,
         scope: CodeGenScope
     ) {
-        scope.builder.apply {
-            addStatement("%N.assertNotSuspendingTransaction()", dbProperty)
+        when (scope.language) {
+            CodeLanguage.JAVA -> convertAndReturnJava(
+                parameters, adapters, dbProperty, scope
+            )
+            CodeLanguage.KOTLIN -> convertAndReturnKotlin(
+                parameters, adapters, dbProperty, scope
+            )
         }
-        adapter?.createMethodBody(
+    }
+
+    private fun convertAndReturnJava(
+        parameters: List<ShortcutQueryParameter>,
+        adapters: Map<String, Pair<XPropertySpec, Any>>,
+        dbProperty: XPropertySpec,
+        scope: CodeGenScope
+    ) {
+        if (adapter == null) {
+            return
+        }
+        val connectionVar = scope.getTmpVar("_connection")
+        val returnPrefix = if (adapter.returnType.isNotVoid()) { "return " } else { "" }
+        scope.builder.addStatement(
+            "$returnPrefix%M(%N, %L, %L, %L)",
+            RoomTypeNames.DB_UTIL.packageMember("performBlocking"),
+            dbProperty,
+            false, // isReadOnly
+            true, // inTransaction
+            Function1TypeSpec(
+                language = scope.language,
+                parameterTypeName = SQLiteDriverTypeNames.CONNECTION,
+                parameterName = connectionVar,
+                returnTypeName = adapter.returnType.asTypeName().box()
+            ) {
+                val functionScope = scope.fork()
+                val functionCode = functionScope.builder.apply {
+                    adapter.generateMethodBody(
+                        scope = functionScope,
+                        connectionVar = connectionVar,
+                        parameters = parameters,
+                        adapters = adapters
+                    )
+                }.build()
+                this.addCode(functionCode)
+            }
+        )
+    }
+
+    private fun convertAndReturnKotlin(
+        parameters: List<ShortcutQueryParameter>,
+        adapters: Map<String, Pair<XPropertySpec, Any>>,
+        dbProperty: XPropertySpec,
+        scope: CodeGenScope
+    ) {
+        if (adapter == null) {
+            return
+        }
+        val connectionVar = scope.getTmpVar("_connection")
+        scope.builder.apply {
+            beginControlFlow(
+                "return %M(%N, %L, %L) { %L ->",
+                RoomTypeNames.DB_UTIL.packageMember("performBlocking"),
+                dbProperty,
+                false, // isReadOnly
+                true, // inTransaction
+                connectionVar
+            ).apply {
+                adapter.generateMethodBody(
+                    scope = scope,
+                    connectionVar = connectionVar,
+                    parameters = parameters,
+                    adapters = adapters
+                )
+            }.endControlFlow()
+        }
+    }
+
+    override fun convertAndReturnCompat(
+        parameters: List<ShortcutQueryParameter>,
+        adapters: Map<String, Pair<XPropertySpec, Any>>,
+        dbProperty: XPropertySpec,
+        scope: CodeGenScope
+    ) {
+        adapter?.generateMethodBodyCompat(
             parameters = parameters,
             adapters = adapters,
             dbProperty = dbProperty,
             scope = scope
         )
     }
+
+    override fun isMigratedToDriver() = true
 }
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/result/DeleteOrUpdateMethodAdapter.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/result/DeleteOrUpdateMethodAdapter.kt
index 69fc2c3..0215445 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/result/DeleteOrUpdateMethodAdapter.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/result/DeleteOrUpdateMethodAdapter.kt
@@ -20,7 +20,6 @@
 import androidx.room.compiler.codegen.XCodeBlock
 import androidx.room.compiler.codegen.XPropertySpec
 import androidx.room.compiler.codegen.XTypeName
-import androidx.room.compiler.codegen.XTypeSpec
 import androidx.room.compiler.processing.XType
 import androidx.room.compiler.processing.isInt
 import androidx.room.compiler.processing.isKotlinUnit
@@ -36,7 +35,7 @@
 /**
  * Class that knows how to generate a delete or update method body.
  */
-class DeleteOrUpdateMethodAdapter private constructor(private val returnType: XType) {
+class DeleteOrUpdateMethodAdapter private constructor(val returnType: XType) {
     companion object {
         fun create(returnType: XType): DeleteOrUpdateMethodAdapter? {
             if (isDeleteOrUpdateValid(returnType)) {
@@ -53,13 +52,69 @@
         }
     }
 
-    fun createDeleteOrUpdateMethodBody(
+    fun generateMethodBody(
+        scope: CodeGenScope,
         parameters: List<ShortcutQueryParameter>,
-        adapters: Map<String, Pair<XPropertySpec, XTypeSpec>>,
+        adapters: Map<String, Pair<XPropertySpec, Any>>,
+        connectionVar: String
+    ) {
+        scope.builder.apply {
+            val hasReturnValue = returnType.isNotVoid() &&
+                returnType.isNotVoidObject() &&
+                returnType.isNotKotlinUnit()
+            val resultVar = if (hasReturnValue) {
+                scope.getTmpVar("_result")
+            } else {
+                null
+            }
+            if (resultVar != null) {
+                addLocalVariable(
+                    name = resultVar,
+                    typeName = XTypeName.PRIMITIVE_INT,
+                    isMutable = true,
+                    assignExpr = XCodeBlock.of(language, "0")
+                )
+            }
+            parameters.forEach { param ->
+                val adapter = adapters.getValue(param.name).first
+                addStatement(
+                    "%L%L.%L(%L, %L)",
+                    if (resultVar == null) "" else "$resultVar += ",
+                    adapter.name,
+                    param.handleMethodName,
+                    connectionVar,
+                    param.name
+                )
+            }
+            when (scope.language) {
+                CodeLanguage.KOTLIN ->
+                    if (resultVar != null) {
+                        addStatement("%L", resultVar)
+                    } else if (returnType.isVoidObject()) {
+                        addStatement("null")
+                    }
+                CodeLanguage.JAVA ->
+                    if (resultVar != null) {
+                        addStatement("return %L", resultVar)
+                    } else if (returnType.isVoidObject() || returnType.isVoid()) {
+                        addStatement("return null")
+                    } else {
+                        addStatement("return %T.INSTANCE", KotlinTypeNames.UNIT)
+                    }
+            }
+        }
+    }
+
+    fun generateMethodBodyCompat(
+        parameters: List<ShortcutQueryParameter>,
+        adapters: Map<String, Pair<XPropertySpec, Any>>,
         dbProperty: XPropertySpec,
         scope: CodeGenScope
     ) {
-        val resultVar = if (hasResultValue(returnType)) {
+        val resultVar = if (returnType.isNotVoid() &&
+            returnType.isNotVoidObject() &&
+            returnType.isNotKotlinUnit()
+            ) {
             scope.getTmpVar("_total")
         } else {
             null
@@ -81,7 +136,7 @@
                         "%L%L.%L(%L)",
                         if (resultVar == null) "" else "$resultVar += ",
                         adapter.name,
-                        param.handleMethodName(),
+                        param.handleMethodName,
                         param.name
                     )
                 }
@@ -100,10 +155,4 @@
             endControlFlow()
         }
     }
-
-    private fun hasResultValue(returnType: XType): Boolean {
-        return returnType.isNotVoid() &&
-            returnType.isNotVoidObject() &&
-            returnType.isNotKotlinUnit()
-    }
 }
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/result/InsertOrUpsertMethodAdapter.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/result/InsertOrUpsertMethodAdapter.kt
index 4c5f47e..93a44fd 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/result/InsertOrUpsertMethodAdapter.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/result/InsertOrUpsertMethodAdapter.kt
@@ -29,6 +29,9 @@
 import androidx.room.ext.CommonTypeNames
 import androidx.room.ext.KotlinTypeNames
 import androidx.room.ext.isList
+import androidx.room.ext.isNotKotlinUnit
+import androidx.room.ext.isNotVoid
+import androidx.room.ext.isNotVoidObject
 import androidx.room.processor.Context
 import androidx.room.processor.ProcessorErrors
 import androidx.room.solver.CodeGenScope
@@ -37,6 +40,8 @@
 class InsertOrUpsertMethodAdapter private constructor(
     private val methodInfo: MethodInfo
 ) {
+    internal val returnType = methodInfo.returnType
+
     companion object {
         fun createInsert(
             context: Context,
@@ -172,7 +177,89 @@
         }
     }
 
-    fun createMethodBody(
+    fun generateMethodBody(
+        scope: CodeGenScope,
+        connectionVar: String,
+        parameters: List<ShortcutQueryParameter>,
+        adapters: Map<String, Pair<XPropertySpec, Any>>
+    ) {
+        scope.builder.apply {
+            val hasReturnValue = returnType.isNotVoid() &&
+                returnType.isNotVoidObject() &&
+                returnType.isNotKotlinUnit()
+            val resultVar = if (hasReturnValue) {
+                scope.getTmpVar("_result")
+            } else {
+                null
+            }
+            parameters.forEach { param ->
+                val upsertAdapter = adapters.getValue(param.name).first
+                val resultFormat = XCodeBlock.of(
+                    language,
+                    "%L.%L(%L, %L)",
+                    upsertAdapter.name,
+                    methodInfo.methodName,
+                    connectionVar,
+                    param.name
+                ).let {
+                    if (
+                        scope.language == CodeLanguage.KOTLIN &&
+                        methodInfo.returnInfo == ReturnInfo.ID_ARRAY_BOX &&
+                        methodInfo.returnType.asTypeName() == methodInfo.returnInfo.typeName
+                    ) {
+                        XCodeBlock.ofCast(
+                            language = language,
+                            typeName = methodInfo.returnInfo.typeName,
+                            expressionBlock = it
+                        )
+                    } else {
+                        it
+                    }
+                }
+                when (scope.language) {
+                    CodeLanguage.JAVA -> {
+                        when (methodInfo.returnInfo) {
+                            ReturnInfo.VOID, ReturnInfo.VOID_OBJECT -> {
+                                if (param == parameters.last()) {
+                                    addStatement("%L", resultFormat)
+                                    addStatement("return null")
+                                } else {
+                                    addStatement("%L", resultFormat)
+                                }
+                            }
+                            ReturnInfo.UNIT -> {
+                                if (param == parameters.last()) {
+                                    addStatement("%L", resultFormat)
+                                    addStatement("return %T.INSTANCE", KotlinTypeNames.UNIT)
+                                } else {
+                                    addStatement("%L", resultFormat)
+                                }
+                            }
+                            else -> addStatement("return %L", resultFormat)
+                        }
+                    }
+                    CodeLanguage.KOTLIN -> {
+                        if (resultVar != null) {
+                            // if it has more than 1 parameter, we would've already printed the error
+                            // so we don't care about re-declaring the variable here
+                            addLocalVariable(
+                                name = resultVar,
+                                typeName = returnType.asTypeName(),
+                                assignExpr = resultFormat
+                            )
+                        } else {
+                            addStatement("%L", resultFormat)
+                        }
+                    }
+                }
+            }
+            if (scope.language == CodeLanguage.KOTLIN && resultVar != null) {
+                addStatement("%L", resultVar)
+            }
+        }
+    }
+
+    fun generateMethodBodyCompat(
         parameters: List<ShortcutQueryParameter>,
         adapters: Map<String, Pair<XPropertySpec, Any>>,
         dbProperty: XPropertySpec,
@@ -192,13 +279,13 @@
 
             beginControlFlow("try").apply {
                 parameters.forEach { param ->
-                    val upsertionAdapter = adapters.getValue(param.name).first
+                    val upsertAdapter = adapters.getValue(param.name).first
                     // We want to keep the e.g. Array<out Long> generic type function signature, so
                     // need to do a cast.
                     val resultFormat = XCodeBlock.of(
                         language,
                         "%L.%L(%L)",
-                        upsertionAdapter.name,
+                        upsertAdapter.name,
                         methodName,
                         param.name
                     ).let {
@@ -222,7 +309,7 @@
                         // so we don't care about re-declaring the variable here
                         addLocalVariable(
                             name = resultVar,
-                            typeName = methodInfo.returnType.asTypeName(),
+                            typeName = returnType.asTypeName(),
                             assignExpr = resultFormat
                         )
                     } else {
@@ -256,14 +343,14 @@
         returnInfo: ReturnInfo,
         returnType: XType
     ) : MethodInfo(returnInfo, returnType) {
-        override val methodName = "insert" + returnInfo.methodSuffix
+        override val methodName: String = "insert${returnInfo.methodSuffix}"
     }
 
     class UpsertMethodInfo(
         returnInfo: ReturnInfo,
         returnType: XType
     ) : MethodInfo(returnInfo, returnType) {
-        override val methodName = "upsert" + returnInfo.methodSuffix
+        override val methodName: String = "upsert${returnInfo.methodSuffix}"
     }
 
     enum class ReturnInfo(
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/vo/ShortcutQueryParameter.kt b/room/room-compiler/src/main/kotlin/androidx/room/vo/ShortcutQueryParameter.kt
index ae942bd..1615f86 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/vo/ShortcutQueryParameter.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/vo/ShortcutQueryParameter.kt
@@ -32,11 +32,9 @@
     /**
      * Method name in entity insertion or update adapter.
      */
-    fun handleMethodName(): String {
-        return if (isMultiple) {
-            "handleMultiple"
-        } else {
-            "handle"
-        }
+    val handleMethodName = if (isMultiple) {
+        "handleMultiple"
+    } else {
+        "handle"
     }
 }
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/writer/DaoWriter.kt b/room/room-compiler/src/main/kotlin/androidx/room/writer/DaoWriter.kt
index eb980f0..7a610ad 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/writer/DaoWriter.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/writer/DaoWriter.kt
@@ -34,9 +34,12 @@
 import androidx.room.ext.RoomMemberNames
 import androidx.room.ext.RoomTypeNames
 import androidx.room.ext.RoomTypeNames.DELETE_OR_UPDATE_ADAPTER
+import androidx.room.ext.RoomTypeNames.DELETE_OR_UPDATE_ADAPTER_COMPAT
 import androidx.room.ext.RoomTypeNames.INSERT_ADAPTER
+import androidx.room.ext.RoomTypeNames.INSERT_ADAPTER_COMPAT
 import androidx.room.ext.RoomTypeNames.ROOM_DB
 import androidx.room.ext.RoomTypeNames.UPSERT_ADAPTER
+import androidx.room.ext.RoomTypeNames.UPSERT_ADAPTER_COMPAT
 import androidx.room.ext.SupportDbTypeNames
 import androidx.room.processor.OnConflictProcessor
 import androidx.room.solver.CodeGenScope
@@ -352,11 +355,13 @@
             .map { insertMethod ->
                 val onConflict = OnConflictProcessor.onConflictText(insertMethod.onConflict)
                 val entities = insertMethod.entities
+                val useDriverApi = insertMethod.methodBinder?.isMigratedToDriver() == true
 
                 val fields = entities.mapValues {
-                    val spec = getOrCreateProperty(InsertMethodProperty(it.value, onConflict))
+                    val spec = getOrCreateProperty(
+                        InsertMethodProperty(it.value, onConflict, useDriverApi))
                     val impl = EntityInsertAdapterWriter.create(it.value, onConflict)
-                        .createAnonymous(this@DaoWriter, dbProperty)
+                        .createAnonymous(this@DaoWriter, dbProperty, useDriverApi)
                     spec to impl
                 }
                 val methodImpl = overrideWithoutAnnotations(
@@ -376,13 +381,23 @@
         if (insertAdapters.isEmpty() || method.methodBinder == null) {
             return XCodeBlock.builder(codeLanguage).build()
         }
-        val scope = CodeGenScope(this)
-        method.methodBinder.convertAndReturn(
-            parameters = method.parameters,
-            adapters = insertAdapters,
-            dbProperty = dbProperty,
-            scope = scope
-        )
+        val useDriverApi = method.methodBinder.isMigratedToDriver()
+        val scope = CodeGenScope(writer = this, useDriverApi = useDriverApi)
+        if (useDriverApi) {
+            method.methodBinder.convertAndReturn(
+                parameters = method.parameters,
+                adapters = insertAdapters,
+                dbProperty = dbProperty,
+                scope = scope
+            )
+        } else {
+            method.methodBinder.convertAndReturnCompat(
+                parameters = method.parameters,
+                adapters = insertAdapters,
+                dbProperty = dbProperty,
+                scope = scope
+            )
+        }
         return scope.generate()
     }
 
@@ -390,9 +405,10 @@
      * Creates EntityUpdateAdapter for each delete method.
      */
     private fun createDeleteMethods(): List<PreparedStmtQuery> {
-        return createShortcutMethods(dao.deleteMethods, "delete") { _, entity ->
+        return createShortcutMethods(dao.deleteMethods, "delete") { deleteMethod, entity ->
+            val useDriverApi = deleteMethod.methodBinder?.isMigratedToDriver() == true
             EntityDeleteAdapterWriter.create(entity)
-                .createAnonymous(this@DaoWriter, dbProperty.name)
+                .createAnonymous(this@DaoWriter, dbProperty.name, useDriverApi)
         }
     }
 
@@ -402,8 +418,9 @@
     private fun createUpdateMethods(): List<PreparedStmtQuery> {
         return createShortcutMethods(dao.updateMethods, "update") { update, entity ->
             val onConflict = OnConflictProcessor.onConflictText(update.onConflictStrategy)
+            val useDriverApi = update.methodBinder?.isMigratedToDriver() == true
             EntityUpdateAdapterWriter.create(entity, onConflict)
-                .createAnonymous(this@DaoWriter, dbProperty.name)
+                .createAnonymous(this@DaoWriter, dbProperty.name, useDriverApi)
         }
     }
 
@@ -422,9 +439,15 @@
                 } else {
                     ""
                 }
+                val useDriverApi = method.methodBinder?.isMigratedToDriver() == true
                 val fields = entities.mapValues {
                     val spec = getOrCreateProperty(
-                        DeleteOrUpdateAdapterProperty(it.value, methodPrefix, onConflict)
+                        DeleteOrUpdateAdapterProperty(
+                            it.value,
+                            methodPrefix,
+                            onConflict,
+                            useDriverApi
+                        )
                     )
                     val impl = implCallback(method, it.value)
                     spec to impl
@@ -444,14 +467,23 @@
         if (adapters.isEmpty() || method.methodBinder == null) {
             return XCodeBlock.builder(codeLanguage).build()
         }
-        val scope = CodeGenScope(this)
-
-        method.methodBinder.convertAndReturn(
-            parameters = method.parameters,
-            adapters = adapters,
-            dbProperty = dbProperty,
-            scope = scope
-        )
+        val useDriverApi = method.methodBinder.isMigratedToDriver()
+        val scope = CodeGenScope(writer = this, useDriverApi = useDriverApi)
+        if (useDriverApi) {
+            method.methodBinder.convertAndReturn(
+                parameters = method.parameters,
+                adapters = adapters,
+                dbProperty = dbProperty,
+                scope = scope
+            )
+        } else {
+            method.methodBinder.convertAndReturnCompat(
+                parameters = method.parameters,
+                adapters = adapters,
+                dbProperty = dbProperty,
+                scope = scope
+            )
+        }
         return scope.generate()
     }
 
@@ -464,9 +496,10 @@
             .map { upsertMethod ->
                 val entities = upsertMethod.entities
                 val fields = entities.mapValues {
-                    val spec = getOrCreateProperty(UpsertAdapterProperty(it.value))
+                    val useDriverApi = upsertMethod.methodBinder?.isMigratedToDriver() == true
+                    val spec = getOrCreateProperty(UpsertAdapterProperty(it.value, useDriverApi))
                     val impl = EntityUpsertAdapterWriter.create(it.value)
-                        .createConcrete(it.value, this@DaoWriter, dbProperty)
+                        .createConcrete(it.value, this@DaoWriter, dbProperty, useDriverApi)
                     spec to impl
                 }
                 val methodImpl = overrideWithoutAnnotations(
@@ -486,14 +519,24 @@
         if (upsertAdapters.isEmpty() || method.methodBinder == null) {
             return XCodeBlock.builder(codeLanguage).build()
         }
-        val scope = CodeGenScope(this)
+        val useDriverApi = method.methodBinder.isMigratedToDriver()
+        val scope = CodeGenScope(writer = this, useDriverApi = useDriverApi)
 
-        method.methodBinder.convertAndReturn(
-            parameters = method.parameters,
-            adapters = upsertAdapters,
-            dbProperty = dbProperty,
-            scope = scope
-        )
+        if (useDriverApi) {
+            method.methodBinder.convertAndReturn(
+                parameters = method.parameters,
+                adapters = upsertAdapters,
+                dbProperty = dbProperty,
+                scope = scope
+            )
+        } else {
+            method.methodBinder.convertAndReturnCompat(
+                parameters = method.parameters,
+                adapters = upsertAdapters,
+                dbProperty = dbProperty,
+                scope = scope
+            )
+        }
         return scope.generate()
     }
 
@@ -640,13 +683,23 @@
 
     private class InsertMethodProperty(
         val shortcutEntity: ShortcutEntity,
-        val onConflictText: String
+        val onConflictText: String,
+        val useDriverApi: Boolean
     ) : SharedPropertySpec(
-        baseName = "insertAdapterOf${shortcutEntityFieldNamePart(shortcutEntity)}",
-        type = INSERT_ADAPTER.parametrizedBy(shortcutEntity.pojo.typeName)
+        baseName = if (useDriverApi) {
+            "insertAdapterOf${shortcutEntityFieldNamePart(shortcutEntity)}"
+        } else {
+            "insertionAdapterOf${shortcutEntityFieldNamePart(shortcutEntity)}"
+               },
+        type = if (useDriverApi) {
+            INSERT_ADAPTER
+        } else {
+            INSERT_ADAPTER_COMPAT
+        }.parametrizedBy(shortcutEntity.pojo.typeName)
     ) {
         override fun getUniqueKey(): String {
-            return "${shortcutEntity.pojo.typeName}-${shortcutEntity.entityTypeName}$onConflictText"
+            return "${shortcutEntity.pojo.typeName}-${shortcutEntity.entityTypeName}" +
+                "$onConflictText-$useDriverApi"
         }
 
         override fun prepare(writer: TypeWriter, builder: XPropertySpec.Builder) {
@@ -656,28 +709,46 @@
     class DeleteOrUpdateAdapterProperty(
         val shortcutEntity: ShortcutEntity,
         val methodPrefix: String,
-        val onConflictText: String
+        val onConflictText: String,
+        val useDriverApi: Boolean
     ) : SharedPropertySpec(
-        baseName = "${methodPrefix}AdapterOf${shortcutEntityFieldNamePart(shortcutEntity)}",
-        type = DELETE_OR_UPDATE_ADAPTER.parametrizedBy(shortcutEntity.pojo.typeName)
+        baseName = if (useDriverApi) {
+            "${methodPrefix}AdapterOf${shortcutEntityFieldNamePart(shortcutEntity)}"
+        } else {
+            "${methodPrefix}CompatAdapterOf${shortcutEntityFieldNamePart(shortcutEntity)}"
+               },
+        type = if (useDriverApi) {
+            DELETE_OR_UPDATE_ADAPTER.parametrizedBy(shortcutEntity.pojo.typeName)
+        } else {
+            DELETE_OR_UPDATE_ADAPTER_COMPAT.parametrizedBy(shortcutEntity.pojo.typeName)
+        }
     ) {
         override fun prepare(writer: TypeWriter, builder: XPropertySpec.Builder) {
         }
 
         override fun getUniqueKey(): String {
             return "${shortcutEntity.pojo.typeName}-${shortcutEntity.entityTypeName}" +
-                "$methodPrefix$onConflictText"
+                "$methodPrefix$onConflictText-$useDriverApi"
         }
     }
 
     class UpsertAdapterProperty(
-        val shortcutEntity: ShortcutEntity
+        val shortcutEntity: ShortcutEntity,
+        val useDriverApi: Boolean
     ) : SharedPropertySpec(
-        baseName = "upsertAdapterOf${shortcutEntityFieldNamePart(shortcutEntity)}",
-        type = UPSERT_ADAPTER.parametrizedBy(shortcutEntity.pojo.typeName)
+        baseName = if (useDriverApi) {
+            "upsertAdapterOf${shortcutEntityFieldNamePart(shortcutEntity)}"
+        } else {
+            "upsertionAdapterOf${shortcutEntityFieldNamePart(shortcutEntity)}"
+               },
+        type = if (useDriverApi) {
+            UPSERT_ADAPTER.parametrizedBy(shortcutEntity.pojo.typeName)
+        } else {
+            UPSERT_ADAPTER_COMPAT.parametrizedBy(shortcutEntity.pojo.typeName)
+        }
     ) {
         override fun getUniqueKey(): String {
-            return "${shortcutEntity.pojo.typeName}-${shortcutEntity.entityTypeName}"
+            return "${shortcutEntity.pojo.typeName}-${shortcutEntity.entityTypeName}-$useDriverApi"
         }
 
         override fun prepare(writer: TypeWriter, builder: XPropertySpec.Builder) {
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/writer/EntityDeleteAdapterWriter.kt b/room/room-compiler/src/main/kotlin/androidx/room/writer/EntityDeleteAdapterWriter.kt
index 33eacfa..e74e47f 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/writer/EntityDeleteAdapterWriter.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/writer/EntityDeleteAdapterWriter.kt
@@ -23,6 +23,7 @@
 import androidx.room.compiler.codegen.XTypeSpec
 import androidx.room.ext.CommonTypeNames
 import androidx.room.ext.RoomTypeNames
+import androidx.room.ext.SQLiteDriverTypeNames
 import androidx.room.ext.SupportDbTypeNames
 import androidx.room.solver.CodeGenScope
 import androidx.room.vo.FieldWithIndex
@@ -51,11 +52,23 @@
         }
     }
 
-    fun createAnonymous(typeWriter: TypeWriter, dbParam: String): XTypeSpec {
-        return XTypeSpec.anonymousClassBuilder(
-            typeWriter.codeLanguage, "%L", dbParam
-        ).apply {
-            superclass(RoomTypeNames.DELETE_OR_UPDATE_ADAPTER.parametrizedBy(pojoTypeName))
+    fun createAnonymous(typeWriter: TypeWriter, dbParam: String, useDriverApi: Boolean): XTypeSpec {
+        return if (useDriverApi) {
+            XTypeSpec.anonymousClassBuilder(
+                typeWriter.codeLanguage
+            )
+        } else {
+            XTypeSpec.anonymousClassBuilder(
+                typeWriter.codeLanguage, "%L", dbParam
+            )
+        }.apply {
+            superclass(
+                if (useDriverApi) {
+                    RoomTypeNames.DELETE_OR_UPDATE_ADAPTER
+                } else {
+                    RoomTypeNames.DELETE_OR_UPDATE_ADAPTER_COMPAT
+                }.parametrizedBy(pojoTypeName)
+            )
             addFunction(
                 XFunSpec.builder(
                     language = language,
@@ -77,11 +90,18 @@
                     isOverride = true
                 ).apply {
                     val stmtParam = "statement"
-                    addParameter(SupportDbTypeNames.SQLITE_STMT, stmtParam)
+                    addParameter(
+                        if (useDriverApi) {
+                            SQLiteDriverTypeNames.STATEMENT
+                        } else {
+                            SupportDbTypeNames.SQLITE_STMT
+                        },
+                        stmtParam
+                    )
                     val entityParam = "entity"
                     addParameter(pojoTypeName, entityParam)
                     val mapped = FieldWithIndex.byOrder(fields)
-                    val bindScope = CodeGenScope(typeWriter)
+                    val bindScope = CodeGenScope(writer = typeWriter, useDriverApi = useDriverApi)
                     FieldReadWriteWriter.bindToStatement(
                         ownerVar = entityParam,
                         stmtParamVar = stmtParam,
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/writer/EntityInsertAdapterWriter.kt b/room/room-compiler/src/main/kotlin/androidx/room/writer/EntityInsertAdapterWriter.kt
index 77c59b9..14946ab 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/writer/EntityInsertAdapterWriter.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/writer/EntityInsertAdapterWriter.kt
@@ -25,6 +25,7 @@
 import androidx.room.compiler.processing.XNullability
 import androidx.room.ext.CommonTypeNames
 import androidx.room.ext.RoomTypeNames
+import androidx.room.ext.SQLiteDriverTypeNames
 import androidx.room.ext.SupportDbTypeNames
 import androidx.room.solver.CodeGenScope
 import androidx.room.vo.FieldWithIndex
@@ -64,12 +65,26 @@
         }
     }
 
-    fun createAnonymous(typeWriter: TypeWriter, dbProperty: XPropertySpec): XTypeSpec {
-        return XTypeSpec.anonymousClassBuilder(
-            typeWriter.codeLanguage, "%N", dbProperty
-        ).apply {
+    fun createAnonymous(
+        typeWriter: TypeWriter,
+        dbProperty: XPropertySpec,
+        useDriverApi: Boolean
+    ): XTypeSpec {
+        return if (useDriverApi) {
+            XTypeSpec.anonymousClassBuilder(
+                typeWriter.codeLanguage
+            )
+        } else {
+            XTypeSpec.anonymousClassBuilder(
+                typeWriter.codeLanguage, "%N", dbProperty
+            )
+        }.apply {
             superclass(
-                RoomTypeNames.INSERT_ADAPTER.parametrizedBy(pojo.typeName)
+                if (useDriverApi) {
+                    RoomTypeNames.INSERT_ADAPTER
+                } else {
+                    RoomTypeNames.INSERT_ADAPTER_COMPAT
+                }.parametrizedBy(pojo.typeName)
             )
             addFunction(
                 XFunSpec.builder(
@@ -110,11 +125,21 @@
                 ).apply {
                     returns(XTypeName.UNIT_VOID)
                     val stmtParam = "statement"
-                    addParameter(SupportDbTypeNames.SQLITE_STMT, stmtParam)
+                    addParameter(
+                        if (useDriverApi) {
+                            SQLiteDriverTypeNames.STATEMENT
+                        } else {
+                            SupportDbTypeNames.SQLITE_STMT
+                        },
+                        stmtParam
+                    )
                     val entityParam = "entity"
                     addParameter(pojo.typeName, entityParam)
                     val mapped = FieldWithIndex.byOrder(pojo.fields)
-                    val bindScope = CodeGenScope(typeWriter)
+                    val bindScope = CodeGenScope(
+                        writer = typeWriter,
+                        useDriverApi = useDriverApi
+                    )
                     FieldReadWriteWriter.bindToStatement(
                         ownerVar = entityParam,
                         stmtParamVar = stmtParam,
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/writer/EntityUpdateAdapterWriter.kt b/room/room-compiler/src/main/kotlin/androidx/room/writer/EntityUpdateAdapterWriter.kt
index f06592d..6245ddd 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/writer/EntityUpdateAdapterWriter.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/writer/EntityUpdateAdapterWriter.kt
@@ -22,6 +22,7 @@
 import androidx.room.compiler.codegen.XTypeSpec
 import androidx.room.ext.CommonTypeNames
 import androidx.room.ext.RoomTypeNames
+import androidx.room.ext.SQLiteDriverTypeNames
 import androidx.room.ext.SupportDbTypeNames
 import androidx.room.solver.CodeGenScope
 import androidx.room.vo.FieldWithIndex
@@ -46,9 +47,21 @@
             )
     }
 
-    fun createAnonymous(typeWriter: TypeWriter, dbParam: String): XTypeSpec {
-        return XTypeSpec.anonymousClassBuilder(typeWriter.codeLanguage, "%L", dbParam).apply {
-            superclass(RoomTypeNames.DELETE_OR_UPDATE_ADAPTER.parametrizedBy(pojo.typeName))
+    fun createAnonymous(typeWriter: TypeWriter, dbParam: String, useDriverApi: Boolean): XTypeSpec {
+        return if (useDriverApi) {
+            XTypeSpec.anonymousClassBuilder(typeWriter.codeLanguage)
+        } else {
+            XTypeSpec.anonymousClassBuilder(typeWriter.codeLanguage, "%L", dbParam)
+        }.apply {
+            superclass(
+                if (useDriverApi) {
+                    RoomTypeNames.DELETE_OR_UPDATE_ADAPTER
+                        .parametrizedBy(pojo.typeName)
+                } else {
+                    RoomTypeNames.DELETE_OR_UPDATE_ADAPTER_COMPAT
+                        .parametrizedBy(pojo.typeName)
+                }
+            )
             addFunction(
                 XFunSpec.builder(
                     language = language,
@@ -84,11 +97,18 @@
                     isOverride = true
                 ).apply {
                     val stmtParam = "statement"
-                    addParameter(SupportDbTypeNames.SQLITE_STMT, stmtParam)
+                    addParameter(
+                        if (useDriverApi) {
+                            SQLiteDriverTypeNames.STATEMENT
+                        } else {
+                            SupportDbTypeNames.SQLITE_STMT
+                        },
+                        stmtParam
+                    )
                     val entityParam = "entity"
                     addParameter(pojo.typeName, entityParam)
                     val mappedField = FieldWithIndex.byOrder(pojo.fields)
-                    val bindScope = CodeGenScope(typeWriter)
+                    val bindScope = CodeGenScope(writer = typeWriter, useDriverApi = useDriverApi)
                     FieldReadWriteWriter.bindToStatement(
                         ownerVar = entityParam,
                         stmtParamVar = stmtParam,
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/writer/EntityUpsertAdapterWriter.kt b/room/room-compiler/src/main/kotlin/androidx/room/writer/EntityUpsertAdapterWriter.kt
index 4c5609c..db6fcb1 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/writer/EntityUpsertAdapterWriter.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/writer/EntityUpsertAdapterWriter.kt
@@ -38,13 +38,18 @@
     fun createConcrete(
         entity: ShortcutEntity,
         typeWriter: TypeWriter,
-        dbProperty: XPropertySpec
+        dbProperty: XPropertySpec,
+        useDriverApi: Boolean
     ): XCodeBlock {
-        val upsertAdapter = RoomTypeNames.UPSERT_ADAPTER.parametrizedBy(pojo.typeName)
+        val upsertAdapter = if (useDriverApi) {
+            RoomTypeNames.UPSERT_ADAPTER
+        } else {
+            RoomTypeNames.UPSERT_ADAPTER_COMPAT
+        }.parametrizedBy(pojo.typeName)
         val insertHelper = EntityInsertAdapterWriter.create(entity, "")
-            .createAnonymous(typeWriter, dbProperty)
+            .createAnonymous(typeWriter, dbProperty, useDriverApi)
         val updateHelper = EntityUpdateAdapterWriter.create(entity, "")
-            .createAnonymous(typeWriter, dbProperty.name)
+            .createAnonymous(typeWriter, dbProperty.name, useDriverApi)
         return XCodeBlock.ofNewInstance(
             language = typeWriter.codeLanguage,
             typeName = upsertAdapter,
diff --git a/room/room-compiler/src/test/test-data/daoWriter/output/javac/DeletionDao.java b/room/room-compiler/src/test/test-data/daoWriter/output/javac/DeletionDao.java
index ffacaa6..e53b33d 100644
--- a/room/room-compiler/src/test/test-data/daoWriter/output/javac/DeletionDao.java
+++ b/room/room-compiler/src/test/test-data/daoWriter/output/javac/DeletionDao.java
@@ -2,6 +2,7 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.room.EntityDeleteOrUpdateAdapter;
 import androidx.room.EntityDeletionOrUpdateAdapter;
 import androidx.room.RoomDatabase;
 import androidx.room.util.DBUtil;
@@ -32,15 +33,29 @@
 public final class DeletionDao_Impl implements DeletionDao {
     private final RoomDatabase __db;
 
-    private final EntityDeletionOrUpdateAdapter<User> __deleteAdapterOfUser;
+    private final EntityDeleteOrUpdateAdapter<User> __deleteAdapterOfUser;
 
-    private final EntityDeletionOrUpdateAdapter<MultiPKeyEntity> __deleteAdapterOfMultiPKeyEntity;
+    private final EntityDeletionOrUpdateAdapter<User> __deleteCompatAdapterOfUser;
 
-    private final EntityDeletionOrUpdateAdapter<Book> __deleteAdapterOfBook;
+    private final EntityDeleteOrUpdateAdapter<MultiPKeyEntity> __deleteAdapterOfMultiPKeyEntity;
+
+    private final EntityDeleteOrUpdateAdapter<Book> __deleteAdapterOfBook;
 
     public DeletionDao_Impl(@NonNull final RoomDatabase __db) {
         this.__db = __db;
-        this.__deleteAdapterOfUser = new EntityDeletionOrUpdateAdapter<User>(__db) {
+        this.__deleteAdapterOfUser = new EntityDeleteOrUpdateAdapter<User>() {
+            @Override
+            @NonNull
+            protected String createQuery() {
+                return "DELETE FROM `User` WHERE `uid` = ?";
+            }
+
+            @Override
+            protected void bind(@NonNull final SQLiteStatement statement, final User entity) {
+                statement.bindLong(1, entity.uid);
+            }
+        };
+        this.__deleteCompatAdapterOfUser = new EntityDeletionOrUpdateAdapter<User>(__db) {
             @Override
             @NonNull
             protected String createQuery() {
@@ -52,7 +67,7 @@
                 statement.bindLong(1, entity.uid);
             }
         };
-        this.__deleteAdapterOfMultiPKeyEntity = new EntityDeletionOrUpdateAdapter<MultiPKeyEntity>(__db) {
+        this.__deleteAdapterOfMultiPKeyEntity = new EntityDeleteOrUpdateAdapter<MultiPKeyEntity>() {
             @Override
             @NonNull
             protected String createQuery() {
@@ -60,21 +75,20 @@
             }
 
             @Override
-            protected void bind(@NonNull final SupportSQLiteStatement statement,
-                    final MultiPKeyEntity entity) {
+            protected void bind(@NonNull final SQLiteStatement statement, final MultiPKeyEntity entity) {
                 if (entity.name == null) {
                     statement.bindNull(1);
                 } else {
-                    statement.bindString(1, entity.name);
+                    statement.bindText(1, entity.name);
                 }
                 if (entity.lastName == null) {
                     statement.bindNull(2);
                 } else {
-                    statement.bindString(2, entity.lastName);
+                    statement.bindText(2, entity.lastName);
                 }
             }
         };
-        this.__deleteAdapterOfBook = new EntityDeletionOrUpdateAdapter<Book>(__db) {
+        this.__deleteAdapterOfBook = new EntityDeleteOrUpdateAdapter<Book>() {
             @Override
             @NonNull
             protected String createQuery() {
@@ -82,7 +96,7 @@
             }
 
             @Override
-            protected void bind(@NonNull final SupportSQLiteStatement statement, final Book entity) {
+            protected void bind(@NonNull final SQLiteStatement statement, final Book entity) {
                 statement.bindLong(1, entity.bookId);
             }
         };
@@ -90,96 +104,92 @@
 
     @Override
     public void deleteUser(final User user) {
-        __db.assertNotSuspendingTransaction();
-        __db.beginTransaction();
-        try {
-            __deleteAdapterOfUser.handle(user);
-            __db.setTransactionSuccessful();
-        } finally {
-            __db.endTransaction();
-        }
+        DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
+            @Override
+            @NonNull
+            public Void invoke(@NonNull final SQLiteConnection _connection) {
+                __deleteAdapterOfUser.handle(_connection, user);
+                return null;
+            }
+        });
     }
 
     @Override
     public void deleteUsers(final User user1, final List<User> others) {
-        __db.assertNotSuspendingTransaction();
-        __db.beginTransaction();
-        try {
-            __deleteAdapterOfUser.handle(user1);
-            __deleteAdapterOfUser.handleMultiple(others);
-            __db.setTransactionSuccessful();
-        } finally {
-            __db.endTransaction();
-        }
+        DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
+            @Override
+            @NonNull
+            public Void invoke(@NonNull final SQLiteConnection _connection) {
+                __deleteAdapterOfUser.handle(_connection, user1);
+                __deleteAdapterOfUser.handleMultiple(_connection, others);
+                return null;
+            }
+        });
     }
 
     @Override
     public void deleteArrayOfUsers(final User[] users) {
-        __db.assertNotSuspendingTransaction();
-        __db.beginTransaction();
-        try {
-            __deleteAdapterOfUser.handleMultiple(users);
-            __db.setTransactionSuccessful();
-        } finally {
-            __db.endTransaction();
-        }
+        DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
+            @Override
+            @NonNull
+            public Void invoke(@NonNull final SQLiteConnection _connection) {
+                __deleteAdapterOfUser.handleMultiple(_connection, users);
+                return null;
+            }
+        });
     }
 
     @Override
     public Integer deleteUserAndReturnCountObject(final User user) {
-        __db.assertNotSuspendingTransaction();
-        int _total = 0;
-        __db.beginTransaction();
-        try {
-            _total += __deleteAdapterOfUser.handle(user);
-            __db.setTransactionSuccessful();
-            return _total;
-        } finally {
-            __db.endTransaction();
-        }
+        return DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Integer>() {
+            @Override
+            @NonNull
+            public Integer invoke(@NonNull final SQLiteConnection _connection) {
+                int _result = 0;
+                _result += __deleteAdapterOfUser.handle(_connection, user);
+                return _result;
+            }
+        });
     }
 
     @Override
     public int deleteUserAndReturnCount(final User user) {
-        __db.assertNotSuspendingTransaction();
-        int _total = 0;
-        __db.beginTransaction();
-        try {
-            _total += __deleteAdapterOfUser.handle(user);
-            __db.setTransactionSuccessful();
-            return _total;
-        } finally {
-            __db.endTransaction();
-        }
+        return DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Integer>() {
+            @Override
+            @NonNull
+            public Integer invoke(@NonNull final SQLiteConnection _connection) {
+                int _result = 0;
+                _result += __deleteAdapterOfUser.handle(_connection, user);
+                return _result;
+            }
+        });
     }
 
     @Override
     public int deleteUserAndReturnCount(final User user1, final List<User> others) {
-        __db.assertNotSuspendingTransaction();
-        int _total = 0;
-        __db.beginTransaction();
-        try {
-            _total += __deleteAdapterOfUser.handle(user1);
-            _total += __deleteAdapterOfUser.handleMultiple(others);
-            __db.setTransactionSuccessful();
-            return _total;
-        } finally {
-            __db.endTransaction();
-        }
+        return DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Integer>() {
+            @Override
+            @NonNull
+            public Integer invoke(@NonNull final SQLiteConnection _connection) {
+                int _result = 0;
+                _result += __deleteAdapterOfUser.handle(_connection, user1);
+                _result += __deleteAdapterOfUser.handleMultiple(_connection, others);
+                return _result;
+            }
+        });
     }
 
     @Override
     public int deleteUserAndReturnCount(final User[] users) {
-        __db.assertNotSuspendingTransaction();
-        int _total = 0;
-        __db.beginTransaction();
-        try {
-            _total += __deleteAdapterOfUser.handleMultiple(users);
-            __db.setTransactionSuccessful();
-            return _total;
-        } finally {
-            __db.endTransaction();
-        }
+        return DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Integer>() {
+            @Override
+            @NonNull
+            public Integer invoke(@NonNull final SQLiteConnection _connection) {
+                int _result = 0;
+                _result += __deleteAdapterOfUser.handleMultiple(_connection, users);
+                return _result;
+            }
+        });
     }
 
     @Override
@@ -190,7 +200,7 @@
             public Void call() throws Exception {
                 __db.beginTransaction();
                 try {
-                    __deleteAdapterOfUser.handle(user);
+                    __deleteCompatAdapterOfUser.handle(user);
                     __db.setTransactionSuccessful();
                     return null;
                 } finally {
@@ -209,7 +219,7 @@
                 int _total = 0;
                 __db.beginTransaction();
                 try {
-                    _total += __deleteAdapterOfUser.handle(user);
+                    _total += __deleteCompatAdapterOfUser.handle(user);
                     __db.setTransactionSuccessful();
                     return _total;
                 } finally {
@@ -228,7 +238,7 @@
                 int _total = 0;
                 __db.beginTransaction();
                 try {
-                    _total += __deleteAdapterOfUser.handle(user);
+                    _total += __deleteCompatAdapterOfUser.handle(user);
                     __db.setTransactionSuccessful();
                     return _total;
                 } finally {
@@ -240,29 +250,28 @@
 
     @Override
     public int multiPKey(final MultiPKeyEntity entity) {
-        __db.assertNotSuspendingTransaction();
-        int _total = 0;
-        __db.beginTransaction();
-        try {
-            _total += __deleteAdapterOfMultiPKeyEntity.handle(entity);
-            __db.setTransactionSuccessful();
-            return _total;
-        } finally {
-            __db.endTransaction();
-        }
+        return DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Integer>() {
+            @Override
+            @NonNull
+            public Integer invoke(@NonNull final SQLiteConnection _connection) {
+                int _result = 0;
+                _result += __deleteAdapterOfMultiPKeyEntity.handle(_connection, entity);
+                return _result;
+            }
+        });
     }
 
     @Override
     public void deleteUserAndBook(final User user, final Book book) {
-        __db.assertNotSuspendingTransaction();
-        __db.beginTransaction();
-        try {
-            __deleteAdapterOfUser.handle(user);
-            __deleteAdapterOfBook.handle(book);
-            __db.setTransactionSuccessful();
-        } finally {
-            __db.endTransaction();
-        }
+        DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
+            @Override
+            @NonNull
+            public Void invoke(@NonNull final SQLiteConnection _connection) {
+                __deleteAdapterOfUser.handle(_connection, user);
+                __deleteAdapterOfBook.handle(_connection, book);
+                return null;
+            }
+        });
     }
 
     @Override
diff --git a/room/room-compiler/src/test/test-data/daoWriter/output/javac/UpdateDao.java b/room/room-compiler/src/test/test-data/daoWriter/output/javac/UpdateDao.java
index b2586f1..497d912 100644
--- a/room/room-compiler/src/test/test-data/daoWriter/output/javac/UpdateDao.java
+++ b/room/room-compiler/src/test/test-data/daoWriter/output/javac/UpdateDao.java
@@ -2,6 +2,7 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.room.EntityDeleteOrUpdateAdapter;
 import androidx.room.EntityDeletionOrUpdateAdapter;
 import androidx.room.RoomDatabase;
 import androidx.room.util.DBUtil;
@@ -30,17 +31,67 @@
 public final class UpdateDao_Impl implements UpdateDao {
     private final RoomDatabase __db;
 
-    private final EntityDeletionOrUpdateAdapter<User> __updateAdapterOfUser;
+    private final EntityDeleteOrUpdateAdapter<User> __updateAdapterOfUser;
 
-    private final EntityDeletionOrUpdateAdapter<User> __updateAdapterOfUser_1;
+    private final EntityDeleteOrUpdateAdapter<User> __updateAdapterOfUser_1;
 
-    private final EntityDeletionOrUpdateAdapter<MultiPKeyEntity> __updateAdapterOfMultiPKeyEntity;
+    private final EntityDeletionOrUpdateAdapter<User> __updateCompatAdapterOfUser;
 
-    private final EntityDeletionOrUpdateAdapter<Book> __updateAdapterOfBook;
+    private final EntityDeleteOrUpdateAdapter<MultiPKeyEntity> __updateAdapterOfMultiPKeyEntity;
+
+    private final EntityDeleteOrUpdateAdapter<Book> __updateAdapterOfBook;
 
     public UpdateDao_Impl(@NonNull final RoomDatabase __db) {
         this.__db = __db;
-        this.__updateAdapterOfUser = new EntityDeletionOrUpdateAdapter<User>(__db) {
+        this.__updateAdapterOfUser = new EntityDeleteOrUpdateAdapter<User>() {
+            @Override
+            @NonNull
+            protected String createQuery() {
+                return "UPDATE OR ABORT `User` SET `uid` = ?,`name` = ?,`lastName` = ?,`ageColumn` = ? WHERE `uid` = ?";
+            }
+
+            @Override
+            protected void bind(@NonNull final SQLiteStatement statement, final User entity) {
+                statement.bindLong(1, entity.uid);
+                if (entity.name == null) {
+                    statement.bindNull(2);
+                } else {
+                    statement.bindText(2, entity.name);
+                }
+                if (entity.getLastName() == null) {
+                    statement.bindNull(3);
+                } else {
+                    statement.bindText(3, entity.getLastName());
+                }
+                statement.bindLong(4, entity.age);
+                statement.bindLong(5, entity.uid);
+            }
+        };
+        this.__updateAdapterOfUser_1 = new EntityDeleteOrUpdateAdapter<User>() {
+            @Override
+            @NonNull
+            protected String createQuery() {
+                return "UPDATE `User` SET `uid` = ?,`name` = ?,`lastName` = ?,`ageColumn` = ? WHERE `uid` = ?";
+            }
+
+            @Override
+            protected void bind(@NonNull final SQLiteStatement statement, final User entity) {
+                statement.bindLong(1, entity.uid);
+                if (entity.name == null) {
+                    statement.bindNull(2);
+                } else {
+                    statement.bindText(2, entity.name);
+                }
+                if (entity.getLastName() == null) {
+                    statement.bindNull(3);
+                } else {
+                    statement.bindText(3, entity.getLastName());
+                }
+                statement.bindLong(4, entity.age);
+                statement.bindLong(5, entity.uid);
+            }
+        };
+        this.__updateCompatAdapterOfUser = new EntityDeletionOrUpdateAdapter<User>(__db) {
             @Override
             @NonNull
             protected String createQuery() {
@@ -64,31 +115,7 @@
                 statement.bindLong(5, entity.uid);
             }
         };
-        this.__updateAdapterOfUser_1 = new EntityDeletionOrUpdateAdapter<User>(__db) {
-            @Override
-            @NonNull
-            protected String createQuery() {
-                return "UPDATE `User` SET `uid` = ?,`name` = ?,`lastName` = ?,`ageColumn` = ? WHERE `uid` = ?";
-            }
-
-            @Override
-            protected void bind(@NonNull final SupportSQLiteStatement statement, final User entity) {
-                statement.bindLong(1, entity.uid);
-                if (entity.name == null) {
-                    statement.bindNull(2);
-                } else {
-                    statement.bindString(2, entity.name);
-                }
-                if (entity.getLastName() == null) {
-                    statement.bindNull(3);
-                } else {
-                    statement.bindString(3, entity.getLastName());
-                }
-                statement.bindLong(4, entity.age);
-                statement.bindLong(5, entity.uid);
-            }
-        };
-        this.__updateAdapterOfMultiPKeyEntity = new EntityDeletionOrUpdateAdapter<MultiPKeyEntity>(__db) {
+        this.__updateAdapterOfMultiPKeyEntity = new EntityDeleteOrUpdateAdapter<MultiPKeyEntity>() {
             @Override
             @NonNull
             protected String createQuery() {
@@ -96,31 +123,30 @@
             }
 
             @Override
-            protected void bind(@NonNull final SupportSQLiteStatement statement,
-                    final MultiPKeyEntity entity) {
+            protected void bind(@NonNull final SQLiteStatement statement, final MultiPKeyEntity entity) {
                 if (entity.name == null) {
                     statement.bindNull(1);
                 } else {
-                    statement.bindString(1, entity.name);
+                    statement.bindText(1, entity.name);
                 }
                 if (entity.lastName == null) {
                     statement.bindNull(2);
                 } else {
-                    statement.bindString(2, entity.lastName);
+                    statement.bindText(2, entity.lastName);
                 }
                 if (entity.name == null) {
                     statement.bindNull(3);
                 } else {
-                    statement.bindString(3, entity.name);
+                    statement.bindText(3, entity.name);
                 }
                 if (entity.lastName == null) {
                     statement.bindNull(4);
                 } else {
-                    statement.bindString(4, entity.lastName);
+                    statement.bindText(4, entity.lastName);
                 }
             }
         };
-        this.__updateAdapterOfBook = new EntityDeletionOrUpdateAdapter<Book>(__db) {
+        this.__updateAdapterOfBook = new EntityDeleteOrUpdateAdapter<Book>() {
             @Override
             @NonNull
             protected String createQuery() {
@@ -128,7 +154,7 @@
             }
 
             @Override
-            protected void bind(@NonNull final SupportSQLiteStatement statement, final Book entity) {
+            protected void bind(@NonNull final SQLiteStatement statement, final Book entity) {
                 statement.bindLong(1, entity.bookId);
                 statement.bindLong(2, entity.uid);
                 statement.bindLong(3, entity.bookId);
@@ -138,109 +164,105 @@
 
     @Override
     public void updateUser(final User user) {
-        __db.assertNotSuspendingTransaction();
-        __db.beginTransaction();
-        try {
-            __updateAdapterOfUser.handle(user);
-            __db.setTransactionSuccessful();
-        } finally {
-            __db.endTransaction();
-        }
+        DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
+            @Override
+            @NonNull
+            public Void invoke(@NonNull final SQLiteConnection _connection) {
+                __updateAdapterOfUser.handle(_connection, user);
+                return null;
+            }
+        });
     }
 
     @Override
     public void updateUsers(final User user1, final List<User> others) {
-        __db.assertNotSuspendingTransaction();
-        __db.beginTransaction();
-        try {
-            __updateAdapterOfUser.handle(user1);
-            __updateAdapterOfUser.handleMultiple(others);
-            __db.setTransactionSuccessful();
-        } finally {
-            __db.endTransaction();
-        }
+        DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
+            @Override
+            @NonNull
+            public Void invoke(@NonNull final SQLiteConnection _connection) {
+                __updateAdapterOfUser.handle(_connection, user1);
+                __updateAdapterOfUser.handleMultiple(_connection, others);
+                return null;
+            }
+        });
     }
 
     @Override
     public void updateArrayOfUsers(final User[] users) {
-        __db.assertNotSuspendingTransaction();
-        __db.beginTransaction();
-        try {
-            __updateAdapterOfUser.handleMultiple(users);
-            __db.setTransactionSuccessful();
-        } finally {
-            __db.endTransaction();
-        }
+        DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
+            @Override
+            @NonNull
+            public Void invoke(@NonNull final SQLiteConnection _connection) {
+                __updateAdapterOfUser.handleMultiple(_connection, users);
+                return null;
+            }
+        });
     }
 
     @Override
     public void updateTwoUsers(final User userOne, final User userTwo) {
-        __db.assertNotSuspendingTransaction();
-        __db.beginTransaction();
-        try {
-            __updateAdapterOfUser_1.handle(userOne);
-            __updateAdapterOfUser_1.handle(userTwo);
-            __db.setTransactionSuccessful();
-        } finally {
-            __db.endTransaction();
-        }
+        DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
+            @Override
+            @NonNull
+            public Void invoke(@NonNull final SQLiteConnection _connection) {
+                __updateAdapterOfUser_1.handle(_connection, userOne);
+                __updateAdapterOfUser_1.handle(_connection, userTwo);
+                return null;
+            }
+        });
     }
 
     @Override
     public int updateUserAndReturnCount(final User user) {
-        __db.assertNotSuspendingTransaction();
-        int _total = 0;
-        __db.beginTransaction();
-        try {
-            _total += __updateAdapterOfUser.handle(user);
-            __db.setTransactionSuccessful();
-            return _total;
-        } finally {
-            __db.endTransaction();
-        }
+        return DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Integer>() {
+            @Override
+            @NonNull
+            public Integer invoke(@NonNull final SQLiteConnection _connection) {
+                int _result = 0;
+                _result += __updateAdapterOfUser.handle(_connection, user);
+                return _result;
+            }
+        });
     }
 
     @Override
     public int updateUserAndReturnCount(final User user1, final List<User> others) {
-        __db.assertNotSuspendingTransaction();
-        int _total = 0;
-        __db.beginTransaction();
-        try {
-            _total += __updateAdapterOfUser.handle(user1);
-            _total += __updateAdapterOfUser.handleMultiple(others);
-            __db.setTransactionSuccessful();
-            return _total;
-        } finally {
-            __db.endTransaction();
-        }
+        return DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Integer>() {
+            @Override
+            @NonNull
+            public Integer invoke(@NonNull final SQLiteConnection _connection) {
+                int _result = 0;
+                _result += __updateAdapterOfUser.handle(_connection, user1);
+                _result += __updateAdapterOfUser.handleMultiple(_connection, others);
+                return _result;
+            }
+        });
     }
 
     @Override
     public int updateUserAndReturnCount(final User[] users) {
-        __db.assertNotSuspendingTransaction();
-        int _total = 0;
-        __db.beginTransaction();
-        try {
-            _total += __updateAdapterOfUser.handleMultiple(users);
-            __db.setTransactionSuccessful();
-            return _total;
-        } finally {
-            __db.endTransaction();
-        }
+        return DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Integer>() {
+            @Override
+            @NonNull
+            public Integer invoke(@NonNull final SQLiteConnection _connection) {
+                int _result = 0;
+                _result += __updateAdapterOfUser.handleMultiple(_connection, users);
+                return _result;
+            }
+        });
     }
 
     @Override
     public Integer updateUserAndReturnCountObject(final User user) {
-        __db.assertNotSuspendingTransaction();
-        int _total = 0;
-        __db.beginTransaction();
-        try {
-            _total += __updateAdapterOfUser.handle(user);
-            __db.setTransactionSuccessful();
-            return _total;
-        } finally {
-            __db.endTransaction();
-        }
+        return DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Integer>() {
+            @Override
+            @NonNull
+            public Integer invoke(@NonNull final SQLiteConnection _connection) {
+                int _result = 0;
+                _result += __updateAdapterOfUser.handle(_connection, user);
+                return _result;
+            }
+        });
     }
 
     @Override
@@ -251,7 +273,7 @@
             public Void call() throws Exception {
                 __db.beginTransaction();
                 try {
-                    __updateAdapterOfUser.handle(user);
+                    __updateCompatAdapterOfUser.handle(user);
                     __db.setTransactionSuccessful();
                     return null;
                 } finally {
@@ -270,7 +292,7 @@
                 int _total = 0;
                 __db.beginTransaction();
                 try {
-                    _total += __updateAdapterOfUser.handle(user);
+                    _total += __updateCompatAdapterOfUser.handle(user);
                     __db.setTransactionSuccessful();
                     return _total;
                 } finally {
@@ -289,7 +311,7 @@
                 int _total = 0;
                 __db.beginTransaction();
                 try {
-                    _total += __updateAdapterOfUser.handle(user);
+                    _total += __updateCompatAdapterOfUser.handle(user);
                     __db.setTransactionSuccessful();
                     return _total;
                 } finally {
@@ -301,29 +323,28 @@
 
     @Override
     public int multiPKey(final MultiPKeyEntity entity) {
-        __db.assertNotSuspendingTransaction();
-        int _total = 0;
-        __db.beginTransaction();
-        try {
-            _total += __updateAdapterOfMultiPKeyEntity.handle(entity);
-            __db.setTransactionSuccessful();
-            return _total;
-        } finally {
-            __db.endTransaction();
-        }
+        return DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Integer>() {
+            @Override
+            @NonNull
+            public Integer invoke(@NonNull final SQLiteConnection _connection) {
+                int _result = 0;
+                _result += __updateAdapterOfMultiPKeyEntity.handle(_connection, entity);
+                return _result;
+            }
+        });
     }
 
     @Override
     public void updateUserAndBook(final User user, final Book book) {
-        __db.assertNotSuspendingTransaction();
-        __db.beginTransaction();
-        try {
-            __updateAdapterOfUser.handle(user);
-            __updateAdapterOfBook.handle(book);
-            __db.setTransactionSuccessful();
-        } finally {
-            __db.endTransaction();
-        }
+        DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
+            @Override
+            @NonNull
+            public Void invoke(@NonNull final SQLiteConnection _connection) {
+                __updateAdapterOfUser.handle(_connection, user);
+                __updateAdapterOfBook.handle(_connection, book);
+                return null;
+            }
+        });
     }
 
     @Override
diff --git a/room/room-compiler/src/test/test-data/daoWriter/output/javac/UpsertDao.java b/room/room-compiler/src/test/test-data/daoWriter/output/javac/UpsertDao.java
index a380ba3..4be6009 100644
--- a/room/room-compiler/src/test/test-data/daoWriter/output/javac/UpsertDao.java
+++ b/room/room-compiler/src/test/test-data/daoWriter/output/javac/UpsertDao.java
@@ -1,31 +1,36 @@
 package foo.bar;
 
 import androidx.annotation.NonNull;
-import androidx.room.EntityDeletionOrUpdateAdapter;
-import androidx.room.EntityInsertionAdapter;
-import androidx.room.EntityUpsertionAdapter;
+import androidx.room.EntityDeleteOrUpdateAdapter;
+import androidx.room.EntityInsertAdapter;
+import androidx.room.EntityUpsertAdapter;
 import androidx.room.RoomDatabase;
-import androidx.sqlite.db.SupportSQLiteStatement;
+import androidx.room.util.DBUtil;
+import androidx.sqlite.SQLiteConnection;
+import androidx.sqlite.SQLiteStatement;
 import java.lang.Class;
+import java.lang.Long;
 import java.lang.Override;
 import java.lang.String;
 import java.lang.SuppressWarnings;
+import java.lang.Void;
 import java.util.Collections;
 import java.util.List;
 import javax.annotation.processing.Generated;
+import kotlin.jvm.functions.Function1;
 
 @Generated("androidx.room.RoomProcessor")
 @SuppressWarnings({"unchecked", "deprecation"})
 public final class UpsertDao_Impl implements UpsertDao {
     private final RoomDatabase __db;
 
-    private final EntityUpsertionAdapter<User> __upsertAdapterOfUser;
+    private final EntityUpsertAdapter<User> __upsertAdapterOfUser;
 
-    private final EntityUpsertionAdapter<Book> __upsertAdapterOfBook;
+    private final EntityUpsertAdapter<Book> __upsertAdapterOfBook;
 
     public UpsertDao_Impl(@NonNull final RoomDatabase __db) {
         this.__db = __db;
-        this.__upsertAdapterOfUser = new EntityUpsertionAdapter<User>(new EntityInsertionAdapter<User>(__db) {
+        this.__upsertAdapterOfUser = new EntityUpsertAdapter<User>(new EntityInsertAdapter<User>() {
             @Override
             @NonNull
             protected String createQuery() {
@@ -33,21 +38,21 @@
             }
 
             @Override
-            protected void bind(@NonNull final SupportSQLiteStatement statement, final User entity) {
+            protected void bind(@NonNull final SQLiteStatement statement, final User entity) {
                 statement.bindLong(1, entity.uid);
                 if (entity.name == null) {
                     statement.bindNull(2);
                 } else {
-                    statement.bindString(2, entity.name);
+                    statement.bindText(2, entity.name);
                 }
                 if (entity.getLastName() == null) {
                     statement.bindNull(3);
                 } else {
-                    statement.bindString(3, entity.getLastName());
+                    statement.bindText(3, entity.getLastName());
                 }
                 statement.bindLong(4, entity.age);
             }
-        }, new EntityDeletionOrUpdateAdapter<User>(__db) {
+        }, new EntityDeleteOrUpdateAdapter<User>() {
             @Override
             @NonNull
             protected String createQuery() {
@@ -55,23 +60,23 @@
             }
 
             @Override
-            protected void bind(@NonNull final SupportSQLiteStatement statement, final User entity) {
+            protected void bind(@NonNull final SQLiteStatement statement, final User entity) {
                 statement.bindLong(1, entity.uid);
                 if (entity.name == null) {
                     statement.bindNull(2);
                 } else {
-                    statement.bindString(2, entity.name);
+                    statement.bindText(2, entity.name);
                 }
                 if (entity.getLastName() == null) {
                     statement.bindNull(3);
                 } else {
-                    statement.bindString(3, entity.getLastName());
+                    statement.bindText(3, entity.getLastName());
                 }
                 statement.bindLong(4, entity.age);
                 statement.bindLong(5, entity.uid);
             }
         });
-        this.__upsertAdapterOfBook = new EntityUpsertionAdapter<Book>(new EntityInsertionAdapter<Book>(__db) {
+        this.__upsertAdapterOfBook = new EntityUpsertAdapter<Book>(new EntityInsertAdapter<Book>() {
             @Override
             @NonNull
             protected String createQuery() {
@@ -79,11 +84,11 @@
             }
 
             @Override
-            protected void bind(@NonNull final SupportSQLiteStatement statement, final Book entity) {
+            protected void bind(@NonNull final SQLiteStatement statement, final Book entity) {
                 statement.bindLong(1, entity.bookId);
                 statement.bindLong(2, entity.uid);
             }
-        }, new EntityDeletionOrUpdateAdapter<Book>(__db) {
+        }, new EntityDeleteOrUpdateAdapter<Book>() {
             @Override
             @NonNull
             protected String createQuery() {
@@ -91,7 +96,7 @@
             }
 
             @Override
-            protected void bind(@NonNull final SupportSQLiteStatement statement, final Book entity) {
+            protected void bind(@NonNull final SQLiteStatement statement, final Book entity) {
                 statement.bindLong(1, entity.bookId);
                 statement.bindLong(2, entity.uid);
                 statement.bindLong(3, entity.bookId);
@@ -101,91 +106,87 @@
 
     @Override
     public void upsertUser(final User user) {
-        __db.assertNotSuspendingTransaction();
-        __db.beginTransaction();
-        try {
-            __upsertAdapterOfUser.upsert(user);
-            __db.setTransactionSuccessful();
-        } finally {
-            __db.endTransaction();
-        }
+        DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
+            @Override
+            @NonNull
+            public Void invoke(@NonNull final SQLiteConnection _connection) {
+                __upsertAdapterOfUser.upsert(_connection, user);
+                return null;
+            }
+        });
     }
 
     @Override
     public void upsertUsers(final User user1, final List<User> others) {
-        __db.assertNotSuspendingTransaction();
-        __db.beginTransaction();
-        try {
-            __upsertAdapterOfUser.upsert(user1);
-            __upsertAdapterOfUser.upsert(others);
-            __db.setTransactionSuccessful();
-        } finally {
-            __db.endTransaction();
-        }
+        DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
+            @Override
+            @NonNull
+            public Void invoke(@NonNull final SQLiteConnection _connection) {
+                __upsertAdapterOfUser.upsert(_connection, user1);
+                __upsertAdapterOfUser.upsert(_connection, others);
+                return null;
+            }
+        });
     }
 
     @Override
     public void upsertUsers(final User[] users) {
-        __db.assertNotSuspendingTransaction();
-        __db.beginTransaction();
-        try {
-            __upsertAdapterOfUser.upsert(users);
-            __db.setTransactionSuccessful();
-        } finally {
-            __db.endTransaction();
-        }
+        DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
+            @Override
+            @NonNull
+            public Void invoke(@NonNull final SQLiteConnection _connection) {
+                __upsertAdapterOfUser.upsert(_connection, users);
+                return null;
+            }
+        });
     }
 
     @Override
     public void upsertTwoUsers(final User userOne, final User userTwo) {
-        __db.assertNotSuspendingTransaction();
-        __db.beginTransaction();
-        try {
-            __upsertAdapterOfUser.upsert(userOne);
-            __upsertAdapterOfUser.upsert(userTwo);
-            __db.setTransactionSuccessful();
-        } finally {
-            __db.endTransaction();
-        }
+        DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
+            @Override
+            @NonNull
+            public Void invoke(@NonNull final SQLiteConnection _connection) {
+                __upsertAdapterOfUser.upsert(_connection, userOne);
+                __upsertAdapterOfUser.upsert(_connection, userTwo);
+                return null;
+            }
+        });
     }
 
     @Override
     public void upsertUserAndBook(final User user, final Book book) {
-        __db.assertNotSuspendingTransaction();
-        __db.beginTransaction();
-        try {
-            __upsertAdapterOfUser.upsert(user);
-            __upsertAdapterOfBook.upsert(book);
-            __db.setTransactionSuccessful();
-        } finally {
-            __db.endTransaction();
-        }
+        DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
+            @Override
+            @NonNull
+            public Void invoke(@NonNull final SQLiteConnection _connection) {
+                __upsertAdapterOfUser.upsert(_connection, user);
+                __upsertAdapterOfBook.upsert(_connection, book);
+                return null;
+            }
+        });
     }
 
     @Override
     public long upsertAndReturnId(final User user) {
-        __db.assertNotSuspendingTransaction();
-        __db.beginTransaction();
-        try {
-            final long _result = __upsertAdapterOfUser.upsertAndReturnId(user);
-            __db.setTransactionSuccessful();
-            return _result;
-        } finally {
-            __db.endTransaction();
-        }
+        return DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Long>() {
+            @Override
+            @NonNull
+            public Long invoke(@NonNull final SQLiteConnection _connection) {
+                return __upsertAdapterOfUser.upsertAndReturnId(_connection, user);
+            }
+        });
     }
 
     @Override
     public long[] upsertAndReturnIdsArray(final User[] users) {
-        __db.assertNotSuspendingTransaction();
-        __db.beginTransaction();
-        try {
-            final long[] _result = __upsertAdapterOfUser.upsertAndReturnIdsArray(users);
-            __db.setTransactionSuccessful();
-            return _result;
-        } finally {
-            __db.endTransaction();
-        }
+        return DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, long[]>() {
+            @Override
+            @NonNull
+            public long[] invoke(@NonNull final SQLiteConnection _connection) {
+                return __upsertAdapterOfUser.upsertAndReturnIdsArray(_connection, users);
+            }
+        });
     }
 
     @NonNull
diff --git a/room/room-compiler/src/test/test-data/daoWriter/output/javac/WriterDao.java b/room/room-compiler/src/test/test-data/daoWriter/output/javac/WriterDao.java
index 7a67ff7..fae08a4 100644
--- a/room/room-compiler/src/test/test-data/daoWriter/output/javac/WriterDao.java
+++ b/room/room-compiler/src/test/test-data/daoWriter/output/javac/WriterDao.java
@@ -1,33 +1,37 @@
 package foo.bar;
 
 import androidx.annotation.NonNull;
-import androidx.room.EntityInsertionAdapter;
+import androidx.room.EntityInsertAdapter;
 import androidx.room.RoomDatabase;
-import androidx.sqlite.db.SupportSQLiteStatement;
+import androidx.room.util.DBUtil;
+import androidx.sqlite.SQLiteConnection;
+import androidx.sqlite.SQLiteStatement;
 import java.lang.Class;
 import java.lang.Override;
 import java.lang.String;
 import java.lang.SuppressWarnings;
+import java.lang.Void;
 import java.util.Collections;
 import java.util.List;
 import javax.annotation.processing.Generated;
+import kotlin.jvm.functions.Function1;
 
 @Generated("androidx.room.RoomProcessor")
 @SuppressWarnings({"unchecked", "deprecation"})
 public final class WriterDao_Impl implements WriterDao {
     private final RoomDatabase __db;
 
-    private final EntityInsertionAdapter<User> __insertAdapterOfUser;
+    private final EntityInsertAdapter<User> __insertAdapterOfUser;
 
-    private final EntityInsertionAdapter<User> __insertAdapterOfUser_1;
+    private final EntityInsertAdapter<User> __insertAdapterOfUser_1;
 
-    private final EntityInsertionAdapter<User> __insertAdapterOfUser_2;
+    private final EntityInsertAdapter<User> __insertAdapterOfUser_2;
 
-    private final EntityInsertionAdapter<Book> __insertAdapterOfBook;
+    private final EntityInsertAdapter<Book> __insertAdapterOfBook;
 
     public WriterDao_Impl(@NonNull final RoomDatabase __db) {
         this.__db = __db;
-        this.__insertAdapterOfUser = new EntityInsertionAdapter<User>(__db) {
+        this.__insertAdapterOfUser = new EntityInsertAdapter<User>() {
             @Override
             @NonNull
             protected String createQuery() {
@@ -35,22 +39,22 @@
             }
 
             @Override
-            protected void bind(@NonNull final SupportSQLiteStatement statement, final User entity) {
+            protected void bind(@NonNull final SQLiteStatement statement, final User entity) {
                 statement.bindLong(1, entity.uid);
                 if (entity.name == null) {
                     statement.bindNull(2);
                 } else {
-                    statement.bindString(2, entity.name);
+                    statement.bindText(2, entity.name);
                 }
                 if (entity.getLastName() == null) {
                     statement.bindNull(3);
                 } else {
-                    statement.bindString(3, entity.getLastName());
+                    statement.bindText(3, entity.getLastName());
                 }
                 statement.bindLong(4, entity.age);
             }
         };
-        this.__insertAdapterOfUser_1 = new EntityInsertionAdapter<User>(__db) {
+        this.__insertAdapterOfUser_1 = new EntityInsertAdapter<User>() {
             @Override
             @NonNull
             protected String createQuery() {
@@ -58,22 +62,22 @@
             }
 
             @Override
-            protected void bind(@NonNull final SupportSQLiteStatement statement, final User entity) {
+            protected void bind(@NonNull final SQLiteStatement statement, final User entity) {
                 statement.bindLong(1, entity.uid);
                 if (entity.name == null) {
                     statement.bindNull(2);
                 } else {
-                    statement.bindString(2, entity.name);
+                    statement.bindText(2, entity.name);
                 }
                 if (entity.getLastName() == null) {
                     statement.bindNull(3);
                 } else {
-                    statement.bindString(3, entity.getLastName());
+                    statement.bindText(3, entity.getLastName());
                 }
                 statement.bindLong(4, entity.age);
             }
         };
-        this.__insertAdapterOfUser_2 = new EntityInsertionAdapter<User>(__db) {
+        this.__insertAdapterOfUser_2 = new EntityInsertAdapter<User>() {
             @Override
             @NonNull
             protected String createQuery() {
@@ -81,22 +85,22 @@
             }
 
             @Override
-            protected void bind(@NonNull final SupportSQLiteStatement statement, final User entity) {
+            protected void bind(@NonNull final SQLiteStatement statement, final User entity) {
                 statement.bindLong(1, entity.uid);
                 if (entity.name == null) {
                     statement.bindNull(2);
                 } else {
-                    statement.bindString(2, entity.name);
+                    statement.bindText(2, entity.name);
                 }
                 if (entity.getLastName() == null) {
                     statement.bindNull(3);
                 } else {
-                    statement.bindString(3, entity.getLastName());
+                    statement.bindText(3, entity.getLastName());
                 }
                 statement.bindLong(4, entity.age);
             }
         };
-        this.__insertAdapterOfBook = new EntityInsertionAdapter<Book>(__db) {
+        this.__insertAdapterOfBook = new EntityInsertAdapter<Book>() {
             @Override
             @NonNull
             protected String createQuery() {
@@ -104,7 +108,7 @@
             }
 
             @Override
-            protected void bind(@NonNull final SupportSQLiteStatement statement, final Book entity) {
+            protected void bind(@NonNull final SQLiteStatement statement, final Book entity) {
                 statement.bindLong(1, entity.bookId);
                 statement.bindLong(2, entity.uid);
             }
@@ -113,65 +117,65 @@
 
     @Override
     public void insertUser(final User user) {
-        __db.assertNotSuspendingTransaction();
-        __db.beginTransaction();
-        try {
-            __insertAdapterOfUser.insert(user);
-            __db.setTransactionSuccessful();
-        } finally {
-            __db.endTransaction();
-        }
+        DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
+            @Override
+            @NonNull
+            public Void invoke(@NonNull final SQLiteConnection _connection) {
+                __insertAdapterOfUser.insert(_connection, user);
+                return null;
+            }
+        });
     }
 
     @Override
     public void insertUsers(final User user1, final List<User> others) {
-        __db.assertNotSuspendingTransaction();
-        __db.beginTransaction();
-        try {
-            __insertAdapterOfUser.insert(user1);
-            __insertAdapterOfUser.insert(others);
-            __db.setTransactionSuccessful();
-        } finally {
-            __db.endTransaction();
-        }
+        DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
+            @Override
+            @NonNull
+            public Void invoke(@NonNull final SQLiteConnection _connection) {
+                __insertAdapterOfUser.insert(_connection, user1);
+                __insertAdapterOfUser.insert(_connection, others);
+                return null;
+            }
+        });
     }
 
     @Override
     public void insertUsers(final User[] users) {
-        __db.assertNotSuspendingTransaction();
-        __db.beginTransaction();
-        try {
-            __insertAdapterOfUser_1.insert(users);
-            __db.setTransactionSuccessful();
-        } finally {
-            __db.endTransaction();
-        }
+        DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
+            @Override
+            @NonNull
+            public Void invoke(@NonNull final SQLiteConnection _connection) {
+                __insertAdapterOfUser_1.insert(_connection, users);
+                return null;
+            }
+        });
     }
 
     @Override
     public void insertTwoUsers(final User userOne, final User userTwo) {
-        __db.assertNotSuspendingTransaction();
-        __db.beginTransaction();
-        try {
-            __insertAdapterOfUser_2.insert(userOne);
-            __insertAdapterOfUser_2.insert(userTwo);
-            __db.setTransactionSuccessful();
-        } finally {
-            __db.endTransaction();
-        }
+        DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
+            @Override
+            @NonNull
+            public Void invoke(@NonNull final SQLiteConnection _connection) {
+                __insertAdapterOfUser_2.insert(_connection, userOne);
+                __insertAdapterOfUser_2.insert(_connection, userTwo);
+                return null;
+            }
+        });
     }
 
     @Override
     public void insertUserAndBook(final User user, final Book book) {
-        __db.assertNotSuspendingTransaction();
-        __db.beginTransaction();
-        try {
-            __insertAdapterOfUser.insert(user);
-            __insertAdapterOfBook.insert(book);
-            __db.setTransactionSuccessful();
-        } finally {
-            __db.endTransaction();
-        }
+        DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
+            @Override
+            @NonNull
+            public Void invoke(@NonNull final SQLiteConnection _connection) {
+                __insertAdapterOfUser.insert(_connection, user);
+                __insertAdapterOfBook.insert(_connection, book);
+                return null;
+            }
+        });
     }
 
     @NonNull
diff --git a/room/room-compiler/src/test/test-data/daoWriter/output/ksp/DeletionDao.java b/room/room-compiler/src/test/test-data/daoWriter/output/ksp/DeletionDao.java
index 2a085ab..55bf225 100644
--- a/room/room-compiler/src/test/test-data/daoWriter/output/ksp/DeletionDao.java
+++ b/room/room-compiler/src/test/test-data/daoWriter/output/ksp/DeletionDao.java
@@ -2,6 +2,7 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.room.EntityDeleteOrUpdateAdapter;
 import androidx.room.EntityDeletionOrUpdateAdapter;
 import androidx.room.RoomDatabase;
 import androidx.room.util.DBUtil;
@@ -32,15 +33,29 @@
 public final class DeletionDao_Impl implements DeletionDao {
     private final RoomDatabase __db;
 
-    private final EntityDeletionOrUpdateAdapter<User> __deleteAdapterOfUser;
+    private final EntityDeleteOrUpdateAdapter<User> __deleteAdapterOfUser;
 
-    private final EntityDeletionOrUpdateAdapter<MultiPKeyEntity> __deleteAdapterOfMultiPKeyEntity;
+    private final EntityDeletionOrUpdateAdapter<User> __deleteCompatAdapterOfUser;
 
-    private final EntityDeletionOrUpdateAdapter<Book> __deleteAdapterOfBook;
+    private final EntityDeleteOrUpdateAdapter<MultiPKeyEntity> __deleteAdapterOfMultiPKeyEntity;
+
+    private final EntityDeleteOrUpdateAdapter<Book> __deleteAdapterOfBook;
 
     public DeletionDao_Impl(@NonNull final RoomDatabase __db) {
         this.__db = __db;
-        this.__deleteAdapterOfUser = new EntityDeletionOrUpdateAdapter<User>(__db) {
+        this.__deleteAdapterOfUser = new EntityDeleteOrUpdateAdapter<User>() {
+            @Override
+            @NonNull
+            protected String createQuery() {
+                return "DELETE FROM `User` WHERE `uid` = ?";
+            }
+
+            @Override
+            protected void bind(@NonNull final SQLiteStatement statement, @NonNull final User entity) {
+                statement.bindLong(1, entity.uid);
+            }
+        };
+        this.__deleteCompatAdapterOfUser = new EntityDeletionOrUpdateAdapter<User>(__db) {
             @Override
             @NonNull
             protected String createQuery() {
@@ -53,7 +68,7 @@
                 statement.bindLong(1, entity.uid);
             }
         };
-        this.__deleteAdapterOfMultiPKeyEntity = new EntityDeletionOrUpdateAdapter<MultiPKeyEntity>(__db) {
+        this.__deleteAdapterOfMultiPKeyEntity = new EntityDeleteOrUpdateAdapter<MultiPKeyEntity>() {
             @Override
             @NonNull
             protected String createQuery() {
@@ -61,13 +76,13 @@
             }
 
             @Override
-            protected void bind(@NonNull final SupportSQLiteStatement statement,
+            protected void bind(@NonNull final SQLiteStatement statement,
                     @NonNull final MultiPKeyEntity entity) {
-                statement.bindString(1, entity.name);
-                statement.bindString(2, entity.lastName);
+                statement.bindText(1, entity.name);
+                statement.bindText(2, entity.lastName);
             }
         };
-        this.__deleteAdapterOfBook = new EntityDeletionOrUpdateAdapter<Book>(__db) {
+        this.__deleteAdapterOfBook = new EntityDeleteOrUpdateAdapter<Book>() {
             @Override
             @NonNull
             protected String createQuery() {
@@ -75,8 +90,7 @@
             }
 
             @Override
-            protected void bind(@NonNull final SupportSQLiteStatement statement,
-                    @NonNull final Book entity) {
+            protected void bind(@NonNull final SQLiteStatement statement, @NonNull final Book entity) {
                 statement.bindLong(1, entity.bookId);
             }
         };
@@ -84,96 +98,92 @@
 
     @Override
     public void deleteUser(final User user) {
-        __db.assertNotSuspendingTransaction();
-        __db.beginTransaction();
-        try {
-            __deleteAdapterOfUser.handle(user);
-            __db.setTransactionSuccessful();
-        } finally {
-            __db.endTransaction();
-        }
+        DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
+            @Override
+            @NonNull
+            public Void invoke(@NonNull final SQLiteConnection _connection) {
+                __deleteAdapterOfUser.handle(_connection, user);
+                return null;
+            }
+        });
     }
 
     @Override
     public void deleteUsers(final User user1, final List<User> others) {
-        __db.assertNotSuspendingTransaction();
-        __db.beginTransaction();
-        try {
-            __deleteAdapterOfUser.handle(user1);
-            __deleteAdapterOfUser.handleMultiple(others);
-            __db.setTransactionSuccessful();
-        } finally {
-            __db.endTransaction();
-        }
+        DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
+            @Override
+            @NonNull
+            public Void invoke(@NonNull final SQLiteConnection _connection) {
+                __deleteAdapterOfUser.handle(_connection, user1);
+                __deleteAdapterOfUser.handleMultiple(_connection, others);
+                return null;
+            }
+        });
     }
 
     @Override
     public void deleteArrayOfUsers(final User[] users) {
-        __db.assertNotSuspendingTransaction();
-        __db.beginTransaction();
-        try {
-            __deleteAdapterOfUser.handleMultiple(users);
-            __db.setTransactionSuccessful();
-        } finally {
-            __db.endTransaction();
-        }
+        DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
+            @Override
+            @NonNull
+            public Void invoke(@NonNull final SQLiteConnection _connection) {
+                __deleteAdapterOfUser.handleMultiple(_connection, users);
+                return null;
+            }
+        });
     }
 
     @Override
     public Integer deleteUserAndReturnCountObject(final User user) {
-        __db.assertNotSuspendingTransaction();
-        int _total = 0;
-        __db.beginTransaction();
-        try {
-            _total += __deleteAdapterOfUser.handle(user);
-            __db.setTransactionSuccessful();
-            return _total;
-        } finally {
-            __db.endTransaction();
-        }
+        return DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Integer>() {
+            @Override
+            @NonNull
+            public Integer invoke(@NonNull final SQLiteConnection _connection) {
+                int _result = 0;
+                _result += __deleteAdapterOfUser.handle(_connection, user);
+                return _result;
+            }
+        });
     }
 
     @Override
     public int deleteUserAndReturnCount(final User user) {
-        __db.assertNotSuspendingTransaction();
-        int _total = 0;
-        __db.beginTransaction();
-        try {
-            _total += __deleteAdapterOfUser.handle(user);
-            __db.setTransactionSuccessful();
-            return _total;
-        } finally {
-            __db.endTransaction();
-        }
+        return DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Integer>() {
+            @Override
+            @NonNull
+            public Integer invoke(@NonNull final SQLiteConnection _connection) {
+                int _result = 0;
+                _result += __deleteAdapterOfUser.handle(_connection, user);
+                return _result;
+            }
+        });
     }
 
     @Override
     public int deleteUserAndReturnCount(final User user1, final List<User> others) {
-        __db.assertNotSuspendingTransaction();
-        int _total = 0;
-        __db.beginTransaction();
-        try {
-            _total += __deleteAdapterOfUser.handle(user1);
-            _total += __deleteAdapterOfUser.handleMultiple(others);
-            __db.setTransactionSuccessful();
-            return _total;
-        } finally {
-            __db.endTransaction();
-        }
+        return DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Integer>() {
+            @Override
+            @NonNull
+            public Integer invoke(@NonNull final SQLiteConnection _connection) {
+                int _result = 0;
+                _result += __deleteAdapterOfUser.handle(_connection, user1);
+                _result += __deleteAdapterOfUser.handleMultiple(_connection, others);
+                return _result;
+            }
+        });
     }
 
     @Override
     public int deleteUserAndReturnCount(final User[] users) {
-        __db.assertNotSuspendingTransaction();
-        int _total = 0;
-        __db.beginTransaction();
-        try {
-            _total += __deleteAdapterOfUser.handleMultiple(users);
-            __db.setTransactionSuccessful();
-            return _total;
-        } finally {
-            __db.endTransaction();
-        }
+        return DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Integer>() {
+            @Override
+            @NonNull
+            public Integer invoke(@NonNull final SQLiteConnection _connection) {
+                int _result = 0;
+                _result += __deleteAdapterOfUser.handleMultiple(_connection, users);
+                return _result;
+            }
+        });
     }
 
     @Override
@@ -184,7 +194,7 @@
             public Void call() throws Exception {
                 __db.beginTransaction();
                 try {
-                    __deleteAdapterOfUser.handle(user);
+                    __deleteCompatAdapterOfUser.handle(user);
                     __db.setTransactionSuccessful();
                     return null;
                 } finally {
@@ -203,7 +213,7 @@
                 int _total = 0;
                 __db.beginTransaction();
                 try {
-                    _total += __deleteAdapterOfUser.handle(user);
+                    _total += __deleteCompatAdapterOfUser.handle(user);
                     __db.setTransactionSuccessful();
                     return _total;
                 } finally {
@@ -222,7 +232,7 @@
                 int _total = 0;
                 __db.beginTransaction();
                 try {
-                    _total += __deleteAdapterOfUser.handle(user);
+                    _total += __deleteCompatAdapterOfUser.handle(user);
                     __db.setTransactionSuccessful();
                     return _total;
                 } finally {
@@ -234,29 +244,28 @@
 
     @Override
     public int multiPKey(final MultiPKeyEntity entity) {
-        __db.assertNotSuspendingTransaction();
-        int _total = 0;
-        __db.beginTransaction();
-        try {
-            _total += __deleteAdapterOfMultiPKeyEntity.handle(entity);
-            __db.setTransactionSuccessful();
-            return _total;
-        } finally {
-            __db.endTransaction();
-        }
+        return DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Integer>() {
+            @Override
+            @NonNull
+            public Integer invoke(@NonNull final SQLiteConnection _connection) {
+                int _result = 0;
+                _result += __deleteAdapterOfMultiPKeyEntity.handle(_connection, entity);
+                return _result;
+            }
+        });
     }
 
     @Override
     public void deleteUserAndBook(final User user, final Book book) {
-        __db.assertNotSuspendingTransaction();
-        __db.beginTransaction();
-        try {
-            __deleteAdapterOfUser.handle(user);
-            __deleteAdapterOfBook.handle(book);
-            __db.setTransactionSuccessful();
-        } finally {
-            __db.endTransaction();
-        }
+        DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
+            @Override
+            @NonNull
+            public Void invoke(@NonNull final SQLiteConnection _connection) {
+                __deleteAdapterOfUser.handle(_connection, user);
+                __deleteAdapterOfBook.handle(_connection, book);
+                return null;
+            }
+        });
     }
 
     @Override
diff --git a/room/room-compiler/src/test/test-data/daoWriter/output/ksp/UpdateDao.java b/room/room-compiler/src/test/test-data/daoWriter/output/ksp/UpdateDao.java
index 5beccc0..3c9a85c 100644
--- a/room/room-compiler/src/test/test-data/daoWriter/output/ksp/UpdateDao.java
+++ b/room/room-compiler/src/test/test-data/daoWriter/output/ksp/UpdateDao.java
@@ -2,6 +2,7 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.room.EntityDeleteOrUpdateAdapter;
 import androidx.room.EntityDeletionOrUpdateAdapter;
 import androidx.room.RoomDatabase;
 import androidx.room.util.DBUtil;
@@ -30,17 +31,51 @@
 public final class UpdateDao_Impl implements UpdateDao {
     private final RoomDatabase __db;
 
-    private final EntityDeletionOrUpdateAdapter<User> __updateAdapterOfUser;
+    private final EntityDeleteOrUpdateAdapter<User> __updateAdapterOfUser;
 
-    private final EntityDeletionOrUpdateAdapter<User> __updateAdapterOfUser_1;
+    private final EntityDeleteOrUpdateAdapter<User> __updateAdapterOfUser_1;
 
-    private final EntityDeletionOrUpdateAdapter<MultiPKeyEntity> __updateAdapterOfMultiPKeyEntity;
+    private final EntityDeletionOrUpdateAdapter<User> __updateCompatAdapterOfUser;
 
-    private final EntityDeletionOrUpdateAdapter<Book> __updateAdapterOfBook;
+    private final EntityDeleteOrUpdateAdapter<MultiPKeyEntity> __updateAdapterOfMultiPKeyEntity;
+
+    private final EntityDeleteOrUpdateAdapter<Book> __updateAdapterOfBook;
 
     public UpdateDao_Impl(@NonNull final RoomDatabase __db) {
         this.__db = __db;
-        this.__updateAdapterOfUser = new EntityDeletionOrUpdateAdapter<User>(__db) {
+        this.__updateAdapterOfUser = new EntityDeleteOrUpdateAdapter<User>() {
+            @Override
+            @NonNull
+            protected String createQuery() {
+                return "UPDATE OR ABORT `User` SET `uid` = ?,`name` = ?,`lastName` = ?,`ageColumn` = ? WHERE `uid` = ?";
+            }
+
+            @Override
+            protected void bind(@NonNull final SQLiteStatement statement, @NonNull final User entity) {
+                statement.bindLong(1, entity.uid);
+                statement.bindText(2, entity.name);
+                statement.bindText(3, entity.getLastName());
+                statement.bindLong(4, entity.age);
+                statement.bindLong(5, entity.uid);
+            }
+        };
+        this.__updateAdapterOfUser_1 = new EntityDeleteOrUpdateAdapter<User>() {
+            @Override
+            @NonNull
+            protected String createQuery() {
+                return "UPDATE `User` SET `uid` = ?,`name` = ?,`lastName` = ?,`ageColumn` = ? WHERE `uid` = ?";
+            }
+
+            @Override
+            protected void bind(@NonNull final SQLiteStatement statement, @NonNull final User entity) {
+                statement.bindLong(1, entity.uid);
+                statement.bindText(2, entity.name);
+                statement.bindText(3, entity.getLastName());
+                statement.bindLong(4, entity.age);
+                statement.bindLong(5, entity.uid);
+            }
+        };
+        this.__updateCompatAdapterOfUser = new EntityDeletionOrUpdateAdapter<User>(__db) {
             @Override
             @NonNull
             protected String createQuery() {
@@ -57,24 +92,7 @@
                 statement.bindLong(5, entity.uid);
             }
         };
-        this.__updateAdapterOfUser_1 = new EntityDeletionOrUpdateAdapter<User>(__db) {
-            @Override
-            @NonNull
-            protected String createQuery() {
-                return "UPDATE `User` SET `uid` = ?,`name` = ?,`lastName` = ?,`ageColumn` = ? WHERE `uid` = ?";
-            }
-
-            @Override
-            protected void bind(@NonNull final SupportSQLiteStatement statement,
-                    @NonNull final User entity) {
-                statement.bindLong(1, entity.uid);
-                statement.bindString(2, entity.name);
-                statement.bindString(3, entity.getLastName());
-                statement.bindLong(4, entity.age);
-                statement.bindLong(5, entity.uid);
-            }
-        };
-        this.__updateAdapterOfMultiPKeyEntity = new EntityDeletionOrUpdateAdapter<MultiPKeyEntity>(__db) {
+        this.__updateAdapterOfMultiPKeyEntity = new EntityDeleteOrUpdateAdapter<MultiPKeyEntity>() {
             @Override
             @NonNull
             protected String createQuery() {
@@ -82,15 +100,15 @@
             }
 
             @Override
-            protected void bind(@NonNull final SupportSQLiteStatement statement,
+            protected void bind(@NonNull final SQLiteStatement statement,
                     @NonNull final MultiPKeyEntity entity) {
-                statement.bindString(1, entity.name);
-                statement.bindString(2, entity.lastName);
-                statement.bindString(3, entity.name);
-                statement.bindString(4, entity.lastName);
+                statement.bindText(1, entity.name);
+                statement.bindText(2, entity.lastName);
+                statement.bindText(3, entity.name);
+                statement.bindText(4, entity.lastName);
             }
         };
-        this.__updateAdapterOfBook = new EntityDeletionOrUpdateAdapter<Book>(__db) {
+        this.__updateAdapterOfBook = new EntityDeleteOrUpdateAdapter<Book>() {
             @Override
             @NonNull
             protected String createQuery() {
@@ -98,8 +116,7 @@
             }
 
             @Override
-            protected void bind(@NonNull final SupportSQLiteStatement statement,
-                    @NonNull final Book entity) {
+            protected void bind(@NonNull final SQLiteStatement statement, @NonNull final Book entity) {
                 statement.bindLong(1, entity.bookId);
                 statement.bindLong(2, entity.uid);
                 statement.bindLong(3, entity.bookId);
@@ -109,109 +126,105 @@
 
     @Override
     public void updateUser(final User user) {
-        __db.assertNotSuspendingTransaction();
-        __db.beginTransaction();
-        try {
-            __updateAdapterOfUser.handle(user);
-            __db.setTransactionSuccessful();
-        } finally {
-            __db.endTransaction();
-        }
+        DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
+            @Override
+            @NonNull
+            public Void invoke(@NonNull final SQLiteConnection _connection) {
+                __updateAdapterOfUser.handle(_connection, user);
+                return null;
+            }
+        });
     }
 
     @Override
     public void updateUsers(final User user1, final List<User> others) {
-        __db.assertNotSuspendingTransaction();
-        __db.beginTransaction();
-        try {
-            __updateAdapterOfUser.handle(user1);
-            __updateAdapterOfUser.handleMultiple(others);
-            __db.setTransactionSuccessful();
-        } finally {
-            __db.endTransaction();
-        }
+        DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
+            @Override
+            @NonNull
+            public Void invoke(@NonNull final SQLiteConnection _connection) {
+                __updateAdapterOfUser.handle(_connection, user1);
+                __updateAdapterOfUser.handleMultiple(_connection, others);
+                return null;
+            }
+        });
     }
 
     @Override
     public void updateArrayOfUsers(final User[] users) {
-        __db.assertNotSuspendingTransaction();
-        __db.beginTransaction();
-        try {
-            __updateAdapterOfUser.handleMultiple(users);
-            __db.setTransactionSuccessful();
-        } finally {
-            __db.endTransaction();
-        }
+        DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
+            @Override
+            @NonNull
+            public Void invoke(@NonNull final SQLiteConnection _connection) {
+                __updateAdapterOfUser.handleMultiple(_connection, users);
+                return null;
+            }
+        });
     }
 
     @Override
     public void updateTwoUsers(final User userOne, final User userTwo) {
-        __db.assertNotSuspendingTransaction();
-        __db.beginTransaction();
-        try {
-            __updateAdapterOfUser_1.handle(userOne);
-            __updateAdapterOfUser_1.handle(userTwo);
-            __db.setTransactionSuccessful();
-        } finally {
-            __db.endTransaction();
-        }
+        DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
+            @Override
+            @NonNull
+            public Void invoke(@NonNull final SQLiteConnection _connection) {
+                __updateAdapterOfUser_1.handle(_connection, userOne);
+                __updateAdapterOfUser_1.handle(_connection, userTwo);
+                return null;
+            }
+        });
     }
 
     @Override
     public int updateUserAndReturnCount(final User user) {
-        __db.assertNotSuspendingTransaction();
-        int _total = 0;
-        __db.beginTransaction();
-        try {
-            _total += __updateAdapterOfUser.handle(user);
-            __db.setTransactionSuccessful();
-            return _total;
-        } finally {
-            __db.endTransaction();
-        }
+        return DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Integer>() {
+            @Override
+            @NonNull
+            public Integer invoke(@NonNull final SQLiteConnection _connection) {
+                int _result = 0;
+                _result += __updateAdapterOfUser.handle(_connection, user);
+                return _result;
+            }
+        });
     }
 
     @Override
     public int updateUserAndReturnCount(final User user1, final List<User> others) {
-        __db.assertNotSuspendingTransaction();
-        int _total = 0;
-        __db.beginTransaction();
-        try {
-            _total += __updateAdapterOfUser.handle(user1);
-            _total += __updateAdapterOfUser.handleMultiple(others);
-            __db.setTransactionSuccessful();
-            return _total;
-        } finally {
-            __db.endTransaction();
-        }
+        return DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Integer>() {
+            @Override
+            @NonNull
+            public Integer invoke(@NonNull final SQLiteConnection _connection) {
+                int _result = 0;
+                _result += __updateAdapterOfUser.handle(_connection, user1);
+                _result += __updateAdapterOfUser.handleMultiple(_connection, others);
+                return _result;
+            }
+        });
     }
 
     @Override
     public int updateUserAndReturnCount(final User[] users) {
-        __db.assertNotSuspendingTransaction();
-        int _total = 0;
-        __db.beginTransaction();
-        try {
-            _total += __updateAdapterOfUser.handleMultiple(users);
-            __db.setTransactionSuccessful();
-            return _total;
-        } finally {
-            __db.endTransaction();
-        }
+        return DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Integer>() {
+            @Override
+            @NonNull
+            public Integer invoke(@NonNull final SQLiteConnection _connection) {
+                int _result = 0;
+                _result += __updateAdapterOfUser.handleMultiple(_connection, users);
+                return _result;
+            }
+        });
     }
 
     @Override
     public Integer updateUserAndReturnCountObject(final User user) {
-        __db.assertNotSuspendingTransaction();
-        int _total = 0;
-        __db.beginTransaction();
-        try {
-            _total += __updateAdapterOfUser.handle(user);
-            __db.setTransactionSuccessful();
-            return _total;
-        } finally {
-            __db.endTransaction();
-        }
+        return DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Integer>() {
+            @Override
+            @NonNull
+            public Integer invoke(@NonNull final SQLiteConnection _connection) {
+                int _result = 0;
+                _result += __updateAdapterOfUser.handle(_connection, user);
+                return _result;
+            }
+        });
     }
 
     @Override
@@ -222,7 +235,7 @@
             public Void call() throws Exception {
                 __db.beginTransaction();
                 try {
-                    __updateAdapterOfUser.handle(user);
+                    __updateCompatAdapterOfUser.handle(user);
                     __db.setTransactionSuccessful();
                     return null;
                 } finally {
@@ -241,7 +254,7 @@
                 int _total = 0;
                 __db.beginTransaction();
                 try {
-                    _total += __updateAdapterOfUser.handle(user);
+                    _total += __updateCompatAdapterOfUser.handle(user);
                     __db.setTransactionSuccessful();
                     return _total;
                 } finally {
@@ -260,7 +273,7 @@
                 int _total = 0;
                 __db.beginTransaction();
                 try {
-                    _total += __updateAdapterOfUser.handle(user);
+                    _total += __updateCompatAdapterOfUser.handle(user);
                     __db.setTransactionSuccessful();
                     return _total;
                 } finally {
@@ -272,29 +285,28 @@
 
     @Override
     public int multiPKey(final MultiPKeyEntity entity) {
-        __db.assertNotSuspendingTransaction();
-        int _total = 0;
-        __db.beginTransaction();
-        try {
-            _total += __updateAdapterOfMultiPKeyEntity.handle(entity);
-            __db.setTransactionSuccessful();
-            return _total;
-        } finally {
-            __db.endTransaction();
-        }
+        return DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Integer>() {
+            @Override
+            @NonNull
+            public Integer invoke(@NonNull final SQLiteConnection _connection) {
+                int _result = 0;
+                _result += __updateAdapterOfMultiPKeyEntity.handle(_connection, entity);
+                return _result;
+            }
+        });
     }
 
     @Override
     public void updateUserAndBook(final User user, final Book book) {
-        __db.assertNotSuspendingTransaction();
-        __db.beginTransaction();
-        try {
-            __updateAdapterOfUser.handle(user);
-            __updateAdapterOfBook.handle(book);
-            __db.setTransactionSuccessful();
-        } finally {
-            __db.endTransaction();
-        }
+        DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
+            @Override
+            @NonNull
+            public Void invoke(@NonNull final SQLiteConnection _connection) {
+                __updateAdapterOfUser.handle(_connection, user);
+                __updateAdapterOfBook.handle(_connection, book);
+                return null;
+            }
+        });
     }
 
     @Override
diff --git a/room/room-compiler/src/test/test-data/daoWriter/output/ksp/UpsertDao.java b/room/room-compiler/src/test/test-data/daoWriter/output/ksp/UpsertDao.java
index c169994..7159ce2 100644
--- a/room/room-compiler/src/test/test-data/daoWriter/output/ksp/UpsertDao.java
+++ b/room/room-compiler/src/test/test-data/daoWriter/output/ksp/UpsertDao.java
@@ -1,31 +1,36 @@
 package foo.bar;
 
 import androidx.annotation.NonNull;
-import androidx.room.EntityDeletionOrUpdateAdapter;
-import androidx.room.EntityInsertionAdapter;
-import androidx.room.EntityUpsertionAdapter;
+import androidx.room.EntityDeleteOrUpdateAdapter;
+import androidx.room.EntityInsertAdapter;
+import androidx.room.EntityUpsertAdapter;
 import androidx.room.RoomDatabase;
-import androidx.sqlite.db.SupportSQLiteStatement;
+import androidx.room.util.DBUtil;
+import androidx.sqlite.SQLiteConnection;
+import androidx.sqlite.SQLiteStatement;
 import java.lang.Class;
+import java.lang.Long;
 import java.lang.Override;
 import java.lang.String;
 import java.lang.SuppressWarnings;
+import java.lang.Void;
 import java.util.Collections;
 import java.util.List;
 import javax.annotation.processing.Generated;
+import kotlin.jvm.functions.Function1;
 
 @Generated("androidx.room.RoomProcessor")
 @SuppressWarnings({"unchecked", "deprecation"})
 public final class UpsertDao_Impl implements UpsertDao {
     private final RoomDatabase __db;
 
-    private final EntityUpsertionAdapter<User> __upsertAdapterOfUser;
+    private final EntityUpsertAdapter<User> __upsertAdapterOfUser;
 
-    private final EntityUpsertionAdapter<Book> __upsertAdapterOfBook;
+    private final EntityUpsertAdapter<Book> __upsertAdapterOfBook;
 
     public UpsertDao_Impl(@NonNull final RoomDatabase __db) {
         this.__db = __db;
-        this.__upsertAdapterOfUser = new EntityUpsertionAdapter<User>(new EntityInsertionAdapter<User>(__db) {
+        this.__upsertAdapterOfUser = new EntityUpsertAdapter<User>(new EntityInsertAdapter<User>() {
             @Override
             @NonNull
             protected String createQuery() {
@@ -33,14 +38,13 @@
             }
 
             @Override
-            protected void bind(@NonNull final SupportSQLiteStatement statement,
-                    @NonNull final User entity) {
+            protected void bind(@NonNull final SQLiteStatement statement, @NonNull final User entity) {
                 statement.bindLong(1, entity.uid);
-                statement.bindString(2, entity.name);
-                statement.bindString(3, entity.getLastName());
+                statement.bindText(2, entity.name);
+                statement.bindText(3, entity.getLastName());
                 statement.bindLong(4, entity.age);
             }
-        }, new EntityDeletionOrUpdateAdapter<User>(__db) {
+        }, new EntityDeleteOrUpdateAdapter<User>() {
             @Override
             @NonNull
             protected String createQuery() {
@@ -48,16 +52,15 @@
             }
 
             @Override
-            protected void bind(@NonNull final SupportSQLiteStatement statement,
-                    @NonNull final User entity) {
+            protected void bind(@NonNull final SQLiteStatement statement, @NonNull final User entity) {
                 statement.bindLong(1, entity.uid);
-                statement.bindString(2, entity.name);
-                statement.bindString(3, entity.getLastName());
+                statement.bindText(2, entity.name);
+                statement.bindText(3, entity.getLastName());
                 statement.bindLong(4, entity.age);
                 statement.bindLong(5, entity.uid);
             }
         });
-        this.__upsertAdapterOfBook = new EntityUpsertionAdapter<Book>(new EntityInsertionAdapter<Book>(__db) {
+        this.__upsertAdapterOfBook = new EntityUpsertAdapter<Book>(new EntityInsertAdapter<Book>() {
             @Override
             @NonNull
             protected String createQuery() {
@@ -65,12 +68,11 @@
             }
 
             @Override
-            protected void bind(@NonNull final SupportSQLiteStatement statement,
-                    @NonNull final Book entity) {
+            protected void bind(@NonNull final SQLiteStatement statement, @NonNull final Book entity) {
                 statement.bindLong(1, entity.bookId);
                 statement.bindLong(2, entity.uid);
             }
-        }, new EntityDeletionOrUpdateAdapter<Book>(__db) {
+        }, new EntityDeleteOrUpdateAdapter<Book>() {
             @Override
             @NonNull
             protected String createQuery() {
@@ -78,8 +80,7 @@
             }
 
             @Override
-            protected void bind(@NonNull final SupportSQLiteStatement statement,
-                    @NonNull final Book entity) {
+            protected void bind(@NonNull final SQLiteStatement statement, @NonNull final Book entity) {
                 statement.bindLong(1, entity.bookId);
                 statement.bindLong(2, entity.uid);
                 statement.bindLong(3, entity.bookId);
@@ -89,91 +90,87 @@
 
     @Override
     public void upsertUser(final User user) {
-        __db.assertNotSuspendingTransaction();
-        __db.beginTransaction();
-        try {
-            __upsertAdapterOfUser.upsert(user);
-            __db.setTransactionSuccessful();
-        } finally {
-            __db.endTransaction();
-        }
+        DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
+            @Override
+            @NonNull
+            public Void invoke(@NonNull final SQLiteConnection _connection) {
+                __upsertAdapterOfUser.upsert(_connection, user);
+                return null;
+            }
+        });
     }
 
     @Override
     public void upsertUsers(final User user1, final List<User> others) {
-        __db.assertNotSuspendingTransaction();
-        __db.beginTransaction();
-        try {
-            __upsertAdapterOfUser.upsert(user1);
-            __upsertAdapterOfUser.upsert(others);
-            __db.setTransactionSuccessful();
-        } finally {
-            __db.endTransaction();
-        }
+        DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
+            @Override
+            @NonNull
+            public Void invoke(@NonNull final SQLiteConnection _connection) {
+                __upsertAdapterOfUser.upsert(_connection, user1);
+                __upsertAdapterOfUser.upsert(_connection, others);
+                return null;
+            }
+        });
     }
 
     @Override
     public void upsertUsers(final User[] users) {
-        __db.assertNotSuspendingTransaction();
-        __db.beginTransaction();
-        try {
-            __upsertAdapterOfUser.upsert(users);
-            __db.setTransactionSuccessful();
-        } finally {
-            __db.endTransaction();
-        }
+        DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
+            @Override
+            @NonNull
+            public Void invoke(@NonNull final SQLiteConnection _connection) {
+                __upsertAdapterOfUser.upsert(_connection, users);
+                return null;
+            }
+        });
     }
 
     @Override
     public void upsertTwoUsers(final User userOne, final User userTwo) {
-        __db.assertNotSuspendingTransaction();
-        __db.beginTransaction();
-        try {
-            __upsertAdapterOfUser.upsert(userOne);
-            __upsertAdapterOfUser.upsert(userTwo);
-            __db.setTransactionSuccessful();
-        } finally {
-            __db.endTransaction();
-        }
+        DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
+            @Override
+            @NonNull
+            public Void invoke(@NonNull final SQLiteConnection _connection) {
+                __upsertAdapterOfUser.upsert(_connection, userOne);
+                __upsertAdapterOfUser.upsert(_connection, userTwo);
+                return null;
+            }
+        });
     }
 
     @Override
     public void upsertUserAndBook(final User user, final Book book) {
-        __db.assertNotSuspendingTransaction();
-        __db.beginTransaction();
-        try {
-            __upsertAdapterOfUser.upsert(user);
-            __upsertAdapterOfBook.upsert(book);
-            __db.setTransactionSuccessful();
-        } finally {
-            __db.endTransaction();
-        }
+        DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
+            @Override
+            @NonNull
+            public Void invoke(@NonNull final SQLiteConnection _connection) {
+                __upsertAdapterOfUser.upsert(_connection, user);
+                __upsertAdapterOfBook.upsert(_connection, book);
+                return null;
+            }
+        });
     }
 
     @Override
     public long upsertAndReturnId(final User user) {
-        __db.assertNotSuspendingTransaction();
-        __db.beginTransaction();
-        try {
-            final long _result = __upsertAdapterOfUser.upsertAndReturnId(user);
-            __db.setTransactionSuccessful();
-            return _result;
-        } finally {
-            __db.endTransaction();
-        }
+        return DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Long>() {
+            @Override
+            @NonNull
+            public Long invoke(@NonNull final SQLiteConnection _connection) {
+                return __upsertAdapterOfUser.upsertAndReturnId(_connection, user);
+            }
+        });
     }
 
     @Override
     public long[] upsertAndReturnIdsArray(final User[] users) {
-        __db.assertNotSuspendingTransaction();
-        __db.beginTransaction();
-        try {
-            final long[] _result = __upsertAdapterOfUser.upsertAndReturnIdsArray(users);
-            __db.setTransactionSuccessful();
-            return _result;
-        } finally {
-            __db.endTransaction();
-        }
+        return DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, long[]>() {
+            @Override
+            @NonNull
+            public long[] invoke(@NonNull final SQLiteConnection _connection) {
+                return __upsertAdapterOfUser.upsertAndReturnIdsArray(_connection, users);
+            }
+        });
     }
 
     @NonNull
diff --git a/room/room-compiler/src/test/test-data/daoWriter/output/ksp/WriterDao.java b/room/room-compiler/src/test/test-data/daoWriter/output/ksp/WriterDao.java
index 83d3b7a..9e76c4c 100644
--- a/room/room-compiler/src/test/test-data/daoWriter/output/ksp/WriterDao.java
+++ b/room/room-compiler/src/test/test-data/daoWriter/output/ksp/WriterDao.java
@@ -1,33 +1,37 @@
 package foo.bar;
 
 import androidx.annotation.NonNull;
-import androidx.room.EntityInsertionAdapter;
+import androidx.room.EntityInsertAdapter;
 import androidx.room.RoomDatabase;
-import androidx.sqlite.db.SupportSQLiteStatement;
+import androidx.room.util.DBUtil;
+import androidx.sqlite.SQLiteConnection;
+import androidx.sqlite.SQLiteStatement;
 import java.lang.Class;
 import java.lang.Override;
 import java.lang.String;
 import java.lang.SuppressWarnings;
+import java.lang.Void;
 import java.util.Collections;
 import java.util.List;
 import javax.annotation.processing.Generated;
+import kotlin.jvm.functions.Function1;
 
 @Generated("androidx.room.RoomProcessor")
 @SuppressWarnings({"unchecked", "deprecation"})
 public final class WriterDao_Impl implements WriterDao {
     private final RoomDatabase __db;
 
-    private final EntityInsertionAdapter<User> __insertAdapterOfUser;
+    private final EntityInsertAdapter<User> __insertAdapterOfUser;
 
-    private final EntityInsertionAdapter<User> __insertAdapterOfUser_1;
+    private final EntityInsertAdapter<User> __insertAdapterOfUser_1;
 
-    private final EntityInsertionAdapter<User> __insertAdapterOfUser_2;
+    private final EntityInsertAdapter<User> __insertAdapterOfUser_2;
 
-    private final EntityInsertionAdapter<Book> __insertAdapterOfBook;
+    private final EntityInsertAdapter<Book> __insertAdapterOfBook;
 
     public WriterDao_Impl(@NonNull final RoomDatabase __db) {
         this.__db = __db;
-        this.__insertAdapterOfUser = new EntityInsertionAdapter<User>(__db) {
+        this.__insertAdapterOfUser = new EntityInsertAdapter<User>() {
             @Override
             @NonNull
             protected String createQuery() {
@@ -35,15 +39,14 @@
             }
 
             @Override
-            protected void bind(@NonNull final SupportSQLiteStatement statement,
-                    @NonNull final User entity) {
+            protected void bind(@NonNull final SQLiteStatement statement, @NonNull final User entity) {
                 statement.bindLong(1, entity.uid);
-                statement.bindString(2, entity.name);
-                statement.bindString(3, entity.getLastName());
+                statement.bindText(2, entity.name);
+                statement.bindText(3, entity.getLastName());
                 statement.bindLong(4, entity.age);
             }
         };
-        this.__insertAdapterOfUser_1 = new EntityInsertionAdapter<User>(__db) {
+        this.__insertAdapterOfUser_1 = new EntityInsertAdapter<User>() {
             @Override
             @NonNull
             protected String createQuery() {
@@ -51,15 +54,14 @@
             }
 
             @Override
-            protected void bind(@NonNull final SupportSQLiteStatement statement,
-                    @NonNull final User entity) {
+            protected void bind(@NonNull final SQLiteStatement statement, @NonNull final User entity) {
                 statement.bindLong(1, entity.uid);
-                statement.bindString(2, entity.name);
-                statement.bindString(3, entity.getLastName());
+                statement.bindText(2, entity.name);
+                statement.bindText(3, entity.getLastName());
                 statement.bindLong(4, entity.age);
             }
         };
-        this.__insertAdapterOfUser_2 = new EntityInsertionAdapter<User>(__db) {
+        this.__insertAdapterOfUser_2 = new EntityInsertAdapter<User>() {
             @Override
             @NonNull
             protected String createQuery() {
@@ -67,15 +69,14 @@
             }
 
             @Override
-            protected void bind(@NonNull final SupportSQLiteStatement statement,
-                    @NonNull final User entity) {
+            protected void bind(@NonNull final SQLiteStatement statement, @NonNull final User entity) {
                 statement.bindLong(1, entity.uid);
-                statement.bindString(2, entity.name);
-                statement.bindString(3, entity.getLastName());
+                statement.bindText(2, entity.name);
+                statement.bindText(3, entity.getLastName());
                 statement.bindLong(4, entity.age);
             }
         };
-        this.__insertAdapterOfBook = new EntityInsertionAdapter<Book>(__db) {
+        this.__insertAdapterOfBook = new EntityInsertAdapter<Book>() {
             @Override
             @NonNull
             protected String createQuery() {
@@ -83,8 +84,7 @@
             }
 
             @Override
-            protected void bind(@NonNull final SupportSQLiteStatement statement,
-                    @NonNull final Book entity) {
+            protected void bind(@NonNull final SQLiteStatement statement, @NonNull final Book entity) {
                 statement.bindLong(1, entity.bookId);
                 statement.bindLong(2, entity.uid);
             }
@@ -93,65 +93,65 @@
 
     @Override
     public void insertUser(final User user) {
-        __db.assertNotSuspendingTransaction();
-        __db.beginTransaction();
-        try {
-            __insertAdapterOfUser.insert(user);
-            __db.setTransactionSuccessful();
-        } finally {
-            __db.endTransaction();
-        }
+        DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
+            @Override
+            @NonNull
+            public Void invoke(@NonNull final SQLiteConnection _connection) {
+                __insertAdapterOfUser.insert(_connection, user);
+                return null;
+            }
+        });
     }
 
     @Override
     public void insertUsers(final User user1, final List<User> others) {
-        __db.assertNotSuspendingTransaction();
-        __db.beginTransaction();
-        try {
-            __insertAdapterOfUser.insert(user1);
-            __insertAdapterOfUser.insert(others);
-            __db.setTransactionSuccessful();
-        } finally {
-            __db.endTransaction();
-        }
+        DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
+            @Override
+            @NonNull
+            public Void invoke(@NonNull final SQLiteConnection _connection) {
+                __insertAdapterOfUser.insert(_connection, user1);
+                __insertAdapterOfUser.insert(_connection, others);
+                return null;
+            }
+        });
     }
 
     @Override
     public void insertUsers(final User[] users) {
-        __db.assertNotSuspendingTransaction();
-        __db.beginTransaction();
-        try {
-            __insertAdapterOfUser_1.insert(users);
-            __db.setTransactionSuccessful();
-        } finally {
-            __db.endTransaction();
-        }
+        DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
+            @Override
+            @NonNull
+            public Void invoke(@NonNull final SQLiteConnection _connection) {
+                __insertAdapterOfUser_1.insert(_connection, users);
+                return null;
+            }
+        });
     }
 
     @Override
     public void insertTwoUsers(final User userOne, final User userTwo) {
-        __db.assertNotSuspendingTransaction();
-        __db.beginTransaction();
-        try {
-            __insertAdapterOfUser_2.insert(userOne);
-            __insertAdapterOfUser_2.insert(userTwo);
-            __db.setTransactionSuccessful();
-        } finally {
-            __db.endTransaction();
-        }
+        DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
+            @Override
+            @NonNull
+            public Void invoke(@NonNull final SQLiteConnection _connection) {
+                __insertAdapterOfUser_2.insert(_connection, userOne);
+                __insertAdapterOfUser_2.insert(_connection, userTwo);
+                return null;
+            }
+        });
     }
 
     @Override
     public void insertUserAndBook(final User user, final Book book) {
-        __db.assertNotSuspendingTransaction();
-        __db.beginTransaction();
-        try {
-            __insertAdapterOfUser.insert(user);
-            __insertAdapterOfBook.insert(book);
-            __db.setTransactionSuccessful();
-        } finally {
-            __db.endTransaction();
-        }
+        DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
+            @Override
+            @NonNull
+            public Void invoke(@NonNull final SQLiteConnection _connection) {
+                __insertAdapterOfUser.insert(_connection, user);
+                __insertAdapterOfBook.insert(_connection, book);
+                return null;
+            }
+        });
     }
 
     @NonNull
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/deleteOrUpdateMethodAdapter.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/deleteOrUpdateMethodAdapter.kt
index d702c28..28b4f49 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/deleteOrUpdateMethodAdapter.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/deleteOrUpdateMethodAdapter.kt
@@ -1,10 +1,12 @@
-import androidx.room.EntityDeletionOrUpdateAdapter
+import androidx.room.EntityDeleteOrUpdateAdapter
 import androidx.room.RoomDatabase
-import androidx.sqlite.db.SupportSQLiteStatement
+import androidx.room.util.performBlocking
+import androidx.sqlite.SQLiteStatement
 import javax.`annotation`.processing.Generated
 import kotlin.Int
 import kotlin.String
 import kotlin.Suppress
+import kotlin.Unit
 import kotlin.collections.List
 import kotlin.reflect.KClass
 
@@ -15,76 +17,52 @@
 ) : MyDao {
   private val __db: RoomDatabase
 
-  private val __deleteAdapterOfMyEntity: EntityDeletionOrUpdateAdapter<MyEntity>
+  private val __deleteAdapterOfMyEntity: EntityDeleteOrUpdateAdapter<MyEntity>
 
-  private val __updateAdapterOfMyEntity: EntityDeletionOrUpdateAdapter<MyEntity>
+  private val __updateAdapterOfMyEntity: EntityDeleteOrUpdateAdapter<MyEntity>
   init {
     this.__db = __db
-    this.__deleteAdapterOfMyEntity = object : EntityDeletionOrUpdateAdapter<MyEntity>(__db) {
+    this.__deleteAdapterOfMyEntity = object : EntityDeleteOrUpdateAdapter<MyEntity>() {
       protected override fun createQuery(): String = "DELETE FROM `MyEntity` WHERE `pk` = ?"
 
-      protected override fun bind(statement: SupportSQLiteStatement, entity: MyEntity) {
+      protected override fun bind(statement: SQLiteStatement, entity: MyEntity) {
         statement.bindLong(1, entity.pk)
       }
     }
-    this.__updateAdapterOfMyEntity = object : EntityDeletionOrUpdateAdapter<MyEntity>(__db) {
+    this.__updateAdapterOfMyEntity = object : EntityDeleteOrUpdateAdapter<MyEntity>() {
       protected override fun createQuery(): String =
           "UPDATE OR ABORT `MyEntity` SET `pk` = ?,`data` = ? WHERE `pk` = ?"
 
-      protected override fun bind(statement: SupportSQLiteStatement, entity: MyEntity) {
+      protected override fun bind(statement: SQLiteStatement, entity: MyEntity) {
         statement.bindLong(1, entity.pk)
-        statement.bindString(2, entity.data)
+        statement.bindText(2, entity.data)
         statement.bindLong(3, entity.pk)
       }
     }
   }
 
-  public override fun deleteEntity(item: MyEntity) {
-    __db.assertNotSuspendingTransaction()
-    __db.beginTransaction()
-    try {
-      __deleteAdapterOfMyEntity.handle(item)
-      __db.setTransactionSuccessful()
-    } finally {
-      __db.endTransaction()
-    }
+  public override fun deleteEntity(item: MyEntity): Unit = performBlocking(__db, false, true) {
+      _connection ->
+    __deleteAdapterOfMyEntity.handle(_connection, item)
   }
 
-  public override fun deleteEntityAndReturnCount(item: MyEntity): Int {
-    __db.assertNotSuspendingTransaction()
-    var _total: Int = 0
-    __db.beginTransaction()
-    try {
-      _total += __deleteAdapterOfMyEntity.handle(item)
-      __db.setTransactionSuccessful()
-      return _total
-    } finally {
-      __db.endTransaction()
-    }
+  public override fun deleteEntityAndReturnCount(item: MyEntity): Int = performBlocking(__db, false,
+      true) { _connection ->
+    var _result: Int = 0
+    _result += __deleteAdapterOfMyEntity.handle(_connection, item)
+    _result
   }
 
-  public override fun updateEntity(item: MyEntity) {
-    __db.assertNotSuspendingTransaction()
-    __db.beginTransaction()
-    try {
-      __updateAdapterOfMyEntity.handle(item)
-      __db.setTransactionSuccessful()
-    } finally {
-      __db.endTransaction()
-    }
+  public override fun updateEntity(item: MyEntity): Unit = performBlocking(__db, false, true) {
+      _connection ->
+    __updateAdapterOfMyEntity.handle(_connection, item)
   }
 
-  public override fun updateEntityAndReturnCount(item: MyEntity): Int {
-    __db.assertNotSuspendingTransaction()
-    var _total: Int = 0
-    __db.beginTransaction()
-    try {
-      _total += __updateAdapterOfMyEntity.handle(item)
-      __db.setTransactionSuccessful()
-      return _total
-    } finally {
-      __db.endTransaction()
-    }
+  public override fun updateEntityAndReturnCount(item: MyEntity): Int = performBlocking(__db, false,
+      true) { _connection ->
+    var _result: Int = 0
+    _result += __updateAdapterOfMyEntity.handle(_connection, item)
+    _result
   }
 
   public companion object {
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/entityRowAdapter.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/entityRowAdapter.kt
index 462d3d1..22ab7d3 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/entityRowAdapter.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/entityRowAdapter.kt
@@ -1,17 +1,19 @@
 import android.database.Cursor
-import androidx.room.EntityInsertionAdapter
+import androidx.room.EntityInsertAdapter
 import androidx.room.RoomDatabase
 import androidx.room.RoomSQLiteQuery
 import androidx.room.RoomSQLiteQuery.Companion.acquire
 import androidx.room.util.getColumnIndex
+import androidx.room.util.performBlocking
 import androidx.room.util.query
-import androidx.sqlite.db.SupportSQLiteStatement
+import androidx.sqlite.SQLiteStatement
 import javax.`annotation`.processing.Generated
 import kotlin.Boolean
 import kotlin.Int
 import kotlin.Long
 import kotlin.String
 import kotlin.Suppress
+import kotlin.Unit
 import kotlin.collections.List
 import kotlin.reflect.KClass
 
@@ -22,23 +24,23 @@
 ) : MyDao {
   private val __db: RoomDatabase
 
-  private val __insertAdapterOfMyEntity: EntityInsertionAdapter<MyEntity>
+  private val __insertAdapterOfMyEntity: EntityInsertAdapter<MyEntity>
   init {
     this.__db = __db
-    this.__insertAdapterOfMyEntity = object : EntityInsertionAdapter<MyEntity>(__db) {
+    this.__insertAdapterOfMyEntity = object : EntityInsertAdapter<MyEntity>() {
       protected override fun createQuery(): String =
           "INSERT OR ABORT INTO `MyEntity` (`valuePrimitive`,`valueBoolean`,`valueString`,`valueNullableString`,`variablePrimitive`,`variableNullableBoolean`,`variableString`,`variableNullableString`) VALUES (?,?,?,?,?,?,?,?)"
 
-      protected override fun bind(statement: SupportSQLiteStatement, entity: MyEntity) {
+      protected override fun bind(statement: SQLiteStatement, entity: MyEntity) {
         statement.bindLong(1, entity.valuePrimitive)
         val _tmp: Int = if (entity.valueBoolean) 1 else 0
         statement.bindLong(2, _tmp.toLong())
-        statement.bindString(3, entity.valueString)
+        statement.bindText(3, entity.valueString)
         val _tmpValueNullableString: String? = entity.valueNullableString
         if (_tmpValueNullableString == null) {
           statement.bindNull(4)
         } else {
-          statement.bindString(4, _tmpValueNullableString)
+          statement.bindText(4, _tmpValueNullableString)
         }
         statement.bindLong(5, entity.variablePrimitive)
         val _tmpVariableNullableBoolean: Boolean? = entity.variableNullableBoolean
@@ -48,26 +50,20 @@
         } else {
           statement.bindLong(6, _tmp_1.toLong())
         }
-        statement.bindString(7, entity.variableString)
+        statement.bindText(7, entity.variableString)
         val _tmpVariableNullableString: String? = entity.variableNullableString
         if (_tmpVariableNullableString == null) {
           statement.bindNull(8)
         } else {
-          statement.bindString(8, _tmpVariableNullableString)
+          statement.bindText(8, _tmpVariableNullableString)
         }
       }
     }
   }
 
-  public override fun addEntity(item: MyEntity) {
-    __db.assertNotSuspendingTransaction()
-    __db.beginTransaction()
-    try {
-      __insertAdapterOfMyEntity.insert(item)
-      __db.setTransactionSuccessful()
-    } finally {
-      __db.endTransaction()
-    }
+  public override fun addEntity(item: MyEntity): Unit = performBlocking(__db, false, true) {
+      _connection ->
+    __insertAdapterOfMyEntity.insert(_connection, item)
   }
 
   public override fun getEntity(): MyEntity {
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/guavaCallable.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/guavaCallable.kt
index ae37dd9..d69157d 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/guavaCallable.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/guavaCallable.kt
@@ -29,16 +29,16 @@
 ) : MyDao {
   private val __db: RoomDatabase
 
-  private val __insertAdapterOfMyEntity: EntityInsertionAdapter<MyEntity>
+  private val __insertionAdapterOfMyEntity: EntityInsertionAdapter<MyEntity>
 
-  private val __deleteAdapterOfMyEntity: EntityDeletionOrUpdateAdapter<MyEntity>
+  private val __deleteCompatAdapterOfMyEntity: EntityDeletionOrUpdateAdapter<MyEntity>
 
-  private val __updateAdapterOfMyEntity: EntityDeletionOrUpdateAdapter<MyEntity>
+  private val __updateCompatAdapterOfMyEntity: EntityDeletionOrUpdateAdapter<MyEntity>
 
-  private val __upsertAdapterOfMyEntity: EntityUpsertionAdapter<MyEntity>
+  private val __upsertionAdapterOfMyEntity: EntityUpsertionAdapter<MyEntity>
   init {
     this.__db = __db
-    this.__insertAdapterOfMyEntity = object : EntityInsertionAdapter<MyEntity>(__db) {
+    this.__insertionAdapterOfMyEntity = object : EntityInsertionAdapter<MyEntity>(__db) {
       protected override fun createQuery(): String =
           "INSERT OR ABORT INTO `MyEntity` (`pk`,`other`) VALUES (?,?)"
 
@@ -47,14 +47,14 @@
         statement.bindString(2, entity.other)
       }
     }
-    this.__deleteAdapterOfMyEntity = object : EntityDeletionOrUpdateAdapter<MyEntity>(__db) {
+    this.__deleteCompatAdapterOfMyEntity = object : EntityDeletionOrUpdateAdapter<MyEntity>(__db) {
       protected override fun createQuery(): String = "DELETE FROM `MyEntity` WHERE `pk` = ?"
 
       protected override fun bind(statement: SupportSQLiteStatement, entity: MyEntity) {
         statement.bindLong(1, entity.pk.toLong())
       }
     }
-    this.__updateAdapterOfMyEntity = object : EntityDeletionOrUpdateAdapter<MyEntity>(__db) {
+    this.__updateCompatAdapterOfMyEntity = object : EntityDeletionOrUpdateAdapter<MyEntity>(__db) {
       protected override fun createQuery(): String =
           "UPDATE OR ABORT `MyEntity` SET `pk` = ?,`other` = ? WHERE `pk` = ?"
 
@@ -64,7 +64,7 @@
         statement.bindLong(3, entity.pk.toLong())
       }
     }
-    this.__upsertAdapterOfMyEntity = EntityUpsertionAdapter<MyEntity>(object :
+    this.__upsertionAdapterOfMyEntity = EntityUpsertionAdapter<MyEntity>(object :
         EntityInsertionAdapter<MyEntity>(__db) {
       protected override fun createQuery(): String =
           "INSERT INTO `MyEntity` (`pk`,`other`) VALUES (?,?)"
@@ -91,7 +91,7 @@
     public override fun call(): List<Long> {
       __db.beginTransaction()
       try {
-        val _result: List<Long> = __insertAdapterOfMyEntity.insertAndReturnIdsList(entities)
+        val _result: List<Long> = __insertionAdapterOfMyEntity.insertAndReturnIdsList(entities)
         __db.setTransactionSuccessful()
         return _result
       } finally {
@@ -106,7 +106,7 @@
       var _total: Int = 0
       __db.beginTransaction()
       try {
-        _total += __deleteAdapterOfMyEntity.handle(entity)
+        _total += __deleteCompatAdapterOfMyEntity.handle(entity)
         __db.setTransactionSuccessful()
         return _total
       } finally {
@@ -121,7 +121,7 @@
       var _total: Int = 0
       __db.beginTransaction()
       try {
-        _total += __updateAdapterOfMyEntity.handle(entity)
+        _total += __updateCompatAdapterOfMyEntity.handle(entity)
         __db.setTransactionSuccessful()
         return _total
       } finally {
@@ -136,7 +136,7 @@
     public override fun call(): List<Long> {
       __db.beginTransaction()
       try {
-        val _result: List<Long> = __upsertAdapterOfMyEntity.upsertAndReturnIdsList(entities)
+        val _result: List<Long> = __upsertionAdapterOfMyEntity.upsertAndReturnIdsList(entities)
         __db.setTransactionSuccessful()
         return _result
       } finally {
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/insertOrUpsertMethodAdapter.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/insertOrUpsertMethodAdapter.kt
index 9fcec8d..99f78c2 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/insertOrUpsertMethodAdapter.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/insertOrUpsertMethodAdapter.kt
@@ -1,13 +1,15 @@
-import androidx.room.EntityDeletionOrUpdateAdapter
-import androidx.room.EntityInsertionAdapter
-import androidx.room.EntityUpsertionAdapter
+import androidx.room.EntityDeleteOrUpdateAdapter
+import androidx.room.EntityInsertAdapter
+import androidx.room.EntityUpsertAdapter
 import androidx.room.RoomDatabase
-import androidx.sqlite.db.SupportSQLiteStatement
+import androidx.room.util.performBlocking
+import androidx.sqlite.SQLiteStatement
 import javax.`annotation`.processing.Generated
 import kotlin.Array
 import kotlin.Long
 import kotlin.String
 import kotlin.Suppress
+import kotlin.Unit
 import kotlin.collections.List
 import kotlin.reflect.KClass
 
@@ -18,135 +20,87 @@
 ) : MyDao {
   private val __db: RoomDatabase
 
-  private val __insertAdapterOfMyEntity: EntityInsertionAdapter<MyEntity>
+  private val __insertAdapterOfMyEntity: EntityInsertAdapter<MyEntity>
 
-  private val __upsertAdapterOfMyEntity: EntityUpsertionAdapter<MyEntity>
+  private val __upsertAdapterOfMyEntity: EntityUpsertAdapter<MyEntity>
   init {
     this.__db = __db
-    this.__insertAdapterOfMyEntity = object : EntityInsertionAdapter<MyEntity>(__db) {
+    this.__insertAdapterOfMyEntity = object : EntityInsertAdapter<MyEntity>() {
       protected override fun createQuery(): String =
           "INSERT OR ABORT INTO `MyEntity` (`pk`,`data`) VALUES (?,?)"
 
-      protected override fun bind(statement: SupportSQLiteStatement, entity: MyEntity) {
+      protected override fun bind(statement: SQLiteStatement, entity: MyEntity) {
         statement.bindLong(1, entity.pk)
-        statement.bindString(2, entity.data)
+        statement.bindText(2, entity.data)
       }
     }
-    this.__upsertAdapterOfMyEntity = EntityUpsertionAdapter<MyEntity>(object :
-        EntityInsertionAdapter<MyEntity>(__db) {
+    this.__upsertAdapterOfMyEntity = EntityUpsertAdapter<MyEntity>(object :
+        EntityInsertAdapter<MyEntity>() {
       protected override fun createQuery(): String =
           "INSERT INTO `MyEntity` (`pk`,`data`) VALUES (?,?)"
 
-      protected override fun bind(statement: SupportSQLiteStatement, entity: MyEntity) {
+      protected override fun bind(statement: SQLiteStatement, entity: MyEntity) {
         statement.bindLong(1, entity.pk)
-        statement.bindString(2, entity.data)
+        statement.bindText(2, entity.data)
       }
-    }, object : EntityDeletionOrUpdateAdapter<MyEntity>(__db) {
+    }, object : EntityDeleteOrUpdateAdapter<MyEntity>() {
       protected override fun createQuery(): String =
           "UPDATE `MyEntity` SET `pk` = ?,`data` = ? WHERE `pk` = ?"
 
-      protected override fun bind(statement: SupportSQLiteStatement, entity: MyEntity) {
+      protected override fun bind(statement: SQLiteStatement, entity: MyEntity) {
         statement.bindLong(1, entity.pk)
-        statement.bindString(2, entity.data)
+        statement.bindText(2, entity.data)
         statement.bindLong(3, entity.pk)
       }
     })
   }
 
-  public override fun insertEntity(item: MyEntity) {
-    __db.assertNotSuspendingTransaction()
-    __db.beginTransaction()
-    try {
-      __insertAdapterOfMyEntity.insert(item)
-      __db.setTransactionSuccessful()
-    } finally {
-      __db.endTransaction()
-    }
+  public override fun insertEntity(item: MyEntity): Unit = performBlocking(__db, false, true) {
+      _connection ->
+    __insertAdapterOfMyEntity.insert(_connection, item)
   }
 
-  public override fun insertEntityAndReturnRowId(item: MyEntity): Long {
-    __db.assertNotSuspendingTransaction()
-    __db.beginTransaction()
-    try {
-      val _result: Long = __insertAdapterOfMyEntity.insertAndReturnId(item)
-      __db.setTransactionSuccessful()
-      return _result
-    } finally {
-      __db.endTransaction()
-    }
+  public override fun insertEntityAndReturnRowId(item: MyEntity): Long = performBlocking(__db,
+      false, true) { _connection ->
+    val _result: Long = __insertAdapterOfMyEntity.insertAndReturnId(_connection, item)
+    _result
   }
 
-  public override fun insertEntityListAndReturnRowIds(items: List<MyEntity>): List<Long> {
-    __db.assertNotSuspendingTransaction()
-    __db.beginTransaction()
-    try {
-      val _result: List<Long> = __insertAdapterOfMyEntity.insertAndReturnIdsList(items)
-      __db.setTransactionSuccessful()
-      return _result
-    } finally {
-      __db.endTransaction()
-    }
+  public override fun insertEntityListAndReturnRowIds(items: List<MyEntity>): List<Long> =
+      performBlocking(__db, false, true) { _connection ->
+    val _result: List<Long> = __insertAdapterOfMyEntity.insertAndReturnIdsList(_connection, items)
+    _result
   }
 
-  public override fun upsertEntity(item: MyEntity) {
-    __db.assertNotSuspendingTransaction()
-    __db.beginTransaction()
-    try {
-      __upsertAdapterOfMyEntity.upsert(item)
-      __db.setTransactionSuccessful()
-    } finally {
-      __db.endTransaction()
-    }
+  public override fun upsertEntity(item: MyEntity): Unit = performBlocking(__db, false, true) {
+      _connection ->
+    __upsertAdapterOfMyEntity.upsert(_connection, item)
   }
 
-  public override fun upsertEntityAndReturnRowId(item: MyEntity): Long {
-    __db.assertNotSuspendingTransaction()
-    __db.beginTransaction()
-    try {
-      val _result: Long = __upsertAdapterOfMyEntity.upsertAndReturnId(item)
-      __db.setTransactionSuccessful()
-      return _result
-    } finally {
-      __db.endTransaction()
-    }
+  public override fun upsertEntityAndReturnRowId(item: MyEntity): Long = performBlocking(__db,
+      false, true) { _connection ->
+    val _result: Long = __upsertAdapterOfMyEntity.upsertAndReturnId(_connection, item)
+    _result
   }
 
-  public override fun upsertEntityListAndReturnRowIds(items: List<MyEntity>): List<Long> {
-    __db.assertNotSuspendingTransaction()
-    __db.beginTransaction()
-    try {
-      val _result: List<Long> = __upsertAdapterOfMyEntity.upsertAndReturnIdsList(items)
-      __db.setTransactionSuccessful()
-      return _result
-    } finally {
-      __db.endTransaction()
-    }
+  public override fun upsertEntityListAndReturnRowIds(items: List<MyEntity>): List<Long> =
+      performBlocking(__db, false, true) { _connection ->
+    val _result: List<Long> = __upsertAdapterOfMyEntity.upsertAndReturnIdsList(_connection, items)
+    _result
   }
 
-  public override fun upsertEntityListAndReturnRowIdsArray(items: List<MyEntity>): Array<Long> {
-    __db.assertNotSuspendingTransaction()
-    __db.beginTransaction()
-    try {
-      val _result: Array<Long> = (__upsertAdapterOfMyEntity.upsertAndReturnIdsArrayBox(items)) as
-          Array<Long>
-      __db.setTransactionSuccessful()
-      return _result
-    } finally {
-      __db.endTransaction()
-    }
+  public override fun upsertEntityListAndReturnRowIdsArray(items: List<MyEntity>): Array<Long> =
+      performBlocking(__db, false, true) { _connection ->
+    val _result: Array<Long> = (__upsertAdapterOfMyEntity.upsertAndReturnIdsArrayBox(_connection,
+        items)) as Array<Long>
+    _result
   }
 
   public override fun upsertEntityListAndReturnRowIdsOutArray(items: List<MyEntity>):
-      Array<out Long> {
-    __db.assertNotSuspendingTransaction()
-    __db.beginTransaction()
-    try {
-      val _result: Array<out Long> = __upsertAdapterOfMyEntity.upsertAndReturnIdsArrayBox(items)
-      __db.setTransactionSuccessful()
-      return _result
-    } finally {
-      __db.endTransaction()
-    }
+      Array<out Long> = performBlocking(__db, false, true) { _connection ->
+    val _result: Array<out Long> = __upsertAdapterOfMyEntity.upsertAndReturnIdsArrayBox(_connection,
+        items)
+    _result
   }
 
   public companion object {
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_boolean.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_boolean.kt
index b1e1adc..c62a5af 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_boolean.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_boolean.kt
@@ -1,14 +1,14 @@
-import androidx.room.EntityInsertionAdapter
+import androidx.room.EntityInsertAdapter
 import androidx.room.RoomDatabase
 import androidx.room.util.getColumnIndexOrThrow
 import androidx.room.util.performBlocking
 import androidx.sqlite.SQLiteStatement
-import androidx.sqlite.db.SupportSQLiteStatement
 import javax.`annotation`.processing.Generated
 import kotlin.Boolean
 import kotlin.Int
 import kotlin.String
 import kotlin.Suppress
+import kotlin.Unit
 import kotlin.collections.List
 import kotlin.reflect.KClass
 
@@ -19,14 +19,14 @@
 ) : MyDao {
   private val __db: RoomDatabase
 
-  private val __insertAdapterOfMyEntity: EntityInsertionAdapter<MyEntity>
+  private val __insertAdapterOfMyEntity: EntityInsertAdapter<MyEntity>
   init {
     this.__db = __db
-    this.__insertAdapterOfMyEntity = object : EntityInsertionAdapter<MyEntity>(__db) {
+    this.__insertAdapterOfMyEntity = object : EntityInsertAdapter<MyEntity>() {
       protected override fun createQuery(): String =
           "INSERT OR ABORT INTO `MyEntity` (`pk`,`boolean`,`nullableBoolean`) VALUES (?,?,?)"
 
-      protected override fun bind(statement: SupportSQLiteStatement, entity: MyEntity) {
+      protected override fun bind(statement: SQLiteStatement, entity: MyEntity) {
         statement.bindLong(1, entity.pk.toLong())
         val _tmp: Int = if (entity.boolean) 1 else 0
         statement.bindLong(2, _tmp.toLong())
@@ -41,15 +41,9 @@
     }
   }
 
-  public override fun addEntity(item: MyEntity) {
-    __db.assertNotSuspendingTransaction()
-    __db.beginTransaction()
-    try {
-      __insertAdapterOfMyEntity.insert(item)
-      __db.setTransactionSuccessful()
-    } finally {
-      __db.endTransaction()
-    }
+  public override fun addEntity(item: MyEntity): Unit = performBlocking(__db, false, true) {
+      _connection ->
+    __insertAdapterOfMyEntity.insert(_connection, item)
   }
 
   public override fun getEntity(): MyEntity {
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_byteArray.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_byteArray.kt
index 18ff43b..7735646 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_byteArray.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_byteArray.kt
@@ -1,14 +1,14 @@
-import androidx.room.EntityInsertionAdapter
+import androidx.room.EntityInsertAdapter
 import androidx.room.RoomDatabase
 import androidx.room.util.getColumnIndexOrThrow
 import androidx.room.util.performBlocking
 import androidx.sqlite.SQLiteStatement
-import androidx.sqlite.db.SupportSQLiteStatement
 import javax.`annotation`.processing.Generated
 import kotlin.ByteArray
 import kotlin.Int
 import kotlin.String
 import kotlin.Suppress
+import kotlin.Unit
 import kotlin.collections.List
 import kotlin.reflect.KClass
 
@@ -19,14 +19,14 @@
 ) : MyDao {
   private val __db: RoomDatabase
 
-  private val __insertAdapterOfMyEntity: EntityInsertionAdapter<MyEntity>
+  private val __insertAdapterOfMyEntity: EntityInsertAdapter<MyEntity>
   init {
     this.__db = __db
-    this.__insertAdapterOfMyEntity = object : EntityInsertionAdapter<MyEntity>(__db) {
+    this.__insertAdapterOfMyEntity = object : EntityInsertAdapter<MyEntity>() {
       protected override fun createQuery(): String =
           "INSERT OR ABORT INTO `MyEntity` (`pk`,`byteArray`,`nullableByteArray`) VALUES (?,?,?)"
 
-      protected override fun bind(statement: SupportSQLiteStatement, entity: MyEntity) {
+      protected override fun bind(statement: SQLiteStatement, entity: MyEntity) {
         statement.bindLong(1, entity.pk.toLong())
         statement.bindBlob(2, entity.byteArray)
         val _tmpNullableByteArray: ByteArray? = entity.nullableByteArray
@@ -39,15 +39,9 @@
     }
   }
 
-  public override fun addEntity(item: MyEntity) {
-    __db.assertNotSuspendingTransaction()
-    __db.beginTransaction()
-    try {
-      __insertAdapterOfMyEntity.insert(item)
-      __db.setTransactionSuccessful()
-    } finally {
-      __db.endTransaction()
-    }
+  public override fun addEntity(item: MyEntity): Unit = performBlocking(__db, false, true) {
+      _connection ->
+    __insertAdapterOfMyEntity.insert(_connection, item)
   }
 
   public override fun getEntity(): MyEntity {
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_customTypeConverter.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_customTypeConverter.kt
index e2713a9..4b8fcef 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_customTypeConverter.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_customTypeConverter.kt
@@ -1,13 +1,13 @@
-import androidx.room.EntityInsertionAdapter
+import androidx.room.EntityInsertAdapter
 import androidx.room.RoomDatabase
 import androidx.room.util.getColumnIndexOrThrow
 import androidx.room.util.performBlocking
 import androidx.sqlite.SQLiteStatement
-import androidx.sqlite.db.SupportSQLiteStatement
 import javax.`annotation`.processing.Generated
 import kotlin.Int
 import kotlin.String
 import kotlin.Suppress
+import kotlin.Unit
 import kotlin.collections.List
 import kotlin.reflect.KClass
 
@@ -18,32 +18,26 @@
 ) : MyDao {
   private val __db: RoomDatabase
 
-  private val __insertAdapterOfMyEntity: EntityInsertionAdapter<MyEntity>
+  private val __insertAdapterOfMyEntity: EntityInsertAdapter<MyEntity>
 
   private val __fooConverter: FooConverter = FooConverter()
   init {
     this.__db = __db
-    this.__insertAdapterOfMyEntity = object : EntityInsertionAdapter<MyEntity>(__db) {
+    this.__insertAdapterOfMyEntity = object : EntityInsertAdapter<MyEntity>() {
       protected override fun createQuery(): String =
           "INSERT OR ABORT INTO `MyEntity` (`pk`,`foo`) VALUES (?,?)"
 
-      protected override fun bind(statement: SupportSQLiteStatement, entity: MyEntity) {
+      protected override fun bind(statement: SQLiteStatement, entity: MyEntity) {
         statement.bindLong(1, entity.pk.toLong())
         val _tmp: String = __fooConverter.toString(entity.foo)
-        statement.bindString(2, _tmp)
+        statement.bindText(2, _tmp)
       }
     }
   }
 
-  public override fun addEntity(item: MyEntity) {
-    __db.assertNotSuspendingTransaction()
-    __db.beginTransaction()
-    try {
-      __insertAdapterOfMyEntity.insert(item)
-      __db.setTransactionSuccessful()
-    } finally {
-      __db.endTransaction()
-    }
+  public override fun addEntity(item: MyEntity): Unit = performBlocking(__db, false, true) {
+      _connection ->
+    __insertAdapterOfMyEntity.insert(_connection, item)
   }
 
   public override fun getEntity(): MyEntity {
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_customTypeConverter_composite.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_customTypeConverter_composite.kt
index a559701..614a2b2 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_customTypeConverter_composite.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_customTypeConverter_composite.kt
@@ -1,13 +1,13 @@
-import androidx.room.EntityInsertionAdapter
+import androidx.room.EntityInsertAdapter
 import androidx.room.RoomDatabase
 import androidx.room.util.getColumnIndexOrThrow
 import androidx.room.util.performBlocking
 import androidx.sqlite.SQLiteStatement
-import androidx.sqlite.db.SupportSQLiteStatement
 import javax.`annotation`.processing.Generated
 import kotlin.Int
 import kotlin.String
 import kotlin.Suppress
+import kotlin.Unit
 import kotlin.collections.List
 import kotlin.reflect.KClass
 
@@ -18,31 +18,25 @@
 ) : MyDao {
   private val __db: RoomDatabase
 
-  private val __insertAdapterOfMyEntity: EntityInsertionAdapter<MyEntity>
+  private val __insertAdapterOfMyEntity: EntityInsertAdapter<MyEntity>
   init {
     this.__db = __db
-    this.__insertAdapterOfMyEntity = object : EntityInsertionAdapter<MyEntity>(__db) {
+    this.__insertAdapterOfMyEntity = object : EntityInsertAdapter<MyEntity>() {
       protected override fun createQuery(): String =
           "INSERT OR ABORT INTO `MyEntity` (`pk`,`bar`) VALUES (?,?)"
 
-      protected override fun bind(statement: SupportSQLiteStatement, entity: MyEntity) {
+      protected override fun bind(statement: SQLiteStatement, entity: MyEntity) {
         statement.bindLong(1, entity.pk.toLong())
         val _tmp: Foo = FooBarConverter.toFoo(entity.bar)
         val _tmp_1: String = FooBarConverter.toString(_tmp)
-        statement.bindString(2, _tmp_1)
+        statement.bindText(2, _tmp_1)
       }
     }
   }
 
-  public override fun addEntity(item: MyEntity) {
-    __db.assertNotSuspendingTransaction()
-    __db.beginTransaction()
-    try {
-      __insertAdapterOfMyEntity.insert(item)
-      __db.setTransactionSuccessful()
-    } finally {
-      __db.endTransaction()
-    }
+  public override fun addEntity(item: MyEntity): Unit = performBlocking(__db, false, true) {
+      _connection ->
+    __insertAdapterOfMyEntity.insert(_connection, item)
   }
 
   public override fun getEntity(): MyEntity {
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_customTypeConverter_internalVisibility.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_customTypeConverter_internalVisibility.kt
index 031c7fd..3136a41 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_customTypeConverter_internalVisibility.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_customTypeConverter_internalVisibility.kt
@@ -1,13 +1,13 @@
-import androidx.room.EntityInsertionAdapter
+import androidx.room.EntityInsertAdapter
 import androidx.room.RoomDatabase
 import androidx.room.util.getColumnIndexOrThrow
 import androidx.room.util.performBlocking
 import androidx.sqlite.SQLiteStatement
-import androidx.sqlite.db.SupportSQLiteStatement
 import javax.`annotation`.processing.Generated
 import kotlin.Int
 import kotlin.String
 import kotlin.Suppress
+import kotlin.Unit
 import kotlin.collections.List
 import kotlin.reflect.KClass
 
@@ -18,32 +18,26 @@
 ) : MyDao() {
   private val __db: RoomDatabase
 
-  private val __insertAdapterOfMyEntity: EntityInsertionAdapter<MyEntity>
+  private val __insertAdapterOfMyEntity: EntityInsertAdapter<MyEntity>
 
   private val __fooConverter: FooConverter = FooConverter()
   init {
     this.__db = __db
-    this.__insertAdapterOfMyEntity = object : EntityInsertionAdapter<MyEntity>(__db) {
+    this.__insertAdapterOfMyEntity = object : EntityInsertAdapter<MyEntity>() {
       protected override fun createQuery(): String =
           "INSERT OR ABORT INTO `MyEntity` (`pk`,`foo`) VALUES (?,?)"
 
-      protected override fun bind(statement: SupportSQLiteStatement, entity: MyEntity) {
+      protected override fun bind(statement: SQLiteStatement, entity: MyEntity) {
         statement.bindLong(1, entity.pk.toLong())
         val _tmp: String = __fooConverter.toString(entity.foo)
-        statement.bindString(2, _tmp)
+        statement.bindText(2, _tmp)
       }
     }
   }
 
-  internal override fun addEntity(item: MyEntity) {
-    __db.assertNotSuspendingTransaction()
-    __db.beginTransaction()
-    try {
-      __insertAdapterOfMyEntity.insert(item)
-      __db.setTransactionSuccessful()
-    } finally {
-      __db.endTransaction()
-    }
+  internal override fun addEntity(item: MyEntity): Unit = performBlocking(__db, false, true) {
+      _connection ->
+    __insertAdapterOfMyEntity.insert(_connection, item)
   }
 
   internal override fun getEntity(): MyEntity {
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_customTypeConverter_nullAware.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_customTypeConverter_nullAware.kt
index 0e2caeb..cfed9a8 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_customTypeConverter_nullAware.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_customTypeConverter_nullAware.kt
@@ -1,13 +1,13 @@
-import androidx.room.EntityInsertionAdapter
+import androidx.room.EntityInsertAdapter
 import androidx.room.RoomDatabase
 import androidx.room.util.getColumnIndexOrThrow
 import androidx.room.util.performBlocking
 import androidx.sqlite.SQLiteStatement
-import androidx.sqlite.db.SupportSQLiteStatement
 import javax.`annotation`.processing.Generated
 import kotlin.Int
 import kotlin.String
 import kotlin.Suppress
+import kotlin.Unit
 import kotlin.collections.List
 import kotlin.reflect.KClass
 
@@ -18,41 +18,35 @@
 ) : MyDao {
   private val __db: RoomDatabase
 
-  private val __insertAdapterOfMyEntity: EntityInsertionAdapter<MyEntity>
+  private val __insertAdapterOfMyEntity: EntityInsertAdapter<MyEntity>
   init {
     this.__db = __db
-    this.__insertAdapterOfMyEntity = object : EntityInsertionAdapter<MyEntity>(__db) {
+    this.__insertAdapterOfMyEntity = object : EntityInsertAdapter<MyEntity>() {
       protected override fun createQuery(): String =
           "INSERT OR ABORT INTO `MyEntity` (`pk`,`foo`,`bar`) VALUES (?,?,?)"
 
-      protected override fun bind(statement: SupportSQLiteStatement, entity: MyEntity) {
+      protected override fun bind(statement: SQLiteStatement, entity: MyEntity) {
         statement.bindLong(1, entity.pk.toLong())
         val _tmp: String? = FooBarConverter.toString(entity.foo)
         if (_tmp == null) {
           statement.bindNull(2)
         } else {
-          statement.bindString(2, _tmp)
+          statement.bindText(2, _tmp)
         }
         val _tmp_1: Foo = FooBarConverter.toFoo(entity.bar)
         val _tmp_2: String? = FooBarConverter.toString(_tmp_1)
         if (_tmp_2 == null) {
           statement.bindNull(3)
         } else {
-          statement.bindString(3, _tmp_2)
+          statement.bindText(3, _tmp_2)
         }
       }
     }
   }
 
-  public override fun addEntity(item: MyEntity) {
-    __db.assertNotSuspendingTransaction()
-    __db.beginTransaction()
-    try {
-      __insertAdapterOfMyEntity.insert(item)
-      __db.setTransactionSuccessful()
-    } finally {
-      __db.endTransaction()
-    }
+  public override fun addEntity(item: MyEntity): Unit = performBlocking(__db, false, true) {
+      _connection ->
+    __insertAdapterOfMyEntity.insert(_connection, item)
   }
 
   public override fun getEntity(): MyEntity {
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_customTypeConverter_provided.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_customTypeConverter_provided.kt
index 6c38251..1a14c64 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_customTypeConverter_provided.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_customTypeConverter_provided.kt
@@ -1,14 +1,14 @@
-import androidx.room.EntityInsertionAdapter
+import androidx.room.EntityInsertAdapter
 import androidx.room.RoomDatabase
 import androidx.room.util.getColumnIndexOrThrow
 import androidx.room.util.performBlocking
 import androidx.sqlite.SQLiteStatement
-import androidx.sqlite.db.SupportSQLiteStatement
 import javax.`annotation`.processing.Generated
 import kotlin.Int
 import kotlin.Lazy
 import kotlin.String
 import kotlin.Suppress
+import kotlin.Unit
 import kotlin.collections.List
 import kotlin.reflect.KClass
 
@@ -19,7 +19,7 @@
 ) : MyDao {
   private val __db: RoomDatabase
 
-  private val __insertAdapterOfMyEntity: EntityInsertionAdapter<MyEntity>
+  private val __insertAdapterOfMyEntity: EntityInsertAdapter<MyEntity>
 
   private val __fooConverter: Lazy<FooConverter> = lazy {
     checkNotNull(__db.getTypeConverter(FooConverter::class))
@@ -27,27 +27,21 @@
 
   init {
     this.__db = __db
-    this.__insertAdapterOfMyEntity = object : EntityInsertionAdapter<MyEntity>(__db) {
+    this.__insertAdapterOfMyEntity = object : EntityInsertAdapter<MyEntity>() {
       protected override fun createQuery(): String =
           "INSERT OR ABORT INTO `MyEntity` (`pk`,`foo`) VALUES (?,?)"
 
-      protected override fun bind(statement: SupportSQLiteStatement, entity: MyEntity) {
+      protected override fun bind(statement: SQLiteStatement, entity: MyEntity) {
         statement.bindLong(1, entity.pk.toLong())
         val _tmp: String = __fooConverter().toString(entity.foo)
-        statement.bindString(2, _tmp)
+        statement.bindText(2, _tmp)
       }
     }
   }
 
-  public override fun addEntity(item: MyEntity) {
-    __db.assertNotSuspendingTransaction()
-    __db.beginTransaction()
-    try {
-      __insertAdapterOfMyEntity.insert(item)
-      __db.setTransactionSuccessful()
-    } finally {
-      __db.endTransaction()
-    }
+  public override fun addEntity(item: MyEntity): Unit = performBlocking(__db, false, true) {
+      _connection ->
+    __insertAdapterOfMyEntity.insert(_connection, item)
   }
 
   public override fun getEntity(): MyEntity {
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_embedded.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_embedded.kt
index 47f863c..d0bf3d9 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_embedded.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_embedded.kt
@@ -1,14 +1,14 @@
-import androidx.room.EntityInsertionAdapter
+import androidx.room.EntityInsertAdapter
 import androidx.room.RoomDatabase
 import androidx.room.util.getColumnIndexOrThrow
 import androidx.room.util.performBlocking
 import androidx.sqlite.SQLiteStatement
-import androidx.sqlite.db.SupportSQLiteStatement
 import javax.`annotation`.processing.Generated
 import kotlin.Int
 import kotlin.Long
 import kotlin.String
 import kotlin.Suppress
+import kotlin.Unit
 import kotlin.collections.List
 import kotlin.reflect.KClass
 
@@ -19,22 +19,22 @@
 ) : MyDao {
   private val __db: RoomDatabase
 
-  private val __insertAdapterOfMyEntity: EntityInsertionAdapter<MyEntity>
+  private val __insertAdapterOfMyEntity: EntityInsertAdapter<MyEntity>
   init {
     this.__db = __db
-    this.__insertAdapterOfMyEntity = object : EntityInsertionAdapter<MyEntity>(__db) {
+    this.__insertAdapterOfMyEntity = object : EntityInsertAdapter<MyEntity>() {
       protected override fun createQuery(): String =
           "INSERT OR ABORT INTO `MyEntity` (`pk`,`numberData`,`stringData`,`nullablenumberData`,`nullablestringData`) VALUES (?,?,?,?,?)"
 
-      protected override fun bind(statement: SupportSQLiteStatement, entity: MyEntity) {
+      protected override fun bind(statement: SQLiteStatement, entity: MyEntity) {
         statement.bindLong(1, entity.pk.toLong())
         val _tmpFoo: Foo = entity.foo
         statement.bindLong(2, _tmpFoo.numberData)
-        statement.bindString(3, _tmpFoo.stringData)
+        statement.bindText(3, _tmpFoo.stringData)
         val _tmpNullableFoo: Foo? = entity.nullableFoo
         if (_tmpNullableFoo != null) {
           statement.bindLong(4, _tmpNullableFoo.numberData)
-          statement.bindString(5, _tmpNullableFoo.stringData)
+          statement.bindText(5, _tmpNullableFoo.stringData)
         } else {
           statement.bindNull(4)
           statement.bindNull(5)
@@ -43,15 +43,9 @@
     }
   }
 
-  public override fun addEntity(item: MyEntity) {
-    __db.assertNotSuspendingTransaction()
-    __db.beginTransaction()
-    try {
-      __insertAdapterOfMyEntity.insert(item)
-      __db.setTransactionSuccessful()
-    } finally {
-      __db.endTransaction()
-    }
+  public override fun addEntity(item: MyEntity): Unit = performBlocking(__db, false, true) {
+      _connection ->
+    __insertAdapterOfMyEntity.insert(_connection, item)
   }
 
   public override fun getEntity(): MyEntity {
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_enum.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_enum.kt
index a7c26e8..ad6e9f9 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_enum.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_enum.kt
@@ -1,14 +1,14 @@
-import androidx.room.EntityInsertionAdapter
+import androidx.room.EntityInsertAdapter
 import androidx.room.RoomDatabase
 import androidx.room.util.getColumnIndexOrThrow
 import androidx.room.util.performBlocking
 import androidx.sqlite.SQLiteStatement
-import androidx.sqlite.db.SupportSQLiteStatement
 import javax.`annotation`.processing.Generated
 import kotlin.IllegalArgumentException
 import kotlin.Int
 import kotlin.String
 import kotlin.Suppress
+import kotlin.Unit
 import kotlin.collections.List
 import kotlin.reflect.KClass
 
@@ -19,35 +19,29 @@
 ) : MyDao {
   private val __db: RoomDatabase
 
-  private val __insertAdapterOfMyEntity: EntityInsertionAdapter<MyEntity>
+  private val __insertAdapterOfMyEntity: EntityInsertAdapter<MyEntity>
   init {
     this.__db = __db
-    this.__insertAdapterOfMyEntity = object : EntityInsertionAdapter<MyEntity>(__db) {
+    this.__insertAdapterOfMyEntity = object : EntityInsertAdapter<MyEntity>() {
       protected override fun createQuery(): String =
           "INSERT OR ABORT INTO `MyEntity` (`pk`,`enum`,`nullableEnum`) VALUES (?,?,?)"
 
-      protected override fun bind(statement: SupportSQLiteStatement, entity: MyEntity) {
+      protected override fun bind(statement: SQLiteStatement, entity: MyEntity) {
         statement.bindLong(1, entity.pk.toLong())
-        statement.bindString(2, __Fruit_enumToString(entity.enum))
+        statement.bindText(2, __Fruit_enumToString(entity.enum))
         val _tmpNullableEnum: Fruit? = entity.nullableEnum
         if (_tmpNullableEnum == null) {
           statement.bindNull(3)
         } else {
-          statement.bindString(3, __Fruit_enumToString(_tmpNullableEnum))
+          statement.bindText(3, __Fruit_enumToString(_tmpNullableEnum))
         }
       }
     }
   }
 
-  public override fun addEntity(item: MyEntity) {
-    __db.assertNotSuspendingTransaction()
-    __db.beginTransaction()
-    try {
-      __insertAdapterOfMyEntity.insert(item)
-      __db.setTransactionSuccessful()
-    } finally {
-      __db.endTransaction()
-    }
+  public override fun addEntity(item: MyEntity): Unit = performBlocking(__db, false, true) {
+      _connection ->
+    __insertAdapterOfMyEntity.insert(_connection, item)
   }
 
   public override fun getEntity(): MyEntity {
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_internalVisibility.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_internalVisibility.kt
index 92a6c7a..c9efa6f 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_internalVisibility.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_internalVisibility.kt
@@ -1,14 +1,14 @@
-import androidx.room.EntityInsertionAdapter
+import androidx.room.EntityInsertAdapter
 import androidx.room.RoomDatabase
 import androidx.room.util.getColumnIndexOrThrow
 import androidx.room.util.performBlocking
 import androidx.sqlite.SQLiteStatement
-import androidx.sqlite.db.SupportSQLiteStatement
 import javax.`annotation`.processing.Generated
 import kotlin.Int
 import kotlin.Long
 import kotlin.String
 import kotlin.Suppress
+import kotlin.Unit
 import kotlin.collections.List
 import kotlin.reflect.KClass
 
@@ -19,14 +19,14 @@
 ) : MyDao {
   private val __db: RoomDatabase
 
-  private val __insertAdapterOfMyEntity: EntityInsertionAdapter<MyEntity>
+  private val __insertAdapterOfMyEntity: EntityInsertAdapter<MyEntity>
   init {
     this.__db = __db
-    this.__insertAdapterOfMyEntity = object : EntityInsertionAdapter<MyEntity>(__db) {
+    this.__insertAdapterOfMyEntity = object : EntityInsertAdapter<MyEntity>() {
       protected override fun createQuery(): String =
           "INSERT OR ABORT INTO `MyEntity` (`pk`,`internalVal`,`internalVar`,`internalSetterVar`) VALUES (?,?,?,?)"
 
-      protected override fun bind(statement: SupportSQLiteStatement, entity: MyEntity) {
+      protected override fun bind(statement: SQLiteStatement, entity: MyEntity) {
         statement.bindLong(1, entity.pk.toLong())
         statement.bindLong(2, entity.internalVal)
         statement.bindLong(3, entity.internalVar)
@@ -35,15 +35,9 @@
     }
   }
 
-  public override fun addEntity(item: MyEntity) {
-    __db.assertNotSuspendingTransaction()
-    __db.beginTransaction()
-    try {
-      __insertAdapterOfMyEntity.insert(item)
-      __db.setTransactionSuccessful()
-    } finally {
-      __db.endTransaction()
-    }
+  public override fun addEntity(item: MyEntity): Unit = performBlocking(__db, false, true) {
+      _connection ->
+    __insertAdapterOfMyEntity.insert(_connection, item)
   }
 
   public override fun getEntity(): MyEntity {
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_otherModule.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_otherModule.kt
index 5c4799f..0d1dfe3 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_otherModule.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_otherModule.kt
@@ -1,14 +1,14 @@
-import androidx.room.EntityInsertionAdapter
+import androidx.room.EntityInsertAdapter
 import androidx.room.RoomDatabase
 import androidx.room.util.getColumnIndexOrThrow
 import androidx.room.util.performBlocking
 import androidx.sqlite.SQLiteStatement
-import androidx.sqlite.db.SupportSQLiteStatement
 import javax.`annotation`.processing.Generated
 import kotlin.Int
 import kotlin.Long
 import kotlin.String
 import kotlin.Suppress
+import kotlin.Unit
 import kotlin.collections.List
 import kotlin.reflect.KClass
 
@@ -19,58 +19,52 @@
 ) : MyDao {
   private val __db: RoomDatabase
 
-  private val __insertAdapterOfMyEntity: EntityInsertionAdapter<MyEntity>
+  private val __insertAdapterOfMyEntity: EntityInsertAdapter<MyEntity>
   init {
     this.__db = __db
-    this.__insertAdapterOfMyEntity = object : EntityInsertionAdapter<MyEntity>(__db) {
+    this.__insertAdapterOfMyEntity = object : EntityInsertAdapter<MyEntity>() {
       protected override fun createQuery(): String =
           "INSERT OR ABORT INTO `MyEntity` (`pk`,`primitive`,`string`,`nullableString`,`fieldString`,`nullableFieldString`,`variablePrimitive`,`variableString`,`variableNullableString`,`variableFieldString`,`variableNullableFieldString`) VALUES (?,?,?,?,?,?,?,?,?,?,?)"
 
-      protected override fun bind(statement: SupportSQLiteStatement, entity: MyEntity) {
+      protected override fun bind(statement: SQLiteStatement, entity: MyEntity) {
         statement.bindLong(1, entity.pk.toLong())
         statement.bindLong(2, entity.primitive)
-        statement.bindString(3, entity.string)
+        statement.bindText(3, entity.string)
         val _tmpNullableString: String? = entity.nullableString
         if (_tmpNullableString == null) {
           statement.bindNull(4)
         } else {
-          statement.bindString(4, _tmpNullableString)
+          statement.bindText(4, _tmpNullableString)
         }
-        statement.bindString(5, entity.fieldString)
+        statement.bindText(5, entity.fieldString)
         val _tmpNullableFieldString: String? = entity.nullableFieldString
         if (_tmpNullableFieldString == null) {
           statement.bindNull(6)
         } else {
-          statement.bindString(6, _tmpNullableFieldString)
+          statement.bindText(6, _tmpNullableFieldString)
         }
         statement.bindLong(7, entity.variablePrimitive)
-        statement.bindString(8, entity.variableString)
+        statement.bindText(8, entity.variableString)
         val _tmpVariableNullableString: String? = entity.variableNullableString
         if (_tmpVariableNullableString == null) {
           statement.bindNull(9)
         } else {
-          statement.bindString(9, _tmpVariableNullableString)
+          statement.bindText(9, _tmpVariableNullableString)
         }
-        statement.bindString(10, entity.variableFieldString)
+        statement.bindText(10, entity.variableFieldString)
         val _tmpVariableNullableFieldString: String? = entity.variableNullableFieldString
         if (_tmpVariableNullableFieldString == null) {
           statement.bindNull(11)
         } else {
-          statement.bindString(11, _tmpVariableNullableFieldString)
+          statement.bindText(11, _tmpVariableNullableFieldString)
         }
       }
     }
   }
 
-  public override fun addEntity(item: MyEntity) {
-    __db.assertNotSuspendingTransaction()
-    __db.beginTransaction()
-    try {
-      __insertAdapterOfMyEntity.insert(item)
-      __db.setTransactionSuccessful()
-    } finally {
-      __db.endTransaction()
-    }
+  public override fun addEntity(item: MyEntity): Unit = performBlocking(__db, false, true) {
+      _connection ->
+    __insertAdapterOfMyEntity.insert(_connection, item)
   }
 
   public override fun getEntity(): MyEntity {
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_primitives.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_primitives.kt
index 212034f..c61196e 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_primitives.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_primitives.kt
@@ -1,9 +1,8 @@
-import androidx.room.EntityInsertionAdapter
+import androidx.room.EntityInsertAdapter
 import androidx.room.RoomDatabase
 import androidx.room.util.getColumnIndexOrThrow
 import androidx.room.util.performBlocking
 import androidx.sqlite.SQLiteStatement
-import androidx.sqlite.db.SupportSQLiteStatement
 import javax.`annotation`.processing.Generated
 import kotlin.Byte
 import kotlin.Char
@@ -14,6 +13,7 @@
 import kotlin.Short
 import kotlin.String
 import kotlin.Suppress
+import kotlin.Unit
 import kotlin.collections.List
 import kotlin.reflect.KClass
 
@@ -24,14 +24,14 @@
 ) : MyDao {
   private val __db: RoomDatabase
 
-  private val __insertAdapterOfMyEntity: EntityInsertionAdapter<MyEntity>
+  private val __insertAdapterOfMyEntity: EntityInsertAdapter<MyEntity>
   init {
     this.__db = __db
-    this.__insertAdapterOfMyEntity = object : EntityInsertionAdapter<MyEntity>(__db) {
+    this.__insertAdapterOfMyEntity = object : EntityInsertAdapter<MyEntity>() {
       protected override fun createQuery(): String =
           "INSERT OR ABORT INTO `MyEntity` (`int`,`short`,`byte`,`long`,`char`,`float`,`double`) VALUES (?,?,?,?,?,?,?)"
 
-      protected override fun bind(statement: SupportSQLiteStatement, entity: MyEntity) {
+      protected override fun bind(statement: SQLiteStatement, entity: MyEntity) {
         statement.bindLong(1, entity.int.toLong())
         statement.bindLong(2, entity.short.toLong())
         statement.bindLong(3, entity.byte.toLong())
@@ -43,15 +43,9 @@
     }
   }
 
-  public override fun addEntity(item: MyEntity) {
-    __db.assertNotSuspendingTransaction()
-    __db.beginTransaction()
-    try {
-      __insertAdapterOfMyEntity.insert(item)
-      __db.setTransactionSuccessful()
-    } finally {
-      __db.endTransaction()
-    }
+  public override fun addEntity(item: MyEntity): Unit = performBlocking(__db, false, true) {
+      _connection ->
+    __insertAdapterOfMyEntity.insert(_connection, item)
   }
 
   public override fun getEntity(): MyEntity {
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_primitives_nullable.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_primitives_nullable.kt
index db9bec8..0f284ff 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_primitives_nullable.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_primitives_nullable.kt
@@ -1,9 +1,8 @@
-import androidx.room.EntityInsertionAdapter
+import androidx.room.EntityInsertAdapter
 import androidx.room.RoomDatabase
 import androidx.room.util.getColumnIndexOrThrow
 import androidx.room.util.performBlocking
 import androidx.sqlite.SQLiteStatement
-import androidx.sqlite.db.SupportSQLiteStatement
 import javax.`annotation`.processing.Generated
 import kotlin.Byte
 import kotlin.Char
@@ -14,6 +13,7 @@
 import kotlin.Short
 import kotlin.String
 import kotlin.Suppress
+import kotlin.Unit
 import kotlin.collections.List
 import kotlin.reflect.KClass
 
@@ -24,14 +24,14 @@
 ) : MyDao {
   private val __db: RoomDatabase
 
-  private val __insertAdapterOfMyEntity: EntityInsertionAdapter<MyEntity>
+  private val __insertAdapterOfMyEntity: EntityInsertAdapter<MyEntity>
   init {
     this.__db = __db
-    this.__insertAdapterOfMyEntity = object : EntityInsertionAdapter<MyEntity>(__db) {
+    this.__insertAdapterOfMyEntity = object : EntityInsertAdapter<MyEntity>() {
       protected override fun createQuery(): String =
           "INSERT OR ABORT INTO `MyEntity` (`int`,`short`,`byte`,`long`,`char`,`float`,`double`) VALUES (?,?,?,?,?,?,?)"
 
-      protected override fun bind(statement: SupportSQLiteStatement, entity: MyEntity) {
+      protected override fun bind(statement: SQLiteStatement, entity: MyEntity) {
         val _tmpInt: Int? = entity.int
         if (_tmpInt == null) {
           statement.bindNull(1)
@@ -78,15 +78,9 @@
     }
   }
 
-  public override fun addEntity(item: MyEntity) {
-    __db.assertNotSuspendingTransaction()
-    __db.beginTransaction()
-    try {
-      __insertAdapterOfMyEntity.insert(item)
-      __db.setTransactionSuccessful()
-    } finally {
-      __db.endTransaction()
-    }
+  public override fun addEntity(item: MyEntity): Unit = performBlocking(__db, false, true) {
+      _connection ->
+    __insertAdapterOfMyEntity.insert(_connection, item)
   }
 
   public override fun getEntity(): MyEntity {
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_string.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_string.kt
index 65ed82a..32524c3 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_string.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_string.kt
@@ -1,13 +1,13 @@
-import androidx.room.EntityInsertionAdapter
+import androidx.room.EntityInsertAdapter
 import androidx.room.RoomDatabase
 import androidx.room.util.getColumnIndexOrThrow
 import androidx.room.util.performBlocking
 import androidx.sqlite.SQLiteStatement
-import androidx.sqlite.db.SupportSQLiteStatement
 import javax.`annotation`.processing.Generated
 import kotlin.Int
 import kotlin.String
 import kotlin.Suppress
+import kotlin.Unit
 import kotlin.collections.List
 import kotlin.reflect.KClass
 
@@ -18,34 +18,28 @@
 ) : MyDao {
   private val __db: RoomDatabase
 
-  private val __insertAdapterOfMyEntity: EntityInsertionAdapter<MyEntity>
+  private val __insertAdapterOfMyEntity: EntityInsertAdapter<MyEntity>
   init {
     this.__db = __db
-    this.__insertAdapterOfMyEntity = object : EntityInsertionAdapter<MyEntity>(__db) {
+    this.__insertAdapterOfMyEntity = object : EntityInsertAdapter<MyEntity>() {
       protected override fun createQuery(): String =
           "INSERT OR ABORT INTO `MyEntity` (`string`,`nullableString`) VALUES (?,?)"
 
-      protected override fun bind(statement: SupportSQLiteStatement, entity: MyEntity) {
-        statement.bindString(1, entity.string)
+      protected override fun bind(statement: SQLiteStatement, entity: MyEntity) {
+        statement.bindText(1, entity.string)
         val _tmpNullableString: String? = entity.nullableString
         if (_tmpNullableString == null) {
           statement.bindNull(2)
         } else {
-          statement.bindString(2, _tmpNullableString)
+          statement.bindText(2, _tmpNullableString)
         }
       }
     }
   }
 
-  public override fun addEntity(item: MyEntity) {
-    __db.assertNotSuspendingTransaction()
-    __db.beginTransaction()
-    try {
-      __insertAdapterOfMyEntity.insert(item)
-      __db.setTransactionSuccessful()
-    } finally {
-      __db.endTransaction()
-    }
+  public override fun addEntity(item: MyEntity): Unit = performBlocking(__db, false, true) {
+      _connection ->
+    __insertAdapterOfMyEntity.insert(_connection, item)
   }
 
   public override fun getEntity(): MyEntity {
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_uuid.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_uuid.kt
index 9786ab7..31d3d23 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_uuid.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_uuid.kt
@@ -1,16 +1,16 @@
-import androidx.room.EntityInsertionAdapter
+import androidx.room.EntityInsertAdapter
 import androidx.room.RoomDatabase
 import androidx.room.util.convertByteToUUID
 import androidx.room.util.convertUUIDToByte
 import androidx.room.util.getColumnIndexOrThrow
 import androidx.room.util.performBlocking
 import androidx.sqlite.SQLiteStatement
-import androidx.sqlite.db.SupportSQLiteStatement
 import java.util.UUID
 import javax.`annotation`.processing.Generated
 import kotlin.Int
 import kotlin.String
 import kotlin.Suppress
+import kotlin.Unit
 import kotlin.collections.List
 import kotlin.reflect.KClass
 
@@ -21,14 +21,14 @@
 ) : MyDao {
   private val __db: RoomDatabase
 
-  private val __insertAdapterOfMyEntity: EntityInsertionAdapter<MyEntity>
+  private val __insertAdapterOfMyEntity: EntityInsertAdapter<MyEntity>
   init {
     this.__db = __db
-    this.__insertAdapterOfMyEntity = object : EntityInsertionAdapter<MyEntity>(__db) {
+    this.__insertAdapterOfMyEntity = object : EntityInsertAdapter<MyEntity>() {
       protected override fun createQuery(): String =
           "INSERT OR ABORT INTO `MyEntity` (`pk`,`uuid`,`nullableUuid`) VALUES (?,?,?)"
 
-      protected override fun bind(statement: SupportSQLiteStatement, entity: MyEntity) {
+      protected override fun bind(statement: SQLiteStatement, entity: MyEntity) {
         statement.bindLong(1, entity.pk.toLong())
         statement.bindBlob(2, convertUUIDToByte(entity.uuid))
         val _tmpNullableUuid: UUID? = entity.nullableUuid
@@ -41,15 +41,9 @@
     }
   }
 
-  public override fun addEntity(item: MyEntity) {
-    __db.assertNotSuspendingTransaction()
-    __db.beginTransaction()
-    try {
-      __insertAdapterOfMyEntity.insert(item)
-      __db.setTransactionSuccessful()
-    } finally {
-      __db.endTransaction()
-    }
+  public override fun addEntity(item: MyEntity): Unit = performBlocking(__db, false, true) {
+      _connection ->
+    __insertAdapterOfMyEntity.insert(_connection, item)
   }
 
   public override fun getEntity(): MyEntity {
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_valueClassConverter.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_valueClassConverter.kt
index fe0102e..e721101 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_valueClassConverter.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_valueClassConverter.kt
@@ -1,17 +1,17 @@
-import androidx.room.EntityInsertionAdapter
+import androidx.room.EntityInsertAdapter
 import androidx.room.RoomDatabase
 import androidx.room.util.convertByteToUUID
 import androidx.room.util.convertUUIDToByte
 import androidx.room.util.getColumnIndexOrThrow
 import androidx.room.util.performBlocking
 import androidx.sqlite.SQLiteStatement
-import androidx.sqlite.db.SupportSQLiteStatement
 import java.util.UUID
 import javax.`annotation`.processing.Generated
 import kotlin.Int
 import kotlin.Long
 import kotlin.String
 import kotlin.Suppress
+import kotlin.Unit
 import kotlin.collections.List
 import kotlin.reflect.KClass
 
@@ -22,14 +22,14 @@
 ) : MyDao {
   private val __db: RoomDatabase
 
-  private val __insertAdapterOfMyEntity: EntityInsertionAdapter<MyEntity>
+  private val __insertAdapterOfMyEntity: EntityInsertAdapter<MyEntity>
   init {
     this.__db = __db
-    this.__insertAdapterOfMyEntity = object : EntityInsertionAdapter<MyEntity>(__db) {
+    this.__insertAdapterOfMyEntity = object : EntityInsertAdapter<MyEntity>() {
       protected override fun createQuery(): String =
           "INSERT OR ABORT INTO `MyEntity` (`pk`,`uuidData`,`nullableUuidData`,`nullableLongData`,`doubleNullableLongData`,`genericData`) VALUES (?,?,?,?,?,?)"
 
-      protected override fun bind(statement: SupportSQLiteStatement, entity: MyEntity) {
+      protected override fun bind(statement: SQLiteStatement, entity: MyEntity) {
         val _data: Long = checkNotNull(entity.pk.data) {
             "Cannot bind NULLABLE value 'data' of inline class 'LongValueClass' to a NOT NULL column."
             }
@@ -59,20 +59,14 @@
         val _password: String = checkNotNull(entity.genericData.password) {
             "Cannot bind NULLABLE value 'password' of inline class 'GenericValueClass<String>' to a NOT NULL column."
             }
-        statement.bindString(6, _password)
+        statement.bindText(6, _password)
       }
     }
   }
 
-  public override fun addEntity(item: MyEntity) {
-    __db.assertNotSuspendingTransaction()
-    __db.beginTransaction()
-    try {
-      __insertAdapterOfMyEntity.insert(item)
-      __db.setTransactionSuccessful()
-    } finally {
-      __db.endTransaction()
-    }
+  public override fun addEntity(item: MyEntity): Unit = performBlocking(__db, false, true) {
+      _connection ->
+    __insertAdapterOfMyEntity.insert(_connection, item)
   }
 
   public override fun getEntity(): MyEntity {
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_variableProperty.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_variableProperty.kt
index b171058..fd98551 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_variableProperty.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_variableProperty.kt
@@ -1,13 +1,13 @@
-import androidx.room.EntityInsertionAdapter
+import androidx.room.EntityInsertAdapter
 import androidx.room.RoomDatabase
 import androidx.room.util.getColumnIndexOrThrow
 import androidx.room.util.performBlocking
 import androidx.sqlite.SQLiteStatement
-import androidx.sqlite.db.SupportSQLiteStatement
 import javax.`annotation`.processing.Generated
 import kotlin.Int
 import kotlin.String
 import kotlin.Suppress
+import kotlin.Unit
 import kotlin.collections.List
 import kotlin.reflect.KClass
 
@@ -18,36 +18,30 @@
 ) : MyDao {
   private val __db: RoomDatabase
 
-  private val __insertAdapterOfMyEntity: EntityInsertionAdapter<MyEntity>
+  private val __insertAdapterOfMyEntity: EntityInsertAdapter<MyEntity>
   init {
     this.__db = __db
-    this.__insertAdapterOfMyEntity = object : EntityInsertionAdapter<MyEntity>(__db) {
+    this.__insertAdapterOfMyEntity = object : EntityInsertAdapter<MyEntity>() {
       protected override fun createQuery(): String =
           "INSERT OR ABORT INTO `MyEntity` (`pk`,`variablePrimitive`,`variableString`,`variableNullableString`) VALUES (?,?,?,?)"
 
-      protected override fun bind(statement: SupportSQLiteStatement, entity: MyEntity) {
+      protected override fun bind(statement: SQLiteStatement, entity: MyEntity) {
         statement.bindLong(1, entity.pk.toLong())
         statement.bindLong(2, entity.variablePrimitive)
-        statement.bindString(3, entity.variableString)
+        statement.bindText(3, entity.variableString)
         val _tmpVariableNullableString: String? = entity.variableNullableString
         if (_tmpVariableNullableString == null) {
           statement.bindNull(4)
         } else {
-          statement.bindString(4, _tmpVariableNullableString)
+          statement.bindText(4, _tmpVariableNullableString)
         }
       }
     }
   }
 
-  public override fun addEntity(item: MyEntity) {
-    __db.assertNotSuspendingTransaction()
-    __db.beginTransaction()
-    try {
-      __insertAdapterOfMyEntity.insert(item)
-      __db.setTransactionSuccessful()
-    } finally {
-      __db.endTransaction()
-    }
+  public override fun addEntity(item: MyEntity): Unit = performBlocking(__db, false, true) {
+      _connection ->
+    __insertAdapterOfMyEntity.insert(_connection, item)
   }
 
   public override fun getEntity(): MyEntity {
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_variableProperty_java.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_variableProperty_java.kt
index a1f5fc7..1e4f409 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_variableProperty_java.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_variableProperty_java.kt
@@ -1,14 +1,14 @@
-import androidx.room.EntityInsertionAdapter
+import androidx.room.EntityInsertAdapter
 import androidx.room.RoomDatabase
 import androidx.room.util.getColumnIndexOrThrow
 import androidx.room.util.performBlocking
 import androidx.sqlite.SQLiteStatement
-import androidx.sqlite.db.SupportSQLiteStatement
 import javax.`annotation`.processing.Generated
 import kotlin.Int
 import kotlin.Long
 import kotlin.String
 import kotlin.Suppress
+import kotlin.Unit
 import kotlin.collections.List
 import kotlin.reflect.KClass
 
@@ -19,34 +19,28 @@
 ) : MyDao {
   private val __db: RoomDatabase
 
-  private val __insertAdapterOfMyEntity: EntityInsertionAdapter<MyEntity>
+  private val __insertAdapterOfMyEntity: EntityInsertAdapter<MyEntity>
   init {
     this.__db = __db
-    this.__insertAdapterOfMyEntity = object : EntityInsertionAdapter<MyEntity>(__db) {
+    this.__insertAdapterOfMyEntity = object : EntityInsertAdapter<MyEntity>() {
       protected override fun createQuery(): String =
           "INSERT OR ABORT INTO `MyEntity` (`mValue`,`mNullableValue`) VALUES (?,?)"
 
-      protected override fun bind(statement: SupportSQLiteStatement, entity: MyEntity) {
+      protected override fun bind(statement: SQLiteStatement, entity: MyEntity) {
         statement.bindLong(1, entity.getValue())
         val _tmpMNullableValue: String? = entity.getNullableValue()
         if (_tmpMNullableValue == null) {
           statement.bindNull(2)
         } else {
-          statement.bindString(2, _tmpMNullableValue)
+          statement.bindText(2, _tmpMNullableValue)
         }
       }
     }
   }
 
-  public override fun addEntity(item: MyEntity) {
-    __db.assertNotSuspendingTransaction()
-    __db.beginTransaction()
-    try {
-      __insertAdapterOfMyEntity.insert(item)
-      __db.setTransactionSuccessful()
-    } finally {
-      __db.endTransaction()
-    }
+  public override fun addEntity(item: MyEntity): Unit = performBlocking(__db, false, true) {
+      _connection ->
+    __insertAdapterOfMyEntity.insert(_connection, item)
   }
 
   public override fun getEntity(): MyEntity {
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/shortcutMethods_rx2.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/shortcutMethods_rx2.kt
index a553570..7b6deb0 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/shortcutMethods_rx2.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/shortcutMethods_rx2.kt
@@ -22,16 +22,16 @@
 ) : MyDao {
   private val __db: RoomDatabase
 
-  private val __insertAdapterOfMyEntity: EntityInsertionAdapter<MyEntity>
+  private val __insertionAdapterOfMyEntity: EntityInsertionAdapter<MyEntity>
 
-  private val __deleteAdapterOfMyEntity: EntityDeletionOrUpdateAdapter<MyEntity>
+  private val __deleteCompatAdapterOfMyEntity: EntityDeletionOrUpdateAdapter<MyEntity>
 
-  private val __updateAdapterOfMyEntity: EntityDeletionOrUpdateAdapter<MyEntity>
+  private val __updateCompatAdapterOfMyEntity: EntityDeletionOrUpdateAdapter<MyEntity>
 
-  private val __upsertAdapterOfMyEntity: EntityUpsertionAdapter<MyEntity>
+  private val __upsertionAdapterOfMyEntity: EntityUpsertionAdapter<MyEntity>
   init {
     this.__db = __db
-    this.__insertAdapterOfMyEntity = object : EntityInsertionAdapter<MyEntity>(__db) {
+    this.__insertionAdapterOfMyEntity = object : EntityInsertionAdapter<MyEntity>(__db) {
       protected override fun createQuery(): String =
           "INSERT OR ABORT INTO `MyEntity` (`pk`,`other`) VALUES (?,?)"
 
@@ -40,14 +40,14 @@
         statement.bindString(2, entity.other)
       }
     }
-    this.__deleteAdapterOfMyEntity = object : EntityDeletionOrUpdateAdapter<MyEntity>(__db) {
+    this.__deleteCompatAdapterOfMyEntity = object : EntityDeletionOrUpdateAdapter<MyEntity>(__db) {
       protected override fun createQuery(): String = "DELETE FROM `MyEntity` WHERE `pk` = ?"
 
       protected override fun bind(statement: SupportSQLiteStatement, entity: MyEntity) {
         statement.bindLong(1, entity.pk.toLong())
       }
     }
-    this.__updateAdapterOfMyEntity = object : EntityDeletionOrUpdateAdapter<MyEntity>(__db) {
+    this.__updateCompatAdapterOfMyEntity = object : EntityDeletionOrUpdateAdapter<MyEntity>(__db) {
       protected override fun createQuery(): String =
           "UPDATE OR ABORT `MyEntity` SET `pk` = ?,`other` = ? WHERE `pk` = ?"
 
@@ -57,7 +57,7 @@
         statement.bindLong(3, entity.pk.toLong())
       }
     }
-    this.__upsertAdapterOfMyEntity = EntityUpsertionAdapter<MyEntity>(object :
+    this.__upsertionAdapterOfMyEntity = EntityUpsertionAdapter<MyEntity>(object :
         EntityInsertionAdapter<MyEntity>(__db) {
       protected override fun createQuery(): String =
           "INSERT INTO `MyEntity` (`pk`,`other`) VALUES (?,?)"
@@ -83,7 +83,7 @@
     public override fun call(): List<Long>? {
       __db.beginTransaction()
       try {
-        val _result: List<Long>? = __insertAdapterOfMyEntity.insertAndReturnIdsList(entities)
+        val _result: List<Long>? = __insertionAdapterOfMyEntity.insertAndReturnIdsList(entities)
         __db.setTransactionSuccessful()
         return _result
       } finally {
@@ -97,7 +97,7 @@
     public override fun call(): Void? {
       __db.beginTransaction()
       try {
-        __insertAdapterOfMyEntity.insert(entities)
+        __insertionAdapterOfMyEntity.insert(entities)
         __db.setTransactionSuccessful()
         return null
       } finally {
@@ -112,7 +112,7 @@
       var _total: Int = 0
       __db.beginTransaction()
       try {
-        _total += __deleteAdapterOfMyEntity.handle(entity)
+        _total += __deleteCompatAdapterOfMyEntity.handle(entity)
         __db.setTransactionSuccessful()
         return _total
       } finally {
@@ -126,7 +126,7 @@
     public override fun call(): Void? {
       __db.beginTransaction()
       try {
-        __deleteAdapterOfMyEntity.handle(entity)
+        __deleteCompatAdapterOfMyEntity.handle(entity)
         __db.setTransactionSuccessful()
         return null
       } finally {
@@ -141,7 +141,7 @@
       var _total: Int = 0
       __db.beginTransaction()
       try {
-        _total += __updateAdapterOfMyEntity.handle(entity)
+        _total += __updateCompatAdapterOfMyEntity.handle(entity)
         __db.setTransactionSuccessful()
         return _total
       } finally {
@@ -155,7 +155,7 @@
     public override fun call(): Void? {
       __db.beginTransaction()
       try {
-        __updateAdapterOfMyEntity.handle(entity)
+        __updateCompatAdapterOfMyEntity.handle(entity)
         __db.setTransactionSuccessful()
         return null
       } finally {
@@ -169,7 +169,7 @@
     public override fun call(): List<Long>? {
       __db.beginTransaction()
       try {
-        val _result: List<Long>? = __upsertAdapterOfMyEntity.upsertAndReturnIdsList(entities)
+        val _result: List<Long>? = __upsertionAdapterOfMyEntity.upsertAndReturnIdsList(entities)
         __db.setTransactionSuccessful()
         return _result
       } finally {
@@ -183,7 +183,7 @@
     public override fun call(): Void? {
       __db.beginTransaction()
       try {
-        __upsertAdapterOfMyEntity.upsert(entities)
+        __upsertionAdapterOfMyEntity.upsert(entities)
         __db.setTransactionSuccessful()
         return null
       } finally {
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/shortcutMethods_rx3.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/shortcutMethods_rx3.kt
index 70f13eb..f0716a7 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/shortcutMethods_rx3.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/shortcutMethods_rx3.kt
@@ -22,16 +22,16 @@
 ) : MyDao {
   private val __db: RoomDatabase
 
-  private val __insertAdapterOfMyEntity: EntityInsertionAdapter<MyEntity>
+  private val __insertionAdapterOfMyEntity: EntityInsertionAdapter<MyEntity>
 
-  private val __deleteAdapterOfMyEntity: EntityDeletionOrUpdateAdapter<MyEntity>
+  private val __deleteCompatAdapterOfMyEntity: EntityDeletionOrUpdateAdapter<MyEntity>
 
-  private val __updateAdapterOfMyEntity: EntityDeletionOrUpdateAdapter<MyEntity>
+  private val __updateCompatAdapterOfMyEntity: EntityDeletionOrUpdateAdapter<MyEntity>
 
-  private val __upsertAdapterOfMyEntity: EntityUpsertionAdapter<MyEntity>
+  private val __upsertionAdapterOfMyEntity: EntityUpsertionAdapter<MyEntity>
   init {
     this.__db = __db
-    this.__insertAdapterOfMyEntity = object : EntityInsertionAdapter<MyEntity>(__db) {
+    this.__insertionAdapterOfMyEntity = object : EntityInsertionAdapter<MyEntity>(__db) {
       protected override fun createQuery(): String =
           "INSERT OR ABORT INTO `MyEntity` (`pk`,`other`) VALUES (?,?)"
 
@@ -40,14 +40,14 @@
         statement.bindString(2, entity.other)
       }
     }
-    this.__deleteAdapterOfMyEntity = object : EntityDeletionOrUpdateAdapter<MyEntity>(__db) {
+    this.__deleteCompatAdapterOfMyEntity = object : EntityDeletionOrUpdateAdapter<MyEntity>(__db) {
       protected override fun createQuery(): String = "DELETE FROM `MyEntity` WHERE `pk` = ?"
 
       protected override fun bind(statement: SupportSQLiteStatement, entity: MyEntity) {
         statement.bindLong(1, entity.pk.toLong())
       }
     }
-    this.__updateAdapterOfMyEntity = object : EntityDeletionOrUpdateAdapter<MyEntity>(__db) {
+    this.__updateCompatAdapterOfMyEntity = object : EntityDeletionOrUpdateAdapter<MyEntity>(__db) {
       protected override fun createQuery(): String =
           "UPDATE OR ABORT `MyEntity` SET `pk` = ?,`other` = ? WHERE `pk` = ?"
 
@@ -57,7 +57,7 @@
         statement.bindLong(3, entity.pk.toLong())
       }
     }
-    this.__upsertAdapterOfMyEntity = EntityUpsertionAdapter<MyEntity>(object :
+    this.__upsertionAdapterOfMyEntity = EntityUpsertionAdapter<MyEntity>(object :
         EntityInsertionAdapter<MyEntity>(__db) {
       protected override fun createQuery(): String =
           "INSERT INTO `MyEntity` (`pk`,`other`) VALUES (?,?)"
@@ -83,7 +83,7 @@
     public override fun call(): List<Long>? {
       __db.beginTransaction()
       try {
-        val _result: List<Long>? = __insertAdapterOfMyEntity.insertAndReturnIdsList(entities)
+        val _result: List<Long>? = __insertionAdapterOfMyEntity.insertAndReturnIdsList(entities)
         __db.setTransactionSuccessful()
         return _result
       } finally {
@@ -97,7 +97,7 @@
     public override fun call(): Void? {
       __db.beginTransaction()
       try {
-        __insertAdapterOfMyEntity.insert(entities)
+        __insertionAdapterOfMyEntity.insert(entities)
         __db.setTransactionSuccessful()
         return null
       } finally {
@@ -112,7 +112,7 @@
       var _total: Int = 0
       __db.beginTransaction()
       try {
-        _total += __deleteAdapterOfMyEntity.handle(entity)
+        _total += __deleteCompatAdapterOfMyEntity.handle(entity)
         __db.setTransactionSuccessful()
         return _total
       } finally {
@@ -126,7 +126,7 @@
     public override fun call(): Void? {
       __db.beginTransaction()
       try {
-        __deleteAdapterOfMyEntity.handle(entity)
+        __deleteCompatAdapterOfMyEntity.handle(entity)
         __db.setTransactionSuccessful()
         return null
       } finally {
@@ -141,7 +141,7 @@
       var _total: Int = 0
       __db.beginTransaction()
       try {
-        _total += __updateAdapterOfMyEntity.handle(entity)
+        _total += __updateCompatAdapterOfMyEntity.handle(entity)
         __db.setTransactionSuccessful()
         return _total
       } finally {
@@ -155,7 +155,7 @@
     public override fun call(): Void? {
       __db.beginTransaction()
       try {
-        __updateAdapterOfMyEntity.handle(entity)
+        __updateCompatAdapterOfMyEntity.handle(entity)
         __db.setTransactionSuccessful()
         return null
       } finally {
@@ -169,7 +169,7 @@
     public override fun call(): List<Long>? {
       __db.beginTransaction()
       try {
-        val _result: List<Long>? = __upsertAdapterOfMyEntity.upsertAndReturnIdsList(entities)
+        val _result: List<Long>? = __upsertionAdapterOfMyEntity.upsertAndReturnIdsList(entities)
         __db.setTransactionSuccessful()
         return _result
       } finally {
@@ -183,7 +183,7 @@
     public override fun call(): Void? {
       __db.beginTransaction()
       try {
-        __upsertAdapterOfMyEntity.upsert(entities)
+        __upsertionAdapterOfMyEntity.upsert(entities)
         __db.setTransactionSuccessful()
         return null
       } finally {
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/shortcutMethods_suspend.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/shortcutMethods_suspend.kt
index bbdaf84..4fc3cb4 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/shortcutMethods_suspend.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/shortcutMethods_suspend.kt
@@ -1,10 +1,9 @@
-import androidx.room.CoroutinesRoom
-import androidx.room.EntityDeletionOrUpdateAdapter
-import androidx.room.EntityInsertionAdapter
-import androidx.room.EntityUpsertionAdapter
+import androidx.room.EntityDeleteOrUpdateAdapter
+import androidx.room.EntityInsertAdapter
+import androidx.room.EntityUpsertAdapter
 import androidx.room.RoomDatabase
-import androidx.sqlite.db.SupportSQLiteStatement
-import java.util.concurrent.Callable
+import androidx.room.util.performSuspending
+import androidx.sqlite.SQLiteStatement
 import javax.`annotation`.processing.Generated
 import kotlin.Int
 import kotlin.Long
@@ -20,119 +19,89 @@
 ) : MyDao {
   private val __db: RoomDatabase
 
-  private val __insertAdapterOfMyEntity: EntityInsertionAdapter<MyEntity>
+  private val __insertAdapterOfMyEntity: EntityInsertAdapter<MyEntity>
 
-  private val __deleteAdapterOfMyEntity: EntityDeletionOrUpdateAdapter<MyEntity>
+  private val __deleteAdapterOfMyEntity: EntityDeleteOrUpdateAdapter<MyEntity>
 
-  private val __updateAdapterOfMyEntity: EntityDeletionOrUpdateAdapter<MyEntity>
+  private val __updateAdapterOfMyEntity: EntityDeleteOrUpdateAdapter<MyEntity>
 
-  private val __upsertAdapterOfMyEntity: EntityUpsertionAdapter<MyEntity>
+  private val __upsertAdapterOfMyEntity: EntityUpsertAdapter<MyEntity>
   init {
     this.__db = __db
-    this.__insertAdapterOfMyEntity = object : EntityInsertionAdapter<MyEntity>(__db) {
+    this.__insertAdapterOfMyEntity = object : EntityInsertAdapter<MyEntity>() {
       protected override fun createQuery(): String =
           "INSERT OR ABORT INTO `MyEntity` (`pk`,`other`) VALUES (?,?)"
 
-      protected override fun bind(statement: SupportSQLiteStatement, entity: MyEntity) {
+      protected override fun bind(statement: SQLiteStatement, entity: MyEntity) {
         statement.bindLong(1, entity.pk.toLong())
-        statement.bindString(2, entity.other)
+        statement.bindText(2, entity.other)
       }
     }
-    this.__deleteAdapterOfMyEntity = object : EntityDeletionOrUpdateAdapter<MyEntity>(__db) {
+    this.__deleteAdapterOfMyEntity = object : EntityDeleteOrUpdateAdapter<MyEntity>() {
       protected override fun createQuery(): String = "DELETE FROM `MyEntity` WHERE `pk` = ?"
 
-      protected override fun bind(statement: SupportSQLiteStatement, entity: MyEntity) {
+      protected override fun bind(statement: SQLiteStatement, entity: MyEntity) {
         statement.bindLong(1, entity.pk.toLong())
       }
     }
-    this.__updateAdapterOfMyEntity = object : EntityDeletionOrUpdateAdapter<MyEntity>(__db) {
+    this.__updateAdapterOfMyEntity = object : EntityDeleteOrUpdateAdapter<MyEntity>() {
       protected override fun createQuery(): String =
           "UPDATE OR ABORT `MyEntity` SET `pk` = ?,`other` = ? WHERE `pk` = ?"
 
-      protected override fun bind(statement: SupportSQLiteStatement, entity: MyEntity) {
+      protected override fun bind(statement: SQLiteStatement, entity: MyEntity) {
         statement.bindLong(1, entity.pk.toLong())
-        statement.bindString(2, entity.other)
+        statement.bindText(2, entity.other)
         statement.bindLong(3, entity.pk.toLong())
       }
     }
-    this.__upsertAdapterOfMyEntity = EntityUpsertionAdapter<MyEntity>(object :
-        EntityInsertionAdapter<MyEntity>(__db) {
+    this.__upsertAdapterOfMyEntity = EntityUpsertAdapter<MyEntity>(object :
+        EntityInsertAdapter<MyEntity>() {
       protected override fun createQuery(): String =
           "INSERT INTO `MyEntity` (`pk`,`other`) VALUES (?,?)"
 
-      protected override fun bind(statement: SupportSQLiteStatement, entity: MyEntity) {
+      protected override fun bind(statement: SQLiteStatement, entity: MyEntity) {
         statement.bindLong(1, entity.pk.toLong())
-        statement.bindString(2, entity.other)
+        statement.bindText(2, entity.other)
       }
-    }, object : EntityDeletionOrUpdateAdapter<MyEntity>(__db) {
+    }, object : EntityDeleteOrUpdateAdapter<MyEntity>() {
       protected override fun createQuery(): String =
           "UPDATE `MyEntity` SET `pk` = ?,`other` = ? WHERE `pk` = ?"
 
-      protected override fun bind(statement: SupportSQLiteStatement, entity: MyEntity) {
+      protected override fun bind(statement: SQLiteStatement, entity: MyEntity) {
         statement.bindLong(1, entity.pk.toLong())
-        statement.bindString(2, entity.other)
+        statement.bindText(2, entity.other)
         statement.bindLong(3, entity.pk.toLong())
       }
     })
   }
 
   public override suspend fun insert(vararg entities: MyEntity): List<Long> =
-      CoroutinesRoom.execute(__db, true, object : Callable<List<Long>> {
-    public override fun call(): List<Long> {
-      __db.beginTransaction()
-      try {
-        val _result: List<Long> = __insertAdapterOfMyEntity.insertAndReturnIdsList(entities)
-        __db.setTransactionSuccessful()
-        return _result
-      } finally {
-        __db.endTransaction()
-      }
-    }
-  })
+      performSuspending(__db, false, true) { _connection ->
+    val _result: List<Long> = __insertAdapterOfMyEntity.insertAndReturnIdsList(_connection,
+        entities)
+    _result
+  }
 
-  public override suspend fun delete(entity: MyEntity): Int = CoroutinesRoom.execute(__db, true,
-      object : Callable<Int> {
-    public override fun call(): Int {
-      var _total: Int = 0
-      __db.beginTransaction()
-      try {
-        _total += __deleteAdapterOfMyEntity.handle(entity)
-        __db.setTransactionSuccessful()
-        return _total
-      } finally {
-        __db.endTransaction()
-      }
-    }
-  })
+  public override suspend fun delete(entity: MyEntity): Int = performSuspending(__db, false, true) {
+      _connection ->
+    var _result: Int = 0
+    _result += __deleteAdapterOfMyEntity.handle(_connection, entity)
+    _result
+  }
 
-  public override suspend fun update(entity: MyEntity): Int = CoroutinesRoom.execute(__db, true,
-      object : Callable<Int> {
-    public override fun call(): Int {
-      var _total: Int = 0
-      __db.beginTransaction()
-      try {
-        _total += __updateAdapterOfMyEntity.handle(entity)
-        __db.setTransactionSuccessful()
-        return _total
-      } finally {
-        __db.endTransaction()
-      }
-    }
-  })
+  public override suspend fun update(entity: MyEntity): Int = performSuspending(__db, false, true) {
+      _connection ->
+    var _result: Int = 0
+    _result += __updateAdapterOfMyEntity.handle(_connection, entity)
+    _result
+  }
 
   public override suspend fun upsert(vararg entities: MyEntity): List<Long> =
-      CoroutinesRoom.execute(__db, true, object : Callable<List<Long>> {
-    public override fun call(): List<Long> {
-      __db.beginTransaction()
-      try {
-        val _result: List<Long> = __upsertAdapterOfMyEntity.upsertAndReturnIdsList(entities)
-        __db.setTransactionSuccessful()
-        return _result
-      } finally {
-        __db.endTransaction()
-      }
-    }
-  })
+      performSuspending(__db, false, true) { _connection ->
+    val _result: List<Long> = __upsertAdapterOfMyEntity.upsertAndReturnIdsList(_connection,
+        entities)
+    _result
+  }
 
   public companion object {
     public fun getRequiredConverters(): List<KClass<*>> = emptyList()
diff --git a/room/room-runtime/api/restricted_current.txt b/room/room-runtime/api/restricted_current.txt
index 72b8059..7f7d3a1 100644
--- a/room/room-runtime/api/restricted_current.txt
+++ b/room/room-runtime/api/restricted_current.txt
@@ -75,6 +75,12 @@
     method public final void insert(androidx.sqlite.SQLiteConnection connection, T entity);
     method public final void insert(androidx.sqlite.SQLiteConnection connection, T[] entities);
     method public final long insertAndReturnId(androidx.sqlite.SQLiteConnection connection, T entity);
+    method public final long[] insertAndReturnIdsArray(androidx.sqlite.SQLiteConnection connection, java.util.Collection<? extends T> entities);
+    method public final long[] insertAndReturnIdsArray(androidx.sqlite.SQLiteConnection connection, T[] entities);
+    method public final Long[] insertAndReturnIdsArrayBox(androidx.sqlite.SQLiteConnection connection, java.util.Collection<? extends T> entities);
+    method public final Long[] insertAndReturnIdsArrayBox(androidx.sqlite.SQLiteConnection connection, T[] entities);
+    method public final java.util.List<java.lang.Long> insertAndReturnIdsList(androidx.sqlite.SQLiteConnection connection, java.util.Collection<? extends T> entities);
+    method public final java.util.List<java.lang.Long> insertAndReturnIdsList(androidx.sqlite.SQLiteConnection connection, T[] entities);
   }
 
   @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public abstract class EntityInsertionAdapter<T> extends androidx.room.SharedSQLiteStatement {
diff --git a/room/room-runtime/src/commonMain/kotlin/androidx/room/EntityInsertAdapter.kt b/room/room-runtime/src/commonMain/kotlin/androidx/room/EntityInsertAdapter.kt
index 3b29a47..900c0e6 100644
--- a/room/room-runtime/src/commonMain/kotlin/androidx/room/EntityInsertAdapter.kt
+++ b/room/room-runtime/src/commonMain/kotlin/androidx/room/EntityInsertAdapter.kt
@@ -24,7 +24,7 @@
 /**
  * Implementations of this class knows how to insert a particular entity.
  *
- * This is an internal library class and all of its implementations are auto-generated.
+ * This is a library class and all of its implementations are auto-generated.
  *
  * @constructor Creates an InsertionAdapter that can insert the entity type T into the given
  * database.
@@ -120,7 +120,7 @@
      * @param entities Entities to insert
      * @return The SQLite row ids, for entities that are not inserted the row id returned will be -1
      */
-    internal fun insertAndReturnIdsArray(
+    fun insertAndReturnIdsArray(
         connection: SQLiteConnection,
         entities: Collection<T>
     ): LongArray {
@@ -140,7 +140,7 @@
      * @param entities Entities to insert
      * @return The SQLite row ids, for entities that are not inserted the row id returned will be -1
      */
-    internal fun insertAndReturnIdsArray(
+    fun insertAndReturnIdsArray(
         connection: SQLiteConnection,
         entities: Array<out T>
     ): LongArray {
@@ -160,7 +160,7 @@
      * @param entities Entities to insert
      * @return The SQLite row ids, for entities that are not inserted the row id returned will be -1
      */
-    internal fun insertAndReturnIdsArrayBox(
+    fun insertAndReturnIdsArrayBox(
         connection: SQLiteConnection,
         entities: Collection<T>
     ): Array<out Long> {
@@ -180,7 +180,7 @@
      * @param entities Entities to insert
      * @return The SQLite row ids, for entities that are not inserted the row id returned will be -1
      */
-    internal fun insertAndReturnIdsArrayBox(
+    fun insertAndReturnIdsArrayBox(
         connection: SQLiteConnection,
         entities: Array<out T>
     ): Array<out Long> {
@@ -200,7 +200,7 @@
      * @param entities Entities to insert
      * @return The SQLite row ids, for entities that are not inserted the row id returned will be -1
      */
-    internal fun insertAndReturnIdsList(
+    fun insertAndReturnIdsList(
         connection: SQLiteConnection,
         entities: Array<out T>
     ): List<Long> {
@@ -222,7 +222,7 @@
      * @param entities Entities to insert
      * @return The SQLite row ids, for entities that are not inserted the row id returned will be -1
      */
-    internal fun insertAndReturnIdsList(
+    fun insertAndReturnIdsList(
         connection: SQLiteConnection,
         entities: Collection<T>
     ): List<Long> {
diff --git a/tv/tv-material/api/current.txt b/tv/tv-material/api/current.txt
index fa7ecbc7..e574b41 100644
--- a/tv/tv-material/api/current.txt
+++ b/tv/tv-material/api/current.txt
@@ -16,7 +16,7 @@
     field public static final androidx.tv.material3.AssistChipDefaults INSTANCE;
   }
 
-  @SuppressCompatibility @androidx.compose.runtime.Immutable @androidx.tv.material3.ExperimentalTvMaterial3Api public final class Border {
+  @androidx.compose.runtime.Immutable public final class Border {
     ctor public Border(androidx.compose.foundation.BorderStroke border, optional float inset, optional androidx.compose.ui.graphics.Shape shape);
     method public androidx.tv.material3.Border copy(optional androidx.compose.foundation.BorderStroke? border, optional androidx.compose.ui.unit.Dp? inset, optional androidx.compose.ui.graphics.Shape? shape);
     method public androidx.compose.foundation.BorderStroke getBorder();
@@ -342,7 +342,7 @@
     field public static final androidx.tv.material3.FilterChipDefaults INSTANCE;
   }
 
-  @SuppressCompatibility @androidx.compose.runtime.Immutable @androidx.tv.material3.ExperimentalTvMaterial3Api public final class Glow {
+  @androidx.compose.runtime.Immutable public final class Glow {
     ctor public Glow(long elevationColor, float elevation);
     method public androidx.tv.material3.Glow copy(optional androidx.compose.ui.graphics.Color? glowColor, optional androidx.compose.ui.unit.Dp? glowElevation);
     method public float getElevation();
diff --git a/tv/tv-material/api/restricted_current.txt b/tv/tv-material/api/restricted_current.txt
index fa7ecbc7..e574b41 100644
--- a/tv/tv-material/api/restricted_current.txt
+++ b/tv/tv-material/api/restricted_current.txt
@@ -16,7 +16,7 @@
     field public static final androidx.tv.material3.AssistChipDefaults INSTANCE;
   }
 
-  @SuppressCompatibility @androidx.compose.runtime.Immutable @androidx.tv.material3.ExperimentalTvMaterial3Api public final class Border {
+  @androidx.compose.runtime.Immutable public final class Border {
     ctor public Border(androidx.compose.foundation.BorderStroke border, optional float inset, optional androidx.compose.ui.graphics.Shape shape);
     method public androidx.tv.material3.Border copy(optional androidx.compose.foundation.BorderStroke? border, optional androidx.compose.ui.unit.Dp? inset, optional androidx.compose.ui.graphics.Shape? shape);
     method public androidx.compose.foundation.BorderStroke getBorder();
@@ -342,7 +342,7 @@
     field public static final androidx.tv.material3.FilterChipDefaults INSTANCE;
   }
 
-  @SuppressCompatibility @androidx.compose.runtime.Immutable @androidx.tv.material3.ExperimentalTvMaterial3Api public final class Glow {
+  @androidx.compose.runtime.Immutable public final class Glow {
     ctor public Glow(long elevationColor, float elevation);
     method public androidx.tv.material3.Glow copy(optional androidx.compose.ui.graphics.Color? glowColor, optional androidx.compose.ui.unit.Dp? glowElevation);
     method public float getElevation();
diff --git a/tv/tv-material/src/main/java/androidx/tv/material3/Border.kt b/tv/tv-material/src/main/java/androidx/tv/material3/Border.kt
index cec521e..3e076f2 100644
--- a/tv/tv-material/src/main/java/androidx/tv/material3/Border.kt
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/Border.kt
@@ -31,7 +31,6 @@
  * @param inset defines how far (in dp) should the border be from the component it's applied to
  * @param shape defines the [Shape] of the border
  */
-@ExperimentalTvMaterial3Api
 @Immutable
 class Border(
     val border: BorderStroke,
diff --git a/tv/tv-material/src/main/java/androidx/tv/material3/Glow.kt b/tv/tv-material/src/main/java/androidx/tv/material3/Glow.kt
index a246c10..41cd8df 100644
--- a/tv/tv-material/src/main/java/androidx/tv/material3/Glow.kt
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/Glow.kt
@@ -27,7 +27,6 @@
  * @param elevation defines how strong should be the shadow. Larger its value, further the
  * shadow goes from the center of the component.
  */
-@ExperimentalTvMaterial3Api
 @Immutable
 class Glow(
     val elevationColor: Color,
diff --git a/vectordrawable/vectordrawable/src/main/java/androidx/vectordrawable/graphics/drawable/VectorDrawableCompat.java b/vectordrawable/vectordrawable/src/main/java/androidx/vectordrawable/graphics/drawable/VectorDrawableCompat.java
index d1c0a37..187b211f 100644
--- a/vectordrawable/vectordrawable/src/main/java/androidx/vectordrawable/graphics/drawable/VectorDrawableCompat.java
+++ b/vectordrawable/vectordrawable/src/main/java/androidx/vectordrawable/graphics/drawable/VectorDrawableCompat.java
@@ -1738,7 +1738,7 @@
         public void toPath(Path path) {
             path.reset();
             if (mNodes != null) {
-                PathParser.PathDataNode.nodesToPath(mNodes, path);
+                PathParser.nodesToPath(mNodes, path);
             }
         }
 
diff --git a/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/CompositionLocals.kt b/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/CompositionLocals.kt
index 6c6eb62..ba80a25e 100644
--- a/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/CompositionLocals.kt
+++ b/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/CompositionLocals.kt
@@ -25,6 +25,7 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.ProvidableCompositionLocal
 import androidx.compose.runtime.compositionLocalOf
+import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.runtime.staticCompositionLocalOf
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.platform.LocalContext
@@ -37,6 +38,7 @@
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.callbackFlow
 import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
 
 /**
  * CompositionLocal for global reduce-motion setting, which turns off animations and
@@ -83,9 +85,11 @@
 
 // Callers of this function should pass an application context. Passing an activity context might
 // result in activity leaks.
+@Composable
 private fun getReduceMotionFlowFor(applicationContext: Context): StateFlow<Boolean> {
     val resolver = applicationContext.contentResolver
     val reduceMotionUri = Settings.Global.getUriFor(REDUCE_MOTION)
+    val coroutineScope = rememberCoroutineScope()
 
     return reduceMotionCache.updateAndGet {
         it ?: callbackFlow {
@@ -103,10 +107,11 @@
                     }
                 }
 
-            resolver.registerContentObserver(reduceMotionUri, false, contentObserver)
-
-            // Force send value when flow is initialized
-            resolver.notifyChange(reduceMotionUri, contentObserver)
+            coroutineScope.launch {
+                resolver.registerContentObserver(reduceMotionUri, false, contentObserver)
+                // Force send value when flow is initialized
+                resolver.notifyChange(reduceMotionUri, contentObserver)
+            }
 
             awaitClose {
                 resolver.unregisterContentObserver(contentObserver)
diff --git a/wear/compose/compose-material-core/src/main/java/androidx/wear/compose/materialcore/Resources.kt b/wear/compose/compose-material-core/src/main/java/androidx/wear/compose/materialcore/Resources.kt
index 6381c51..c18d248 100644
--- a/wear/compose/compose-material-core/src/main/java/androidx/wear/compose/materialcore/Resources.kt
+++ b/wear/compose/compose-material-core/src/main/java/androidx/wear/compose/materialcore/Resources.kt
@@ -19,7 +19,6 @@
 import android.text.format.DateFormat
 import androidx.annotation.RestrictTo
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.remember
 import androidx.compose.ui.platform.LocalConfiguration
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.platform.LocalLayoutDirection
@@ -29,18 +28,14 @@
 @Composable
 fun isLayoutDirectionRtl(): Boolean {
     val layoutDirection: LayoutDirection = LocalLayoutDirection.current
-    return remember(layoutDirection) {
-        layoutDirection == LayoutDirection.Rtl
-    }
+    return layoutDirection == LayoutDirection.Rtl
 }
 
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
 @Composable
 fun isRoundDevice(): Boolean {
     val configuration = LocalConfiguration.current
-    return remember(configuration) {
-        configuration.isScreenRound
-    }
+    return configuration.isScreenRound
 }
 
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
diff --git a/work/work-runtime/api/current.txt b/work/work-runtime/api/current.txt
index 0fd0cead..59abcf4 100644
--- a/work/work-runtime/api/current.txt
+++ b/work/work-runtime/api/current.txt
@@ -65,7 +65,7 @@
     method public androidx.work.Configuration.Builder setRunnableScheduler(androidx.work.RunnableScheduler runnableScheduler);
     method public androidx.work.Configuration.Builder setSchedulingExceptionHandler(androidx.core.util.Consumer<java.lang.Throwable> schedulingExceptionHandler);
     method public androidx.work.Configuration.Builder setTaskExecutor(java.util.concurrent.Executor taskExecutor);
-    method public androidx.work.Configuration.Builder setWorkerCoroutineContext(kotlinx.coroutines.CoroutineDispatcher dispatcher);
+    method public androidx.work.Configuration.Builder setWorkerCoroutineContext(kotlin.coroutines.CoroutineContext context);
     method public androidx.work.Configuration.Builder setWorkerExecutionExceptionHandler(androidx.core.util.Consumer<androidx.work.WorkerExceptionInfo> workerExceptionHandler);
     method public androidx.work.Configuration.Builder setWorkerFactory(androidx.work.WorkerFactory workerFactory);
     method public androidx.work.Configuration.Builder setWorkerInitializationExceptionHandler(androidx.core.util.Consumer<androidx.work.WorkerExceptionInfo> workerExceptionHandler);
diff --git a/work/work-runtime/api/restricted_current.txt b/work/work-runtime/api/restricted_current.txt
index 0fd0cead..59abcf4 100644
--- a/work/work-runtime/api/restricted_current.txt
+++ b/work/work-runtime/api/restricted_current.txt
@@ -65,7 +65,7 @@
     method public androidx.work.Configuration.Builder setRunnableScheduler(androidx.work.RunnableScheduler runnableScheduler);
     method public androidx.work.Configuration.Builder setSchedulingExceptionHandler(androidx.core.util.Consumer<java.lang.Throwable> schedulingExceptionHandler);
     method public androidx.work.Configuration.Builder setTaskExecutor(java.util.concurrent.Executor taskExecutor);
-    method public androidx.work.Configuration.Builder setWorkerCoroutineContext(kotlinx.coroutines.CoroutineDispatcher dispatcher);
+    method public androidx.work.Configuration.Builder setWorkerCoroutineContext(kotlin.coroutines.CoroutineContext context);
     method public androidx.work.Configuration.Builder setWorkerExecutionExceptionHandler(androidx.core.util.Consumer<androidx.work.WorkerExceptionInfo> workerExceptionHandler);
     method public androidx.work.Configuration.Builder setWorkerFactory(androidx.work.WorkerFactory workerFactory);
     method public androidx.work.Configuration.Builder setWorkerInitializationExceptionHandler(androidx.core.util.Consumer<androidx.work.WorkerExceptionInfo> workerExceptionHandler);
diff --git a/work/work-runtime/src/main/java/androidx/work/Configuration.kt b/work/work-runtime/src/main/java/androidx/work/Configuration.kt
index 8f33b1d..2224980 100644
--- a/work/work-runtime/src/main/java/androidx/work/Configuration.kt
+++ b/work/work-runtime/src/main/java/androidx/work/Configuration.kt
@@ -292,16 +292,17 @@
         }
 
         /**
-         * Specifies a custom [CoroutineDispatcher] to run [CoroutineWorker.doWork].
+         * Specifies a custom [CoroutineContext] to run [CoroutineWorker.doWork].
+         * WorkManager will use its own `Job` with the provided [CoroutineContext].
          *
-         * If [setExecutor] wasn't called then [dispatcher] will be used as [Executor]
+         * If [setExecutor] wasn't called then [context] will be used as [Executor]
          * to run [Worker] as well.
          *
-         * @param dispatcher A [CoroutineDispatcher] for running [CoroutineWorker]s
+         * @param context A [CoroutineContext] for running [CoroutineWorker]s
          * @return This [Builder] instance
          */
-        fun setWorkerCoroutineContext(dispatcher: CoroutineDispatcher): Builder {
-            this.workerContext = dispatcher
+        fun setWorkerCoroutineContext(context: CoroutineContext): Builder {
+            this.workerContext = context
             return this
         }