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
}