Merge "[Screen Flash] Add quirk for setting FLASH_MODE_TORCH in Pixels" into androidx-main
diff --git a/activity/activity/src/androidTest/java/androidx/activity/result/PickVisualMediaRequestTest.kt b/activity/activity/src/androidTest/java/androidx/activity/result/PickVisualMediaRequestTest.kt
index b3b3f9a..4d388bc 100644
--- a/activity/activity/src/androidTest/java/androidx/activity/result/PickVisualMediaRequestTest.kt
+++ b/activity/activity/src/androidTest/java/androidx/activity/result/PickVisualMediaRequestTest.kt
@@ -19,14 +19,12 @@
import androidx.activity.result.contract.ActivityResultContracts
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
-import androidx.test.filters.SdkSuppress
import com.google.common.truth.Truth.assertThat
import leakcanary.DetectLeaksAfterTestSuccess
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
-@SdkSuppress(minSdkVersion = 19) // API 19 in the minimum for PickVisualMediaRequest
@MediumTest
@RunWith(AndroidJUnit4::class)
class PickVisualMediaRequestTest {
diff --git a/appsearch/appsearch-builtin-types/src/androidTest/java/androidx/appsearch/utils/BootCountUtilTest.java b/appsearch/appsearch-builtin-types/src/androidTest/java/androidx/appsearch/utils/BootCountUtilTest.java
deleted file mode 100644
index 421ce9b..0000000
--- a/appsearch/appsearch-builtin-types/src/androidTest/java/androidx/appsearch/utils/BootCountUtilTest.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * 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.appsearch.utils;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.content.Context;
-
-import androidx.test.core.app.ApplicationProvider;
-import androidx.test.filters.SdkSuppress;
-
-import org.junit.Before;
-import org.junit.Test;
-
-public class BootCountUtilTest {
- private Context mContext;
-
- @Before
- public void setUp() {
- mContext = ApplicationProvider.getApplicationContext();
- }
-
- @Test
- @SdkSuppress(maxSdkVersion = 16)
- public void testGetCurrentBootCount_belowAPI17_returnsNeg1() {
- assertThat(BootCountUtil.getCurrentBootCount(mContext)).isEqualTo(-1);
- }
-}
diff --git a/biometric/biometric/src/androidTest/java/androidx/biometric/BiometricPromptTest.java b/biometric/biometric/src/androidTest/java/androidx/biometric/BiometricPromptTest.java
index c8d92bc..8797f80 100644
--- a/biometric/biometric/src/androidTest/java/androidx/biometric/BiometricPromptTest.java
+++ b/biometric/biometric/src/androidTest/java/androidx/biometric/BiometricPromptTest.java
@@ -27,7 +27,6 @@
import androidx.test.core.app.ActivityScenario;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.MediumTest;
-import androidx.test.filters.SdkSuppress;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.uiautomator.UiDevice;
@@ -47,11 +46,9 @@
}
@Test // prevents test runner from failing since all tests are ignored
- @SdkSuppress(minSdkVersion = 18)
public void dummy() {}
@Test
- @SdkSuppress(minSdkVersion = 18)
public void testViewModel_inActivity() {
try (ActivityScenario<TestActivity> scenario =
ActivityScenario.launch(TestActivity.class)) {
@@ -75,7 +72,6 @@
}
@Test
- @SdkSuppress(minSdkVersion = 18)
public void testViewModel_inFragment() {
try (FragmentScenario<TestFragment> scenario =
FragmentScenario.launchInContainer(TestFragment.class)) {
diff --git a/bluetooth/integration-tests/testapp/src/main/java/androidx/bluetooth/integration/testapp/ui/connections/ConnectionsViewModel.kt b/bluetooth/integration-tests/testapp/src/main/java/androidx/bluetooth/integration/testapp/ui/connections/ConnectionsViewModel.kt
index 0305623..8ca27ee 100644
--- a/bluetooth/integration-tests/testapp/src/main/java/androidx/bluetooth/integration/testapp/ui/connections/ConnectionsViewModel.kt
+++ b/bluetooth/integration-tests/testapp/src/main/java/androidx/bluetooth/integration/testapp/ui/connections/ConnectionsViewModel.kt
@@ -101,8 +101,12 @@
Log.d(TAG, "bluetoothLe.connectGatt result: services() = $services")
deviceConnection.status = Status.CONNECTED
- deviceConnection.services = services
- updateUi()
+ launch {
+ servicesFlow.collect {
+ deviceConnection.services = it
+ updateUi()
+ }
+ }
deviceConnection.>
object : OnCharacteristicActionClick {
@@ -111,14 +115,6 @@
characteristic: GattCharacteristic,
action: @OnCharacteristicActionClick.Action Int
) {
- Log.d(
- TAG,
- "onClick() called with: " +
- "deviceConnection = $deviceConnection, " +
- "characteristic = $characteristic, " +
- "action = $action"
- )
-
when (action) {
OnCharacteristicActionClick.READ -> readCharacteristic(
this@connectGatt,
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/ExternalRequestProcessor.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/ExternalRequestProcessor.kt
index 396ace8..1726cce 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/ExternalRequestProcessor.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/ExternalRequestProcessor.kt
@@ -138,9 +138,12 @@
)
}
- override fun submit(captureSequence: ExternalCaptureSequence): Int {
- check(!closed.value)
+ override fun submit(captureSequence: ExternalCaptureSequence): Int? {
check(captureSequence.captureRequestList.isNotEmpty())
+ if (closed.value) {
+ Log.warn { "Cannot submit $captureSequence because $this is closed" }
+ return null
+ }
if (captureSequence.repeating) {
check(captureSequence.captureRequestList.size == 1)
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/ProcessingCaptureSession.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/ProcessingCaptureSession.java
index a95a720..cfba49c 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/ProcessingCaptureSession.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/ProcessingCaptureSession.java
@@ -425,49 +425,41 @@
updateParameters(mSessionOptions, mStillCaptureOptions);
mSessionProcessor.startCapture(captureConfig.isPostviewEnabled(),
new SessionProcessor.CaptureCallback() {
- @Override
- public void onCaptureStarted(int captureSequenceId, long timestamp) {
- mExecutor.execute(() -> {
- for (CameraCaptureCallback cameraCaptureCallback :
- captureConfig.getCameraCaptureCallbacks()) {
- cameraCaptureCallback.onCaptureStarted();
+ @Override
+ public void onCaptureStarted(int captureSequenceId, long timestamp) {
+ for (CameraCaptureCallback cameraCaptureCallback :
+ captureConfig.getCameraCaptureCallbacks()) {
+ cameraCaptureCallback.onCaptureStarted();
+ }
}
- });
- }
- @Override
- public void onCaptureFailed(
- int captureSequenceId) {
- mExecutor.execute(() -> {
- for (CameraCaptureCallback cameraCaptureCallback :
- captureConfig.getCameraCaptureCallbacks()) {
- cameraCaptureCallback.onCaptureFailed(new CameraCaptureFailure(
- CameraCaptureFailure.Reason.ERROR));
+ @Override
+ public void onCaptureFailed(
+ int captureSequenceId) {
+ for (CameraCaptureCallback cameraCaptureCallback :
+ captureConfig.getCameraCaptureCallbacks()) {
+ cameraCaptureCallback.onCaptureFailed(new CameraCaptureFailure(
+ CameraCaptureFailure.Reason.ERROR));
+ }
}
- });
- }
- @Override
- public void onCaptureSequenceCompleted(int captureSequenceId) {
- mExecutor.execute(() -> {
- for (CameraCaptureCallback cameraCaptureCallback :
- captureConfig.getCameraCaptureCallbacks()) {
- cameraCaptureCallback.onCaptureCompleted(
- new CameraCaptureResult.EmptyCameraCaptureResult());
+ @Override
+ public void onCaptureSequenceCompleted(int captureSequenceId) {
+ for (CameraCaptureCallback cameraCaptureCallback :
+ captureConfig.getCameraCaptureCallbacks()) {
+ cameraCaptureCallback.onCaptureCompleted(
+ new CameraCaptureResult.EmptyCameraCaptureResult());
+ }
}
- });
- }
- @Override
- public void onCaptureProcessProgressed(int progress) {
- mExecutor.execute(() -> {
- for (CameraCaptureCallback cameraCaptureCallback :
- captureConfig.getCameraCaptureCallbacks()) {
- cameraCaptureCallback.onCaptureProcessProgressed(progress);
+ @Override
+ public void onCaptureProcessProgressed(int progress) {
+ for (CameraCaptureCallback cameraCaptureCallback :
+ captureConfig.getCameraCaptureCallbacks()) {
+ cameraCaptureCallback.onCaptureProcessProgressed(progress);
+ }
}
});
- }
- });
}
/**
diff --git a/camera/camera-core/api/current.txt b/camera/camera-core/api/current.txt
index 57be5be..4a977b9 100644
--- a/camera/camera-core/api/current.txt
+++ b/camera/camera-core/api/current.txt
@@ -288,11 +288,14 @@
@RequiresApi(21) public final class ImageCapture extends androidx.camera.core.UseCase {
method public int getCaptureMode();
method public int getFlashMode();
+ method public static androidx.camera.core.ImageCaptureCapabilities getImageCaptureCapabilities(androidx.camera.core.CameraInfo);
method @IntRange(from=1, to=100) public int getJpegQuality();
+ method public androidx.camera.core.resolutionselector.ResolutionSelector? getPostviewResolutionSelector();
method public androidx.camera.core.ResolutionInfo? getResolutionInfo();
method public androidx.camera.core.resolutionselector.ResolutionSelector? getResolutionSelector();
method public androidx.camera.core.ImageCapture.ScreenFlashUiControl? getScreenFlashUiControl();
method public int getTargetRotation();
+ method public boolean isPostviewEnabled();
method public void setCropAspectRatio(android.util.Rational);
method public void setFlashMode(int);
method public void setScreenFlashUiControl(androidx.camera.core.ImageCapture.ScreenFlashUiControl?);
@@ -321,6 +324,8 @@
method public androidx.camera.core.ImageCapture.Builder setFlashMode(int);
method public androidx.camera.core.ImageCapture.Builder setIoExecutor(java.util.concurrent.Executor);
method public androidx.camera.core.ImageCapture.Builder setJpegQuality(@IntRange(from=1, to=100) int);
+ method public androidx.camera.core.ImageCapture.Builder setPostviewEnabled(boolean);
+ method public androidx.camera.core.ImageCapture.Builder setPostviewResolutionSelector(androidx.camera.core.resolutionselector.ResolutionSelector);
method public androidx.camera.core.ImageCapture.Builder setResolutionSelector(androidx.camera.core.resolutionselector.ResolutionSelector);
method public androidx.camera.core.ImageCapture.Builder setScreenFlashUiControl(androidx.camera.core.ImageCapture.ScreenFlashUiControl);
method @Deprecated public androidx.camera.core.ImageCapture.Builder setTargetAspectRatio(int);
@@ -341,15 +346,19 @@
public abstract static class ImageCapture.OnImageCapturedCallback {
ctor public ImageCapture.OnImageCapturedCallback();
+ method public void onCaptureProcessProgressed(int);
method public void onCaptureStarted();
method public void onCaptureSuccess(androidx.camera.core.ImageProxy);
method public void onError(androidx.camera.core.ImageCaptureException);
+ method public void onPostviewBitmapAvailable(android.graphics.Bitmap);
}
public static interface ImageCapture.OnImageSavedCallback {
+ method public default void onCaptureProcessProgressed(int);
method public default void onCaptureStarted();
method public void onError(androidx.camera.core.ImageCaptureException);
method public void onImageSaved(androidx.camera.core.ImageCapture.OutputFileResults);
+ method public default void onPostviewBitmapAvailable(android.graphics.Bitmap);
}
public static final class ImageCapture.OutputFileOptions {
@@ -376,6 +385,11 @@
method @UiThread public void clearScreenFlashUi();
}
+ public interface ImageCaptureCapabilities {
+ method public boolean isCaptureProcessProgressSupported();
+ method public boolean isPostviewSupported();
+ }
+
@RequiresApi(21) public class ImageCaptureException extends java.lang.Exception {
ctor public ImageCaptureException(int, String, Throwable?);
method public int getImageCaptureError();
@@ -491,6 +505,7 @@
public interface SurfaceOutput extends java.io.Closeable {
method public void close();
+ method public default android.graphics.Matrix getSensorToBufferTransform();
method public android.util.Size getSize();
method public android.view.Surface getSurface(java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.camera.core.SurfaceOutput.Event!>);
method public int getTargets();
@@ -531,7 +546,10 @@
@com.google.auto.value.AutoValue public abstract static class SurfaceRequest.TransformationInfo {
method public abstract android.graphics.Rect getCropRect();
+ method public abstract boolean getMirroring();
method public abstract int getRotationDegrees();
+ method public abstract android.graphics.Matrix getSensorToBufferTransform();
+ method public abstract boolean hasCameraTransform();
}
public static interface SurfaceRequest.TransformationInfoListener {
diff --git a/camera/camera-core/api/restricted_current.txt b/camera/camera-core/api/restricted_current.txt
index 57be5be..4a977b9 100644
--- a/camera/camera-core/api/restricted_current.txt
+++ b/camera/camera-core/api/restricted_current.txt
@@ -288,11 +288,14 @@
@RequiresApi(21) public final class ImageCapture extends androidx.camera.core.UseCase {
method public int getCaptureMode();
method public int getFlashMode();
+ method public static androidx.camera.core.ImageCaptureCapabilities getImageCaptureCapabilities(androidx.camera.core.CameraInfo);
method @IntRange(from=1, to=100) public int getJpegQuality();
+ method public androidx.camera.core.resolutionselector.ResolutionSelector? getPostviewResolutionSelector();
method public androidx.camera.core.ResolutionInfo? getResolutionInfo();
method public androidx.camera.core.resolutionselector.ResolutionSelector? getResolutionSelector();
method public androidx.camera.core.ImageCapture.ScreenFlashUiControl? getScreenFlashUiControl();
method public int getTargetRotation();
+ method public boolean isPostviewEnabled();
method public void setCropAspectRatio(android.util.Rational);
method public void setFlashMode(int);
method public void setScreenFlashUiControl(androidx.camera.core.ImageCapture.ScreenFlashUiControl?);
@@ -321,6 +324,8 @@
method public androidx.camera.core.ImageCapture.Builder setFlashMode(int);
method public androidx.camera.core.ImageCapture.Builder setIoExecutor(java.util.concurrent.Executor);
method public androidx.camera.core.ImageCapture.Builder setJpegQuality(@IntRange(from=1, to=100) int);
+ method public androidx.camera.core.ImageCapture.Builder setPostviewEnabled(boolean);
+ method public androidx.camera.core.ImageCapture.Builder setPostviewResolutionSelector(androidx.camera.core.resolutionselector.ResolutionSelector);
method public androidx.camera.core.ImageCapture.Builder setResolutionSelector(androidx.camera.core.resolutionselector.ResolutionSelector);
method public androidx.camera.core.ImageCapture.Builder setScreenFlashUiControl(androidx.camera.core.ImageCapture.ScreenFlashUiControl);
method @Deprecated public androidx.camera.core.ImageCapture.Builder setTargetAspectRatio(int);
@@ -341,15 +346,19 @@
public abstract static class ImageCapture.OnImageCapturedCallback {
ctor public ImageCapture.OnImageCapturedCallback();
+ method public void onCaptureProcessProgressed(int);
method public void onCaptureStarted();
method public void onCaptureSuccess(androidx.camera.core.ImageProxy);
method public void onError(androidx.camera.core.ImageCaptureException);
+ method public void onPostviewBitmapAvailable(android.graphics.Bitmap);
}
public static interface ImageCapture.OnImageSavedCallback {
+ method public default void onCaptureProcessProgressed(int);
method public default void onCaptureStarted();
method public void onError(androidx.camera.core.ImageCaptureException);
method public void onImageSaved(androidx.camera.core.ImageCapture.OutputFileResults);
+ method public default void onPostviewBitmapAvailable(android.graphics.Bitmap);
}
public static final class ImageCapture.OutputFileOptions {
@@ -376,6 +385,11 @@
method @UiThread public void clearScreenFlashUi();
}
+ public interface ImageCaptureCapabilities {
+ method public boolean isCaptureProcessProgressSupported();
+ method public boolean isPostviewSupported();
+ }
+
@RequiresApi(21) public class ImageCaptureException extends java.lang.Exception {
ctor public ImageCaptureException(int, String, Throwable?);
method public int getImageCaptureError();
@@ -491,6 +505,7 @@
public interface SurfaceOutput extends java.io.Closeable {
method public void close();
+ method public default android.graphics.Matrix getSensorToBufferTransform();
method public android.util.Size getSize();
method public android.view.Surface getSurface(java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.camera.core.SurfaceOutput.Event!>);
method public int getTargets();
@@ -531,7 +546,10 @@
@com.google.auto.value.AutoValue public abstract static class SurfaceRequest.TransformationInfo {
method public abstract android.graphics.Rect getCropRect();
+ method public abstract boolean getMirroring();
method public abstract int getRotationDegrees();
+ method public abstract android.graphics.Matrix getSensorToBufferTransform();
+ method public abstract boolean hasCameraTransform();
}
public static interface SurfaceRequest.TransformationInfoListener {
diff --git a/camera/camera-core/src/androidTest/java/androidx/camera/core/imagecapture/FakeTakePictureCallback.kt b/camera/camera-core/src/androidTest/java/androidx/camera/core/imagecapture/FakeTakePictureCallback.kt
index ac60c54..128a2c3 100644
--- a/camera/camera-core/src/androidTest/java/androidx/camera/core/imagecapture/FakeTakePictureCallback.kt
+++ b/camera/camera-core/src/androidTest/java/androidx/camera/core/imagecapture/FakeTakePictureCallback.kt
@@ -16,6 +16,7 @@
package androidx.camera.core.imagecapture
+import android.graphics.Bitmap
import androidx.camera.core.ImageCapture.OutputFileResults
import androidx.camera.core.ImageCaptureException
import androidx.camera.core.ImageProxy
@@ -33,7 +34,7 @@
private var onDiskResult: OutputFileResults? = null
private var onDiskResultCont: Continuation<OutputFileResults>? = null
- override fun onPostviewImageAvailable(imageProxy: ImageProxy) {
+ override fun onPostviewBitmapAvailable(bitmap: Bitmap) {
}
override fun onCaptureProcessProgressed(progress: Int) {
diff --git a/camera/camera-core/src/androidTest/java/androidx/camera/core/imagecapture/Image2BitmapTest.kt b/camera/camera-core/src/androidTest/java/androidx/camera/core/imagecapture/Image2BitmapTest.kt
new file mode 100644
index 0000000..71e4ad9
--- /dev/null
+++ b/camera/camera-core/src/androidTest/java/androidx/camera/core/imagecapture/Image2BitmapTest.kt
@@ -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.imagecapture
+
+import android.graphics.BitmapFactory
+import android.graphics.Matrix
+import android.graphics.Rect
+import androidx.camera.core.internal.utils.ImageUtil
+import androidx.camera.core.processing.Packet
+import androidx.camera.testing.impl.ExifUtil
+import androidx.camera.testing.impl.TestImageUtil
+import androidx.camera.testing.impl.fakes.FakeImageInfo
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SdkSuppress
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@SdkSuppress(minSdkVersion = 21)
+class Image2BitmapTest {
+ private val operation = Image2Bitmap()
+
+ @Test
+ fun processYuvImage_rotation0_assertOutput() {
+ processYuvImage_assertOutput(rotationDegrees = 0)
+ }
+ @Test
+ fun processYuvImage_rotation90_assertOutput() {
+ processYuvImage_assertOutput(rotationDegrees = 90)
+ }
+
+ @Test
+ fun processYuvImage_rotation180_assertOutput() {
+ processYuvImage_assertOutput(rotationDegrees = 180)
+ }
+
+ @Test
+ fun processYuvImage_rotation270_assertOutput() {
+ processYuvImage_assertOutput(rotationDegrees = 270)
+ }
+
+ private fun processYuvImage_assertOutput(rotationDegrees: Int) {
+ // Arrange.
+ val imageInfo = FakeImageInfo()
+ val yuvImage = TestImageUtil.createYuvFakeImageProxy(imageInfo, Utils.WIDTH, Utils.HEIGHT)
+ val input = Packet.of(
+ yuvImage,
+ null, // YuvImage doesn't have exif info.
+ Rect(0, 0, Utils.WIDTH, Utils.HEIGHT),
+ rotationDegrees,
+ Matrix(),
+ Utils.CAMERA_CAPTURE_RESULT
+ )
+ val inputDecodedBitmap = ImageUtil.createBitmapFromImageProxy(yuvImage)
+
+ // Act.
+ val outputBitmap = operation.apply(input)
+
+ // Assert: the image is the same.
+ val inputRotatedBitmap = TestImageUtil.rotateBitmap(inputDecodedBitmap, rotationDegrees)
+ Truth.assertThat(TestImageUtil.getAverageDiff(
+ inputRotatedBitmap, outputBitmap)).isEqualTo(0)
+
+ // Assert: image is closed.
+ Truth.assertThat(yuvImage.isClosed).isTrue()
+ }
+
+ @Test
+ fun processJpegImage_rotation0_assertOutput() {
+ processJpegImage_assertOutput(rotationDegrees = 0)
+ }
+
+ @Test
+ fun processJpegImage_rotation90_assertOutput() {
+ processJpegImage_assertOutput(rotationDegrees = 90)
+ }
+
+ @Test
+ fun processJpegImage_rotation180_assertOutput() {
+ processJpegImage_assertOutput(rotationDegrees = 180)
+ }
+
+ @Test
+ fun processJpegImage_rotation270_assertOutput() {
+ processJpegImage_assertOutput(rotationDegrees = 270)
+ }
+
+ private fun processJpegImage_assertOutput(rotationDegrees: Int) {
+ // Arrange.
+ val imageInfo = FakeImageInfo()
+ val jpegBytes = TestImageUtil.createJpegBytes(Utils.WIDTH, Utils.HEIGHT)
+ val jpegImage = TestImageUtil.createJpegFakeImageProxy(imageInfo, jpegBytes)
+ val exif = ExifUtil.createExif(jpegBytes)
+ val input = Packet.of(
+ jpegImage,
+ exif,
+ Rect(0, 0, Utils.WIDTH, Utils.HEIGHT),
+ rotationDegrees,
+ Matrix(),
+ Utils.CAMERA_CAPTURE_RESULT
+ )
+ val inputDecodedBitmap = BitmapFactory.decodeByteArray(jpegBytes, 0, jpegBytes.size)
+
+ // Act.
+ val outputBitmap = operation.apply(input)
+
+ // Assert: the image is the same.
+ val inputRotatedBitmap = TestImageUtil.rotateBitmap(inputDecodedBitmap, rotationDegrees)
+ Truth.assertThat(TestImageUtil.getAverageDiff(
+ inputRotatedBitmap, outputBitmap)).isEqualTo(0)
+
+ // Assert: image is closed.
+ Truth.assertThat(jpegImage.isClosed).isTrue()
+ }
+}
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 683aab2..58a3170 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
@@ -59,6 +59,7 @@
import android.content.ContentResolver;
import android.content.ContentValues;
+import android.graphics.Bitmap;
import android.graphics.ImageFormat;
import android.graphics.Rect;
import android.location.Location;
@@ -883,7 +884,6 @@
*
* @return {@link ImageCaptureCapabilities}
*/
- @RestrictTo(Scope.LIBRARY_GROUP)
@NonNull
public static ImageCaptureCapabilities getImageCaptureCapabilities(
@NonNull CameraInfo cameraInfo) {
@@ -1174,6 +1174,7 @@
boolean isPostviewEnabled =
getCurrentConfig().retrieveOption(OPTION_POSTVIEW_ENABLED, false);
Size postViewSize = null;
+ int postviewFormat = ImageFormat.YUV_420_888;
if (isPostviewEnabled) {
SessionProcessor sessionProcessor = getSessionProcessor();
@@ -1183,9 +1184,14 @@
null);
Map<Integer, List<Size>> map =
sessionProcessor.getSupportedPostviewSize(resolution);
- List<Size> sizes = map.get(ImageFormat.JPEG);
+ // Prefer YUV because it takes less time to decode to bitmap.
+ List<Size> sizes = map.get(ImageFormat.YUV_420_888);
+ if (sizes == null || sizes.isEmpty()) {
+ sizes = map.get(ImageFormat.JPEG);
+ postviewFormat = ImageFormat.JPEG;
+ }
- if (sizes != null) {
+ if (sizes != null && !sizes.isEmpty()) {
if (postviewSizeSelector != null) {
Collections.sort(sizes, new CompareSizesByArea(true));
CameraInternal camera = getCamera();
@@ -1215,7 +1221,7 @@
}
mImagePipeline = new ImagePipeline(config, resolution, getEffect(), isVirtualCamera,
- postViewSize);
+ postViewSize, postviewFormat);
if (mTakePictureManager == null) {
// mTakePictureManager is reused when the Surface is reset.
@@ -1420,6 +1426,26 @@
}
/**
+ * Returns if postview is enabled or not.
+ *
+ * @see Builder#setPostviewEnabled(boolean)
+ */
+ public boolean isPostviewEnabled() {
+ return getCurrentConfig().retrieveOption(OPTION_POSTVIEW_ENABLED, false);
+ }
+
+ /**
+ * Returns the {@link ResolutionSelector} used to select the postview size.
+ *
+ * @see Builder#setPostviewResolutionSelector(ResolutionSelector)
+ */
+ @Nullable
+ public ResolutionSelector getPostviewResolutionSelector() {
+ return getCurrentConfig().retrieveOption(OPTION_POSTVIEW_RESOLUTION_SELECTOR,
+ null);
+ }
+
+ /**
* Describes the error that occurred during an image capture operation (such as {@link
* ImageCapture#takePicture(Executor, OnImageCapturedCallback)}).
*
@@ -1503,46 +1529,38 @@
*
* <p>To know in advanced if this callback will be invoked or not, check the
* capabilities by {@link #getImageCaptureCapabilities(CameraInfo)} and
- * {@link ImageCaptureCapabilities#isCaptureProcessProgressSupported()}.
+ * {@link ImageCaptureCapabilities#isCaptureProcessProgressSupported()}. If supported,
+ * this callback will be called multiple times with monotonically increasing
+ * values. At the minimum the callback will be called once with value 100 to
+ * indicate the processing is finished. This callback will always be called before
+ * {@link #onImageSaved(OutputFileResults)}.
*
* @param progress the progress ranging from 0 to 100.
*/
- @RestrictTo(Scope.LIBRARY_GROUP)
default void onCaptureProcessProgressed(int progress) {
}
/**
- * Callback to notify that the postview image is available. The postview is intended to be
+ * Callback to notify that the postview bitmap is available. The postview is intended to be
* shown on UI before the long-processing capture is completed in order to provide a
- * better UX. The image format is {@link ImageFormat#JPEG}.
+ * better UX.
*
* <p>The postview is only available when the
* {@link ImageCaptureCapabilities#isPostviewSupported()} returns true for the specified
* {@link CameraInfo} and applications must explicitly enable the postview using the
- * {@link Builder#setPostviewEnabled(boolean)}. Please note that if something goes wrong
- * when processing the postview, this callback method won't be invoked.
+ * {@link Builder#setPostviewEnabled(boolean)}. This callback will be called before
+ * {@link #onImageSaved(OutputFileResults)}. But if something goes wrong when processing
+ * the postview, this callback method could be skipped.
*
- * <p>Please close the {@link ImageProxy} once you no longer need it. The default
- * implementation of this method will close it in case apps don't implement the method.
- *
- * <p>The image is provided as captured by the underlying {@link ImageReader} without
- * rotation applied. The value in {@code image.getImageInfo().getRotationDegrees()}
- * describes the magnitude of clockwise rotation, which if applied to the image will make
- * it match the currently configured target rotation.
- *
- * <p>For example, if the current target rotation is set to the display rotation,
- * rotationDegrees is the rotation to apply to the image to match the display orientation.
- * A rotation of 90 degrees would mean rotating the image 90 degrees clockwise produces an
- * image that will match the display orientation.
+ * <p>The bitmap is rotated according to the target rotation set to the {@link ImageCapture}
+ * to make it upright. If target rotation is not set, the display rotation is used.
*
* <p>See also {@link ImageCapture.Builder#setTargetRotation(int)} and
* {@link #setTargetRotation(int)}.
*
- * @param image the postview {@link ImageProxy}
+ * @param bitmap the postview bitmap.
*/
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
- default void onPostviewImageAvailable(@NonNull ImageProxy image) {
- image.close();
+ default void onPostviewBitmapAvailable(@NonNull Bitmap bitmap) {
}
}
@@ -1610,46 +1628,39 @@
*
* <p>To know in advanced if this callback will be invoked or not, check the
* capabilities by {@link #getImageCaptureCapabilities(CameraInfo)} and
- * {@link ImageCaptureCapabilities#isCaptureProcessProgressSupported()}.
+ * {@link ImageCaptureCapabilities#isCaptureProcessProgressSupported()}. If supported,
+ * this callback will be called multiple times with monotonically increasing
+ * values. At the minimum the callback will be called once with value 100 to
+ * indicate the processing is finished. This callback will always be called before
+ * {@link #onCaptureSuccess(ImageProxy)}.
*
* @param progress the progress ranging from 0 to 100.
*/
- @RestrictTo(Scope.LIBRARY_GROUP)
public void onCaptureProcessProgressed(int progress) {
}
/**
- * Callback to notify that the postview image is available. The postview is intended to be
+ * Callback to notify that the postview bitmap is available. The postview is intended to be
* shown on UI before the long-processing capture is completed in order to provide a
- * better UX. The image format is {@link ImageFormat#JPEG}.
+ * better UX.
*
* <p>The postview is only available when the
* {@link ImageCaptureCapabilities#isPostviewSupported()} returns true for the specified
* {@link CameraInfo} and applications must explicitly enable the postview using the
- * {@link Builder#setPostviewEnabled(boolean)}. Please note that if something goes wrong
- * when processing the postview, this callback method won't be invoked.
+ * {@link Builder#setPostviewEnabled(boolean)}. This callback will be called before
+ * {@link #onCaptureSuccess(ImageProxy)}. But if something goes wrong when processing the
+ * postview, this callback method could be skipped.
*
- * <p>Please close the {@link ImageProxy} once you no longer need it. The default
- * implementation of this method will close it in case apps don't implement the method.
- *
- * <p>The image is provided as captured by the underlying {@link ImageReader} without
- * rotation applied. The value in {@code image.getImageInfo().getRotationDegrees()}
- * describes the magnitude of clockwise rotation, which if applied to the image will make
- * it match the currently configured target rotation.
- *
- * <p>For example, if the current target rotation is set to the display rotation,
- * rotationDegrees is the rotation to apply to the image to match the display orientation.
- * A rotation of 90 degrees would mean rotating the image 90 degrees clockwise produces an
- * image that will match the display orientation.
+ * <p>The bitmap is rotated according to the target rotation set to the {@link ImageCapture}
+ * to make it upright. If target rotation is not set, the display rotation is used.
*
* <p>See also {@link ImageCapture.Builder#setTargetRotation(int)} and
* {@link #setTargetRotation(int)}.
*
- * @param image the postview {@link ImageProxy}
+ * @param bitmap the postview bitmap.
+
*/
- @RestrictTo(Scope.LIBRARY_GROUP)
- public void onPostviewImageAvailable(@NonNull ImageProxy image) {
- image.close();
+ public void onPostviewBitmapAvailable(@NonNull Bitmap bitmap) {
}
}
@@ -2533,22 +2544,27 @@
}
/**
- * Enables the postview which allows you to get the unprocessed image before the processing
- * is done during the <code>takePicture</code> call.
+ * Enables postview image generation. A postview image is a low-quality image
+ * that's produced earlier during image capture than the final high-quality image,
+ * and can be used as a thumbnail or placeholder until the final image is ready.
*
- * <p>By default the largest available postview size that are smaller or equal to the
+ * <p>When the postview is available,
+ * {@link OnImageCapturedCallback#onPostviewBitmapAvailable(Bitmap)} or
+ * {@link OnImageSavedCallback#onPostviewBitmapAvailable(Bitmap)} will be called.
+ *
+ * <p>By default the largest available postview size that is smaller or equal to the
* ImagaeCapture size will be used to configure the postview. The {@link ResolutionSelector}
* can also be used to select a specific size via
* {@link #setPostviewResolutionSelector(ResolutionSelector)}.
*
- * <p>It is recommended to query the capture capability via
- * {@link #getImageCaptureCapabilities(CameraInfo)} before enabling this feature to avoid
- * unnecessary initializations.
+ * <p>You can query the postview capability by invoking
+ * {@link #getImageCaptureCapabilities(CameraInfo)}. If
+ * {@link ImageCaptureCapabilities#isPostviewSupported()} returns false and you still
+ * enable the postview, the postview image won't be generated.
*
* @param postviewEnabled whether postview is enabled or not
* @return the current Builder.
*/
- @RestrictTo(Scope.LIBRARY_GROUP)
@NonNull
public Builder setPostviewEnabled(boolean postviewEnabled) {
getMutableConfig().insertOption(OPTION_POSTVIEW_ENABLED,
@@ -2558,15 +2574,17 @@
/**
* Set the {@link ResolutionSelector} to select the postview size from the available
- * postview sizes. Please note the selected size will be smaller or equal to the
- * ImageCapture size.
+ * postview sizes. These available postview sizes are smaller or equal to the
+ * ImageCapture size. You can implement the
+ * {@link androidx.camera.core.resolutionselector.ResolutionFilter} and set it to the
+ * {@link ResolutionSelector} to get the list of available sizes and determine which size
+ * to use.
*
* <p>If no sizes can be selected using the given {@link ResolutionSelector}, it will throw
* an {@link IllegalArgumentException} when {@code bindToLifecycle()} is invoked.
*
* @return the current Builder.
*/
- @RestrictTo(Scope.LIBRARY_GROUP)
@NonNull
public Builder setPostviewResolutionSelector(
@NonNull ResolutionSelector resolutionSelector) {
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 1c81d44..9c05020 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,24 +16,29 @@
package androidx.camera.core;
-import androidx.annotation.RestrictTo;
-
/**
* ImageCaptureCapabilities is used to query {@link ImageCapture} use case capabilities on the
* device.
*/
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public interface ImageCaptureCapabilities {
/**
- * Returns if the takePicture() call in {@link ImageCapture} is capable of outputting post
- * view images ahead of final images. If supported, apps can enable the postview using
+ * Returns if the takePicture() call in {@link ImageCapture} is capable of outputting
+ * postview images.
+ *
+ * <p>A postview image is a low-quality image that's produced earlier during image capture
+ * than the final high-quality image, and can be used as a thumbnail or placeholder until the
+ * final image is ready.
+ *
+ * If supported, apps can enable the postview using
* {@link ImageCapture.Builder#setPostviewEnabled(boolean)}.
*/
boolean isPostviewSupported();
/**
* Returns if the takePicture() call in {@link ImageCapture} is capable of notifying the
- * onCaptureProcessProgressed callback to the apps.
+ * {@link ImageCapture.OnImageSavedCallback#onCaptureProcessProgressed(int)} or
+ * {@link ImageCapture.OnImageCapturedCallback#onCaptureProcessProgressed(int)} callback to
+ * the apps.
*/
boolean isCaptureProcessProgressSupported();
}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/SurfaceOutput.java b/camera/camera-core/src/main/java/androidx/camera/core/SurfaceOutput.java
index 1c24d84e..943b069 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/SurfaceOutput.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/SurfaceOutput.java
@@ -156,8 +156,7 @@
*
* <p>The value is a mapping from sensor coordinates to buffer coordinates, which is,
* from the rect of {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE} to the
- * rect defined by {@code (0, 0, SurfaceRequest#getResolution#getWidth(),
- * SurfaceRequest#getResolution#getHeight())}. The matrix can
+ * rect defined by {@code (0, 0, #getSize()#getWidth(), #getSize()#getHeight())}. The matrix can
* be used to map the coordinates from one {@link UseCase} to another. For example,
* detecting face with {@link ImageAnalysis}, and then highlighting the face in
* {@link Preview}.
@@ -174,7 +173,6 @@
* analysisToEffect.postConcat(sensorToEffect);
* </pre></code>
*/
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@NonNull
default Matrix getSensorToBufferTransform() {
return new Matrix();
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/SurfaceRequest.java b/camera/camera-core/src/main/java/androidx/camera/core/SurfaceRequest.java
index e2d2292..e110e52 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/SurfaceRequest.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/SurfaceRequest.java
@@ -892,23 +892,45 @@
/**
* Whether the {@link Surface} contains the camera transform.
*
- * <p>The {@link Surface} may contain a transformation, which will be used by Android
- * components such as {@link TextureView} and {@link SurfaceView} to transform the output.
- * The app may need to handle the transformation differently based on whether this value
- * exists.
+ * <p>When the Surface is connected to the camera directly, camera writes the
+ * camera orientation value to the Surface. For example, the value can be retrieved via
+ * {@link SurfaceTexture#getTransformMatrix(float[])}. Android components such
+ * as {@link TextureView} and {@link SurfaceView} use the value to transform the output.
+ * When the Surface is not connect to the camera directly, for example, when it was
+ * copied with OpenGL, the Surface will not contain the camera orientation value.
*
- * <ul>
- * <li>If the producer is the camera, then the {@link Surface} will contain a
- * transformation that represents the camera orientation. In that case, this method will
- * return {@code true}.
- * <li>If the producer is not the camera, for example, if the stream has been edited by
- * CameraX, then the {@link Surface} will not contain any transformation. In that case,
- * this method will return {@code false}.
- * </ul>
+ * <p>The app may need to transform the UI differently based on this flag. If this value
+ * is true, the app only needs to apply the Surface transformation; otherwise, the app
+ * needs to apply the value of {@link #getRotationDegrees()}. For example, if the preview
+ * is displayed in a {@link TextureView}:
*
- * @return true if the producer writes the camera transformation to the {@link Surface}.
+ * <pre><code>
+ * int rotationDegrees;
+ * if (surfaceRequest.hasCameraTransform()) {
+ * switch (textureView.getDisplay().getRotation()) {
+ * case Surface.ROTATION_0:
+ * rotationDegrees = 0;
+ * break;
+ * case Surface.ROTATION_90:
+ * rotationDegrees = 90;
+ * break;
+ * case Surface.ROTATION_180:
+ * rotationDegrees = 180;
+ * break;
+ * case Surface.ROTATION_270:
+ * rotationDegrees = 270;
+ * break;
+ * }
+ * } else {
+ * rotationDegrees = transformationInfo.getRotationDegrees();
+ * }
+ * Matrix textureViewTransform = new Matrix();
+ * textureViewTransform.postRotate(rotationDegrees);
+ * textureView.setTransform(textureViewTransform);
+ * </code></pre>
+ *
+ * @return true if the {@link Surface} contains the camera transformation.
*/
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public abstract boolean hasCameraTransform();
/**
@@ -916,15 +938,24 @@
*
* <p>The value is a mapping from sensor coordinates to buffer coordinates, which is,
* from the rect of {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE} to the
- * rect defined by {@code (0, 0, SurfaceRequest#getResolution#getWidth(),
- * SurfaceRequest#getResolution#getHeight())}. The matrix can
- * be used to map the coordinates from one {@link UseCase} to another. For example,
- * detecting face with {@link ImageAnalysis}, and then highlighting the face in
+ * rect defined by {@code (0, 0, #getResolution#getWidth(), #getResolution#getHeight())}.
+ * The matrix can be used to map the coordinates from one {@link UseCase} to another. For
+ * example, detecting face with {@link ImageAnalysis}, and then highlighting the face in
* {@link Preview}.
+ *
+ * <p>Code sample
+ * <code><pre>
+ * // Get the transformation from sensor to effect input.
+ * Matrix sensorToEffect = surfaceRequest.getSensorToBufferTransform();
+ * // Get the transformation from sensor to ImageAnalysis.
+ * Matrix sensorToAnalysis = imageProxy.getSensorToBufferTransform();
+ * // Concatenate the two matrices to get the transformation from ImageAnalysis to effect.
+ * Matrix analysisToEffect = Matrix()
+ * sensorToAnalysis.invert(analysisToEffect);
+ * analysisToEffect.postConcat(sensorToEffect);
+ * </pre></code>
*/
- // TODO(b/292286071): make this public in 1.4 alpha.
@NonNull
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public abstract Matrix getSensorToBufferTransform();
/**
@@ -934,8 +965,6 @@
* example, for front camera preview, the buffer should usually be mirrored. The
* mirroring should be applied after the {@link #getRotationDegrees()} is applied.
*/
- // TODO(b/292286071): make this public in 1.4 alpha.
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public abstract boolean getMirroring();
/**
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 0bfb216..2baba27 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
@@ -177,7 +177,7 @@
createImageReaderProxy(inputEdge.getImageReaderProxyProvider(),
inputEdge.getPostviewSize().getWidth(),
inputEdge.getPostviewSize().getHeight(),
- ImageFormat.JPEG);
+ inputEdge.getPostviewImageFormat());
postviewImageReader.setOnImageAvailableListener(imageReader -> {
try {
ImageProxy image = imageReader.acquireLatestImage();
@@ -191,7 +191,8 @@
mSafeCloseImageReaderForPostview = new SafeCloseImageReaderProxy(postviewImageReader);
inputEdge.setPostviewSurface(
- postviewImageReader.getSurface(), inputEdge.getPostviewSize());
+ postviewImageReader.getSurface(),
+ inputEdge.getPostviewSize(), inputEdge.getPostviewImageFormat());
}
inputEdge.getRequestEdge().setListener(requestConsumer);
@@ -393,6 +394,11 @@
abstract Size getPostviewSize();
/**
+ * The image format of the postview.
+ */
+ abstract int getPostviewImageFormat();
+
+ /**
* Edge that accepts {@link ProcessingRequest}.
*/
@NonNull
@@ -428,8 +434,8 @@
mSurface = new ImmediateSurface(surface, getSize(), getInputFormat());
}
- void setPostviewSurface(@NonNull Surface surface, @NonNull Size size) {
- mPostviewSurface = new ImmediateSurface(surface, size, ImageFormat.JPEG);
+ void setPostviewSurface(@NonNull Surface surface, @NonNull Size size, int imageFormat) {
+ mPostviewSurface = new ImmediateSurface(surface, size, imageFormat);
}
/**
@@ -448,10 +454,19 @@
@NonNull
static In of(Size size, int inputFormat, int outputFormat, boolean isVirtualCamera,
- @Nullable ImageReaderProxyProvider imageReaderProxyProvider,
- @Nullable Size postviewSize) {
+ @Nullable ImageReaderProxyProvider imageReaderProxyProvider) {
return new AutoValue_CaptureNode_In(size, inputFormat, outputFormat, isVirtualCamera,
- imageReaderProxyProvider, postviewSize, new Edge<>(), new Edge<>());
+ imageReaderProxyProvider, null, ImageFormat.YUV_420_888,
+ new Edge<>(), new Edge<>());
+ }
+
+ @NonNull
+ static In of(Size size, int inputFormat, int outputFormat, boolean isVirtualCamera,
+ @Nullable ImageReaderProxyProvider imageReaderProxyProvider,
+ @Nullable Size postviewSize, int postviewImageFormat) {
+ return new AutoValue_CaptureNode_In(size, inputFormat, outputFormat, isVirtualCamera,
+ imageReaderProxyProvider, postviewSize, postviewImageFormat,
+ new Edge<>(), new Edge<>());
}
}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/Image2Bitmap.java b/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/Image2Bitmap.java
new file mode 100644
index 0000000..1f0dd60
--- /dev/null
+++ b/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/Image2Bitmap.java
@@ -0,0 +1,107 @@
+/*
+ * 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.imagecapture;
+
+import static androidx.camera.core.ImageCapture.ERROR_UNKNOWN;
+import static androidx.camera.core.ImageProcessingUtil.convertYUVToRGB;
+
+import android.graphics.Bitmap;
+import android.graphics.ImageFormat;
+import android.graphics.PixelFormat;
+import android.os.Build;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+import androidx.camera.core.ImageCaptureException;
+import androidx.camera.core.ImageProxy;
+import androidx.camera.core.ImageReaderProxys;
+import androidx.camera.core.SafeCloseImageReaderProxy;
+import androidx.camera.core.internal.utils.ImageUtil;
+import androidx.camera.core.processing.Operation;
+import androidx.camera.core.processing.Packet;
+
+import java.nio.ByteBuffer;
+
+/**
+ * Convert an {@link ImageProxy} to a {@link Bitmap}.
+ *
+ * <p>An {@link ImageCaptureException} will be thrown if the conversion failed.
+ * Currently it supports only {@link ImageFormat#YUV_420_888} and
+ * {@link ImageFormat#JPEG} image. {@link IllegalArgumentException} will be thrown if the input
+ * image format is not supported.
+ */
+@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
+public class Image2Bitmap implements
+ Operation<Packet<ImageProxy>, Bitmap> {
+ @NonNull
+ @Override
+ public Bitmap apply(@NonNull Packet<ImageProxy> imageProxyPacket)
+ throws ImageCaptureException {
+ Bitmap result;
+ SafeCloseImageReaderProxy rgbImageReader = null;
+ try {
+ if (imageProxyPacket.getFormat() == ImageFormat.YUV_420_888) {
+ ImageProxy yuvImage = imageProxyPacket.getData();
+ boolean needFlip = (imageProxyPacket.getRotationDegrees() % 180) != 0;
+ int tempImageReaderWidth = needFlip ? yuvImage.getHeight() : yuvImage.getWidth();
+ int tempImageReaderHeight = needFlip ? yuvImage.getWidth() : yuvImage.getHeight();
+
+ // TODO(b/313548792): remove the usage of ImageReader by creating a version of
+ // convertYUVToBitmap that also rotates the output.
+ rgbImageReader = new SafeCloseImageReaderProxy(
+ ImageReaderProxys.createIsolatedReader(
+ tempImageReaderWidth, tempImageReaderHeight,
+ PixelFormat.RGBA_8888, 2)
+ );
+
+ ByteBuffer rgbConvertedBuffer = ByteBuffer.allocateDirect(
+ yuvImage.getWidth() * yuvImage.getHeight() * 4);
+ ImageProxy imageProxyRGB = convertYUVToRGB(
+ yuvImage,
+ rgbImageReader,
+ rgbConvertedBuffer,
+ imageProxyPacket.getRotationDegrees(),
+ /* onePixelShiftEnabled */false);
+ yuvImage.close();
+ if (imageProxyRGB == null) {
+ throw new ImageCaptureException(ERROR_UNKNOWN, "Can't covert YUV to RGB", null);
+ }
+ Bitmap bitmap = ImageUtil.createBitmapFromImageProxy(imageProxyRGB);
+ imageProxyRGB.close();
+ result = bitmap;
+ } else if (imageProxyPacket.getFormat() == ImageFormat.JPEG) {
+ ImageProxy jpegImage = imageProxyPacket.getData();
+ Bitmap bitmap = ImageUtil.createBitmapFromImageProxy(jpegImage);
+ jpegImage.close();
+ result = ImageUtil.rotateBitmap(bitmap, imageProxyPacket.getRotationDegrees());
+ } else {
+ throw new IllegalArgumentException("Invalid postview image format : "
+ + imageProxyPacket.getFormat());
+ }
+ return result;
+ } catch (UnsupportedOperationException e) {
+ String format = imageProxyPacket.getFormat() == ImageFormat.YUV_420_888
+ ? "YUV" : "JPEG";
+ throw new ImageCaptureException(ERROR_UNKNOWN,
+ "Can't convert " + format + " to bitmap", e);
+ } finally {
+ if (rgbImageReader != null) {
+ rgbImageReader.close();
+ }
+ }
+ }
+}
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 9aeb2b3..6b17899 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
@@ -91,7 +91,17 @@
@NonNull ImageCaptureConfig useCaseConfig,
@NonNull Size cameraSurfaceSize) {
this(useCaseConfig, cameraSurfaceSize, /*cameraEffect=*/ null,
- /*isVirtualCamera=*/ false, /* postviewSize */ null);
+ /*isVirtualCamera=*/ false, /* postviewSize */ null, ImageFormat.YUV_420_888);
+ }
+
+ @MainThread
+ public ImagePipeline(
+ @NonNull ImageCaptureConfig useCaseConfig,
+ @NonNull Size cameraSurfaceSize,
+ @Nullable CameraEffect cameraEffect,
+ boolean isVirtualCamera) {
+ this(useCaseConfig, cameraSurfaceSize, cameraEffect, isVirtualCamera,
+ null, ImageFormat.YUV_420_888);
}
@MainThread
@@ -100,7 +110,8 @@
@NonNull Size cameraSurfaceSize,
@Nullable CameraEffect cameraEffect,
boolean isVirtualCamera,
- @Nullable Size postviewSize) {
+ @Nullable Size postviewSize,
+ int postviewImageFormat) {
checkMainThread();
mUseCaseConfig = useCaseConfig;
mCaptureConfig = CaptureConfig.Builder.createFrom(useCaseConfig).build();
@@ -119,7 +130,8 @@
getOutputFormat(),
isVirtualCamera,
mUseCaseConfig.getImageReaderProxyProvider(),
- postviewSize);
+ postviewSize,
+ postviewImageFormat);
CaptureNode.Out captureOut = mCaptureNode.transform(mPipelineIn);
ProcessingNode.In processingIn = mBundlingNode.transform(captureOut);
mProcessingNode.transform(processingIn);
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 e889383..5b57a4f 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
@@ -74,6 +74,7 @@
private Operation<Packet<byte[]>, Packet<Bitmap>> mJpegBytes2CroppedBitmap;
private Operation<Packet<ImageProxy>, ImageProxy> mJpegImage2Result;
private Operation<Packet<byte[]>, Packet<ImageProxy>> mJpegBytes2Image;
+ private Operation<Packet<ImageProxy>, Bitmap> mImage2Bitmap;
private Operation<Packet<Bitmap>, Packet<Bitmap>> mBitmapEffect;
/**
@@ -110,6 +111,7 @@
inputPacket -> {
if (inputPacket.getProcessingRequest().isAborted()) {
// No-ops if the request is aborted.
+ inputPacket.getImageProxy().close();
return;
}
mBlockingExecutor.execute(() -> processInputPacket(inputPacket));
@@ -131,6 +133,7 @@
mBitmap2JpegBytes = new Bitmap2JpegBytes();
mJpegBytes2Disk = new JpegBytes2Disk();
mJpegImage2Result = new JpegImage2Result();
+ mImage2Bitmap = new Image2Bitmap();
if (inputEdge.getInputFormat() == YUV_420_888 || mImageProcessor != null) {
// Convert JPEG bytes to ImageProxy for:
// - YUV input: YUV -> JPEG -> ImageProxy
@@ -178,8 +181,8 @@
ProcessingRequest request = inputPacket.getProcessingRequest();
try {
Packet<ImageProxy> image = mInput2Packet.apply(inputPacket);
- ImageProxy result = mJpegImage2Result.apply(image);
- mainThreadExecutor().execute(() -> request.onPostviewImageAvailable(result));
+ Bitmap bitmap = mImage2Bitmap.apply(image);
+ mainThreadExecutor().execute(() -> request.onPostviewBitmapAvailable(bitmap));
} catch (Exception e) {
inputPacket.getImageProxy().close();
Logger.e(TAG, "process postview input packet failed.", e);
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/ProcessingRequest.java b/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/ProcessingRequest.java
index 807a54f..f112e2d 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/ProcessingRequest.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/ProcessingRequest.java
@@ -18,6 +18,7 @@
import static java.util.Objects.requireNonNull;
+import android.graphics.Bitmap;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.os.Build;
@@ -149,8 +150,8 @@
mCallback.onFinalResult(outputFileResults);
}
- void onPostviewImageAvailable(@NonNull ImageProxy imageProxy) {
- mCallback.onPostviewImageAvailable(imageProxy);
+ void onPostviewBitmapAvailable(@NonNull Bitmap bitmap) {
+ mCallback.onPostviewBitmapAvailable(bitmap);
}
/**
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/RequestWithCallback.java b/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/RequestWithCallback.java
index e01ba85..6cbd546 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/RequestWithCallback.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/RequestWithCallback.java
@@ -21,6 +21,7 @@
import static java.util.Objects.requireNonNull;
+import android.graphics.Bitmap;
import android.os.Build;
import androidx.annotation.MainThread;
@@ -143,6 +144,7 @@
public void onFinalResult(@NonNull ImageProxy imageProxy) {
checkMainThread();
if (mIsAborted) {
+ imageProxy.close();
// Do not deliver result if the request has been aborted.
return;
}
@@ -162,14 +164,14 @@
}
@Override
- public void onPostviewImageAvailable(@NonNull ImageProxy imageProxy) {
+ public void onPostviewBitmapAvailable(@NonNull Bitmap bitmap) {
checkMainThread();
if (mIsAborted) {
// Do not deliver result if the request has been aborted.
return;
}
- mTakePictureRequest.onPostviewImageAvailable(imageProxy);
+ mTakePictureRequest.onPostviewBitmapAvailable(bitmap);
}
@MainThread
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/SingleBundlingNode.java b/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/SingleBundlingNode.java
index 73cd04b..8c95d23 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/SingleBundlingNode.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/SingleBundlingNode.java
@@ -103,7 +103,12 @@
@MainThread
private void matchPostviewImageWithRequest(@NonNull ImageProxy imageProxy) {
checkMainThread();
- checkState(mPendingRequest != null);
+ // if the final image arrives earlier than the post image, mPendingRequest will be set to
+ // null in matchImageWithRequest. In this case, we will ignore the postview processing.
+ if (mPendingRequest == null) {
+ imageProxy.close();
+ return;
+ }
mOutputEdge.getPostviewEdge().accept(
ProcessingNode.InputPacket.of(mPendingRequest, imageProxy));
}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/TakePictureCallback.java b/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/TakePictureCallback.java
index 5b61165..ce23053 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/TakePictureCallback.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/TakePictureCallback.java
@@ -16,6 +16,8 @@
package androidx.camera.core.imagecapture;
+import android.graphics.Bitmap;
+
import androidx.annotation.MainThread;
import androidx.annotation.NonNull;
import androidx.camera.core.ImageCapture;
@@ -70,9 +72,9 @@
void onFinalResult(@NonNull ImageProxy imageProxy);
/**
- * Invoked when the postview image is available.
+ * Invoked when the postview bitmap is available.
*/
- void onPostviewImageAvailable(@NonNull ImageProxy imageProxy);
+ void onPostviewBitmapAvailable(@NonNull Bitmap bitmap);
/**
* Invoked when camera fails to return the image.
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/TakePictureRequest.java b/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/TakePictureRequest.java
index 1851232..5feb8a4 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/TakePictureRequest.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/TakePictureRequest.java
@@ -21,6 +21,7 @@
import static java.util.Objects.requireNonNull;
+import android.graphics.Bitmap;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.os.Build;
@@ -212,14 +213,14 @@
}
/**
- * Delivers postview image result to the app.
+ * Delivers postview bitmap result to the app.
*/
- void onPostviewImageAvailable(@NonNull ImageProxy imageProxy) {
+ void onPostviewBitmapAvailable(@NonNull Bitmap bitmap) {
getAppExecutor().execute(() -> {
if (getOnDiskCallback() != null) {
- getOnDiskCallback().onPostviewImageAvailable(imageProxy);
+ getOnDiskCallback().onPostviewBitmapAvailable(bitmap);
} else if (getInMemoryCallback() != null) {
- getInMemoryCallback().onPostviewImageAvailable(imageProxy);
+ getInMemoryCallback().onPostviewBitmapAvailable(bitmap);
}
});
}
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 c198150..3ca31bf 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
@@ -114,6 +114,17 @@
}
/**
+ * Rotates the bitmap by the given rotation degrees.
+ */
+ @NonNull
+ public static Bitmap rotateBitmap(@NonNull Bitmap bitmap, int rotationDegrees) {
+ Matrix matrix = new Matrix();
+ matrix.postRotate(rotationDegrees);
+ return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix,
+ true);
+ }
+
+ /**
* Creates a direct {@link ByteBuffer} and copy the content of the {@link Bitmap}.
*/
@NonNull
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/CaptureNodeTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/CaptureNodeTest.kt
index 6c2969f..c25d59a 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/CaptureNodeTest.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/CaptureNodeTest.kt
@@ -16,8 +16,8 @@
package androidx.camera.core.imagecapture
-import android.graphics.ImageFormat
import android.graphics.ImageFormat.JPEG
+import android.graphics.ImageFormat.YUV_420_888
import android.os.Build
import android.os.Looper.getMainLooper
import android.util.Size
@@ -55,7 +55,7 @@
@Before
fun setUp() {
- captureNodeIn = CaptureNode.In.of(Size(10, 10), JPEG, JPEG, false, null, null)
+ captureNodeIn = CaptureNode.In.of(Size(10, 10), JPEG, JPEG, false, null)
captureNodeOut = captureNode.transform(captureNodeIn)
captureNodeOut.imageEdge.setListener {
imagePropagated.add(it)
@@ -77,7 +77,7 @@
val imageReaderProvider = ImageReaderProxyProvider { _, _, _, _, _ ->
imageReader
}
- val input = CaptureNode.In.of(Size(10, 10), JPEG, JPEG, false, imageReaderProvider, null)
+ val input = CaptureNode.In.of(Size(10, 10), JPEG, JPEG, false, imageReaderProvider)
// Act: transform.
val node = CaptureNode()
node.transform(input)
@@ -171,12 +171,13 @@
}
@Test
- fun transformWithPostviewSize() {
+ fun transformWithPostviewSizeAndYuv() {
// Arrange: set the postviewSize to the CaptureNode.In
val postviewSize = Size(640, 480)
val input = CaptureNode.In.of(Size(10, 10), JPEG, JPEG, false, null,
- postviewSize)
+ postviewSize, YUV_420_888
+ )
// Act: transform.
val node = CaptureNode()
@@ -185,7 +186,26 @@
// Assert: postview surface is created
assertThat(input.postviewSurface).isNotNull()
assertThat(input.postviewSurface!!.prescribedSize).isEqualTo(postviewSize)
- assertThat(input.postviewSurface!!.prescribedStreamFormat).isEqualTo(ImageFormat.JPEG)
+ assertThat(input.postviewSurface!!.prescribedStreamFormat).isEqualTo(YUV_420_888)
+ node.release()
+ }
+
+ @Test
+ fun transformWithPostviewSizeAndJpeg() {
+ // Arrange: set the postviewSize to the CaptureNode.In
+ val postviewSize = Size(640, 480)
+
+ val input = CaptureNode.In.of(Size(10, 10), JPEG, JPEG, false, null,
+ postviewSize, JPEG)
+
+ // Act: transform.
+ val node = CaptureNode()
+ node.transform(input)
+
+ // Assert: postview surface is created
+ assertThat(input.postviewSurface).isNotNull()
+ assertThat(input.postviewSurface!!.prescribedSize).isEqualTo(postviewSize)
+ assertThat(input.postviewSurface!!.prescribedStreamFormat).isEqualTo(JPEG)
node.release()
}
}
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/FakeTakePictureCallback.kt b/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/FakeTakePictureCallback.kt
index 018c083..866772e 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/FakeTakePictureCallback.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/FakeTakePictureCallback.kt
@@ -16,6 +16,7 @@
package androidx.camera.core.imagecapture
+import android.graphics.Bitmap
import androidx.camera.core.ImageCapture.OutputFileResults
import androidx.camera.core.ImageCaptureException
import androidx.camera.core.ImageProxy
@@ -32,7 +33,7 @@
var processFailure: ImageCaptureException? = null
var onDiskResult: OutputFileResults? = null
var captureProcessProgress = -1
- var onPostviewImageAvailable: ImageProxy? = null
+ var onPostviewBitmapAvailable: Bitmap? = null
var aborted = false
@@ -68,7 +69,7 @@
captureProcessProgress = progress
}
- override fun onPostviewImageAvailable(imageProxy: ImageProxy) {
- >
+ override fun onPostviewBitmapAvailable(bitmap: Bitmap) {
+ >
}
}
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 0c108e2..47151d6 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
@@ -137,7 +137,7 @@
imagePipeline.close()
imagePipeline =
ImagePipeline(imageCaptureConfig, SIZE,
- /*cameraEffect=*/null, /*isVirtualCamera=*/true, /*postviewSize*/ null)
+ /*cameraEffect=*/null, /*isVirtualCamera=*/true)
// Act & assert: send and receive ImageProxy.
sendInMemoryRequest_receivesImageProxy()
@@ -155,8 +155,7 @@
imageCaptureConfig,
SIZE,
GrayscaleImageEffect(),
- false,
- /*postviewSize*/ null
+ false
).processingNode.mImageProcessor
).isNotNull()
}
@@ -260,10 +259,29 @@
}
@Test
- fun createSessionConfigBuilderWithPostviewEnabled() {
+ fun createSessionConfigBuilderWithYuvPostviewEnabled() {
// Arrange.
val postviewSize = Size(640, 480)
- imagePipeline = ImagePipeline(imageCaptureConfig, SIZE, null, false, postviewSize)
+ imagePipeline = ImagePipeline(imageCaptureConfig, SIZE, null, false,
+ postviewSize, ImageFormat.YUV_420_888)
+
+ // Act: create SessionConfig
+ val sessionConfig = imagePipeline.createSessionConfigBuilder(SIZE).build()
+
+ // Assert: SessionConfig contains the postview output config.
+ assertThat(sessionConfig.postviewOutputConfig).isNotNull()
+ assertThat(sessionConfig.postviewOutputConfig!!.surface.prescribedSize)
+ .isEqualTo(postviewSize)
+ assertThat(sessionConfig.postviewOutputConfig!!.surface.prescribedStreamFormat)
+ .isEqualTo(ImageFormat.YUV_420_888)
+ }
+
+ @Test
+ fun createSessionConfigBuilderWithJpegPostviewEnabled() {
+ // Arrange.
+ val postviewSize = Size(640, 480)
+ imagePipeline = ImagePipeline(imageCaptureConfig, SIZE, null, false,
+ postviewSize, ImageFormat.JPEG)
// Act: create SessionConfig
val sessionConfig = imagePipeline.createSessionConfigBuilder(SIZE).build()
@@ -280,7 +298,8 @@
fun createCameraRequestWithPostviewEnabled() {
// Arrange.
val postviewSize = Size(640, 480)
- imagePipeline = ImagePipeline(imageCaptureConfig, SIZE, null, false, postviewSize)
+ imagePipeline = ImagePipeline(imageCaptureConfig, SIZE, null, false,
+ postviewSize, ImageFormat.YUV_420_888)
// Act: create requests
val result =
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 31ed105..0e0be4b 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
@@ -135,7 +135,7 @@
shadowOf(getMainLooper()).idle()
// Assert: postview image is received.
- assertThat(callback.onPostviewImageAvailable).isNotNull()
+ assertThat(callback.onPostviewBitmapAvailable).isNotNull()
}
@Test
@@ -161,7 +161,7 @@
shadowOf(getMainLooper()).idle()
// Assert: the postview image is not received.
- assertThat(callback.onPostviewImageAvailable).isNull()
+ assertThat(callback.onPostviewBitmapAvailable).isNull()
}
@Test
diff --git a/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/ImageCaptureExtenderImpl.java b/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/ImageCaptureExtenderImpl.java
index dbcd1ef..98cf915 100644
--- a/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/ImageCaptureExtenderImpl.java
+++ b/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/ImageCaptureExtenderImpl.java
@@ -91,7 +91,7 @@
/**
* Returns supported output format/size map for postview image. OEM is required to support
- * both JPEG and YUV_420_888 format output.
+ * YUV_420_888 format output.
*
* <p>Pair list composed with {@link ImageFormat} and {@link Size} array will be returned.
* The sizes must be smaller than or equal to the provided capture size and have the same
diff --git a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ImageCaptureTest.kt b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ImageCaptureTest.kt
index 3a8d018..0328edf 100644
--- a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ImageCaptureTest.kt
+++ b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ImageCaptureTest.kt
@@ -17,10 +17,13 @@
package androidx.camera.extensions
import android.content.Context
+import android.graphics.Bitmap
import android.graphics.ImageFormat
import android.graphics.SurfaceTexture
import android.util.Size
+import android.view.Surface
import androidx.camera.camera2.Camera2Config
+import androidx.camera.core.Camera
import androidx.camera.core.CameraSelector
import androidx.camera.core.ImageCapture
import androidx.camera.core.ImageCaptureException
@@ -47,6 +50,7 @@
import kotlinx.coroutines.withContext
import kotlinx.coroutines.withTimeoutOrNull
import org.junit.After
+import org.junit.Assert.assertTrue
import org.junit.Assume.assumeTrue
import org.junit.Before
import org.junit.Rule
@@ -216,14 +220,16 @@
private suspend fun bindAndTakePicture(
onImageCaptureCallback: ImageCapture.OnImageCapturedCallback,
+ targetRotation: Int? = null,
enablePostview: Boolean = false
- ) {
+ ): Camera {
// To test bind/unbind and take picture.
- val imageCapture = ImageCapture.Builder()
- .setPostviewEnabled(enablePostview)
- .build()
+ val imageCapture = ImageCapture.Builder().apply {
+ targetRotation?.let { setTargetRotation(it) }
+ setPostviewEnabled(enablePostview)
+ }.build()
val preview = Preview.Builder().build()
- withContext(Dispatchers.Main) {
+ return withContext(Dispatchers.Main) {
// To set the update listener and Preview will change to active state.
preview.setSurfaceProvider(
SurfaceTextureProvider.createSurfaceTextureProvider(
@@ -243,7 +249,7 @@
})
)
- cameraProvider.bindToLifecycle(
+ val camera = cameraProvider.bindToLifecycle(
fakeLifecycleOwner,
extensionsCameraSelector,
preview,
@@ -254,19 +260,22 @@
CameraXExecutors.mainThreadExecutor(),
onImageCaptureCallback
)
+ camera
}
}
private suspend fun bindAndTakePicture(
onImageSavedCallback: ImageCapture.OnImageSavedCallback,
+ targetRotation: Int? = null,
enablePostview: Boolean = false
- ) {
+ ): Camera {
// To test bind/unbind and take picture.
- val imageCapture = ImageCapture.Builder()
- .setPostviewEnabled(enablePostview)
- .build()
+ val imageCapture = ImageCapture.Builder().apply {
+ targetRotation?.let { setTargetRotation(it) }
+ setPostviewEnabled(enablePostview)
+ }.build()
val preview = Preview.Builder().build()
- withContext(Dispatchers.Main) {
+ return withContext(Dispatchers.Main) {
// To set the update listener and Preview will change to active state.
preview.setSurfaceProvider(
SurfaceTextureProvider.createSurfaceTextureProvider(
@@ -286,7 +295,7 @@
})
)
- cameraProvider.bindToLifecycle(
+ val camera = cameraProvider.bindToLifecycle(
fakeLifecycleOwner,
extensionsCameraSelector,
preview,
@@ -302,6 +311,7 @@
CameraXExecutors.mainThreadExecutor(),
onImageSavedCallback
)
+ camera
}
}
@@ -385,10 +395,11 @@
val captureStartedDeferred = CompletableDeferred<Boolean>()
val captureSuccessDeferred = CompletableDeferred<ImageProxy>()
- val PostviewDeferred = CompletableDeferred<ImageProxy>()
+ val PostviewDeferred = CompletableDeferred<Bitmap>()
var hasError = false
+ val targetRotation = Surface.ROTATION_0
- bindAndTakePicture(object : ImageCapture.OnImageCapturedCallback() {
+ val camera = bindAndTakePicture(object : ImageCapture.OnImageCapturedCallback() {
override fun onError(exception: ImageCaptureException) {
hasError = true
}
@@ -398,19 +409,21 @@
override fun onCaptureSuccess(image: ImageProxy) {
captureSuccessDeferred.complete(image)
}
- override fun onPostviewImageAvailable(image: ImageProxy) {
- PostviewDeferred.complete(image)
+ override fun onPostviewBitmapAvailable(bitmap: Bitmap) {
+ PostviewDeferred.complete(bitmap)
}
- }, enablePostview = true)
+ }, enablePostview = true, targetRotation = targetRotation)
+ val rotationDegree = camera.cameraInfo.getSensorRotationDegrees(targetRotation)
+ val isFlipped = (rotationDegree % 180) != 0
assertThat(withTimeoutOrNull(5000) { captureStartedDeferred.await() }).isTrue()
- withTimeoutOrNull(5000) { PostviewDeferred.await() }.use {
+ withTimeoutOrNull(5000) { PostviewDeferred.await() }.let {
assertThat(it).isNotNull()
- assertThat(it!!.format).isEqualTo(ImageFormat.JPEG)
- if (isRotationOptionSupportedDevice()) {
- val exif = ExifUtil.getExif(it)
- assertThat(exif!!.rotation).isEqualTo(it.imageInfo.rotationDegrees)
+ if (isFlipped) {
+ assertTrue(it!!.width <= it.height)
+ } else {
+ assertTrue(it!!.height <= it.width)
}
}
@@ -432,10 +445,11 @@
val captureStartedDeferred = CompletableDeferred<Boolean>()
val imageSavedDeferred = CompletableDeferred<ImageCapture.OutputFileResults>()
- val PostviewDeferred = CompletableDeferred<ImageProxy>()
+ val PostviewDeferred = CompletableDeferred<Bitmap>()
var hasError = false
+ val targetRotation = Surface.ROTATION_0
- bindAndTakePicture(object : ImageCapture.OnImageSavedCallback {
+ val camera = bindAndTakePicture(object : ImageCapture.OnImageSavedCallback {
override fun onError(exception: ImageCaptureException) {
hasError = true
}
@@ -446,19 +460,21 @@
override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
imageSavedDeferred.complete(outputFileResults)
}
- override fun onPostviewImageAvailable(image: ImageProxy) {
- PostviewDeferred.complete(image)
+ override fun onPostviewBitmapAvailable(bitmap: Bitmap) {
+ PostviewDeferred.complete(bitmap)
}
}, enablePostview = true)
+ val rotationDegree = camera.cameraInfo.getSensorRotationDegrees(targetRotation)
+ val isFlipped = (rotationDegree % 180) != 0
assertThat(withTimeoutOrNull(5000) { captureStartedDeferred.await() }).isTrue()
- withTimeoutOrNull(5000) { PostviewDeferred.await() }.use {
+ withTimeoutOrNull(5000) { PostviewDeferred.await() }.let {
assertThat(it).isNotNull()
- assertThat(it!!.format).isEqualTo(ImageFormat.JPEG)
- if (isRotationOptionSupportedDevice()) {
- val exif = ExifUtil.getExif(it)
- assertThat(exif!!.rotation).isEqualTo(it.imageInfo.rotationDegrees)
+ if (isFlipped) {
+ assertTrue(it!!.width <= it.height)
+ } else {
+ assertTrue(it!!.height <= it.width)
}
}
diff --git a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/internal/sessionprocessor/BasicExtenderSessionProcessorTest.kt b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/internal/sessionprocessor/BasicExtenderSessionProcessorTest.kt
index 22d42d2..a2757be 100644
--- a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/internal/sessionprocessor/BasicExtenderSessionProcessorTest.kt
+++ b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/internal/sessionprocessor/BasicExtenderSessionProcessorTest.kt
@@ -662,10 +662,8 @@
fakeCaptureExtenderImpl.postviewSupportedSizes = postviewSizes
// 2. Act and Assert
- // BasiccVendorExtender is supposed to convert the YUV supported sizes into JPEG supported
- // size.s
assertThat(basicExtenderSessionProcessor.getSupportedPostviewSize(Size(1920, 1080))
- .get(ImageFormat.JPEG)).containsExactly(Size(1920, 1080), Size(640, 480))
+ .get(ImageFormat.YUV_420_888)).containsExactly(Size(1920, 1080), Size(640, 480))
}
private suspend fun initBasicExtenderSessionProcessor(): AutoCloseable {
diff --git a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/internal/sessionprocessor/StillCaptureProcessorTest.kt b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/internal/sessionprocessor/StillCaptureProcessorTest.kt
index ceb3377..8519505 100644
--- a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/internal/sessionprocessor/StillCaptureProcessorTest.kt
+++ b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/internal/sessionprocessor/StillCaptureProcessorTest.kt
@@ -263,7 +263,7 @@
}
@Test
- fun canStartCaptureWithPostviewWithRotation(): Unit = runBlocking {
+ fun canStartCaptureWithPostview(): Unit = runBlocking {
Assume.assumeTrue(
ClientVersion.isMinimumCompatibleVersion(Version.VERSION_1_4) &&
ExtensionVersion.isMinimumCompatibleVersion(Version.VERSION_1_4)
@@ -281,20 +281,18 @@
)
val postviewImageReader = ImageReaderProxys.createIsolatedReader(
- WIDTH, HEIGHT, ImageFormat.JPEG, 2)
+ WIDTH, HEIGHT, ImageFormat.YUV_420_888, 2)
val postviewOutputSurface = OutputSurface.create(
postviewImageReader.surface!!,
- Size(WIDTH, HEIGHT), ImageFormat.JPEG
+ Size(WIDTH, HEIGHT), ImageFormat.YUV_420_888
)
- val rotationDegrees = 270
stillCaptureProcessor = StillCaptureProcessor(
fakeCaptureProcessorImpl,
imageReaderJpeg.surface!!,
Size(WIDTH, HEIGHT),
postviewOutputSurface
)
- stillCaptureProcessor.setRotationDegrees(rotationDegrees)
val captureSession = Camera2Util.openCaptureSession(
cameraDevice!!.unwrap(), listOf(cameraYuvImageReader!!.surface), backgroundHandler
@@ -318,9 +316,7 @@
}
val postviewImage = postviewDeferred.await()
- assertThat(postviewImage.format).isEqualTo(ImageFormat.JPEG)
- val exif = Exif.createFromImageProxy(postviewImage)
- assertThat(exif.rotation).isEqualTo(rotationDegrees)
+ assertThat(postviewImage.format).isEqualTo(ImageFormat.YUV_420_888)
}
postviewImageReader.close()
diff --git a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/BasicVendorExtender.java b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/BasicVendorExtender.java
index 657cfd5..d36458c 100644
--- a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/BasicVendorExtender.java
+++ b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/BasicVendorExtender.java
@@ -379,18 +379,13 @@
public Map<Integer, List<Size>> getSupportedPostviewResolutions(@NonNull Size captureSize) {
if (ClientVersion.isMinimumCompatibleVersion(Version.VERSION_1_4)
&& ExtensionVersion.isMinimumCompatibleVersion(Version.VERSION_1_4)) {
- // For OEMs implementing basic extender, the supported format of the postview
- // can only be YUV.
List<Pair<Integer, Size[]>> list =
mImageCaptureExtenderImpl.getSupportedPostviewResolutions(captureSize);
Map<Integer, List<Size>> result = new HashMap<>();
for (Pair<Integer, Size[]> pair : list) {
int format = pair.first;
Size[] sizes = pair.second;
- if (format == ImageFormat.YUV_420_888) {
- // Basic Extender convert the YUV format into JPEG format.
- result.put(ImageFormat.JPEG, Arrays.asList(sizes));
- }
+ result.put(format, Arrays.asList(sizes));
}
return Collections.unmodifiableMap(result);
}
diff --git a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/sessionprocessor/StillCaptureProcessor.java b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/sessionprocessor/StillCaptureProcessor.java
index 5646194..639b172 100644
--- a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/sessionprocessor/StillCaptureProcessor.java
+++ b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/sessionprocessor/StillCaptureProcessor.java
@@ -44,6 +44,7 @@
import androidx.camera.extensions.internal.ClientVersion;
import androidx.camera.extensions.internal.ExtensionVersion;
import androidx.camera.extensions.internal.Version;
+import androidx.core.util.Preconditions;
import org.jetbrains.annotations.TestOnly;
@@ -78,14 +79,9 @@
final CaptureResultImageMatcher mCaptureResultImageMatcher = new CaptureResultImageMatcher();
@NonNull
final ImageReaderProxy mProcessedYuvImageReader;
- @Nullable
- private ImageReaderProxy mPostviewYuvImageReader;
private boolean mIsPostviewConfigured;
@NonNull
YuvToJpegConverter mYuvToJpegConverter;
- @Nullable
- YuvToJpegConverter mYuvToJpegConverterPostview;
-
final Object mLock = new Object();
@GuardedBy("mLock")
@NonNull
@@ -166,33 +162,10 @@
if (postviewOutputSurface != null
&& ClientVersion.isMinimumCompatibleVersion(Version.VERSION_1_4)
&& ExtensionVersion.isMinimumCompatibleVersion(Version.VERSION_1_4)) {
- mPostviewYuvImageReader = ImageReaderProxys.createIsolatedReader(
- postviewOutputSurface.getSize().getWidth(),
- postviewOutputSurface.getSize().getHeight(),
- ImageFormat.YUV_420_888, MAX_IMAGES);
- mPostviewYuvImageReader.setOnImageAvailableListener(
- imageReader -> {
- synchronized (mLock) {
- if (mIsClosed) {
- Logger.d(TAG, "Ignore JPEG processing in closed state");
- return;
- }
- ImageProxy imageProxy = imageReader.acquireNextImage();
- if (imageProxy != null) {
- try {
- mYuvToJpegConverterPostview.writeYuvImage(imageProxy);
- } catch (YuvToJpegConverter.ConversionFailedException e) {
- }
- }
- }
- }, CameraXExecutors.ioExecutor());
-
-
+ Preconditions.checkArgument(
+ postviewOutputSurface.getImageFormat() == ImageFormat.YUV_420_888);
mCaptureProcessorImpl.onResolutionUpdate(surfaceSize, postviewOutputSurface.getSize());
- mCaptureProcessorImpl.onPostviewOutputSurface(mPostviewYuvImageReader.getSurface());
-
- mYuvToJpegConverterPostview =
- new YuvToJpegConverter(90, postviewOutputSurface.getSurface());
+ mCaptureProcessorImpl.onPostviewOutputSurface(postviewOutputSurface.getSurface());
} else {
mCaptureProcessorImpl.onResolutionUpdate(surfaceSize);
@@ -289,7 +262,7 @@
progress);
}
- }, CameraXExecutors.ioExecutor());
+ }, CameraXExecutors.directExecutor());
} else if (ExtensionVersion.isMinimumCompatibleVersion(
Version.VERSION_1_3)
&& ClientVersion.isMinimumCompatibleVersion(
@@ -311,7 +284,7 @@
onCaptureResultCallback
.onCaptureProcessProgressed(progress);
}
- }, CameraXExecutors.ioExecutor());
+ }, CameraXExecutors.directExecutor());
} else {
mCaptureProcessorImpl.process(convertedResult);
}
@@ -348,17 +321,11 @@
void setJpegQuality(@IntRange(from = 0, to = 100) int quality) {
mYuvToJpegConverter.setJpegQuality(quality);
- if (mYuvToJpegConverterPostview != null) {
- mYuvToJpegConverterPostview.setJpegQuality(quality);
- }
}
void setRotationDegrees(
@ImageOutputConfig.RotationDegreesValue int rotationDegrees) {
mYuvToJpegConverter.setRotationDegrees(rotationDegrees);
- if (mYuvToJpegConverterPostview != null) {
- mYuvToJpegConverterPostview.setRotationDegrees(rotationDegrees);
- }
}
/**
@@ -374,10 +341,6 @@
mCaptureResultImageMatcher.clearImageReferenceListener();
mCaptureResultImageMatcher.clear();
mProcessedYuvImageReader.close();
- if (mPostviewYuvImageReader != null) {
- mPostviewYuvImageReader.clearOnImageAvailableListener();
- mPostviewYuvImageReader.close();
- }
}
}
}
diff --git a/camera/camera-video/src/androidTest/java/androidx/camera/video/AudioVideoSyncTest.kt b/camera/camera-video/src/androidTest/java/androidx/camera/video/AudioVideoSyncTest.kt
index 48b91fd..257ecd5 100644
--- a/camera/camera-video/src/androidTest/java/androidx/camera/video/AudioVideoSyncTest.kt
+++ b/camera/camera-video/src/androidTest/java/androidx/camera/video/AudioVideoSyncTest.kt
@@ -198,6 +198,8 @@
assertThat(timeDiff).isLessThan(diffThresholdUs)
recording.stopSafely()
+ inOrder.verify(videoRecordEventListener, Mockito.timeout(5000L))
+ .accept(ArgumentMatchers.any(VideoRecordEvent.Finalize::class.java))
file.delete()
}
diff --git a/camera/camera-video/src/androidTest/java/androidx/camera/video/RecorderTest.kt b/camera/camera-video/src/androidTest/java/androidx/camera/video/RecorderTest.kt
index 0ccbd4e..c132254 100644
--- a/camera/camera-video/src/androidTest/java/androidx/camera/video/RecorderTest.kt
+++ b/camera/camera-video/src/androidTest/java/androidx/camera/video/RecorderTest.kt
@@ -276,6 +276,13 @@
fun tearDown() {
for (recording in recordingsToStop) {
recording.stop()
+ try {
+ // Wait for recording done to avoid overlapping to next recording test.
+ // Overlapping recording tests may lead to uncertainty and flaky-ness.
+ recording.listener.verifyFinalize(inOrder = false)
+ } catch (e: AssertionError) {
+ // Ignored.
+ }
}
if (this::cameraUseCaseAdapter.isInitialized) {
@@ -739,6 +746,8 @@
@Test
fun stop_WhenUseCaseDetached() {
+ assumeStopCodecAfterSurfaceRemovalCrashMediaServerQuirk()
+
// Arrange.
val recording = createRecordingProcess()
diff --git a/camera/camera-video/src/androidTest/java/androidx/camera/video/VideoRecordingTest.kt b/camera/camera-video/src/androidTest/java/androidx/camera/video/VideoRecordingTest.kt
index a00c5d8..8b56757 100644
--- a/camera/camera-video/src/androidTest/java/androidx/camera/video/VideoRecordingTest.kt
+++ b/camera/camera-video/src/androidTest/java/androidx/camera/video/VideoRecordingTest.kt
@@ -61,6 +61,8 @@
import androidx.camera.testing.impl.mocks.helpers.CallTimesAtLeast
import androidx.camera.video.VideoRecordEvent.Finalize.ERROR_NONE
import androidx.camera.video.VideoRecordEvent.Finalize.ERROR_SOURCE_INACTIVE
+import androidx.camera.video.internal.compat.quirk.DeviceQuirks
+import androidx.camera.video.internal.compat.quirk.StopCodecAfterSurfaceRemovalCrashMediaServerQuirk
import androidx.core.util.Consumer
import androidx.test.core.app.ApplicationProvider
import androidx.test.filters.LargeTest
@@ -426,6 +428,8 @@
@Test
fun stopRecording_when_useCaseUnbind() {
+ assumeStopCodecAfterSurfaceRemovalCrashMediaServerQuirk()
+
// Arrange.
val file = File.createTempFile("CameraX", ".tmp").apply { deleteOnExit() }
latchForVideoSaved = CountDownLatch(1)
@@ -454,6 +458,8 @@
@Test
fun stopRecordingWhenLifecycleStops() {
+ assumeStopCodecAfterSurfaceRemovalCrashMediaServerQuirk()
+
// Arrange.
val file = File.createTempFile("CameraX", ".tmp").apply { deleteOnExit() }
latchForVideoSaved = CountDownLatch(1)
@@ -482,6 +488,8 @@
@Test
fun start_finalizeImmediatelyWhenSourceInactive() {
+ assumeStopCodecAfterSurfaceRemovalCrashMediaServerQuirk()
+
val file = File.createTempFile("CameraX", ".tmp").apply { deleteOnExit() }
instrumentation.runOnMainSync {
@@ -701,6 +709,12 @@
)
}
+ mockVideoRecordEventConsumer.verifyAcceptCall(
+ VideoRecordEvent.Finalize::class.java,
+ true,
+ GENERAL_TIMEOUT
+ )
+
file1.delete()
file2.delete()
}
@@ -812,6 +826,8 @@
@Test
fun canReuseRecorder_sourceInactive() {
+ assumeStopCodecAfterSurfaceRemovalCrashMediaServerQuirk()
+
val recorder = Recorder.Builder().build()
val videoCapture1 = VideoCapture.withOutput(recorder)
@@ -1355,6 +1371,7 @@
)
}
+@RequiresApi(21)
private class VideoCaptureMonitor : Consumer<VideoRecordEvent> {
private var countDown: CountDownLatch? = null
@@ -1409,3 +1426,13 @@
assumeTrue(msg, Camera2DeviceQuirks.get(Camera2ExtraCroppingQuirk::class.java) == null)
}
}
+
+@RequiresApi(21)
+fun assumeStopCodecAfterSurfaceRemovalCrashMediaServerQuirk() {
+ // Skip for b/293978082. For tests that will unbind the VideoCapture before stop the recording,
+ // they should be skipped since media server will crash if the codec surface has been removed
+ // before MediaCodec.stop() is called.
+ assumeTrue(
+ DeviceQuirks.get(StopCodecAfterSurfaceRemovalCrashMediaServerQuirk::class.java) == null
+ )
+}
diff --git a/camera/camera-video/src/main/java/androidx/camera/video/internal/compat/quirk/DeviceQuirksLoader.java b/camera/camera-video/src/main/java/androidx/camera/video/internal/compat/quirk/DeviceQuirksLoader.java
index c5bc087..0234352 100644
--- a/camera/camera-video/src/main/java/androidx/camera/video/internal/compat/quirk/DeviceQuirksLoader.java
+++ b/camera/camera-video/src/main/java/androidx/camera/video/internal/compat/quirk/DeviceQuirksLoader.java
@@ -98,6 +98,9 @@
if (CodecStuckOnFlushQuirk.load()) {
quirks.add(new CodecStuckOnFlushQuirk());
}
+ if (StopCodecAfterSurfaceRemovalCrashMediaServerQuirk.load()) {
+ quirks.add(new StopCodecAfterSurfaceRemovalCrashMediaServerQuirk());
+ }
return quirks;
}
diff --git a/camera/camera-video/src/main/java/androidx/camera/video/internal/compat/quirk/StopCodecAfterSurfaceRemovalCrashMediaServerQuirk.java b/camera/camera-video/src/main/java/androidx/camera/video/internal/compat/quirk/StopCodecAfterSurfaceRemovalCrashMediaServerQuirk.java
new file mode 100644
index 0000000..fa1f22f
--- /dev/null
+++ b/camera/camera-video/src/main/java/androidx/camera/video/internal/compat/quirk/StopCodecAfterSurfaceRemovalCrashMediaServerQuirk.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.video.internal.compat.quirk;
+
+import android.os.Build;
+
+import androidx.annotation.RequiresApi;
+import androidx.camera.core.impl.Quirk;
+
+/**
+ * <p>QuirkSummary
+ * Bug Id: 293978082
+ * Description: Quirk denotes that the media server die when codec surface has been removed
+ * from the camera repeating and then MediaCodec.stop() is called. Media server
+ * will recover soon but sometimes the camera server will get stuck and need to
+ * reboot the device to recover. We are not able to prevent camera from stopped
+ * by all paths but we should try to call MediaCodec.stop() as soon as possible.
+ * Device(s): moto c
+ */
+@RequiresApi(21)
+public class StopCodecAfterSurfaceRemovalCrashMediaServerQuirk implements Quirk {
+
+ static boolean load() {
+ return isMotoC();
+ }
+
+ private static boolean isMotoC() {
+ return "motorola".equalsIgnoreCase(Build.BRAND) && "moto c".equalsIgnoreCase(Build.MODEL);
+ }
+}
diff --git a/camera/camera-video/src/main/java/androidx/camera/video/internal/encoder/EncoderImpl.java b/camera/camera-video/src/main/java/androidx/camera/video/internal/encoder/EncoderImpl.java
index 92fc179..5eae605 100644
--- a/camera/camera-video/src/main/java/androidx/camera/video/internal/encoder/EncoderImpl.java
+++ b/camera/camera-video/src/main/java/androidx/camera/video/internal/encoder/EncoderImpl.java
@@ -56,6 +56,7 @@
import androidx.camera.video.internal.compat.quirk.CodecStuckOnFlushQuirk;
import androidx.camera.video.internal.compat.quirk.DeviceQuirks;
import androidx.camera.video.internal.compat.quirk.EncoderNotUsePersistentInputSurfaceQuirk;
+import androidx.camera.video.internal.compat.quirk.StopCodecAfterSurfaceRemovalCrashMediaServerQuirk;
import androidx.camera.video.internal.compat.quirk.VideoEncoderSuspendDoesNotIncludeSuspendTimeQuirk;
import androidx.camera.video.internal.workaround.EncoderFinder;
import androidx.camera.video.internal.workaround.VideoTimebaseConverter;
@@ -850,7 +851,9 @@
if (!futures.isEmpty()) {
Logger.d(mTag, "encoded data and input buffers are returned");
}
- if (mEncoderInput instanceof SurfaceInput && !mSourceStoppedSignalled) {
+ if (mEncoderInput instanceof SurfaceInput && !mSourceStoppedSignalled
+ && !hasStopCodecAfterSurfaceRemovalCrashMediaServerQuirk()
+ ) {
// For a SurfaceInput, the codec is in control of de-queuing buffers from the
// underlying BufferQueue. If we stop the codec, then it will stop de-queuing
// buffers and the BufferQueue may run out of input buffers, causing the camera
@@ -1028,6 +1031,10 @@
return (bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0;
}
+ private boolean hasStopCodecAfterSurfaceRemovalCrashMediaServerQuirk() {
+ return DeviceQuirks.get(StopCodecAfterSurfaceRemovalCrashMediaServerQuirk.class) != null;
+ }
+
@SuppressWarnings("WeakerAccess") // synthetic accessor
@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
class MediaCodecCallback extends MediaCodec.Callback {
diff --git a/camera/camera-view/src/androidTest/java/androidx/camera/view/VideoCaptureDeviceTest.kt b/camera/camera-view/src/androidTest/java/androidx/camera/view/VideoCaptureDeviceTest.kt
index 00c47c2..10f4461 100644
--- a/camera/camera-view/src/androidTest/java/androidx/camera/view/VideoCaptureDeviceTest.kt
+++ b/camera/camera-view/src/androidTest/java/androidx/camera/view/VideoCaptureDeviceTest.kt
@@ -27,6 +27,7 @@
import android.provider.MediaStore
import android.util.Log
import androidx.annotation.MainThread
+import androidx.annotation.RequiresApi
import androidx.camera.camera2.Camera2Config
import androidx.camera.core.impl.utils.executor.CameraXExecutors
import androidx.camera.testing.impl.AndroidUtil.skipVideoRecordingTestIfNotSupportedByEmulator
@@ -48,6 +49,7 @@
import androidx.camera.video.VideoRecordEvent.Finalize.ERROR_SOURCE_INACTIVE
import androidx.camera.video.internal.compat.quirk.DeviceQuirks
import androidx.camera.video.internal.compat.quirk.MediaStoreVideoCannotWrite
+import androidx.camera.video.internal.compat.quirk.StopCodecAfterSurfaceRemovalCrashMediaServerQuirk
import androidx.camera.view.CameraController.IMAGE_ANALYSIS
import androidx.camera.view.CameraController.VIDEO_CAPTURE
import androidx.camera.view.video.AudioConfig
@@ -65,6 +67,7 @@
import java.util.concurrent.TimeUnit
import org.junit.After
import org.junit.Assume
+import org.junit.Assume.assumeTrue
import org.junit.Before
import org.junit.BeforeClass
import org.junit.Rule
@@ -217,7 +220,7 @@
@Test
fun canRecordToMediaStore() {
- Assume.assumeTrue(
+ assumeTrue(
"Ignore the test since the MediaStore.Video has compatibility issues.",
DeviceQuirks.get(MediaStoreVideoCannotWrite::class.java) == null
)
@@ -296,6 +299,8 @@
@Test
fun canRecordToFile_whenLifecycleStops() {
+ assumeStopCodecAfterSurfaceRemovalCrashMediaServerQuirk()
+
// Arrange.
val file = createTempFile()
val outputOptions = FileOutputOptions.Builder(file).build()
@@ -319,6 +324,8 @@
@Test
fun canRecordToFile_whenTargetQualityChanged() {
+ assumeStopCodecAfterSurfaceRemovalCrashMediaServerQuirk()
+
// Arrange.
val file = createTempFile()
val outputOptions = FileOutputOptions.Builder(file).build()
@@ -342,6 +349,8 @@
@Test
fun canRecordToFile_whenEnabledUseCasesChanged() {
+ assumeStopCodecAfterSurfaceRemovalCrashMediaServerQuirk()
+
// Arrange.
val file = createTempFile()
val outputOptions = FileOutputOptions.Builder(file).build()
@@ -682,3 +691,13 @@
)
}
}
+
+@RequiresApi(21)
+fun assumeStopCodecAfterSurfaceRemovalCrashMediaServerQuirk() {
+ // Skip for b/293978082. For tests that will unbind the VideoCapture before stop the recording,
+ // they should be skipped since media server will crash if the codec surface has been removed
+ // before MediaCodec.stop() is called.
+ assumeTrue(
+ DeviceQuirks.get(StopCodecAfterSurfaceRemovalCrashMediaServerQuirk::class.java) == null
+ )
+}
diff --git a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/Bezier.kt b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/Bezier.kt
index bc16918..6585d83 100644
--- a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/Bezier.kt
+++ b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/Bezier.kt
@@ -18,6 +18,7 @@
import androidx.collection.FloatFloatPair
import androidx.compose.ui.graphics.PathSegment
+import androidx.compose.ui.util.fastCoerceIn
import kotlin.math.abs
import kotlin.math.acos
import kotlin.math.cbrt
@@ -138,9 +139,9 @@
p2: Float,
t: Float
): Float {
- val by = 2.0 * (p1 - p0)
- val ay = p2 - 2.0 * p1 + p0
- return ((ay * t + by) * t + p0).toFloat()
+ val by = 2.0f * (p1 - p0)
+ val ay = p2 - 2.0f * p1 + p0
+ return (ay * t + by) * t + p0
}
private fun evaluateCubic(
@@ -150,10 +151,10 @@
p3: Float,
t: Float
): Float {
- val a = p3 + 3.0 * (p1 - p2) - p0
- val b = 3.0 * (p2 - 2.0 * p1 + p0)
- val c = 3.0 * (p1 - p0)
- return (((a * t + b) * t + c) * t + p0).toFloat()
+ val a = p3 + 3.0f * (p1 - p2) - p0
+ val b = 3.0f * (p2 - 2.0f * p1 + p0)
+ val c = 3.0f * (p1 - p0)
+ return ((a * t + b) * t + c) * t + p0
}
/**
@@ -167,10 +168,10 @@
p2: Float,
t: Float
): Float {
- val a = 1.0 / 3.0 + (p1 - p2)
- val b = (p2 - 2.0 * p1)
+ val a = 1.0f / 3.0f + (p1 - p2)
+ val b = (p2 - 2.0f * p1)
val c = p1
- return 3.0f * (((a * t + b) * t + c) * t).toFloat()
+ return 3.0f * ((a * t + b) * t + c) * t
}
/**
@@ -271,10 +272,10 @@
// The math used to find the roots is explained in "Solving the Cubic Equation":
// http://www.trans4mind.com/personal_development/mathematics/polynomials/cubicAlgebra.htm
- var a = 3.0 * p0 - 6.0 * p1 + 3.0 * p2
- var b = -3.0 * p0 + 3.0 * p1
+ var a = 3.0 * (p0 - 2.0 * p1 + p2)
+ var b = 3.0 * (p1 - p0)
var c = p0.toDouble()
- val d = -p0 + 3.0 * p1 - 3.0 * p2 + p3
+ val d = -p0 + 3.0 * (p1 - p2) + p3
// Not a cubic
if (d.closeTo(0.0)) {
@@ -300,42 +301,40 @@
b /= d
c /= d
- val o = (3.0 * b - a * a) / 3.0
- val o3 = o / 3.0
- val q = (2.0 * a * a * a - 9.0 * a * b + 27.0 * c) / 27.0
- val q2 = q / 2.0
+ val o3 = (3.0 * b - a * a) / 9.0
+ val q2 = (2.0 * a * a * a - 9.0 * a * b + 27.0 * c) / 54.0
val discriminant = q2 * q2 + o3 * o3 * o3
+ val a3 = a / 3.0
if (discriminant < 0.0) {
- val mp3 = -o / 3.0
- val mp33 = mp3 * mp3 * mp3
+ val mp33 = -(o3 * o3 * o3)
val r = sqrt(mp33)
- val t = -q / (2.0 * r)
- val cosPhi = min(1.0, max(-1.0, t))
+ val t = -q2 / r
+ val cosPhi = t.fastCoerceIn(-1.0, 1.0)
val phi = acos(cosPhi)
val t1 = 2.0 * cbrt(r)
- var root = clampValidRootInUnitRange((t1 * cos(phi / 3.0) - a / 3.0).toFloat())
+ var root = clampValidRootInUnitRange((t1 * cos(phi / 3.0) - a3).toFloat())
if (!root.isNaN()) return root
- root = clampValidRootInUnitRange((t1 * cos((phi + Tau) / 3.0) - a / 3.0).toFloat())
+ root = clampValidRootInUnitRange((t1 * cos((phi + Tau) / 3.0) - a3).toFloat())
if (!root.isNaN()) return root
- return clampValidRootInUnitRange((t1 * cos((phi + 2.0 * Tau) / 3.0) - a / 3.0).toFloat())
+ return clampValidRootInUnitRange((t1 * cos((phi + 2.0 * Tau) / 3.0) - a3).toFloat())
} else if (discriminant == 0.0) { // TODO: closeTo(0.0)?
- val u1 = if (q2 < 0.0) cbrt(-q2) else -cbrt(q2)
+ val u1 = -cbrt(q2)
- val root = clampValidRootInUnitRange((2.0 * u1 - a / 3.0).toFloat())
+ val root = clampValidRootInUnitRange((2.0 * u1 - a3).toFloat())
if (!root.isNaN()) return root
- return clampValidRootInUnitRange((-u1 - a / 3.0).toFloat())
+ return clampValidRootInUnitRange((-u1 - a3).toFloat())
}
val sd = sqrt(discriminant)
val u1 = cbrt(-q2 + sd)
val v1 = cbrt(q2 + sd)
- return clampValidRootInUnitRange((u1 - v1 - a / 3.0).toFloat())
+ return clampValidRootInUnitRange((u1 - v1 - a3).toFloat())
}
/**
@@ -423,14 +422,14 @@
// Quadratic derivative of a cubic function
// We do the computation inline to avoid using arrays of other data
// structures to return the result
- val d0 = 3 * (points[2] - points[0])
- val d1 = 3 * (points[4] - points[2])
- val d2 = 3 * (points[6] - points[4])
+ val d0 = 3.0f * (points[2] - points[0])
+ val d1 = 3.0f * (points[4] - points[2])
+ val d2 = 3.0f * (points[6] - points[4])
val count = findQuadraticRoots(d0, d1, d2, roots, index)
// Compute the second derivative as a line
- val dd0 = 2 * (d1 - d0)
- val dd1 = 2 * (d2 - d1)
+ val dd0 = 2.0f * (d1 - d0)
+ val dd1 = 2.0f * (d2 - d1)
count + findLineRoot(dd0, dd1, roots, index + count)
}
@@ -489,8 +488,7 @@
* range are considered to be in the [0..1] range and clamped appropriately. Returns 0 if
* no value was written, 1 otherwise.
*/
-@Suppress("NOTHING_TO_INLINE")
-private inline fun writeValidRootInUnitRange(r: Float, roots: FloatArray, index: Int): Int {
+private fun writeValidRootInUnitRange(r: Float, roots: FloatArray, index: Int): Int {
val v = clampValidRootInUnitRange(r)
roots[index] = v
return if (v.isNaN()) 0 else 1
diff --git a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/Easing.kt b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/Easing.kt
index b1081ad..fbcabe4 100644
--- a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/Easing.kt
+++ b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/Easing.kt
@@ -18,6 +18,7 @@
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.Stable
+import androidx.compose.ui.util.fastCoerceIn
/**
* Easing is a way to adjust an animation’s fraction. Easing allows transitioning
@@ -110,8 +111,16 @@
}
}
+ /**
+ * Transforms the specified [fraction] in the range 0..1 by this cubic Bézier curve.
+ * To solve the curve, [fraction] is used as the x coordinate along the curve, and
+ * the corresponding y coordinate on the curve is returned. If no solution exists,
+ * this method throws an [IllegalArgumentException].
+ *
+ * @throws IllegalArgumentException If the cubic Bézier curve cannot be solved
+ */
override fun transform(fraction: Float): Float {
- if (fraction > 0f && fraction < 1f) {
+ return if (fraction > 0f && fraction < 1f) {
val t = findFirstCubicRoot(
0.0f - fraction,
a - fraction,
@@ -121,13 +130,15 @@
// No root, the cubic curve has no solution
if (t.isNaN()) {
- return fraction
+ throw IllegalArgumentException(
+ "The cubic curve with parameters ($a, $b, $c, $d) has no solution at $fraction"
+ )
}
// Clamp to clean up numerical imprecision at the extremes
- return evaluateCubic(b, d, t).coerceAtLeast(0.0f).coerceAtMost(1.0f)
+ evaluateCubic(b, d, t).fastCoerceIn(0f, 1f)
} else {
- return fraction
+ fraction
}
}
diff --git a/compose/foundation/foundation/api/current.txt b/compose/foundation/foundation/api/current.txt
index f6e6a59..972e769 100644
--- a/compose/foundation/foundation/api/current.txt
+++ b/compose/foundation/foundation/api/current.txt
@@ -1262,6 +1262,7 @@
}
public final class PagerStateKt {
+ method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static androidx.compose.foundation.pager.PagerState PagerState(optional int currentPage, optional float currentPageOffsetFraction, kotlin.jvm.functions.Function0<java.lang.Integer> pageCount);
method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Composable public static androidx.compose.foundation.pager.PagerState rememberPagerState(optional int initialPage, optional float initialPageOffsetFraction, kotlin.jvm.functions.Function0<java.lang.Integer> pageCount);
}
diff --git a/compose/foundation/foundation/api/restricted_current.txt b/compose/foundation/foundation/api/restricted_current.txt
index 4ea377e..e802b3d 100644
--- a/compose/foundation/foundation/api/restricted_current.txt
+++ b/compose/foundation/foundation/api/restricted_current.txt
@@ -1264,6 +1264,7 @@
}
public final class PagerStateKt {
+ method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static androidx.compose.foundation.pager.PagerState PagerState(optional int currentPage, optional float currentPageOffsetFraction, kotlin.jvm.functions.Function0<java.lang.Integer> pageCount);
method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Composable public static androidx.compose.foundation.pager.PagerState rememberPagerState(optional int initialPage, optional float initialPageOffsetFraction, kotlin.jvm.functions.Function0<java.lang.Integer> pageCount);
}
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/BrushDemo.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/BrushDemo.kt
index 1405a4a..32edcc8 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/BrushDemo.kt
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/BrushDemo.kt
@@ -34,8 +34,13 @@
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.geometry.center
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.RadialGradientShader
+import androidx.compose.ui.graphics.Shader
+import androidx.compose.ui.graphics.ShaderBrush
import androidx.compose.ui.graphics.Shadow
import androidx.compose.ui.graphics.TileMode
import androidx.compose.ui.text.ExperimentalTextApi
@@ -206,14 +211,22 @@
repeatMode = RepeatMode.Reverse
)
)
+ val brush = remember {
+ // postpone the state read to shader creation time which happens during draw.
+ ShaderBrush { size ->
+ RadialGradientShader(
+ center = size.center,
+ radius = radius,
+ colors = RainbowColors,
+ colorStops = RainbowStops,
+ tileMode = TileMode.Mirror
+ )
+ }
+ }
Text(
text = loremIpsum(wordCount = 29),
style = TextStyle(
- brush = Brush.radialGradient(
- *RainbowStops.zip(RainbowColors).toTypedArray(),
- radius = radius,
- tileMode = TileMode.Mirror
- ),
+ brush = brush,
fontSize = 30.sp
)
)
@@ -264,3 +277,9 @@
Color(0xff2aa8f2)
)
private val RainbowStops = listOf(0f, 0.2f, 0.4f, 0.6f, 0.8f, 1f)
+
+private fun ShaderBrush(block: (Size) -> Shader): ShaderBrush {
+ return object : ShaderBrush() {
+ override fun createShader(size: Size): Shader = block(size)
+ }
+}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/PagerContentTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/PagerContentTest.kt
index d4ca45f..a55a2a4 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/PagerContentTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/PagerContentTest.kt
@@ -129,10 +129,10 @@
@Test
fun scrollableState_isScrollableWhenChangingPages() {
val states = mutableMapOf<Int, ScrollState>()
- val pagerState = PagerStateImpl(
- initialPage = 0,
- initialPageOffsetFraction = 0.0f,
- updatedPageCount = { 2 })
+ val pagerState = PagerState(
+ currentPage = 0,
+ currentPageOffsetFraction = 0.0f,
+ pageCount = { 2 })
rule.setContent {
HorizontalPager(
state = pagerState,
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/PagerPinnableContainerTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/PagerPinnableContainerTest.kt
index 3de9193..762f095 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/PagerPinnableContainerTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/PagerPinnableContainerTest.kt
@@ -312,10 +312,10 @@
@Test
fun unpinnedWhenPagerStateChanges() {
var state by mutableStateOf(
- PagerStateImpl(
- initialPage = 2,
- initialPageOffsetFraction = 0f,
- updatedPageCount = { 100 })
+ PagerState(
+ currentPage = 2,
+ currentPageOffsetFraction = 0f,
+ pageCount = { 100 })
)
// Arrange.
rule.setContent {
@@ -349,10 +349,10 @@
rule.runOnIdle {
assertThat(composed).contains(2)
- state = PagerStateImpl(
- initialPage = 0,
- initialPageOffsetFraction = 0f,
- updatedPageCount = { 100 })
+ state = PagerState(
+ currentPage = 0,
+ currentPageOffsetFraction = 0f,
+ pageCount = { 100 })
}
rule.waitUntil {
@@ -367,10 +367,10 @@
@Test
fun pinAfterPagerStateChange() {
var state by mutableStateOf(
- PagerStateImpl(
- initialPage = 0,
- initialPageOffsetFraction = 0f,
- updatedPageCount = { 100 })
+ PagerState(
+ currentPage = 0,
+ currentPageOffsetFraction = 0f,
+ pageCount = { 100 })
)
// Arrange.
rule.setContent {
@@ -387,10 +387,10 @@
}
rule.runOnIdle {
- state = PagerStateImpl(
- initialPage = 0,
- initialPageOffsetFraction = 0f,
- updatedPageCount = { 100 })
+ state = PagerState(
+ currentPage = 0,
+ currentPageOffsetFraction = 0f,
+ pageCount = { 100 })
}
rule.runOnIdle {
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/PagerStateNonGestureScrollingTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/PagerStateNonGestureScrollingTest.kt
index 825ddb5..219d247 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/PagerStateNonGestureScrollingTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/PagerStateNonGestureScrollingTest.kt
@@ -58,7 +58,7 @@
@Test
fun pagerStateNotAttached_shouldReturnDefaultValues_andChangeAfterAttached() = runBlocking {
// Arrange
- val state = PagerStateImpl(5, 0.2f) { DefaultPageCount }
+ val state = PagerState(5, 0.2f) { DefaultPageCount }
Truth.assertThat(state.currentPage).isEqualTo(5)
Truth.assertThat(state.currentPageOffsetFraction).isEqualTo(0.2f)
@@ -179,9 +179,11 @@
dataset.value.size
}, pageContent = {
val item = dataset.value[it]
- Box(modifier = Modifier
- .fillMaxSize()
- .testTag(item.item))
+ Box(
+ modifier = Modifier
+ .fillMaxSize()
+ .testTag(item.item)
+ )
})
Truth.assertThat(dataset.value[pagerState.currentPage].item).isEqualTo("B")
@@ -382,7 +384,7 @@
fun currentPage_shouldUpdateWithSnapPositionInLayout() {
// snap position is 200dp from edge of Pager
val customSnapPosition = SnapPosition { _, _, _, _, _ ->
- with(rule.density) {
+ with(rule.density) {
200.dp.roundToPx()
}
}
@@ -465,7 +467,7 @@
rule.runOnIdle {
// find page whose offset is closest to the centre
val candidatePage = pagerState.layoutInfo.visiblePagesInfo.fastMaxBy {
- -(abs(it.offset - pagerSize/2))
+ -(abs(it.offset - pagerSize / 2))
}
// check we moved
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/PagerTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/PagerTest.kt
index 106e684..6aabc08 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/PagerTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/PagerTest.kt
@@ -167,7 +167,7 @@
fun pageCount_readBeforeCompositionIsAccurate() {
// Arrange
val pageCount = mutableStateOf(2)
- val state = PagerStateImpl(0, 0f) { pageCount.value }
+ val state = PagerState(0, 0f) { pageCount.value }
assertThat(state.pageCount).isEqualTo(pageCount.value)
rule.setContent {
HorizontalOrVerticalPager(
@@ -194,7 +194,7 @@
// Arrange
var recomposeCount = 0
val pageCount = mutableStateOf(2)
- val state = PagerStateImpl(0, 0f) { pageCount.value }
+ val state = PagerState(0, 0f) { pageCount.value }
assertThat(state.pageCount).isEqualTo(pageCount.value)
rule.setContent {
@@ -226,7 +226,7 @@
fun pageCountDecreased_currentPageIsAdjustedAccordingly() {
// Arrange
val pageCount = mutableStateOf(5)
- val state = PagerStateImpl(0, 0f) { pageCount.value }
+ val state = PagerState(0, 0f) { pageCount.value }
assertThat(state.pageCount).isEqualTo(pageCount.value)
rule.setContent {
@@ -319,7 +319,7 @@
fun pagerStateChange_flingBehaviorShouldRecreate() {
var previousFlingBehavior: SnapFlingBehavior? = null
var latestFlingBehavior: SnapFlingBehavior? = null
- val stateHolder = mutableStateOf(PagerStateImpl(0, 0.0f) { 10 })
+ val stateHolder = mutableStateOf(PagerState(0, 0.0f) { 10 })
rule.setContent {
HorizontalOrVerticalPager(
modifier = Modifier
@@ -339,7 +339,7 @@
}
rule.runOnIdle {
- stateHolder.value = PagerStateImpl(0, 0.0f) { 20 }
+ stateHolder.value = PagerState(0, 0.0f) { 20 }
}
rule.waitForIdle()
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/BasicTextBrushTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/BasicTextBrushTest.kt
new file mode 100644
index 0000000..7dc136e
--- /dev/null
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/BasicTextBrushTest.kt
@@ -0,0 +1,173 @@
+/*
+ * 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.compose.foundation.text
+
+import android.os.Build
+import androidx.compose.foundation.background
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.testutils.assertContainsColor
+import androidx.compose.testutils.assertDoesNotContainColor
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.graphics.Brush
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.LinearGradientShader
+import androidx.compose.ui.graphics.Shader
+import androidx.compose.ui.graphics.ShaderBrush
+import androidx.compose.ui.graphics.SolidColor
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.captureToImage
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.sp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import androidx.test.filters.SdkSuppress
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+class BasicTextBrushTest {
+
+ @get:Rule
+ val rule = createComposeRule()
+
+ private val TAG = "TAG"
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+ @Test
+ fun toggleSolidColorBrush() {
+ val colorState = mutableStateOf<Brush>(SolidColor(Color.Red))
+ rule.setContent {
+ CompositionLocalProvider(LocalDensity provides Density(1f)) {
+ BasicText(
+ text = "Hello",
+ style = TextStyle(
+ brush = colorState.value,
+ fontFamily = TEST_FONT_FAMILY,
+ fontSize = 20.sp
+ ),
+ modifier = Modifier
+ .background(Color.Black)
+ .testTag(TAG)
+ )
+ }
+ }
+
+ with(rule.onNodeWithTag(TAG).captureToImage()) {
+ assertContainsColor(Color.Black)
+ assertContainsColor(Color.Red)
+ assertDoesNotContainColor(Color.Blue)
+ }
+
+ colorState.value = SolidColor(Color.Blue)
+
+ with(rule.onNodeWithTag(TAG).captureToImage()) {
+ assertContainsColor(Color.Black)
+ assertContainsColor(Color.Blue)
+ assertDoesNotContainColor(Color.Red)
+ }
+ }
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+ @Test
+ fun togglePredefinedShaderBrush() {
+ val brushState = mutableStateOf(Brush.horizontalGradient(listOf(Color.Red, Color.Red)))
+ rule.setContent {
+ CompositionLocalProvider(LocalDensity provides Density(1f)) {
+ BasicText(
+ text = "Hello",
+ style = TextStyle(
+ brush = brushState.value,
+ fontFamily = TEST_FONT_FAMILY,
+ fontSize = 20.sp
+ ),
+ modifier = Modifier
+ .background(Color.Black)
+ .testTag(TAG)
+ )
+ }
+ }
+
+ with(rule.onNodeWithTag(TAG).captureToImage()) {
+ assertContainsColor(Color.Black)
+ assertContainsColor(Color.Red)
+ assertDoesNotContainColor(Color.Blue)
+ }
+
+ brushState.value = Brush.horizontalGradient(listOf(Color.Blue, Color.Blue))
+
+ with(rule.onNodeWithTag(TAG).captureToImage()) {
+ assertContainsColor(Color.Black)
+ assertContainsColor(Color.Blue)
+ assertDoesNotContainColor(Color.Red)
+ }
+ }
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+ @Test
+ fun toggleCustomShaderBrush() {
+ var color by mutableStateOf(Color.Red)
+ val brush = object : ShaderBrush() {
+ override fun createShader(size: Size): Shader {
+ return LinearGradientShader(
+ Offset.Zero,
+ Offset(200f, 200f),
+ listOf(color, color),
+ )
+ }
+ }
+ rule.setContent {
+ CompositionLocalProvider(LocalDensity provides Density(1f)) {
+ BasicText(
+ text = "Hello",
+ style = TextStyle(
+ brush = brush,
+ fontFamily = TEST_FONT_FAMILY,
+ fontSize = 20.sp
+ ),
+ modifier = Modifier
+ .background(Color.Black)
+ .testTag(TAG)
+ )
+ }
+ }
+
+ with(rule.onNodeWithTag(TAG).captureToImage()) {
+ assertContainsColor(Color.Black)
+ assertContainsColor(Color.Red)
+ assertDoesNotContainColor(Color.Blue)
+ }
+
+ color = Color.Blue
+
+ with(rule.onNodeWithTag(TAG).captureToImage()) {
+ assertContainsColor(Color.Black)
+ assertContainsColor(Color.Blue)
+ assertDoesNotContainColor(Color.Red)
+ }
+ }
+}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/BasicTextField2Test.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/BasicTextField2Test.kt
index fc37758..29b37de 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/BasicTextField2Test.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/BasicTextField2Test.kt
@@ -46,11 +46,14 @@
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.key.Key
+import androidx.compose.ui.platform.ClipboardManager
+import androidx.compose.ui.platform.LocalClipboardManager
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.platform.LocalWindowInfo
import androidx.compose.ui.platform.WindowInfo
import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.semantics.SemanticsActions
import androidx.compose.ui.semantics.SemanticsProperties.TextSelectionRange
import androidx.compose.ui.semantics.getOrNull
import androidx.compose.ui.test.ExperimentalTestApi
@@ -59,9 +62,11 @@
import androidx.compose.ui.test.assertIsNotFocused
import androidx.compose.ui.test.assertTextEquals
import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.longClick
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performKeyInput
+import androidx.compose.ui.test.performSemanticsAction
import androidx.compose.ui.test.performTextInput
import androidx.compose.ui.test.performTextInputSelection
import androidx.compose.ui.test.performTextReplacement
@@ -1150,6 +1155,36 @@
}
}
+ // Regression test for b/311834126
+ @Test
+ fun whenPastingTextThatIncreasesEndOffset_noCrashAndCursorAtEndOfPastedText() {
+ val longText = "Text".repeat(4)
+ val shortText = "Text".repeat(2)
+
+ lateinit var tfs: TextFieldState
+ lateinit var clipboardManager: ClipboardManager
+ inputMethodInterceptor.setTextFieldTestContent {
+ tfs = rememberTextFieldState(shortText)
+ clipboardManager = LocalClipboardManager.current
+ BasicTextField2(
+ state = tfs,
+ modifier = Modifier.testTag(Tag),
+ )
+ }
+ clipboardManager.setText(AnnotatedString(longText))
+ rule.waitForIdle()
+
+ val node = rule.onNodeWithTag(Tag)
+ node.performTouchInput { longClick(center) }
+ rule.waitForIdle()
+
+ node.performSemanticsAction(SemanticsActions.PasteText) { it() }
+ rule.waitForIdle()
+
+ assertThat(tfs.text.toString()).isEqualTo(longText)
+ assertThat(tfs.text.selectionInChars).isEqualTo(TextRange(longText.length))
+ }
+
private fun requestFocus(tag: String) =
rule.onNodeWithTag(tag).requestFocus()
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/textfield/TextFieldTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/textfield/TextFieldTest.kt
index 51379d4..122622c 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/textfield/TextFieldTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/textfield/TextFieldTest.kt
@@ -66,7 +66,10 @@
import androidx.compose.ui.graphics.Shadow
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.toPixelMap
+import androidx.compose.ui.input.key.Key
import androidx.compose.ui.layout.onGloballyPositioned
+import androidx.compose.ui.platform.ClipboardManager
+import androidx.compose.ui.platform.LocalClipboardManager
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.platform.LocalFontFamilyResolver
@@ -93,9 +96,11 @@
import androidx.compose.ui.test.isNotFocused
import androidx.compose.ui.test.junit4.StateRestorationTester
import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.longClick
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performImeAction
+import androidx.compose.ui.test.performKeyInput
import androidx.compose.ui.test.performSemanticsAction
import androidx.compose.ui.test.performTextClearance
import androidx.compose.ui.test.performTextInput
@@ -1479,6 +1484,40 @@
assertThat(actual).isEqualTo(TextRange(0))
}
+ // Regression test for b/311834126
+ @Test
+ fun whenPastingTextThatIncreasesEndOffset_noCrashAndCursorAtEndOfPastedText() {
+ val longText = "Text".repeat(4)
+ val shortText = "Text".repeat(2)
+
+ var tfv by mutableStateOf(TextFieldValue(shortText))
+ lateinit var clipboardManager: ClipboardManager
+ rule.setTextFieldTestContent {
+ clipboardManager = LocalClipboardManager.current
+ BasicTextField(
+ value = tfv,
+ tfv = it },
+ modifier = Modifier.testTag(Tag)
+ )
+ }
+ clipboardManager.setText(AnnotatedString(longText))
+ rule.waitForIdle()
+
+ val node = rule.onNodeWithTag(Tag)
+ node.performTouchInput { longClick(center) }
+ rule.waitForIdle()
+
+ node.performSemanticsAction(SemanticsActions.PasteText) { it() }
+ rule.waitForIdle()
+
+ val expectedTfv = TextFieldValue(
+ text = longText,
+ selection = TextRange(longText.length)
+ )
+ assertThat(tfv.text).isEqualTo(expectedTfv.text)
+ assertThat(tfv.selection).isEqualTo(expectedTfv.selection)
+ }
+
@Test
fun decorationBoxIntrinsics() {
var size: IntSize? = null
@@ -1556,6 +1595,57 @@
rule.onNodeWithTag(decorationTag, true).assertDoesNotExist()
}
+
+ // Regression test for b/311007530
+ @OptIn(ExperimentalTestApi::class)
+ @Test
+ fun whenToggleReadOnly_onEditedTextField_noChangeNorCrash() {
+ val tag = "tag"
+
+ val text = "text"
+ val tfv = TextFieldValue(
+ text = text,
+ selection = TextRange(text.length)
+ )
+
+ val textAfterBackspace = text.run { substring(0, length - 1) }
+ val tfvAfterBackspace = TextFieldValue(
+ text = textAfterBackspace,
+ selection = TextRange(textAfterBackspace.length),
+ )
+
+ var value by mutableStateOf(tfv)
+ var readOnly by mutableStateOf(false)
+ rule.setTextFieldTestContent {
+ BasicTextField(
+ value = value,
+ value = it },
+ readOnly = readOnly,
+ modifier = Modifier.testTag(tag),
+ )
+ }
+ val node = rule.onNodeWithTag(tag)
+ // gain focus and place cursor at end of text
+ node.performTouchInput { click(centerRight - Offset(5f, 0f)) }
+ rule.waitForIdle()
+ assertThat(value.text).isEqualTo(tfv.text)
+ assertThat(value.selection).isEqualTo(tfv.selection)
+
+ node.performKeyInput { keyDown(Key.Backspace) }
+ rule.waitForIdle()
+ assertThat(value.text).isEqualTo(tfvAfterBackspace.text)
+ assertThat(value.selection).isEqualTo(tfvAfterBackspace.selection)
+
+ rule.runOnUiThread { readOnly = true }
+ rule.waitForIdle()
+ assertThat(value.text).isEqualTo(tfvAfterBackspace.text)
+ assertThat(value.selection).isEqualTo(tfvAfterBackspace.selection)
+
+ rule.runOnUiThread { readOnly = false }
+ rule.waitForIdle()
+ assertThat(value.text).isEqualTo(tfvAfterBackspace.text)
+ assertThat(value.selection).isEqualTo(tfvAfterBackspace.selection)
+ }
}
private fun SemanticsNodeInteraction.assertEditableTextEquals(
diff --git a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/selection/TextFieldSelectionManagerTest.kt b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/selection/TextFieldSelectionManagerTest.kt
index 012152c..591b4d6 100644
--- a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/selection/TextFieldSelectionManagerTest.kt
+++ b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/selection/TextFieldSelectionManagerTest.kt
@@ -18,6 +18,7 @@
import androidx.compose.foundation.text.HandleState
import androidx.compose.foundation.text.InternalFoundationTextApi
+import androidx.compose.foundation.text.TextDelegate
import androidx.compose.foundation.text.TextFieldState
import androidx.compose.foundation.text.TextLayoutResultProxy
import androidx.compose.ui.focus.FocusRequester
@@ -63,6 +64,7 @@
@RunWith(JUnit4::class)
class TextFieldSelectionManagerTest {
private val text = "Hello World"
+ private val textAnnotatedString = AnnotatedString(text)
private val density = Density(density = 1f)
private val offsetMapping = OffsetMapping.Identity
private val maxLines = 2
@@ -100,7 +102,7 @@
whenever(layoutResult.layoutInput).thenReturn(
TextLayoutInput(
- text = AnnotatedString(text),
+ text = textAnnotatedString,
style = TextStyle.Default,
placeholders = mock(),
maxLines = maxLines,
@@ -135,8 +137,12 @@
whenever(layoutResultProxy.value).thenReturn(layoutResult)
+ val textDelegate = mock<TextDelegate> {
+ on { this.text }.thenReturn(textAnnotatedString)
+ }
+
state = TextFieldState(
- textDelegate = mock(),
+ textDelegate = textDelegate,
recomposeScope = mock(),
keyboardController = null
)
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerState.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerState.kt
index 25d0931..5781bf4 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerState.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerState.kt
@@ -76,8 +76,8 @@
initialPageOffsetFraction: Float = 0f,
pageCount: () -> Int
): PagerState {
- return rememberSaveable(saver = PagerStateImpl.Saver) {
- PagerStateImpl(
+ return rememberSaveable(saver = DefaultPagerState.Saver) {
+ DefaultPagerState(
initialPage,
initialPageOffsetFraction,
pageCount
@@ -87,12 +87,31 @@
}
}
+/**
+ * Creates a default [PagerState] to be used with a [Pager]
+ *
+ * Please refer to the sample to learn how to use this API.
+ * @sample androidx.compose.foundation.samples.PagerWithStateSample
+ *
+ * @param currentPage The pager that should be shown first.
+ * @param currentPageOffsetFraction The offset of the initial page as a fraction of the page size.
+ * This should vary between -0.5 and 0.5 and indicates how to offset the initial page from the
+ * snapped position.
+ * @param pageCount The amount of pages this Pager will have.
+ */
@ExperimentalFoundationApi
-internal class PagerStateImpl(
- initialPage: Int,
- initialPageOffsetFraction: Float,
+fun PagerState(
+ currentPage: Int = 0,
+ currentPageOffsetFraction: Float = 0f,
+ pageCount: () -> Int
+): PagerState = DefaultPagerState(currentPage, currentPageOffsetFraction, pageCount)
+
+@ExperimentalFoundationApi
+private class DefaultPagerState(
+ currentPage: Int,
+ currentPageOffsetFraction: Float,
updatedPageCount: () -> Int
-) : PagerState(initialPage, initialPageOffsetFraction) {
+) : PagerState(currentPage, currentPageOffsetFraction) {
var pageCountState = mutableStateOf(updatedPageCount)
override val pageCount: Int get() = pageCountState.value.invoke()
@@ -101,7 +120,7 @@
/**
* To keep current page and current page offset saved
*/
- val Saver: Saver<PagerStateImpl, *> = listSaver(
+ val Saver: Saver<DefaultPagerState, *> = listSaver(
save = {
listOf(
it.currentPage,
@@ -110,9 +129,9 @@
)
},
restore = {
- PagerStateImpl(
- initialPage = it[0] as Int,
- initialPageOffsetFraction = it[1] as Float,
+ DefaultPagerState(
+ currentPage = it[0] as Int,
+ currentPageOffsetFraction = it[1] as Float,
updatedPageCount = { it[2] as Int }
)
}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/CoreTextField.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/CoreTextField.kt
index 5213275..0ad563e 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/CoreTextField.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/CoreTextField.kt
@@ -353,7 +353,7 @@
startInputSession(
textInputService,
state,
- value,
+ manager.value,
imeOptions,
offsetMapping
)
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/TextFieldSelectionManager.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/TextFieldSelectionManager.kt
index e3b7eb4..f7a6ffb 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/TextFieldSelectionManager.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/TextFieldSelectionManager.kt
@@ -648,9 +648,17 @@
}
internal fun getHandlePosition(isStartHandle: Boolean): Offset {
+ val textLayoutResult = state?.layoutResult?.value ?: return Offset.Unspecified
+
+ // If layout and value are out of sync, return unspecified.
+ // This will be called again once they are in sync.
+ val transformedText = transformedText ?: return Offset.Unspecified
+ val layoutInputText = textLayoutResult.layoutInput.text.text
+ if (transformedText.text != layoutInputText) return Offset.Unspecified
+
val offset = if (isStartHandle) value.selection.start else value.selection.end
return getSelectionHandleCoordinates(
- textLayoutResult = state?.layoutResult!!.value,
+ textLayoutResult = textLayoutResult,
offset = offsetMapping.originalToTransformed(offset),
isStart = isStartHandle,
areHandlesCrossed = value.selection.reversed
diff --git a/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/DesktopOverscroll.kt b/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/DesktopOverscroll.desktop.kt
similarity index 100%
rename from compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/DesktopOverscroll.kt
rename to compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/DesktopOverscroll.desktop.kt
diff --git a/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/text2/input/internal/selection/DesktopTextFieldMagnifier.kt b/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/text2/input/internal/selection/DesktopTextFieldMagnifier.desktop.kt
similarity index 100%
rename from compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/text2/input/internal/selection/DesktopTextFieldMagnifier.kt
rename to compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/text2/input/internal/selection/DesktopTextFieldMagnifier.desktop.kt
diff --git a/compose/material/material/api/current.txt b/compose/material/material/api/current.txt
index cbe0157..0de7448 100644
--- a/compose/material/material/api/current.txt
+++ b/compose/material/material/api/current.txt
@@ -51,24 +51,32 @@
public final class BackdropScaffoldKt {
method @SuppressCompatibility @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Composable public static void BackdropScaffold(kotlin.jvm.functions.Function0<kotlin.Unit> appBar, kotlin.jvm.functions.Function0<kotlin.Unit> backLayerContent, kotlin.jvm.functions.Function0<kotlin.Unit> frontLayerContent, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material.BackdropScaffoldState scaffoldState, optional boolean gesturesEnabled, optional float peekHeight, optional float headerHeight, optional boolean persistentAppBar, optional boolean stickyFrontLayer, optional long backLayerBackgroundColor, optional long backLayerContentColor, optional androidx.compose.ui.graphics.Shape frontLayerShape, optional float frontLayerElevation, optional long frontLayerBackgroundColor, optional long frontLayerContentColor, optional long frontLayerScrimColor, optional kotlin.jvm.functions.Function1<? super androidx.compose.material.SnackbarHostState,kotlin.Unit> snackbarHost);
+ method @SuppressCompatibility @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Stable public static androidx.compose.material.BackdropScaffoldState BackdropScaffoldState(androidx.compose.material.BackdropValue initialValue, androidx.compose.ui.unit.Density density, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.material.BackdropValue,java.lang.Boolean> confirmValueChange, optional androidx.compose.material.SnackbarHostState snackbarHostState);
method @SuppressCompatibility @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Composable public static androidx.compose.material.BackdropScaffoldState rememberBackdropScaffoldState(androidx.compose.material.BackdropValue initialValue, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.material.BackdropValue,java.lang.Boolean> confirmStateChange, optional androidx.compose.material.SnackbarHostState snackbarHostState);
}
- @SuppressCompatibility @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Stable public final class BackdropScaffoldState extends androidx.compose.material.SwipeableState<androidx.compose.material.BackdropValue> {
- ctor public BackdropScaffoldState(androidx.compose.material.BackdropValue initialValue, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.material.BackdropValue,java.lang.Boolean> confirmStateChange, optional androidx.compose.material.SnackbarHostState snackbarHostState);
+ @SuppressCompatibility @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Stable public final class BackdropScaffoldState {
+ ctor @Deprecated public BackdropScaffoldState(androidx.compose.material.BackdropValue initialValue, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.material.BackdropValue,java.lang.Boolean> confirmValueChange, optional androidx.compose.material.SnackbarHostState snackbarHostState);
method public suspend Object? conceal(kotlin.coroutines.Continuation<? super kotlin.Unit>);
+ method public kotlin.jvm.functions.Function1<androidx.compose.material.BackdropValue,java.lang.Boolean> getConfirmValueChange();
+ method public androidx.compose.material.BackdropValue getCurrentValue();
method public androidx.compose.material.SnackbarHostState getSnackbarHostState();
+ method public androidx.compose.material.BackdropValue getTargetValue();
method public boolean isConcealed();
method public boolean isRevealed();
+ method public float requireOffset();
method public suspend Object? reveal(kotlin.coroutines.Continuation<? super kotlin.Unit>);
+ property public final kotlin.jvm.functions.Function1<androidx.compose.material.BackdropValue,java.lang.Boolean> confirmValueChange;
+ property public final androidx.compose.material.BackdropValue currentValue;
property public final boolean isConcealed;
property public final boolean isRevealed;
property public final androidx.compose.material.SnackbarHostState snackbarHostState;
+ property public final androidx.compose.material.BackdropValue targetValue;
field public static final androidx.compose.material.BackdropScaffoldState.Companion Companion;
}
public static final class BackdropScaffoldState.Companion {
- method public androidx.compose.runtime.saveable.Saver<androidx.compose.material.BackdropScaffoldState,?> Saver(androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec, kotlin.jvm.functions.Function1<? super androidx.compose.material.BackdropValue,java.lang.Boolean> confirmStateChange, androidx.compose.material.SnackbarHostState snackbarHostState);
+ method public androidx.compose.runtime.saveable.Saver<androidx.compose.material.BackdropScaffoldState,?> Saver(androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec, kotlin.jvm.functions.Function1<? super androidx.compose.material.BackdropValue,java.lang.Boolean> confirmStateChange, androidx.compose.material.SnackbarHostState snackbarHostState, androidx.compose.ui.unit.Density density);
}
@SuppressCompatibility @androidx.compose.material.ExperimentalMaterialApi public enum BackdropValue {
diff --git a/compose/material/material/api/restricted_current.txt b/compose/material/material/api/restricted_current.txt
index cbe0157..0de7448 100644
--- a/compose/material/material/api/restricted_current.txt
+++ b/compose/material/material/api/restricted_current.txt
@@ -51,24 +51,32 @@
public final class BackdropScaffoldKt {
method @SuppressCompatibility @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Composable public static void BackdropScaffold(kotlin.jvm.functions.Function0<kotlin.Unit> appBar, kotlin.jvm.functions.Function0<kotlin.Unit> backLayerContent, kotlin.jvm.functions.Function0<kotlin.Unit> frontLayerContent, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material.BackdropScaffoldState scaffoldState, optional boolean gesturesEnabled, optional float peekHeight, optional float headerHeight, optional boolean persistentAppBar, optional boolean stickyFrontLayer, optional long backLayerBackgroundColor, optional long backLayerContentColor, optional androidx.compose.ui.graphics.Shape frontLayerShape, optional float frontLayerElevation, optional long frontLayerBackgroundColor, optional long frontLayerContentColor, optional long frontLayerScrimColor, optional kotlin.jvm.functions.Function1<? super androidx.compose.material.SnackbarHostState,kotlin.Unit> snackbarHost);
+ method @SuppressCompatibility @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Stable public static androidx.compose.material.BackdropScaffoldState BackdropScaffoldState(androidx.compose.material.BackdropValue initialValue, androidx.compose.ui.unit.Density density, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.material.BackdropValue,java.lang.Boolean> confirmValueChange, optional androidx.compose.material.SnackbarHostState snackbarHostState);
method @SuppressCompatibility @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Composable public static androidx.compose.material.BackdropScaffoldState rememberBackdropScaffoldState(androidx.compose.material.BackdropValue initialValue, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.material.BackdropValue,java.lang.Boolean> confirmStateChange, optional androidx.compose.material.SnackbarHostState snackbarHostState);
}
- @SuppressCompatibility @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Stable public final class BackdropScaffoldState extends androidx.compose.material.SwipeableState<androidx.compose.material.BackdropValue> {
- ctor public BackdropScaffoldState(androidx.compose.material.BackdropValue initialValue, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.material.BackdropValue,java.lang.Boolean> confirmStateChange, optional androidx.compose.material.SnackbarHostState snackbarHostState);
+ @SuppressCompatibility @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Stable public final class BackdropScaffoldState {
+ ctor @Deprecated public BackdropScaffoldState(androidx.compose.material.BackdropValue initialValue, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.material.BackdropValue,java.lang.Boolean> confirmValueChange, optional androidx.compose.material.SnackbarHostState snackbarHostState);
method public suspend Object? conceal(kotlin.coroutines.Continuation<? super kotlin.Unit>);
+ method public kotlin.jvm.functions.Function1<androidx.compose.material.BackdropValue,java.lang.Boolean> getConfirmValueChange();
+ method public androidx.compose.material.BackdropValue getCurrentValue();
method public androidx.compose.material.SnackbarHostState getSnackbarHostState();
+ method public androidx.compose.material.BackdropValue getTargetValue();
method public boolean isConcealed();
method public boolean isRevealed();
+ method public float requireOffset();
method public suspend Object? reveal(kotlin.coroutines.Continuation<? super kotlin.Unit>);
+ property public final kotlin.jvm.functions.Function1<androidx.compose.material.BackdropValue,java.lang.Boolean> confirmValueChange;
+ property public final androidx.compose.material.BackdropValue currentValue;
property public final boolean isConcealed;
property public final boolean isRevealed;
property public final androidx.compose.material.SnackbarHostState snackbarHostState;
+ property public final androidx.compose.material.BackdropValue targetValue;
field public static final androidx.compose.material.BackdropScaffoldState.Companion Companion;
}
public static final class BackdropScaffoldState.Companion {
- method public androidx.compose.runtime.saveable.Saver<androidx.compose.material.BackdropScaffoldState,?> Saver(androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec, kotlin.jvm.functions.Function1<? super androidx.compose.material.BackdropValue,java.lang.Boolean> confirmStateChange, androidx.compose.material.SnackbarHostState snackbarHostState);
+ method public androidx.compose.runtime.saveable.Saver<androidx.compose.material.BackdropScaffoldState,?> Saver(androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec, kotlin.jvm.functions.Function1<? super androidx.compose.material.BackdropValue,java.lang.Boolean> confirmStateChange, androidx.compose.material.SnackbarHostState snackbarHostState, androidx.compose.ui.unit.Density density);
}
@SuppressCompatibility @androidx.compose.material.ExperimentalMaterialApi public enum BackdropValue {
diff --git a/compose/material/material/src/androidInstrumentedTest/kotlin/androidx/compose/material/BackdropScaffoldTest.kt b/compose/material/material/src/androidInstrumentedTest/kotlin/androidx/compose/material/BackdropScaffoldTest.kt
index f8b99e2..d7f1dd7 100644
--- a/compose/material/material/src/androidInstrumentedTest/kotlin/androidx/compose/material/BackdropScaffoldTest.kt
+++ b/compose/material/material/src/androidInstrumentedTest/kotlin/androidx/compose/material/BackdropScaffoldTest.kt
@@ -344,16 +344,43 @@
}
}
+ @Suppress("DEPRECATION")
+ @Test
+ fun backdropScaffold_deprecateScaffoldState_doesNotCrash() {
+ lateinit var scaffoldState: BackdropScaffoldState
+ rule.setContent {
+ scaffoldState = BackdropScaffoldState(
+ initialValue = Revealed
+ )
+ BackdropScaffold(
+ scaffoldState = scaffoldState,
+ peekHeight = peekHeight,
+ headerHeight = headerHeight,
+ appBar = { Box(Modifier.height(peekHeight)) },
+ backLayerContent = { Box(Modifier.height(contentHeight)) },
+ frontLayerContent = { Box(
+ Modifier
+ .fillMaxSize()
+ .testTag(frontLayer)) }
+ )
+ }
+
+ rule.runOnIdle {
+ assertThat(scaffoldState.currentValue).isEqualTo(Revealed)
+ }
+ }
+
/**
* Tests that the state and offset of [swipeable] are updated when swiping.
*/
@Test
fun backdropScaffold_syncThresholdUpdate() {
val increasedAnchor = mutableStateOf(false)
- val scaffoldState = BackdropScaffoldState(Revealed)
+ var scaffoldState: BackdropScaffoldState? = null
rule.setContent {
+ scaffoldState = rememberBackdropScaffoldState(initialValue = Revealed)
BackdropScaffold(
- scaffoldState = scaffoldState,
+ scaffoldState = scaffoldState!!,
frontLayerScrimColor = Color.Red,
appBar = { },
backLayerContent = {
@@ -374,21 +401,21 @@
}
val revealedOffset = rule.runOnIdle {
- assertThat(scaffoldState.currentValue).isEqualTo(BackdropValue.Revealed)
+ assertThat(scaffoldState?.currentValue).isEqualTo(BackdropValue.Revealed)
// state change changes the anchors, causing the recalculation
increasedAnchor.value = true
- scaffoldState.offset.value
+ scaffoldState?.requireOffset()
}
rule.runOnIdle {
- assertThat(scaffoldState.offset.value).isNotEqualTo(revealedOffset)
+ assertThat(scaffoldState?.requireOffset()).isNotEqualTo(revealedOffset)
// swap back, causing threshold update during update-caused settle
increasedAnchor.value = false
}
rule.runOnIdle {
// no crash and assert passes
- assertThat(scaffoldState.offset.value).isEqualTo(revealedOffset)
+ assertThat(scaffoldState?.requireOffset()).isEqualTo(revealedOffset)
}
}
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/BackdropScaffold.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/BackdropScaffold.kt
index 2376ad5..e833b1e 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/BackdropScaffold.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/BackdropScaffold.kt
@@ -33,6 +33,7 @@
import androidx.compose.material.BackdropValue.Concealed
import androidx.compose.material.BackdropValue.Revealed
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.Stable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
@@ -42,20 +43,27 @@
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.UiComposable
+import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.graphics.isSpecified
+import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
+import androidx.compose.ui.input.nestedscroll.NestedScrollSource
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.layout.SubcomposeLayout
+import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.semantics.collapse
import androidx.compose.ui.semantics.expand
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.IntOffset
+import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.Velocity
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.offset
import androidx.compose.ui.util.fastCoerceIn
@@ -85,73 +93,154 @@
}
/**
+ * State of the persistent bottom sheet in [BackdropScaffold].
+ *
+ * @param initialValue The initial value of the state.
+ * @param density The density that this state can use to convert values to and from dp.
+ * @param animationSpec The default animation that will be used to animate to a new state.
+ * @param confirmValueChange Optional callback invoked to confirm or veto a pending state change.
+ * @param snackbarHostState The [SnackbarHostState] used to show snackbars inside the scaffold.
+ */
+@Suppress("Deprecation")
+@ExperimentalMaterialApi
+@Stable
+fun BackdropScaffoldState(
+ initialValue: BackdropValue,
+ density: Density,
+ animationSpec: AnimationSpec<Float> = SwipeableDefaults.AnimationSpec,
+ confirmValueChange: (BackdropValue) -> Boolean = { true },
+ snackbarHostState: SnackbarHostState = SnackbarHostState(),
+) = BackdropScaffoldState(
+ initialValue,
+ animationSpec,
+ confirmValueChange,
+ snackbarHostState
+).also {
+ it.density = density
+}
+
+/**
* State of the [BackdropScaffold] composable.
*
* @param initialValue The initial value of the state.
* @param animationSpec The default animation that will be used to animate to a new state.
- * @param confirmStateChange Optional callback invoked to confirm or veto a pending state change.
+ * @param confirmValueChange Optional callback invoked to confirm or veto a pending state change.
* @param snackbarHostState The [SnackbarHostState] used to show snackbars inside the scaffold.
*/
@ExperimentalMaterialApi
@Stable
-class BackdropScaffoldState(
+class BackdropScaffoldState @Deprecated(
+ "This constructor is deprecated. Density must be provided by the component. " +
+ "Please use the constructor that provides a [Density].",
+ ReplaceWith(
+ """
+ BackdropScaffoldState(
+ initialValue = initialValue,
+ density = LocalDensity.current,
+ animationSpec = animationSpec,
+ confirmValueChange = confirmValueChange
+ )
+ """
+ )
+) constructor(
initialValue: BackdropValue,
animationSpec: AnimationSpec<Float> = SwipeableDefaults.AnimationSpec,
- confirmStateChange: (BackdropValue) -> Boolean = { true },
- val snackbarHostState: SnackbarHostState = SnackbarHostState()
-) : SwipeableState<BackdropValue>(
- initialValue = initialValue,
- animationSpec = animationSpec,
- confirmStateChange = confirmStateChange
+ val confirmValueChange: (BackdropValue) -> Boolean = { true },
+ val snackbarHostState: SnackbarHostState = SnackbarHostState(),
) {
/**
+ * The current value of the [BottomSheetState].
+ */
+ val currentValue: BackdropValue
+ get() = anchoredDraggableState.currentValue
+
+ /**
+ * The target value the state will settle at once the current interaction ends, or the
+ * [currentValue] if there is no interaction in progress.
+ */
+ val targetValue: BackdropValue
+ get() = anchoredDraggableState.targetValue
+
+ /**
+ * Require the current offset.
+ *
+ * @throws IllegalStateException If the offset has not been initialized yet
+ */
+ fun requireOffset() = anchoredDraggableState.requireOffset()
+
+ /**
* Whether the back layer is revealed.
*/
val isRevealed: Boolean
- get() = currentValue == Revealed
+ get() = anchoredDraggableState.currentValue == Revealed
/**
* Whether the back layer is concealed.
*/
val isConcealed: Boolean
- get() = currentValue == Concealed
+ get() = anchoredDraggableState.currentValue == Concealed
/**
* Reveal the back layer with animation and suspend until it if fully revealed or animation
* has been cancelled. This method will throw [CancellationException] if the animation is
* interrupted
- *
- * @return the reason the reveal animation ended
*/
- suspend fun reveal() = animateTo(targetValue = Revealed)
+ suspend fun reveal() = anchoredDraggableState.animateTo(targetValue = Revealed)
/**
* Conceal the back layer with animation and suspend until it if fully concealed or animation
* has been cancelled. This method will throw [CancellationException] if the animation is
* interrupted
- *
- * @return the reason the conceal animation ended
*/
- suspend fun conceal() = animateTo(targetValue = Concealed)
+ suspend fun conceal() = anchoredDraggableState.animateTo(targetValue = Concealed)
- internal val nestedScrollConnection = this.PreUpPostDownNestedScrollConnection
+ internal val anchoredDraggableState = AnchoredDraggableState(
+ initialValue = initialValue,
+ animationSpec = animationSpec,
+ confirmValueChange = confirmValueChange,
+ positionalThreshold = {
+ with(requireDensity()) {
+ PositionalThreshold.toPx()
+ }
+ },
+ velocityThreshold = {
+ with(requireDensity()) {
+ VelocityThreshold.toPx()
+ }
+ }
+ )
+
+ internal var density: Density? = null
+ private fun requireDensity() = requireNotNull(density) {
+ "The density on BackdropScaffoldState ($this) was not set." +
+ " Did you use BackdropScaffoldState with " +
+ "the BackdropScaffold composable?"
+ }
+
+ internal val nestedScrollConnection = ConsumeSwipeNestedScrollConnection(
+ anchoredDraggableState,
+ Orientation.Vertical
+ )
companion object {
+
/**
* The default [Saver] implementation for [BackdropScaffoldState].
*/
fun Saver(
animationSpec: AnimationSpec<Float>,
confirmStateChange: (BackdropValue) -> Boolean,
- snackbarHostState: SnackbarHostState
+ snackbarHostState: SnackbarHostState,
+ density: Density
): Saver<BackdropScaffoldState, *> = Saver(
- save = { it.currentValue },
+ save = { it.anchoredDraggableState.currentValue },
restore = {
BackdropScaffoldState(
initialValue = it,
animationSpec = animationSpec,
- confirmStateChange = confirmStateChange,
- snackbarHostState = snackbarHostState
+ confirmValueChange = confirmStateChange,
+ snackbarHostState = snackbarHostState,
+ density = density
)
}
)
@@ -172,8 +261,9 @@
initialValue: BackdropValue,
animationSpec: AnimationSpec<Float> = SwipeableDefaults.AnimationSpec,
confirmStateChange: (BackdropValue) -> Boolean = { true },
- snackbarHostState: SnackbarHostState = remember { SnackbarHostState() }
+ snackbarHostState: SnackbarHostState = remember { SnackbarHostState() },
): BackdropScaffoldState {
+ val density = LocalDensity.current
return rememberSaveable(
animationSpec,
confirmStateChange,
@@ -181,14 +271,16 @@
saver = BackdropScaffoldState.Saver(
animationSpec = animationSpec,
confirmStateChange = confirmStateChange,
- snackbarHostState = snackbarHostState
+ snackbarHostState = snackbarHostState,
+ density = density
)
) {
BackdropScaffoldState(
initialValue = initialValue,
animationSpec = animationSpec,
- confirmStateChange = confirmStateChange,
- snackbarHostState = snackbarHostState
+ confirmValueChange = confirmStateChange,
+ snackbarHostState = snackbarHostState,
+ density = density
)
}
}
@@ -276,6 +368,12 @@
frontLayerScrimColor: Color = BackdropScaffoldDefaults.frontLayerScrimColor,
snackbarHost: @Composable (SnackbarHostState) -> Unit = { SnackbarHost(it) }
) {
+ // b/278692145 Remove this once deprecated methods without density are removed
+ val density = LocalDensity.current
+ SideEffect {
+ scaffoldState.density = density
+ }
+
val peekHeightPx = with(LocalDensity.current) { peekHeight.toPx() }
val headerHeightPx = with(LocalDensity.current) { headerHeight.toPx() }
@@ -286,13 +384,16 @@
backLayerContent()
}
} else {
- BackLayerTransition(scaffoldState.targetValue, appBar, backLayerContent)
+ BackLayerTransition(scaffoldState.anchoredDraggableState.targetValue,
+ appBar, backLayerContent)
}
}
val calculateBackLayerConstraints: (Constraints) -> Constraints = {
it.copy(minWidth = 0, minHeight = 0).offset(vertical = -headerHeightPx.roundToInt())
}
+ val state = scaffoldState.anchoredDraggableState
+
// Back layer
Surface(
color = backLayerBackgroundColor,
@@ -315,27 +416,46 @@
Modifier
}
+ val calculateAnchors = { layoutSize: IntSize ->
+ val sheetHeight = layoutSize.height.toFloat()
+ val collapsedHeight = layoutSize.height - peekHeightPx
+ DraggableAnchors {
+ if (sheetHeight == 0f || sheetHeight == peekHeightPx) {
+ Concealed at collapsedHeight
+ } else {
+ Concealed at peekHeightPx
+ Revealed at revealedHeight
+ }
+ }
+ }
val swipeable = Modifier
.then(nestedScroll)
- .swipeable(
- state = scaffoldState,
- anchors = mapOf(
- peekHeightPx to Concealed,
- revealedHeight to Revealed
- ),
+ .anchoredDraggable(
+ state = state,
orientation = Orientation.Vertical,
- enabled = gesturesEnabled
+ enabled = gesturesEnabled,
)
+ .onSizeChanged { layoutSize ->
+ val newAnchors = calculateAnchors(layoutSize)
+ val newTarget = when (scaffoldState.targetValue) {
+ Concealed -> Concealed
+ Revealed -> if (newAnchors.hasAnchorFor(Revealed)) Revealed else Concealed
+ }
+ state.updateAnchors(
+ newAnchors = newAnchors,
+ newTarget = newTarget
+ )
+ }
.semantics {
if (scaffoldState.isConcealed) {
collapse {
- if (scaffoldState.confirmStateChange(Revealed)) {
+ if (scaffoldState.confirmValueChange(Revealed)) {
scope.launch { scaffoldState.reveal() }
}; true
}
} else {
expand {
- if (scaffoldState.confirmStateChange(Concealed)) {
+ if (scaffoldState.confirmValueChange(Concealed)) {
scope.launch { scaffoldState.conceal() }
}; true
}
@@ -345,7 +465,7 @@
// Front layer
Surface(
Modifier
- .offset { IntOffset(0, scaffoldState.offset.value.roundToInt()) }
+ .offset { IntOffset(0, state.requireOffset().toInt()) }
.then(swipeable),
shape = frontLayerShape,
elevation = frontLayerElevation,
@@ -357,11 +477,11 @@
Scrim(
color = frontLayerScrimColor,
>
- if (gesturesEnabled && scaffoldState.confirmStateChange(Concealed)) {
+ if (gesturesEnabled && scaffoldState.confirmValueChange(Concealed)) {
scope.launch { scaffoldState.conceal() }
}
},
- visible = scaffoldState.targetValue == Revealed
+ visible = scaffoldState.anchoredDraggableState.targetValue == Revealed
)
}
}
@@ -432,18 +552,22 @@
Box {
Box(
- Modifier.zIndex(appBarFloat).graphicsLayer(
- alpha = appBarFloat,
- translationY = (1 - appBarFloat) * animationSlideOffset
- )
+ Modifier
+ .zIndex(appBarFloat)
+ .graphicsLayer(
+ alpha = appBarFloat,
+ translationY = (1 - appBarFloat) * animationSlideOffset
+ )
) {
appBar()
}
Box(
- Modifier.zIndex(contentFloat).graphicsLayer(
- alpha = contentFloat,
- translationY = (1 - contentFloat) * -animationSlideOffset
- )
+ Modifier
+ .zIndex(contentFloat)
+ .graphicsLayer(
+ alpha = contentFloat,
+ translationY = (1 - contentFloat) * -animationSlideOffset
+ )
) {
content()
}
@@ -522,3 +646,60 @@
}
private val AnimationSlideOffset = 20.dp
+private val VelocityThreshold = 125.dp
+private val PositionalThreshold = 56.dp
+
+@OptIn(ExperimentalMaterialApi::class)
+internal fun ConsumeSwipeNestedScrollConnection(
+ state: AnchoredDraggableState<*>,
+ orientation: Orientation
+): NestedScrollConnection = object : NestedScrollConnection {
+ override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
+ val delta = available.toFloat()
+ return if (delta < 0 && source == NestedScrollSource.Drag) {
+ state.dispatchRawDelta(delta).toOffset()
+ } else {
+ Offset.Zero
+ }
+ }
+
+ override fun onPostScroll(
+ consumed: Offset,
+ available: Offset,
+ source: NestedScrollSource
+ ): Offset {
+ return if (source == NestedScrollSource.Drag) {
+ state.dispatchRawDelta(available.toFloat()).toOffset()
+ } else {
+ Offset.Zero
+ }
+ }
+
+ override suspend fun onPreFling(available: Velocity): Velocity {
+ val toFling = available.toFloat()
+ val currentOffset = state.requireOffset()
+ return if (toFling < 0 && currentOffset > state.anchors.minAnchor()) {
+ state.settle(velocity = toFling)
+ // since we go to the anchor with tween settling, consume all for the best UX
+ available
+ } else {
+ Velocity.Zero
+ }
+ }
+
+ override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity {
+ state.settle(velocity = available.toFloat())
+ return available
+ }
+
+ private fun Float.toOffset(): Offset = Offset(
+ x = if (orientation == Orientation.Horizontal) this else 0f,
+ y = if (orientation == Orientation.Vertical) this else 0f
+ )
+
+ @JvmName("velocityToFloat")
+ private fun Velocity.toFloat() = if (orientation == Orientation.Horizontal) x else y
+
+ @JvmName("offsetToFloat")
+ private fun Offset.toFloat(): Float = if (orientation == Orientation.Horizontal) x else y
+}
diff --git a/compose/material3/benchmark/src/androidTest/java/androidx/compose/material3/benchmark/TabRowBenchmark.kt b/compose/material3/benchmark/src/androidTest/java/androidx/compose/material3/benchmark/TabRowBenchmark.kt
index e892880..d69b104 100644
--- a/compose/material3/benchmark/src/androidTest/java/androidx/compose/material3/benchmark/TabRowBenchmark.kt
+++ b/compose/material3/benchmark/src/androidTest/java/androidx/compose/material3/benchmark/TabRowBenchmark.kt
@@ -28,6 +28,7 @@
import androidx.compose.testutils.LayeredComposeTestCase
import androidx.compose.testutils.ToggleableTestCase
import androidx.compose.testutils.benchmark.ComposeBenchmarkRule
+import androidx.compose.testutils.benchmark.benchmarkToFirstPixel
import androidx.compose.testutils.benchmark.toggleStateBenchmarkComposeMeasureLayout
import org.junit.Rule
import org.junit.Test
@@ -40,7 +41,7 @@
@Test
fun firstPixel() {
- benchmarkRule.benchmarkFirstRenderUntilStable(tabRowTestCaseFactory)
+ benchmarkRule.benchmarkToFirstPixel(tabRowTestCaseFactory)
}
@Test
diff --git a/compose/material3/material3/api/current.txt b/compose/material3/material3/api/current.txt
index a623044..c4701d4 100644
--- a/compose/material3/material3/api/current.txt
+++ b/compose/material3/material3/api/current.txt
@@ -1617,6 +1617,11 @@
method @androidx.compose.runtime.Composable public static void Switch(boolean checked, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit>? onCheckedChange, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? thumbContent, optional boolean enabled, optional androidx.compose.material3.SwitchColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource);
}
+ public interface TabIndicatorScope {
+ method public androidx.compose.ui.Modifier tabIndicatorLayout(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function4<? super androidx.compose.ui.layout.MeasureScope,? super androidx.compose.ui.layout.Measurable,? super androidx.compose.ui.unit.Constraints,? super java.util.List<androidx.compose.material3.TabPosition>,? extends androidx.compose.ui.layout.MeasureResult> measure);
+ method public androidx.compose.ui.Modifier tabIndicatorOffset(androidx.compose.ui.Modifier, int selectedTabIndex, optional boolean matchContentSize);
+ }
+
public final class TabKt {
method @androidx.compose.runtime.Composable public static void LeadingIconTab(boolean selected, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, kotlin.jvm.functions.Function0<kotlin.Unit> text, kotlin.jvm.functions.Function0<kotlin.Unit> icon, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional long selectedContentColor, optional long unselectedContentColor, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource);
method @androidx.compose.runtime.Composable public static void Tab(boolean selected, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.jvm.functions.Function0<kotlin.Unit>? text, optional kotlin.jvm.functions.Function0<kotlin.Unit>? icon, optional long selectedContentColor, optional long unselectedContentColor, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource);
@@ -1658,10 +1663,10 @@
public final class TabRowKt {
method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void PrimaryScrollableTabRow(int selectedTabIndex, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.ScrollState scrollState, optional long containerColor, optional long contentColor, optional float edgePadding, optional kotlin.jvm.functions.Function1<? super java.util.List<androidx.compose.material3.TabPosition>,kotlin.Unit> indicator, optional kotlin.jvm.functions.Function0<kotlin.Unit> divider, kotlin.jvm.functions.Function0<kotlin.Unit> tabs);
- method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void PrimaryTabRow(int selectedTabIndex, optional androidx.compose.ui.Modifier modifier, optional long containerColor, optional long contentColor, optional kotlin.jvm.functions.Function1<? super java.util.List<androidx.compose.material3.TabPosition>,kotlin.Unit> indicator, optional kotlin.jvm.functions.Function0<kotlin.Unit> divider, kotlin.jvm.functions.Function0<kotlin.Unit> tabs);
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void PrimaryTabRow(int selectedTabIndex, optional androidx.compose.ui.Modifier modifier, optional long containerColor, optional long contentColor, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.TabIndicatorScope,kotlin.Unit> indicator, optional kotlin.jvm.functions.Function0<kotlin.Unit> divider, kotlin.jvm.functions.Function0<kotlin.Unit> tabs);
method @androidx.compose.runtime.Composable public static void ScrollableTabRow(int selectedTabIndex, optional androidx.compose.ui.Modifier modifier, optional long containerColor, optional long contentColor, optional float edgePadding, optional kotlin.jvm.functions.Function1<? super java.util.List<androidx.compose.material3.TabPosition>,kotlin.Unit> indicator, optional kotlin.jvm.functions.Function0<kotlin.Unit> divider, kotlin.jvm.functions.Function0<kotlin.Unit> tabs);
method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void SecondaryScrollableTabRow(int selectedTabIndex, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.ScrollState scrollState, optional long containerColor, optional long contentColor, optional float edgePadding, optional kotlin.jvm.functions.Function1<? super java.util.List<androidx.compose.material3.TabPosition>,kotlin.Unit> indicator, optional kotlin.jvm.functions.Function0<kotlin.Unit> divider, kotlin.jvm.functions.Function0<kotlin.Unit> tabs);
- method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void SecondaryTabRow(int selectedTabIndex, optional androidx.compose.ui.Modifier modifier, optional long containerColor, optional long contentColor, optional kotlin.jvm.functions.Function1<? super java.util.List<androidx.compose.material3.TabPosition>,kotlin.Unit> indicator, optional kotlin.jvm.functions.Function0<kotlin.Unit> divider, kotlin.jvm.functions.Function0<kotlin.Unit> tabs);
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void SecondaryTabRow(int selectedTabIndex, optional androidx.compose.ui.Modifier modifier, optional long containerColor, optional long contentColor, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.TabIndicatorScope,kotlin.Unit> indicator, optional kotlin.jvm.functions.Function0<kotlin.Unit> divider, kotlin.jvm.functions.Function0<kotlin.Unit> tabs);
method @androidx.compose.runtime.Composable public static void TabRow(int selectedTabIndex, optional androidx.compose.ui.Modifier modifier, optional long containerColor, optional long contentColor, optional kotlin.jvm.functions.Function1<? super java.util.List<androidx.compose.material3.TabPosition>,kotlin.Unit> indicator, optional kotlin.jvm.functions.Function0<kotlin.Unit> divider, kotlin.jvm.functions.Function0<kotlin.Unit> tabs);
}
diff --git a/compose/material3/material3/api/restricted_current.txt b/compose/material3/material3/api/restricted_current.txt
index a623044..c4701d4 100644
--- a/compose/material3/material3/api/restricted_current.txt
+++ b/compose/material3/material3/api/restricted_current.txt
@@ -1617,6 +1617,11 @@
method @androidx.compose.runtime.Composable public static void Switch(boolean checked, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit>? onCheckedChange, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? thumbContent, optional boolean enabled, optional androidx.compose.material3.SwitchColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource);
}
+ public interface TabIndicatorScope {
+ method public androidx.compose.ui.Modifier tabIndicatorLayout(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function4<? super androidx.compose.ui.layout.MeasureScope,? super androidx.compose.ui.layout.Measurable,? super androidx.compose.ui.unit.Constraints,? super java.util.List<androidx.compose.material3.TabPosition>,? extends androidx.compose.ui.layout.MeasureResult> measure);
+ method public androidx.compose.ui.Modifier tabIndicatorOffset(androidx.compose.ui.Modifier, int selectedTabIndex, optional boolean matchContentSize);
+ }
+
public final class TabKt {
method @androidx.compose.runtime.Composable public static void LeadingIconTab(boolean selected, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, kotlin.jvm.functions.Function0<kotlin.Unit> text, kotlin.jvm.functions.Function0<kotlin.Unit> icon, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional long selectedContentColor, optional long unselectedContentColor, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource);
method @androidx.compose.runtime.Composable public static void Tab(boolean selected, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.jvm.functions.Function0<kotlin.Unit>? text, optional kotlin.jvm.functions.Function0<kotlin.Unit>? icon, optional long selectedContentColor, optional long unselectedContentColor, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource);
@@ -1658,10 +1663,10 @@
public final class TabRowKt {
method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void PrimaryScrollableTabRow(int selectedTabIndex, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.ScrollState scrollState, optional long containerColor, optional long contentColor, optional float edgePadding, optional kotlin.jvm.functions.Function1<? super java.util.List<androidx.compose.material3.TabPosition>,kotlin.Unit> indicator, optional kotlin.jvm.functions.Function0<kotlin.Unit> divider, kotlin.jvm.functions.Function0<kotlin.Unit> tabs);
- method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void PrimaryTabRow(int selectedTabIndex, optional androidx.compose.ui.Modifier modifier, optional long containerColor, optional long contentColor, optional kotlin.jvm.functions.Function1<? super java.util.List<androidx.compose.material3.TabPosition>,kotlin.Unit> indicator, optional kotlin.jvm.functions.Function0<kotlin.Unit> divider, kotlin.jvm.functions.Function0<kotlin.Unit> tabs);
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void PrimaryTabRow(int selectedTabIndex, optional androidx.compose.ui.Modifier modifier, optional long containerColor, optional long contentColor, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.TabIndicatorScope,kotlin.Unit> indicator, optional kotlin.jvm.functions.Function0<kotlin.Unit> divider, kotlin.jvm.functions.Function0<kotlin.Unit> tabs);
method @androidx.compose.runtime.Composable public static void ScrollableTabRow(int selectedTabIndex, optional androidx.compose.ui.Modifier modifier, optional long containerColor, optional long contentColor, optional float edgePadding, optional kotlin.jvm.functions.Function1<? super java.util.List<androidx.compose.material3.TabPosition>,kotlin.Unit> indicator, optional kotlin.jvm.functions.Function0<kotlin.Unit> divider, kotlin.jvm.functions.Function0<kotlin.Unit> tabs);
method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void SecondaryScrollableTabRow(int selectedTabIndex, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.ScrollState scrollState, optional long containerColor, optional long contentColor, optional float edgePadding, optional kotlin.jvm.functions.Function1<? super java.util.List<androidx.compose.material3.TabPosition>,kotlin.Unit> indicator, optional kotlin.jvm.functions.Function0<kotlin.Unit> divider, kotlin.jvm.functions.Function0<kotlin.Unit> tabs);
- method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void SecondaryTabRow(int selectedTabIndex, optional androidx.compose.ui.Modifier modifier, optional long containerColor, optional long contentColor, optional kotlin.jvm.functions.Function1<? super java.util.List<androidx.compose.material3.TabPosition>,kotlin.Unit> indicator, optional kotlin.jvm.functions.Function0<kotlin.Unit> divider, kotlin.jvm.functions.Function0<kotlin.Unit> tabs);
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void SecondaryTabRow(int selectedTabIndex, optional androidx.compose.ui.Modifier modifier, optional long containerColor, optional long contentColor, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.TabIndicatorScope,kotlin.Unit> indicator, optional kotlin.jvm.functions.Function0<kotlin.Unit> divider, kotlin.jvm.functions.Function0<kotlin.Unit> tabs);
method @androidx.compose.runtime.Composable public static void TabRow(int selectedTabIndex, optional androidx.compose.ui.Modifier modifier, optional long containerColor, optional long contentColor, optional kotlin.jvm.functions.Function1<? super java.util.List<androidx.compose.material3.TabPosition>,kotlin.Unit> indicator, optional kotlin.jvm.functions.Function0<kotlin.Unit> divider, kotlin.jvm.functions.Function0<kotlin.Unit> tabs);
}
diff --git a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/TabSamples.kt b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/TabSamples.kt
index 65be78f..3ee638f 100644
--- a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/TabSamples.kt
+++ b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/TabSamples.kt
@@ -18,6 +18,10 @@
import androidx.annotation.Sampled
import androidx.compose.animation.animateColor
+import androidx.compose.animation.animateColorAsState
+import androidx.compose.animation.core.Animatable
+import androidx.compose.animation.core.AnimationVector1D
+import androidx.compose.animation.core.VectorConverter
import androidx.compose.animation.core.animateDp
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.animation.core.spring
@@ -48,6 +52,7 @@
import androidx.compose.material3.SecondaryScrollableTabRow
import androidx.compose.material3.SecondaryTabRow
import androidx.compose.material3.Tab
+import androidx.compose.material3.TabIndicatorScope
import androidx.compose.material3.TabPosition
import androidx.compose.material3.TabRowDefaults
import androidx.compose.material3.TabRowDefaults.tabIndicatorOffset
@@ -56,13 +61,21 @@
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.drawWithContent
+import androidx.compose.ui.geometry.CornerRadius
import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.drawscope.Stroke
+import androidx.compose.ui.layout.Measurable
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
+import kotlinx.coroutines.launch
@Preview
@Composable
@@ -386,18 +399,15 @@
var state by remember { mutableStateOf(0) }
val titles = listOf("Tab 1", "Tab 2", "Tab 3")
- // Reuse the default offset animation modifier, but use our own indicator
- val indicator = @Composable { tabPositions: List<TabPosition> ->
- FancyIndicator(
- MaterialTheme.colorScheme.primary,
- Modifier.tabIndicatorOffset(tabPositions[state])
- )
- }
-
Column {
SecondaryTabRow(
selectedTabIndex = state,
- indicator = indicator
+ indicator = {
+ FancyIndicator(
+ MaterialTheme.colorScheme.primary,
+ Modifier.tabIndicatorOffset(state)
+ )
+ }
) {
titles.forEachIndexed { index, title ->
Tab(
@@ -423,14 +433,10 @@
var state by remember { mutableStateOf(0) }
val titles = listOf("Tab 1", "Tab 2", "Tab 3")
- val indicator = @Composable { tabPositions: List<TabPosition> ->
- FancyAnimatedIndicator(tabPositions = tabPositions, selectedTabIndex = state)
- }
-
Column {
SecondaryTabRow(
selectedTabIndex = state,
- indicator = indicator
+ indicator = { FancyAnimatedIndicatorWithModifier(state) }
) {
titles.forEachIndexed { index, title ->
Tab(
@@ -448,6 +454,103 @@
}
}
+@Sampled
+@Composable
+fun FancyIndicator(color: Color, modifier: Modifier = Modifier) {
+ // Draws a rounded rectangular with border around the Tab, with a 5.dp padding from the edges
+ // Color is passed in as a parameter [color]
+ Box(
+ modifier
+ .padding(5.dp)
+ .fillMaxSize()
+ .border(BorderStroke(2.dp, color), RoundedCornerShape(5.dp))
+ )
+}
+
+@Sampled
+@Composable
+fun TabIndicatorScope.FancyAnimatedIndicatorWithModifier(index: Int) {
+ val colors = listOf(
+ MaterialTheme.colorScheme.primary,
+ MaterialTheme.colorScheme.secondary,
+ MaterialTheme.colorScheme.tertiary,
+ )
+ var startAnimatable by remember { mutableStateOf<Animatable<Dp, AnimationVector1D>?>(null) }
+ var endAnimatable by remember { mutableStateOf<Animatable<Dp, AnimationVector1D>?>(null) }
+ val coroutineScope = rememberCoroutineScope()
+ val indicatorColor: Color by animateColorAsState(colors[index % colors.size], label = "")
+
+ Box(
+ Modifier
+ .tabIndicatorLayout { measurable: Measurable, constraints: Constraints,
+ tabPositions: List<TabPosition> ->
+ val newStart = tabPositions[index].left
+ val newEnd = tabPositions[index].right
+ val startAnim = startAnimatable ?: Animatable(newStart, Dp.VectorConverter)
+ .also { startAnimatable = it }
+
+ val endAnim = endAnimatable ?: Animatable(newEnd, Dp.VectorConverter)
+ .also { endAnimatable = it }
+
+ if (endAnim.targetValue != newEnd) {
+ coroutineScope.launch {
+ endAnim.animateTo(
+ newEnd,
+ animationSpec =
+ if (endAnim.targetValue < newEnd) {
+ spring(dampingRatio = 1f, stiffness = 1000f)
+ } else {
+ spring(dampingRatio = 1f, stiffness = 50f)
+ }
+
+ )
+ }
+ }
+
+ if (startAnim.targetValue != newStart) {
+ coroutineScope.launch {
+ startAnim.animateTo(
+ newStart,
+ animationSpec =
+ // Handle directionality here, if we are moving to the right, we
+ // want the right side of the indicator to move faster, if we are
+ // moving to the left, we want the left side to move faster.
+ if (startAnim.targetValue < newStart) {
+ spring(dampingRatio = 1f, stiffness = 50f)
+ } else {
+ spring(dampingRatio = 1f, stiffness = 1000f)
+ }
+
+ )
+ }
+ }
+
+ val indicatorEnd = endAnim.value.roundToPx()
+ val indicatorStart = startAnim.value.roundToPx()
+
+ // Apply an offset from the start to correctly position the indicator around the tab
+ val placeable = measurable.measure(
+ constraints.copy(
+ maxWidth = indicatorEnd - indicatorStart,
+ minWidth = indicatorEnd - indicatorStart,
+ )
+ )
+ layout(constraints.maxWidth, constraints.maxHeight) {
+ placeable.place(indicatorStart, 0)
+ }
+ }
+ .padding(5.dp)
+ .fillMaxSize()
+ .drawWithContent {
+ drawRoundRect(
+ color = indicatorColor,
+ cornerRadius = CornerRadius(5.dp.toPx()),
+ style = Stroke(width = 2.dp.toPx())
+ )
+ }
+ )
+}
+
@Preview
@Composable
@OptIn(ExperimentalMaterial3Api::class)
@@ -495,11 +598,15 @@
fun FancyTab(title: String, onClick: () -> Unit, selected: Boolean) {
Tab(selected, onClick) {
Column(
- Modifier.padding(10.dp).height(50.dp).fillMaxWidth(),
+ Modifier
+ .padding(10.dp)
+ .height(50.dp)
+ .fillMaxWidth(),
verticalArrangement = Arrangement.SpaceBetween
) {
Box(
- Modifier.size(10.dp)
+ Modifier
+ .size(10.dp)
.align(Alignment.CenterHorizontally)
.background(
color = if (selected) MaterialTheme.colorScheme.primary
@@ -517,26 +624,13 @@
@Sampled
@Composable
-fun FancyIndicator(color: Color, modifier: Modifier = Modifier) {
- // Draws a rounded rectangular with border around the Tab, with a 5.dp padding from the edges
- // Color is passed in as a parameter [color]
- Box(
- modifier
- .padding(5.dp)
- .fillMaxSize()
- .border(BorderStroke(2.dp, color), RoundedCornerShape(5.dp))
- )
-}
-
-@Sampled
-@Composable
fun FancyAnimatedIndicator(tabPositions: List<TabPosition>, selectedTabIndex: Int) {
val colors = listOf(
MaterialTheme.colorScheme.primary,
MaterialTheme.colorScheme.secondary,
MaterialTheme.colorScheme.tertiary,
)
- val transition = updateTransition(selectedTabIndex)
+ val transition = updateTransition(selectedTabIndex, label = "")
val indicatorStart by transition.animateDp(
transitionSpec = {
// Handle directionality here, if we are moving to the right, we
@@ -547,7 +641,7 @@
} else {
spring(dampingRatio = 1f, stiffness = 1000f)
}
- }
+ }, label = "fancy_indicator"
) {
tabPositions[it].left
}
@@ -562,12 +656,12 @@
} else {
spring(dampingRatio = 1f, stiffness = 50f)
}
- }
+ }, label = "indicator_position"
) {
tabPositions[it].right
}
- val indicatorColor by transition.animateColor {
+ val indicatorColor by transition.animateColor(label = "indicator_color") {
colors[it % colors.size]
}
diff --git a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/TabScreenshotTest.kt b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/TabScreenshotTest.kt
index 2ad9550..ab22980 100644
--- a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/TabScreenshotTest.kt
+++ b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/TabScreenshotTest.kt
@@ -546,12 +546,7 @@
Modifier
.semantics(mergeDescendants = true) {}
.testTag(TAG)) {
- PrimaryTabRow(selectedTabIndex = 0, indicator = @Composable { tabPositions ->
- TabRowDefaults.PrimaryIndicator(
- modifier = Modifier.tabIndicatorOffset(tabPositions[0]),
- width = tabPositions[0].contentWidth
- )
- }) {
+ PrimaryTabRow(selectedTabIndex = 0) {
Tab(
selected = true,
>
@@ -629,15 +624,10 @@
Modifier
.semantics(mergeDescendants = true) {}
.testTag(TAG)) {
- PrimaryTabRow(selectedTabIndex = 0,
- containerColor = containerColor,
- indicator = @Composable { tabPositions ->
- TabRowDefaults.PrimaryIndicator(
- modifier = Modifier.tabIndicatorOffset(tabPositions[0]),
- width = tabPositions[0].contentWidth,
- color = selectedContentColor
- )
- }) {
+ PrimaryTabRow(
+ selectedTabIndex = 0,
+ containerColor = containerColor
+ ) {
Tab(
selected = true,
>
@@ -685,14 +675,16 @@
Modifier
.semantics(mergeDescendants = true) {}
.testTag(TAG)) {
- SecondaryTabRow(selectedTabIndex = 0,
+ SecondaryTabRow(
+ selectedTabIndex = 0,
containerColor = containerColor,
- indicator = @Composable { tabPositions ->
+ indicator = {
TabRowDefaults.SecondaryIndicator(
- modifier = Modifier.tabIndicatorOffset(tabPositions[0]),
+ modifier = Modifier.tabIndicatorOffset(0),
color = selectedContentColor
)
- }) {
+ }
+ ) {
Tab(
selected = true,
>
@@ -735,12 +727,7 @@
Modifier
.semantics(mergeDescendants = true) {}
.testTag(TAG)) {
- PrimaryTabRow(selectedTabIndex = 0, indicator = @Composable { tabPositions ->
- TabRowDefaults.PrimaryIndicator(
- modifier = Modifier.tabIndicatorOffset(tabPositions[0]),
- width = tabPositions[0].contentWidth
- )
- }) {
+ PrimaryTabRow(selectedTabIndex = 0) {
LeadingIconTab(
selected = true,
>
diff --git a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/TabTest.kt b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/TabTest.kt
index d2364ef..11e09d2 100644
--- a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/TabTest.kt
+++ b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/TabTest.kt
@@ -249,21 +249,19 @@
var state by remember { mutableStateOf(0) }
val titles = listOf("TAB 1", "TAB 2")
- val indicator = @Composable { tabPositions: List<TabPosition> ->
- Box(
- Modifier
- .tabIndicatorOffset(tabPositions[state])
- .fillMaxWidth()
- .height(indicatorHeight)
- .background(color = Color.Red)
- .testTag("indicator")
- )
- }
-
Box(Modifier.testTag("tabRow")) {
SecondaryTabRow(
selectedTabIndex = state,
- indicator = indicator
+ indicator = {
+ Box(
+ Modifier
+ .tabIndicatorOffset(state)
+ .fillMaxWidth()
+ .height(indicatorHeight)
+ .background(color = Color.Red)
+ .testTag("indicator")
+ )
+ }
) {
titles.forEachIndexed { index, title ->
Tab(
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TabRow.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TabRow.kt
index f37a97b..e9c9f99 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TabRow.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TabRow.kt
@@ -16,8 +16,11 @@
package androidx.compose.material3
+import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.AnimationSpec
+import androidx.compose.animation.core.AnimationVector1D
import androidx.compose.animation.core.FastOutSlowInEasing
+import androidx.compose.animation.core.VectorConverter
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.animation.core.tween
import androidx.compose.foundation.ScrollState
@@ -28,7 +31,8 @@
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.offset
-import androidx.compose.foundation.layout.requiredSize
+import androidx.compose.foundation.layout.requiredHeight
+import androidx.compose.foundation.layout.requiredWidth
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.rememberScrollState
@@ -38,7 +42,9 @@
import androidx.compose.material3.tokens.SecondaryNavigationTabTokens
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
+import androidx.compose.runtime.State
import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
@@ -47,8 +53,16 @@
import androidx.compose.ui.draw.clipToBounds
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.layout.Layout
+import androidx.compose.ui.layout.Measurable
+import androidx.compose.ui.layout.MeasureResult
+import androidx.compose.ui.layout.MeasureScope
import androidx.compose.ui.layout.Placeable
import androidx.compose.ui.layout.SubcomposeLayout
+import androidx.compose.ui.layout.layout
+import androidx.compose.ui.node.LayoutModifierNode
+import androidx.compose.ui.node.ModifierNodeElement
+import androidx.compose.ui.platform.InspectorInfo
import androidx.compose.ui.platform.debugInspectorInfo
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.Density
@@ -136,14 +150,14 @@
modifier: Modifier = Modifier,
containerColor: Color = TabRowDefaults.primaryContainerColor,
contentColor: Color = TabRowDefaults.primaryContentColor,
- indicator: @Composable (tabPositions: List<TabPosition>) -> Unit = @Composable { tabPositions ->
- if (selectedTabIndex < tabPositions.size) {
- val width by animateDpAsState(targetValue = tabPositions[selectedTabIndex].contentWidth)
- TabRowDefaults.PrimaryIndicator(
- Modifier.tabIndicatorOffset(tabPositions[selectedTabIndex]),
- width = width
- )
- }
+ indicator: @Composable TabIndicatorScope.() -> Unit = {
+ TabRowDefaults.PrimaryIndicator(
+ modifier = Modifier.tabIndicatorOffset(
+ selectedTabIndex,
+ matchContentSize = true
+ ),
+ width = Dp.Unspecified,
+ )
},
divider: @Composable () -> Unit = @Composable {
HorizontalDivider()
@@ -193,12 +207,10 @@
modifier: Modifier = Modifier,
containerColor: Color = TabRowDefaults.secondaryContainerColor,
contentColor: Color = TabRowDefaults.secondaryContentColor,
- indicator: @Composable (tabPositions: List<TabPosition>) -> Unit = @Composable { tabPositions ->
- if (selectedTabIndex < tabPositions.size) {
- TabRowDefaults.SecondaryIndicator(
- Modifier.tabIndicatorOffset(tabPositions[selectedTabIndex])
- )
- }
+ indicator: @Composable TabIndicatorScope.() -> Unit = @Composable {
+ TabRowDefaults.SecondaryIndicator(
+ Modifier.tabIndicatorOffset(selectedTabIndex, matchContentSize = false)
+ )
},
divider: @Composable () -> Unit = @Composable {
HorizontalDivider()
@@ -299,7 +311,46 @@
},
tabs: @Composable () -> Unit
) {
- TabRowImpl(modifier, containerColor, contentColor, indicator, divider, tabs)
+ TabRowWithSubcomposeImpl(modifier, containerColor, contentColor, indicator, divider, tabs)
+}
+
+/**
+ * Scope for the composable used to render a Tab indicator,
+ * this can be used for more complex indicators requiring layout information about the tabs
+ * like [TabRowDefaults.PrimaryIndicator] and [TabRowDefaults.SecondaryIndicator]
+ */
+interface TabIndicatorScope {
+
+ /**
+ * A layout modifier that provides tab positions, this can be used to animate and layout
+ * a TabIndicator depending on size, position, and content size of each Tab.
+ *
+ * @sample androidx.compose.material3.samples.FancyAnimatedIndicatorWithModifier
+ */
+ fun Modifier.tabIndicatorLayout(
+ measure: MeasureScope.(
+ Measurable,
+ Constraints,
+ List<TabPosition>,
+ ) -> MeasureResult
+ ): Modifier
+
+ /**
+ * A Modifier that follows the default offset and animation
+ *
+ * @param selectedTabIndex the index of the current selected tab
+ * @param matchContentSize this modifier can also animate the width of the indicator \
+ * to match the content size of the tab.
+ */
+ fun Modifier.tabIndicatorOffset(
+ selectedTabIndex: Int,
+ matchContentSize: Boolean = false
+ ): Modifier
+}
+
+internal interface TabPositionsHolder {
+
+ fun setTabPositions(positions: List<TabPosition>)
}
@Composable
@@ -307,6 +358,223 @@
modifier: Modifier,
containerColor: Color,
contentColor: Color,
+ indicator: @Composable TabIndicatorScope.() -> Unit,
+ divider: @Composable () -> Unit,
+ tabs: @Composable () -> Unit
+) {
+ Surface(
+ modifier = modifier.selectableGroup(),
+ color = containerColor,
+ contentColor = contentColor
+ ) {
+ val scope = remember {
+ object : TabIndicatorScope, TabPositionsHolder {
+
+ val tabPositions = mutableStateOf<(List<TabPosition>)>(listOf())
+
+ override fun Modifier.tabIndicatorLayout(
+ measure: MeasureScope.(
+ Measurable,
+ Constraints,
+ List<TabPosition>,
+ ) -> MeasureResult
+ ): Modifier =
+ this.layout { measurable: Measurable, constraints: Constraints ->
+ measure(
+ measurable,
+ constraints,
+ tabPositions.value,
+ )
+ }
+
+ override fun Modifier.tabIndicatorOffset(
+ selectedTabIndex: Int,
+ matchContentSize: Boolean
+ ): Modifier =
+ this.then(
+ TabIndicatorModifier(
+ tabPositions,
+ selectedTabIndex,
+ matchContentSize
+ )
+ )
+
+ override fun setTabPositions(positions: List<TabPosition>) {
+ tabPositions.value = positions
+ }
+ }
+ }
+
+ Layout(
+ modifier = Modifier.fillMaxWidth(),
+ contents = listOf(
+ tabs,
+ divider,
+ { scope.indicator() },
+ )
+ ) { (tabMeasurables, dividerMeasurables, indicatorMeasurables), constraints ->
+ val tabRowWidth = constraints.maxWidth
+ val tabCount = tabMeasurables.size
+ var tabWidth = 0
+ if (tabCount > 0) {
+ tabWidth = (tabRowWidth / tabCount)
+ }
+ val tabRowHeight = tabMeasurables.fastFold(initial = 0) { max, curr ->
+ maxOf(curr.maxIntrinsicHeight(tabWidth), max)
+ }
+
+ scope.setTabPositions(List(tabCount) { index ->
+ var contentWidth =
+ minOf(
+ tabMeasurables[index].maxIntrinsicWidth(tabRowHeight),
+ tabWidth
+ ).toDp()
+ contentWidth -= HorizontalTextPadding * 2
+ // Enforce minimum touch target of 24.dp
+ val indicatorWidth = maxOf(contentWidth, 24.dp)
+
+ TabPosition(tabWidth.toDp() * index, tabWidth.toDp(), indicatorWidth)
+ })
+
+ val tabPlaceables = tabMeasurables.fastMap {
+ it.measure(
+ constraints.copy(
+ minWidth = tabWidth,
+ maxWidth = tabWidth,
+ minHeight = tabRowHeight,
+ maxHeight = tabRowHeight,
+ )
+ )
+ }
+
+ val dividerPlaceables = dividerMeasurables.fastMap {
+ it.measure(constraints.copy(minHeight = 0))
+ }
+
+ val indicatorPlaceables = indicatorMeasurables.fastMap {
+ it.measure(
+ constraints.copy(
+ minWidth = tabWidth,
+ maxWidth = tabWidth,
+ maxHeight = tabRowHeight
+ )
+ )
+ }
+
+ layout(tabRowWidth, tabRowHeight) {
+ tabPlaceables.fastForEachIndexed { index, placeable ->
+ placeable.placeRelative(index * tabWidth, 0)
+ }
+
+ dividerPlaceables.fastForEach { placeable ->
+ placeable.placeRelative(0, tabRowHeight - placeable.height)
+ }
+
+ indicatorPlaceables.fastForEach {
+ it.placeRelative(0, tabRowHeight - it.height)
+ }
+ }
+ }
+ }
+}
+
+internal data class TabIndicatorModifier(
+ val tabPositionsState: State<List<TabPosition>>,
+ val selectedTabIndex: Int,
+ val followContentSize: Boolean,
+) : ModifierNodeElement<TabIndicatorOffsetNode>() {
+
+ override fun create(): TabIndicatorOffsetNode {
+ return TabIndicatorOffsetNode(
+ tabPositionsState = tabPositionsState,
+ selectedTabIndex = selectedTabIndex,
+ followContentSize = followContentSize,
+ )
+ }
+
+ override fun update(node: TabIndicatorOffsetNode) {
+ node.tabPositionsState = tabPositionsState
+ node.selectedTabIndex = selectedTabIndex
+ node.followContentSize = followContentSize
+ }
+
+ override fun InspectorInfo.inspectableProperties() {
+ // Show nothing in the inspector.
+ }
+}
+
+internal class TabIndicatorOffsetNode(
+ var tabPositionsState: State<List<TabPosition>>,
+ var selectedTabIndex: Int,
+ var followContentSize: Boolean
+) : Modifier.Node(), LayoutModifierNode {
+
+ private var offsetAnimatable: Animatable<Dp, AnimationVector1D>? = null
+ private var widthAnimatable: Animatable<Dp, AnimationVector1D>? = null
+ private var initialOffset: Dp? = null
+ private var initialWidth: Dp? = null
+
+ override fun MeasureScope.measure(
+ measurable: Measurable,
+ constraints: Constraints
+ ): MeasureResult {
+ if (tabPositionsState.value.isEmpty()) {
+ return layout(0, 0) { }
+ }
+
+ val currentTabWidth = tabPositionsState.value[selectedTabIndex].contentWidth
+ if (followContentSize) {
+ if (initialWidth != null) {
+ val widthAnim =
+ widthAnimatable ?: Animatable(initialWidth!!, Dp.VectorConverter).also {
+ widthAnimatable = it
+ }
+
+ if (currentTabWidth != widthAnim.targetValue) {
+ coroutineScope.launch { widthAnim.animateTo(currentTabWidth) }
+ }
+ } else {
+ initialWidth = currentTabWidth
+ }
+ }
+
+ val indicatorOffset = tabPositionsState.value[selectedTabIndex].left
+
+ if (initialOffset != null) {
+ val offsetAnim =
+ offsetAnimatable ?: Animatable(initialOffset!!, Dp.VectorConverter).also {
+ offsetAnimatable = it
+ }
+
+ if (indicatorOffset != offsetAnim.targetValue) {
+ coroutineScope.launch { offsetAnim.animateTo(indicatorOffset) }
+ }
+ } else {
+ initialOffset = indicatorOffset
+ }
+
+ val offset = offsetAnimatable?.value ?: indicatorOffset
+
+ val placeable = measurable.measure(
+ if (followContentSize) {
+ val width = widthAnimatable?.value ?: currentTabWidth
+ constraints.copy(minWidth = width.roundToPx(), maxWidth = width.roundToPx())
+ } else {
+ constraints
+ }
+ )
+
+ return layout(placeable.width, constraints.maxHeight) {
+ placeable.place(offset.roundToPx(), constraints.maxHeight - placeable.height)
+ }
+ }
+}
+
+@Composable
+private fun TabRowWithSubcomposeImpl(
+ modifier: Modifier,
+ containerColor: Color,
+ contentColor: Color,
indicator: @Composable (tabPositions: List<TabPosition>) -> Unit,
divider: @Composable () -> Unit,
tabs: @Composable () -> Unit
@@ -819,7 +1087,8 @@
) {
Spacer(
modifier
- .requiredSize(width, height)
+ .requiredHeight(height)
+ .requiredWidth(width)
.background(color = color, shape = shape)
)
}
diff --git a/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/platform/AndroidTextPaint.android.kt b/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/platform/AndroidTextPaint.android.kt
index 5ae8aea..708f622 100644
--- a/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/platform/AndroidTextPaint.android.kt
+++ b/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/platform/AndroidTextPaint.android.kt
@@ -18,6 +18,8 @@
import android.text.TextPaint
import androidx.annotation.VisibleForTesting
+import androidx.compose.runtime.State
+import androidx.compose.runtime.derivedStateOf
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.geometry.isSpecified
import androidx.compose.ui.graphics.BlendMode
@@ -25,6 +27,7 @@
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Paint
import androidx.compose.ui.graphics.PaintingStyle
+import androidx.compose.ui.graphics.Shader
import androidx.compose.ui.graphics.ShaderBrush
import androidx.compose.ui.graphics.Shadow
import androidx.compose.ui.graphics.SolidColor
@@ -36,6 +39,7 @@
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.text.platform.extensions.correctBlurRadius
import androidx.compose.ui.text.style.TextDecoration
+import androidx.compose.ui.text.style.modulate
import androidx.compose.ui.util.fastCoerceIn
import androidx.compose.ui.util.fastRoundToInt
@@ -52,6 +56,14 @@
@VisibleForTesting
internal var shadow: Shadow = Shadow.None
+ @VisibleForTesting
+ internal var brush: Brush? = null
+
+ internal var shaderState: State<Shader?>? = null
+
+ @VisibleForTesting
+ internal var brushSize: Size? = null
+
private var drawStyle: DrawStyle? = null
fun setTextDecoration(textDecoration: TextDecoration?) {
@@ -83,24 +95,41 @@
fun setColor(color: Color) {
if (color.isSpecified) {
composePaint.color = color
- composePaint.shader = null
+ clearShader()
}
}
fun setBrush(brush: Brush?, size: Size, alpha: Float = Float.NaN) {
- // if size is unspecified and brush is not null, nothing should be done.
- // it basically means brush is given but size is not yet calculated at this time.
- if ((brush is SolidColor && brush.value.isSpecified) ||
- (brush is ShaderBrush && size.isSpecified)) {
- // alpha is always applied even if Float.NaN is passed to applyTo function.
- // if it's actually Float.NaN, we simply send the current value
- brush.applyTo(
- size,
- composePaint,
- if (alpha.isNaN()) composePaint.alpha else alpha.fastCoerceIn(0f, 1f)
- )
- } else if (brush == null) {
- composePaint.shader = null
+ when (brush) {
+ // null brush should just clear the shader and leave `color` as the final decider
+ // while painting
+ null -> {
+ clearShader()
+ }
+ // SolidColor brush can be treated just like setting a color.
+ is SolidColor -> {
+ setColor(brush.value.modulate(alpha))
+ }
+ // This is the brush type that we mostly refer to when we talk about brush support.
+ // Below code is almost equivalent to;
+ // val this.shaderState = remember(brush, brushSize) {
+ // derivedStateOf {
+ // brush.createShader(size)
+ // }
+ // }
+ is ShaderBrush -> {
+ if (this.brush != brush || this.brushSize != size) {
+ if (size.isSpecified) {
+ this.brush = brush
+ this.brushSize = size
+ this.shaderState = derivedStateOf {
+ brush.createShader(size)
+ }
+ }
+ }
+ composePaint.shader = this.shaderState?.value
+ setAlpha(alpha)
+ }
}
}
@@ -130,6 +159,16 @@
// BlendMode is only available to DrawScope.drawText.
// not intended to be used by TextStyle/SpanStyle.
var blendMode: BlendMode by composePaint::blendMode
+
+ /**
+ * Clears all shader related cache parameters and native shader property.
+ */
+ private fun clearShader() {
+ this.shaderState = null
+ this.brush = null
+ this.brushSize = null
+ composePaint.shader = null
+ }
}
/**
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/CredentialManagerPreUTest.kt b/credentials/credentials/src/androidTest/java/androidx/credentials/CredentialManagerPreUTest.kt
index 8b89fd5..be677f8 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/CredentialManagerPreUTest.kt
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/CredentialManagerPreUTest.kt
@@ -18,7 +18,6 @@
import android.app.Activity
import android.os.Looper
-import androidx.annotation.RequiresApi
import androidx.credentials.exceptions.ClearCredentialException
import androidx.credentials.exceptions.ClearCredentialProviderConfigurationException
import androidx.credentials.exceptions.CreateCredentialException
@@ -44,8 +43,7 @@
@RunWith(AndroidJUnit4::class)
@SmallTest
-@RequiresApi(16)
-@SdkSuppress(minSdkVersion = 16, maxSdkVersion = android.os.Build.VERSION_CODES.TIRAMISU)
+@SdkSuppress(maxSdkVersion = android.os.Build.VERSION_CODES.TIRAMISU)
class CredentialManagerPreUTest {
private val context = InstrumentationRegistry.getInstrumentation().context
diff --git a/docs-public/build.gradle b/docs-public/build.gradle
index 20bc9fd..9ac736d 100644
--- a/docs-public/build.gradle
+++ b/docs-public/build.gradle
@@ -8,10 +8,10 @@
}
dependencies {
- docs("androidx.activity:activity:1.8.1")
- docs("androidx.activity:activity-compose:1.8.1")
- samples("androidx.activity:activity-compose-samples:1.8.1")
- docs("androidx.activity:activity-ktx:1.8.1")
+ docs("androidx.activity:activity:1.9.0-alpha01")
+ docs("androidx.activity:activity-compose:1.9.0-alpha01")
+ samples("androidx.activity:activity-compose-samples:1.9.0-alpha01")
+ docs("androidx.activity:activity-ktx:1.9.0-alpha01")
// ads-identifier is deprecated
docsWithoutApiSince("androidx.ads:ads-identifier:1.0.0-alpha05")
docsWithoutApiSince("androidx.ads:ads-identifier-common:1.0.0-alpha05")
@@ -39,9 +39,9 @@
docs("androidx.biometric:biometric:1.2.0-alpha05")
docs("androidx.biometric:biometric-ktx:1.2.0-alpha05")
samples("androidx.biometric:biometric-ktx-samples:1.2.0-alpha05")
- docs("androidx.bluetooth:bluetooth:1.0.0-alpha01")
- docs("androidx.bluetooth:bluetooth-testing:1.0.0-alpha01")
- docs("androidx.browser:browser:1.8.0-alpha01")
+ docs("androidx.bluetooth:bluetooth:1.0.0-alpha02")
+ docs("androidx.bluetooth:bluetooth-testing:1.0.0-alpha02")
+ docs("androidx.browser:browser:1.8.0-beta01")
docs("androidx.camera:camera-camera2:1.4.0-alpha02")
docs("androidx.camera:camera-core:1.4.0-alpha02")
docs("androidx.camera:camera-extensions:1.4.0-alpha02")
@@ -58,60 +58,60 @@
docs("androidx.car.app:app-projected:1.4.0-rc01")
docs("androidx.car.app:app-testing:1.4.0-rc01")
docs("androidx.cardview:cardview:1.0.0")
- kmpDocs("androidx.collection:collection:1.4.0-beta01")
- docs("androidx.collection:collection-ktx:1.4.0-beta01")
- kmpDocs("androidx.compose.animation:animation:1.6.0-beta01")
- kmpDocs("androidx.compose.animation:animation-core:1.6.0-beta01")
- kmpDocs("androidx.compose.animation:animation-graphics:1.6.0-beta01")
- samples("androidx.compose.animation:animation-samples:1.6.0-beta01")
- samples("androidx.compose.animation:animation-core-samples:1.6.0-beta01")
- samples("androidx.compose.animation:animation-graphics-samples:1.6.0-beta01")
- kmpDocs("androidx.compose.foundation:foundation:1.6.0-beta01")
- kmpDocs("androidx.compose.foundation:foundation-layout:1.6.0-beta01")
- samples("androidx.compose.foundation:foundation-layout-samples:1.6.0-beta01")
- samples("androidx.compose.foundation:foundation-samples:1.6.0-beta01")
- kmpDocs("androidx.compose.material3:material3:1.2.0-alpha11")
- docs("androidx.compose.material3:material3-adaptive:1.0.0-alpha01")
- samples("androidx.compose.material3:material3-adaptive-navigation-suite-samples:1.2.0-alpha11")
- samples("androidx.compose.material3:material3-adaptive-samples:1.2.0-alpha11")
- samples("androidx.compose.material3:material3-samples:1.2.0-alpha11")
- kmpDocs("androidx.compose.material3:material3-window-size-class:1.2.0-alpha11")
- samples("androidx.compose.material3:material3-window-size-class-samples:1.2.0-alpha10")
- kmpDocs("androidx.compose.material:material:1.6.0-beta01")
- kmpDocs("androidx.compose.material:material-icons-core:1.6.0-beta01")
- samples("androidx.compose.material:material-icons-core-samples:1.6.0-beta01")
- kmpDocs("androidx.compose.material:material-ripple:1.6.0-beta01")
- samples("androidx.compose.material:material-samples:1.6.0-beta01")
- kmpDocs("androidx.compose.runtime:runtime:1.6.0-beta01")
- docs("androidx.compose.runtime:runtime-livedata:1.6.0-beta01")
- samples("androidx.compose.runtime:runtime-livedata-samples:1.6.0-beta01")
- docs("androidx.compose.runtime:runtime-rxjava2:1.6.0-beta01")
- samples("androidx.compose.runtime:runtime-rxjava2-samples:1.6.0-beta01")
- docs("androidx.compose.runtime:runtime-rxjava3:1.6.0-beta01")
- samples("androidx.compose.runtime:runtime-rxjava3-samples:1.6.0-beta01")
- kmpDocs("androidx.compose.runtime:runtime-saveable:1.6.0-beta01")
- samples("androidx.compose.runtime:runtime-saveable-samples:1.6.0-beta01")
- samples("androidx.compose.runtime:runtime-samples:1.6.0-beta01")
- docs("androidx.compose.runtime:runtime-tracing:1.0.0-alpha05")
- kmpDocs("androidx.compose.ui:ui:1.6.0-beta01")
- kmpDocs("androidx.compose.ui:ui-geometry:1.6.0-beta01")
- kmpDocs("androidx.compose.ui:ui-graphics:1.6.0-beta01")
- samples("androidx.compose.ui:ui-graphics-samples:1.6.0-beta01")
- kmpDocs("androidx.compose.ui:ui-test:1.6.0-beta01")
- kmpDocs("androidx.compose.ui:ui-test-junit4:1.6.0-beta01")
- samples("androidx.compose.ui:ui-test-samples:1.6.0-beta01")
- kmpDocs("androidx.compose.ui:ui-text:1.6.0-beta01")
- docs("androidx.compose.ui:ui-text-google-fonts:1.6.0-beta01")
- samples("androidx.compose.ui:ui-text-samples:1.6.0-beta01")
- kmpDocs("androidx.compose.ui:ui-tooling:1.6.0-beta01")
- kmpDocs("androidx.compose.ui:ui-tooling-data:1.6.0-beta01")
- kmpDocs("androidx.compose.ui:ui-tooling-preview:1.6.0-beta01")
- kmpDocs("androidx.compose.ui:ui-unit:1.6.0-beta01")
- samples("androidx.compose.ui:ui-unit-samples:1.6.0-beta01")
- kmpDocs("androidx.compose.ui:ui-util:1.6.0-beta01")
- docs("androidx.compose.ui:ui-viewbinding:1.6.0-beta01")
- samples("androidx.compose.ui:ui-viewbinding-samples:1.6.0-beta01")
- samples("androidx.compose.ui:ui-samples:1.6.0-beta01")
+ kmpDocs("androidx.collection:collection:1.4.0-beta02")
+ docs("androidx.collection:collection-ktx:1.4.0-beta02")
+ kmpDocs("androidx.compose.animation:animation:1.6.0-beta02")
+ kmpDocs("androidx.compose.animation:animation-core:1.6.0-beta02")
+ kmpDocs("androidx.compose.animation:animation-graphics:1.6.0-beta02")
+ samples("androidx.compose.animation:animation-samples:1.6.0-beta02")
+ samples("androidx.compose.animation:animation-core-samples:1.6.0-beta02")
+ samples("androidx.compose.animation:animation-graphics-samples:1.6.0-beta02")
+ kmpDocs("androidx.compose.foundation:foundation:1.6.0-beta02")
+ kmpDocs("androidx.compose.foundation:foundation-layout:1.6.0-beta02")
+ samples("androidx.compose.foundation:foundation-layout-samples:1.6.0-beta02")
+ samples("androidx.compose.foundation:foundation-samples:1.6.0-beta02")
+ kmpDocs("androidx.compose.material3:material3:1.2.0-alpha12")
+ docs("androidx.compose.material3:material3-adaptive:1.0.0-alpha02")
+ samples("androidx.compose.material3:material3-adaptive-navigation-suite-samples:1.2.0-alpha12")
+ samples("androidx.compose.material3:material3-adaptive-samples:1.2.0-alpha12")
+ samples("androidx.compose.material3:material3-samples:1.2.0-alpha12")
+ kmpDocs("androidx.compose.material3:material3-window-size-class:1.2.0-alpha12")
+ samples("androidx.compose.material3:material3-window-size-class-samples:1.2.0-alpha12")
+ kmpDocs("androidx.compose.material:material:1.6.0-beta02")
+ kmpDocs("androidx.compose.material:material-icons-core:1.6.0-beta02")
+ samples("androidx.compose.material:material-icons-core-samples:1.6.0-beta02")
+ kmpDocs("androidx.compose.material:material-ripple:1.6.0-beta02")
+ samples("androidx.compose.material:material-samples:1.6.0-beta02")
+ kmpDocs("androidx.compose.runtime:runtime:1.7.0-alpha01")
+ docs("androidx.compose.runtime:runtime-livedata:1.7.0-alpha01")
+ samples("androidx.compose.runtime:runtime-livedata-samples:1.7.0-alpha01")
+ docs("androidx.compose.runtime:runtime-rxjava2:1.7.0-alpha01")
+ samples("androidx.compose.runtime:runtime-rxjava2-samples:1.7.0-alpha01")
+ docs("androidx.compose.runtime:runtime-rxjava3:1.7.0-alpha01")
+ samples("androidx.compose.runtime:runtime-rxjava3-samples:1.7.0-alpha01")
+ kmpDocs("androidx.compose.runtime:runtime-saveable:1.7.0-alpha01")
+ samples("androidx.compose.runtime:runtime-saveable-samples:1.7.0-alpha01")
+ samples("androidx.compose.runtime:runtime-samples:1.7.0-alpha01")
+ docs("androidx.compose.runtime:runtime-tracing:1.0.0-beta01")
+ kmpDocs("androidx.compose.ui:ui:1.6.0-beta02")
+ kmpDocs("androidx.compose.ui:ui-geometry:1.6.0-beta02")
+ kmpDocs("androidx.compose.ui:ui-graphics:1.6.0-beta02")
+ samples("androidx.compose.ui:ui-graphics-samples:1.6.0-beta02")
+ kmpDocs("androidx.compose.ui:ui-test:1.6.0-beta02")
+ kmpDocs("androidx.compose.ui:ui-test-junit4:1.6.0-beta02")
+ samples("androidx.compose.ui:ui-test-samples:1.6.0-beta02")
+ kmpDocs("androidx.compose.ui:ui-text:1.6.0-beta02")
+ docs("androidx.compose.ui:ui-text-google-fonts:1.6.0-beta02")
+ samples("androidx.compose.ui:ui-text-samples:1.6.0-beta02")
+ kmpDocs("androidx.compose.ui:ui-tooling:1.6.0-beta02")
+ kmpDocs("androidx.compose.ui:ui-tooling-data:1.6.0-beta02")
+ kmpDocs("androidx.compose.ui:ui-tooling-preview:1.6.0-beta02")
+ kmpDocs("androidx.compose.ui:ui-unit:1.6.0-beta02")
+ samples("androidx.compose.ui:ui-unit-samples:1.6.0-beta02")
+ kmpDocs("androidx.compose.ui:ui-util:1.6.0-beta02")
+ docs("androidx.compose.ui:ui-viewbinding:1.6.0-beta02")
+ samples("androidx.compose.ui:ui-viewbinding-samples:1.6.0-beta02")
+ samples("androidx.compose.ui:ui-samples:1.6.0-beta02")
docs("androidx.concurrent:concurrent-futures:1.2.0-alpha02")
docs("androidx.concurrent:concurrent-futures-ktx:1.2.0-alpha02")
docs("androidx.constraintlayout:constraintlayout:2.2.0-alpha13")
@@ -119,13 +119,13 @@
docs("androidx.constraintlayout:constraintlayout-core:1.1.0-alpha13")
docs("androidx.contentpager:contentpager:1.0.0")
docs("androidx.coordinatorlayout:coordinatorlayout:1.3.0-alpha02")
- docs("androidx.core:core:1.13.0-alpha01")
+ docs("androidx.core:core:1.13.0-alpha02")
// TODO(b/294531403): Turn on apiSince for core-animation when it releases as alpha
docsWithoutApiSince("androidx.core:core-animation:1.0.0-rc01")
docsWithoutApiSince("androidx.core:core-animation-testing:1.0.0-rc01")
docs("androidx.core:core-google-shortcuts:1.2.0-alpha01")
docs("androidx.core:core-i18n:1.0.0-alpha01")
- docs("androidx.core:core-ktx:1.13.0-alpha01")
+ docs("androidx.core:core-ktx:1.13.0-alpha02")
docs("androidx.core:core-location-altitude:1.0.0-alpha01")
docs("androidx.core:core-performance:1.0.0-beta02")
docs("androidx.core:core-performance-play-services:1.0.0-beta02")
@@ -135,7 +135,7 @@
docs("androidx.core:core-role:1.2.0-alpha01")
docs("androidx.core:core-splashscreen:1.1.0-alpha02")
docs("androidx.core:core-telecom:1.0.0-alpha02")
- docs("androidx.core:core-testing:1.13.0-alpha01")
+ docs("androidx.core:core-testing:1.13.0-alpha02")
docs("androidx.core.uwb:uwb:1.0.0-alpha07")
docs("androidx.core.uwb:uwb-rxjava3:1.0.0-alpha07")
docs("androidx.credentials:credentials:1.2.0")
@@ -146,15 +146,15 @@
docs("androidx.customview:customview:1.2.0-alpha02")
// TODO(b/294531403): Turn on apiSince for customview-poolingcontainer when it releases as alpha
docsWithoutApiSince("androidx.customview:customview-poolingcontainer:1.0.0-rc01")
- kmpDocs("androidx.datastore:datastore:1.1.0-alpha05")
- kmpDocs("androidx.datastore:datastore-core:1.1.0-alpha05")
- kmpDocs("androidx.datastore:datastore-core-okio:1.1.0-alpha05")
- kmpDocs("androidx.datastore:datastore-preferences:1.1.0-alpha05")
- kmpDocs("androidx.datastore:datastore-preferences-core:1.1.0-alpha05")
- docs("androidx.datastore:datastore-preferences-rxjava2:1.1.0-alpha05")
- docs("androidx.datastore:datastore-preferences-rxjava3:1.1.0-alpha05")
- docs("androidx.datastore:datastore-rxjava2:1.1.0-alpha05")
- docs("androidx.datastore:datastore-rxjava3:1.1.0-alpha05")
+ kmpDocs("androidx.datastore:datastore:1.1.0-alpha07")
+ kmpDocs("androidx.datastore:datastore-core:1.1.0-alpha07")
+ kmpDocs("androidx.datastore:datastore-core-okio:1.1.0-alpha07")
+ kmpDocs("androidx.datastore:datastore-preferences:1.1.0-alpha07")
+ kmpDocs("androidx.datastore:datastore-preferences-core:1.1.0-alpha07")
+ docs("androidx.datastore:datastore-preferences-rxjava2:1.1.0-alpha07")
+ docs("androidx.datastore:datastore-preferences-rxjava3:1.1.0-alpha07")
+ docs("androidx.datastore:datastore-rxjava2:1.1.0-alpha07")
+ docs("androidx.datastore:datastore-rxjava3:1.1.0-alpha07")
docs("androidx.documentfile:documentfile:1.1.0-alpha01")
docs("androidx.draganddrop:draganddrop:1.0.0")
docs("androidx.drawerlayout:drawerlayout:1.2.0")
@@ -171,9 +171,9 @@
docs("androidx.enterprise:enterprise-feedback:1.1.0")
docs("androidx.enterprise:enterprise-feedback-testing:1.1.0")
docs("androidx.exifinterface:exifinterface:1.3.6")
- docs("androidx.fragment:fragment:1.7.0-alpha06")
- docs("androidx.fragment:fragment-ktx:1.7.0-alpha06")
- docs("androidx.fragment:fragment-testing:1.7.0-alpha06")
+ docs("androidx.fragment:fragment:1.7.0-alpha07")
+ docs("androidx.fragment:fragment-ktx:1.7.0-alpha07")
+ docs("androidx.fragment:fragment-testing:1.7.0-alpha07")
docs("androidx.glance:glance:1.0.0")
docs("androidx.glance:glance-appwidget:1.0.0")
samples("androidx.glance:glance-appwidget-samples:1.0.0")
@@ -186,6 +186,7 @@
docs("androidx.glance:glance-wear-tiles-preview:1.0.0-alpha06")
docs("androidx.graphics:graphics-core:1.0.0-alpha05")
samples("androidx.graphics:graphics-core-samples:1.0.0-alpha05")
+ docs("androidx.graphics:graphics-path:1.0.0-beta01")
docs("androidx.gridlayout:gridlayout:1.1.0-beta01")
docs("androidx.health.connect:connect-client:1.1.0-alpha06")
samples("androidx.health.connect:connect-client-samples:1.1.0-alpha06")
@@ -229,11 +230,11 @@
docs("androidx.loader:loader:1.1.0")
// localbroadcastmanager is deprecated
docsWithoutApiSince("androidx.localbroadcastmanager:localbroadcastmanager:1.1.0")
- docs("androidx.media2:media2-common:1.3.0-alpha01")
- docs("androidx.media2:media2-player:1.3.0-alpha01")
- docs("androidx.media2:media2-session:1.3.0-alpha01")
- docs("androidx.media2:media2-widget:1.3.0-alpha01")
- docs("androidx.media:media:1.7.0-rc01")
+ docs("androidx.media2:media2-common:1.3.0-beta01")
+ docs("androidx.media2:media2-player:1.3.0-beta01")
+ docs("androidx.media2:media2-session:1.3.0-beta01")
+ docs("androidx.media2:media2-widget:1.3.0-beta01")
+ docs("androidx.media:media:1.7.0")
// androidx.media3 is not hosted in androidx
docsWithoutApiSince("androidx.media3:media3-cast:1.2.0")
docsWithoutApiSince("androidx.media3:media3-common:1.2.0")
@@ -312,18 +313,18 @@
docs("androidx.recyclerview:recyclerview-selection:2.0.0-alpha01")
docs("androidx.remotecallback:remotecallback:1.0.0-alpha02")
docs("androidx.resourceinspection:resourceinspection-annotation:1.0.1")
- docs("androidx.room:room-common:2.6.0")
- docs("androidx.room:room-guava:2.6.0")
- docs("androidx.room:room-ktx:2.6.0")
- docs("androidx.room:room-migration:2.6.0")
- docs("androidx.room:room-paging:2.6.0")
- docs("androidx.room:room-paging-guava:2.6.0")
- docs("androidx.room:room-paging-rxjava2:2.6.0")
- docs("androidx.room:room-paging-rxjava3:2.6.0")
- docs("androidx.room:room-runtime:2.6.0")
- docs("androidx.room:room-rxjava2:2.6.0")
- docs("androidx.room:room-rxjava3:2.6.0")
- docs("androidx.room:room-testing:2.6.0")
+ docs("androidx.room:room-common:2.6.1")
+ docs("androidx.room:room-guava:2.6.1")
+ docs("androidx.room:room-ktx:2.6.1")
+ docs("androidx.room:room-migration:2.6.1")
+ docs("androidx.room:room-paging:2.6.1")
+ docs("androidx.room:room-paging-guava:2.6.1")
+ docs("androidx.room:room-paging-rxjava2:2.6.1")
+ docs("androidx.room:room-paging-rxjava3:2.6.1")
+ docs("androidx.room:room-runtime:2.6.1")
+ docs("androidx.room:room-rxjava2:2.6.1")
+ docs("androidx.room:room-rxjava3:2.6.1")
+ docs("androidx.room:room-testing:2.6.1")
docs("androidx.savedstate:savedstate:1.2.1")
docs("androidx.savedstate:savedstate-ktx:1.2.1")
docs("androidx.security:security-app-authenticator:1.0.0-alpha02")
@@ -371,8 +372,8 @@
// TODO(243405142) clean-up
docsWithoutApiSince("androidx.tracing:tracing-perfetto-common:1.0.0-alpha16")
docs("androidx.tracing:tracing-perfetto-handshake:1.0.0")
- docs("androidx.transition:transition:1.5.0-alpha04")
- docs("androidx.transition:transition-ktx:1.5.0-alpha04")
+ docs("androidx.transition:transition:1.5.0-alpha05")
+ docs("androidx.transition:transition-ktx:1.5.0-alpha05")
docs("androidx.tv:tv-foundation:1.0.0-alpha10")
docs("androidx.tv:tv-material:1.0.0-alpha10")
samples("androidx.tv:tv-samples:1.0.0-alpha10")
@@ -380,7 +381,7 @@
docs("androidx.vectordrawable:vectordrawable:1.2.0-beta01")
docs("androidx.vectordrawable:vectordrawable-animated:1.2.0-alpha01")
docs("androidx.vectordrawable:vectordrawable-seekable:1.0.0-beta01")
- docs("androidx.versionedparcelable:versionedparcelable:1.2.0-alpha01")
+ docs("androidx.versionedparcelable:versionedparcelable:1.2.0-beta01")
docs("androidx.viewpager2:viewpager2:1.1.0-beta02")
docs("androidx.viewpager:viewpager:1.1.0-alpha01")
docs("androidx.wear.compose:compose-foundation:1.3.0-beta01")
@@ -393,34 +394,34 @@
docs("androidx.wear.compose:compose-navigation:1.3.0-beta01")
samples("androidx.wear.compose:compose-navigation-samples:1.3.0-beta01")
docs("androidx.wear.compose:compose-ui-tooling:1.3.0-beta01")
- docs("androidx.wear.protolayout:protolayout:1.1.0-alpha02")
- docs("androidx.wear.protolayout:protolayout-expression:1.1.0-alpha02")
- docs("androidx.wear.protolayout:protolayout-expression-pipeline:1.1.0-alpha02")
- docs("androidx.wear.protolayout:protolayout-material:1.1.0-alpha02")
- docs("androidx.wear.protolayout:protolayout-material-core:1.1.0-alpha02")
- docs("androidx.wear.protolayout:protolayout-renderer:1.1.0-alpha02")
- docs("androidx.wear.tiles:tiles:1.3.0-alpha02")
- docs("androidx.wear.tiles:tiles-material:1.3.0-alpha02")
- docs("androidx.wear.tiles:tiles-renderer:1.3.0-alpha02")
- docs("androidx.wear.tiles:tiles-testing:1.3.0-alpha02")
- docs("androidx.wear.tiles:tiles-tooling:1.3.0-alpha02")
- docs("androidx.wear.tiles:tiles-tooling-preview:1.3.0-alpha02")
- docs("androidx.wear.watchface:watchface:1.2.0-rc01")
- docs("androidx.wear.watchface:watchface-client:1.2.0-rc01")
- docs("androidx.wear.watchface:watchface-client-guava:1.2.0-rc01")
- docs("androidx.wear.watchface:watchface-complications:1.2.0-rc01")
- docs("androidx.wear.watchface:watchface-complications-data:1.2.0-rc01")
- docs("androidx.wear.watchface:watchface-complications-data-source:1.2.0-rc01")
- docs("androidx.wear.watchface:watchface-complications-data-source-ktx:1.2.0-rc01")
- samples("androidx.wear.watchface:watchface-complications-permission-dialogs-sample:1.2.0-rc01")
- docs("androidx.wear.watchface:watchface-complications-rendering:1.2.0-rc01")
- docs("androidx.wear.watchface:watchface-data:1.2.0-rc01")
- docs("androidx.wear.watchface:watchface-editor:1.2.0-rc01")
- docs("androidx.wear.watchface:watchface-editor-guava:1.2.0-rc01")
- samples("androidx.wear.watchface:watchface-editor-samples:1.2.0-rc01")
- docs("androidx.wear.watchface:watchface-guava:1.2.0-rc01")
- samples("androidx.wear.watchface:watchface-samples:1.2.0-rc01")
- docs("androidx.wear.watchface:watchface-style:1.2.0-rc01")
+ docs("androidx.wear.protolayout:protolayout:1.1.0-alpha03")
+ docs("androidx.wear.protolayout:protolayout-expression:1.1.0-alpha03")
+ docs("androidx.wear.protolayout:protolayout-expression-pipeline:1.1.0-alpha03")
+ docs("androidx.wear.protolayout:protolayout-material:1.1.0-alpha03")
+ docs("androidx.wear.protolayout:protolayout-material-core:1.1.0-alpha03")
+ docs("androidx.wear.protolayout:protolayout-renderer:1.1.0-alpha03")
+ docs("androidx.wear.tiles:tiles:1.3.0-alpha03")
+ docs("androidx.wear.tiles:tiles-material:1.3.0-alpha03")
+ docs("androidx.wear.tiles:tiles-renderer:1.3.0-alpha03")
+ docs("androidx.wear.tiles:tiles-testing:1.3.0-alpha03")
+ docs("androidx.wear.tiles:tiles-tooling:1.3.0-alpha03")
+ docs("androidx.wear.tiles:tiles-tooling-preview:1.3.0-alpha03")
+ docs("androidx.wear.watchface:watchface:1.2.0")
+ docs("androidx.wear.watchface:watchface-client:1.2.0")
+ docs("androidx.wear.watchface:watchface-client-guava:1.2.0")
+ docs("androidx.wear.watchface:watchface-complications:1.2.0")
+ docs("androidx.wear.watchface:watchface-complications-data:1.2.0")
+ docs("androidx.wear.watchface:watchface-complications-data-source:1.2.0")
+ docs("androidx.wear.watchface:watchface-complications-data-source-ktx:1.2.0")
+ samples("androidx.wear.watchface:watchface-complications-permission-dialogs-sample:1.2.0")
+ docs("androidx.wear.watchface:watchface-complications-rendering:1.2.0")
+ docs("androidx.wear.watchface:watchface-data:1.2.0")
+ docs("androidx.wear.watchface:watchface-editor:1.2.0")
+ docs("androidx.wear.watchface:watchface-editor-guava:1.2.0")
+ samples("androidx.wear.watchface:watchface-editor-samples:1.2.0")
+ docs("androidx.wear.watchface:watchface-guava:1.2.0")
+ samples("androidx.wear.watchface:watchface-samples:1.2.0")
+ docs("androidx.wear.watchface:watchface-style:1.2.0")
// TODO(b/294531403): Turn on apiSince for wear when it releases as alpha
docsWithoutApiSince("androidx.wear:wear:1.4.0-alpha01")
stubs(fileTree(dir: "../wear/wear_stubs/", include: ["com.google.android.wearable-stubs.jar"]))
@@ -430,8 +431,8 @@
docs("androidx.wear:wear-ongoing:1.1.0-alpha01")
docs("androidx.wear:wear-phone-interactions:1.1.0-alpha03")
docs("androidx.wear:wear-remote-interactions:1.1.0-alpha01")
- docs("androidx.wear:wear-tooling-preview:1.0.0-rc01")
- docs("androidx.webkit:webkit:1.9.0-rc01")
+ docs("androidx.wear:wear-tooling-preview:1.0.0")
+ docs("androidx.webkit:webkit:1.10.0-alpha01")
docs("androidx.window.extensions.core:core:1.0.0")
docs("androidx.window:window:1.3.0-alpha01")
stubs(fileTree(dir: "../window/stubs/", include: ["window-sidecar-release-0.1.0-alpha01.aar"]))
@@ -442,13 +443,13 @@
docs("androidx.window:window-rxjava3:1.3.0-alpha01")
samples("androidx.window:window-samples:1.3.0-alpha01")
docs("androidx.window:window-testing:1.3.0-alpha01")
- docs("androidx.work:work-gcm:2.9.0-rc01")
- docs("androidx.work:work-multiprocess:2.9.0-rc01")
- docs("androidx.work:work-runtime:2.9.0-rc01")
- docs("androidx.work:work-runtime-ktx:2.9.0-rc01")
- docs("androidx.work:work-rxjava2:2.9.0-rc01")
- docs("androidx.work:work-rxjava3:2.9.0-rc01")
- docs("androidx.work:work-testing:2.9.0-rc01")
+ docs("androidx.work:work-gcm:2.9.0")
+ docs("androidx.work:work-multiprocess:2.9.0")
+ docs("androidx.work:work-runtime:2.9.0")
+ docs("androidx.work:work-runtime-ktx:2.9.0")
+ docs("androidx.work:work-rxjava2:2.9.0")
+ docs("androidx.work:work-rxjava3:2.9.0")
+ docs("androidx.work:work-testing:2.9.0")
// Force upgrade to jsoup 1.16.2 (b/309773103)
stubs("org.jsoup:jsoup:1.16.2")
diff --git a/emoji/emoji/src/androidTest/java/androidx/emoji/text/ConfigTest.java b/emoji/emoji/src/androidTest/java/androidx/emoji/text/ConfigTest.java
index 8c9c7c3..59cd55e 100644
--- a/emoji/emoji/src/androidTest/java/androidx/emoji/text/ConfigTest.java
+++ b/emoji/emoji/src/androidTest/java/androidx/emoji/text/ConfigTest.java
@@ -40,7 +40,6 @@
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.MediumTest;
-import androidx.test.filters.SdkSuppress;
import androidx.test.platform.app.InstrumentationRegistry;
import org.junit.Before;
@@ -74,7 +73,6 @@
}
@Test
- @SdkSuppress(minSdkVersion = 19)
public void testBuild_withDefaultValues() {
final EmojiCompat.Config config = new ValidTestConfig().setReplaceAll(true);
@@ -101,7 +99,6 @@
}
@Test
- @SdkSuppress(minSdkVersion = 19) //Fail callback never called for pre 19
public void testInitCallback_callsFailCallback() {
final EmojiCompat.InitCallback initCallback1 = mock(EmojiCompat.InitCallback.class);
final EmojiCompat.InitCallback initCallback2 = mock(EmojiCompat.InitCallback.class);
@@ -166,7 +163,6 @@
}
@Test
- @SdkSuppress(minSdkVersion = 19)
public void testGlyphCheckerInstance_EmojiSpan_isNotAdded_whenHasGlyph_returnsTrue() {
final EmojiCompat.GlyphChecker glyphChecker = mock(EmojiCompat.GlyphChecker.class);
when(glyphChecker.hasGlyph(any(CharSequence.class), anyInt(), anyInt(), anyInt()))
@@ -186,7 +182,6 @@
}
@Test
- @SdkSuppress(minSdkVersion = 19)
public void testGlyphCheckerInstance_EmojiSpan_isAdded_whenHasGlyph_returnsFalse() {
final EmojiCompat.GlyphChecker glyphChecker = mock(EmojiCompat.GlyphChecker.class);
when(glyphChecker.hasGlyph(any(CharSequence.class), anyInt(), anyInt(), anyInt()))
diff --git a/emoji/emoji/src/androidTest/java/androidx/emoji/text/EmojiCompatTest.java b/emoji/emoji/src/androidTest/java/androidx/emoji/text/EmojiCompatTest.java
index f659cee..f78dc84 100644
--- a/emoji/emoji/src/androidTest/java/androidx/emoji/text/EmojiCompatTest.java
+++ b/emoji/emoji/src/androidTest/java/androidx/emoji/text/EmojiCompatTest.java
@@ -39,7 +39,6 @@
import static androidx.emoji.util.EmojiMatcher.hasEmoji;
import static androidx.emoji.util.EmojiMatcher.hasEmojiAt;
import static androidx.emoji.util.EmojiMatcher.hasEmojiCount;
-import static androidx.emoji.util.KeyboardUtil.del;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.instanceOf;
@@ -58,26 +57,21 @@
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.annotation.SuppressLint;
import android.os.Bundle;
import android.text.Editable;
-import android.text.Selection;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.SpannableStringBuilder;
import android.text.SpannedString;
-import android.view.KeyEvent;
import android.view.inputmethod.EditorInfo;
-import android.view.inputmethod.InputConnection;
import androidx.emoji.util.Emoji.EmojiMapping;
import androidx.emoji.util.TestString;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.MediumTest;
-import androidx.test.filters.SdkSuppress;
import androidx.test.platform.app.InstrumentationRegistry;
import org.junit.Before;
@@ -166,58 +160,36 @@
}
@Test
- @SdkSuppress(maxSdkVersion = 18)
- public void testProcess_returnsSameCharSequence_pre19() {
- assertNull(EmojiCompat.get().process(null));
-
- CharSequence testString = "abc";
- assertSame(testString, EmojiCompat.get().process(testString));
-
- testString = new SpannableString("abc");
- assertSame(testString, EmojiCompat.get().process(testString));
-
- testString = new TestString(new int[]{CHAR_DEFAULT_EMOJI_STYLE}).toString();
- assertSame(testString, EmojiCompat.get().process(testString));
- }
-
- @Test
- @SdkSuppress(minSdkVersion = 19)
public void testProcess_addsSingleCodePointEmoji() {
assertCodePointMatch(EMOJI_SINGLE_CODEPOINT);
}
@Test
- @SdkSuppress(minSdkVersion = 19)
public void testProcess_addsFlagEmoji() {
assertCodePointMatch(EMOJI_FLAG);
}
@Test
- @SdkSuppress(minSdkVersion = 19)
public void testProcess_addsUnknownFlagEmoji() {
assertCodePointMatch(EMOJI_UNKNOWN_FLAG);
}
@Test
- @SdkSuppress(minSdkVersion = 19)
public void testProcess_addsRegionalIndicatorSymbol() {
assertCodePointMatch(EMOJI_REGIONAL_SYMBOL);
}
@Test
- @SdkSuppress(minSdkVersion = 19)
public void testProcess_addsKeyCapEmoji() {
assertCodePointMatch(EMOJI_DIGIT_KEYCAP);
}
@Test
- @SdkSuppress(minSdkVersion = 19)
public void testProcess_doesNotAddEmojiForNumbers() {
assertCodePointDoesNotMatch(new int[] {CHAR_DIGIT});
}
@Test
- @SdkSuppress(minSdkVersion = 19)
public void testProcess_doesNotAddEmojiForNumbers_1() {
final TestString string = new TestString(EMOJI_SINGLE_CODEPOINT).append('1', 'f');
CharSequence charSequence = EmojiCompat.get().process(string.toString());
@@ -225,19 +197,16 @@
}
@Test
- @SdkSuppress(minSdkVersion = 19)
public void testProcess_addsVariantSelectorEmoji() {
assertCodePointMatch(EMOJI_DIGIT_ES);
}
@Test
- @SdkSuppress(minSdkVersion = 19)
public void testProcess_doesNotAddVariantSelectorTextStyle() {
assertCodePointDoesNotMatch(new int[]{CHAR_DIGIT, CHAR_VS_TEXT});
}
@Test
- @SdkSuppress(minSdkVersion = 19)
public void testProcess_addsVariantSelectorAndKeyCapEmoji() {
assertCodePointMatch(EMOJI_DIGIT_ES_KEYCAP);
}
@@ -248,26 +217,22 @@
}
@Test
- @SdkSuppress(minSdkVersion = 19)
public void testProcess_addsAsteriskKeyCapEmoji() {
assertCodePointMatch(EMOJI_ASTERISK_KEYCAP);
}
@Test
- @SdkSuppress(minSdkVersion = 19)
public void testProcess_addsSkinModifierEmoji() {
assertCodePointMatch(EMOJI_SKIN_MODIFIER);
assertCodePointMatch(EMOJI_SKIN_MODIFIER_TYPE_ONE);
}
@Test
- @SdkSuppress(minSdkVersion = 19)
public void testProcess_addsSkinModifierEmoji_withVariantSelector() {
assertCodePointMatch(EMOJI_SKIN_MODIFIER_WITH_VS);
}
@Test
- @SdkSuppress(minSdkVersion = 19)
public void testProcess_addsSkinModifierEmoji_270c_withVariantSelector() {
// 0x270c is a Standardized Variant Base, Emoji Modifier Base and also Emoji
// therefore it is different than i.e. 0x1f3c3. The code actually failed for this test
@@ -276,14 +241,12 @@
}
@Test
- @SdkSuppress(minSdkVersion = 19)
public void testProcess_defaultStyleDoesNotAddSpan() {
assertCodePointDoesNotMatch(new int[]{CHAR_DEFAULT_TEXT_STYLE});
assertCodePointMatch(DEFAULT_TEXT_STYLE);
}
@Test
- @SdkSuppress(minSdkVersion = 19)
public void testProcess_defaultEmojiStyle_withTextStyleVs() {
assertCodePointMatch(EMOJI_SINGLE_CODEPOINT.id(),
new int[]{CHAR_DEFAULT_EMOJI_STYLE, CHAR_VS_EMOJI});
@@ -291,14 +254,12 @@
}
@Test
- @SdkSuppress(minSdkVersion = 19)
public void testProcess_genderEmoji() {
assertCodePointMatch(EMOJI_GENDER);
assertCodePointMatch(EMOJI_GENDER_WITHOUT_VS);
}
@Test
- @SdkSuppress(minSdkVersion = 19)
public void testProcess_standardizedVariantEmojiExceptions() {
final int[][] exceptions = new int[][]{
{0x2600, 0xF034D},
@@ -322,13 +283,11 @@
}
@Test
- @SdkSuppress(minSdkVersion = 19)
public void testProcess_addsZwjEmoji() {
assertCodePointMatch(EMOJI_WITH_ZWJ);
}
@Test
- @SdkSuppress(minSdkVersion = 19)
public void testProcess_doesNotAddEmojiForNumbersAfterZwjEmo() {
TestString string = new TestString(EMOJI_WITH_ZWJ).append(0x20, 0x2B, 0x31)
.withSuffix().withPrefix();
@@ -343,7 +302,6 @@
}
@Test
- @SdkSuppress(minSdkVersion = 19)
public void testProcess_addsEmojiThatFollowsDigit() {
TestString string = new TestString(EMOJI_SINGLE_CODEPOINT).prepend('N', '5');
CharSequence charSequence = EmojiCompat.get().process(string.toString());
@@ -359,7 +317,6 @@
}
@Test
- @SdkSuppress(minSdkVersion = 19)
public void testProcess_withAppend() {
final Editable editable = new SpannableStringBuilder(new TestString('a').withPrefix()
.withSuffix().toString());
@@ -383,7 +340,6 @@
}
@Test
- @SdkSuppress(minSdkVersion = 19)
public void testProcess_reprocess() {
final String string = new TestString(EMOJI_SINGLE_CODEPOINT)
.append(EMOJI_SINGLE_CODEPOINT)
@@ -428,7 +384,6 @@
}
@Test
- @SdkSuppress(minSdkVersion = 19)
public void testProcess_withMaxEmojiSetToOne() {
final String original = new TestString(EMOJI_SINGLE_CODEPOINT).toString();
@@ -440,7 +395,6 @@
}
@Test
- @SdkSuppress(minSdkVersion = 19)
public void testProcess_withMaxEmojiSetToLessThenExistingSpanCount() {
final String original = new TestString(EMOJI_SINGLE_CODEPOINT)
.append(EMOJI_SINGLE_CODEPOINT)
@@ -462,7 +416,6 @@
}
@Test
- @SdkSuppress(minSdkVersion = 19)
public void testProcess_withMaxEmojiSet_withExistingEmojis() {
// test string with two emoji characters
final String original = new TestString(EMOJI_SINGLE_CODEPOINT)
@@ -500,7 +453,6 @@
}
@Test
- @SdkSuppress(minSdkVersion = 19)
public void testProcess_withReplaceNonExistent_callsGlyphChecker() {
final EmojiCompat.GlyphChecker glyphChecker = mock(EmojiCompat.GlyphChecker.class);
final EmojiCompat.Config config = TestConfigBuilder.freshConfig()
@@ -525,7 +477,6 @@
}
@Test
- @SdkSuppress(minSdkVersion = 19)
public void testProcess_withReplaceDefault_doesNotCallGlyphChecker() {
final EmojiCompat.GlyphChecker glyphChecker = mock(EmojiCompat.GlyphChecker.class);
final EmojiCompat.Config config = TestConfigBuilder.freshConfig()
@@ -550,7 +501,6 @@
}
@Test
- @SdkSuppress(minSdkVersion = 19)
public void testProcess_withSpanned_replaceNonExistent() {
final EmojiCompat.GlyphChecker glyphChecker = mock(EmojiCompat.GlyphChecker.class);
final EmojiCompat.Config config = TestConfigBuilder.freshConfig()
@@ -597,21 +547,6 @@
}
@Test
- @SdkSuppress(maxSdkVersion = 18)
- public void testHasEmojiGlyph_pre19() {
- String sequence = new TestString(new int[]{CHAR_DEFAULT_EMOJI_STYLE}).toString();
- assertFalse(EmojiCompat.get().hasEmojiGlyph(sequence));
- }
-
- @Test
- @SdkSuppress(maxSdkVersion = 18)
- public void testHasEmojiGlyph_withMetaVersion_pre19() {
- String sequence = new TestString(new int[]{CHAR_DEFAULT_EMOJI_STYLE}).toString();
- assertFalse(EmojiCompat.get().hasEmojiGlyph(sequence, Integer.MAX_VALUE));
- }
-
- @Test
- @SdkSuppress(minSdkVersion = 19)
public void testHasEmojiGlyph_returnsTrueForExistingEmoji() {
final String sequence = new TestString(EMOJI_FLAG).toString();
assertTrue(EmojiCompat.get().hasEmojiGlyph(sequence));
@@ -624,7 +559,6 @@
}
@Test
- @SdkSuppress(minSdkVersion = 19)
public void testHashEmojiGlyph_withDefaultEmojiStyles() {
String sequence = new TestString(new int[]{CHAR_DEFAULT_EMOJI_STYLE}).toString();
assertTrue(EmojiCompat.get().hasEmojiGlyph(sequence));
@@ -637,7 +571,6 @@
}
@Test
- @SdkSuppress(minSdkVersion = 19)
public void testHashEmojiGlyph_withMetadataVersion() {
final String sequence = new TestString(EMOJI_SINGLE_CODEPOINT).toString();
assertFalse(EmojiCompat.get().hasEmojiGlyph(sequence, 0));
@@ -645,13 +578,6 @@
}
@Test
- @SdkSuppress(maxSdkVersion = 18)
- public void testGetLoadState_returnsSuccess_pre19() {
- assertEquals(EmojiCompat.get().getLoadState(), EmojiCompat.LOAD_STATE_SUCCEEDED);
- }
-
- @Test
- @SdkSuppress(minSdkVersion = 19)
public void testGetLoadState_returnsSuccessIfLoadSuccess() throws InterruptedException {
final TestConfigBuilder.WaitingDataLoader
metadataLoader = new TestConfigBuilder.WaitingDataLoader(true /*success*/);
@@ -668,7 +594,6 @@
}
@Test
- @SdkSuppress(minSdkVersion = 19)
public void testGetLoadState_returnsFailIfLoadFail() throws InterruptedException {
final TestConfigBuilder.WaitingDataLoader
metadataLoader = new TestConfigBuilder.WaitingDataLoader(false/*fail*/);
@@ -713,24 +638,6 @@
}
@Test
- @SdkSuppress(maxSdkVersion = 18)
- public void testLoad_pre19() {
- final EmojiCompat.MetadataRepoLoader loader = spy(new TestConfigBuilder
- .TestEmojiDataLoader());
- final EmojiCompat.Config config = new TestConfigBuilder.TestConfig(loader)
- .setMetadataLoadStrategy(EmojiCompat.LOAD_STRATEGY_MANUAL);
-
- EmojiCompat.reset(config);
-
- verify(loader, never()).load(any(EmojiCompat.MetadataRepoLoaderCallback.class));
- assertEquals(EmojiCompat.LOAD_STATE_DEFAULT, EmojiCompat.get().getLoadState());
-
- EmojiCompat.get().load();
- assertEquals(EmojiCompat.LOAD_STATE_SUCCEEDED, EmojiCompat.get().getLoadState());
- }
-
- @Test
- @SdkSuppress(minSdkVersion = 19)
public void testLoad_startsLoading() {
final EmojiCompat.MetadataRepoLoader loader = spy(new TestConfigBuilder
.TestEmojiDataLoader());
@@ -748,7 +655,6 @@
}
@Test
- @SdkSuppress(minSdkVersion = 19)
public void testLoad_onceSuccessDoesNotStartLoading() {
final EmojiCompat.MetadataRepoLoader loader = spy(new TestConfigBuilder
.TestEmojiDataLoader());
@@ -768,7 +674,6 @@
}
@Test
- @SdkSuppress(minSdkVersion = 19)
public void testLoad_onceLoadingDoesNotStartLoading() throws InterruptedException {
final TestConfigBuilder.WaitingDataLoader loader = spy(
new TestConfigBuilder.WaitingDataLoader(true /*success*/));
@@ -795,14 +700,6 @@
}
@Test
- @SdkSuppress(maxSdkVersion = 18)
- public void testGetAssetSignature() {
- final String signature = EmojiCompat.get().getAssetSignature();
- assertTrue(signature.isEmpty());
- }
-
- @Test
- @SdkSuppress(minSdkVersion = 19)
public void testGetAssetSignature_api19() {
final String signature = EmojiCompat.get().getAssetSignature();
assertNotNull(signature);
@@ -810,7 +707,6 @@
}
@Test
- @SdkSuppress(minSdkVersion = 19)
public void testUpdateEditorInfoAttrs_setsKeysIfInitialized() {
final EditorInfo editorInfo = new EditorInfo();
editorInfo.extras = new Bundle();
@@ -833,7 +729,6 @@
}
@Test
- @SdkSuppress(minSdkVersion = 19)
public void testUpdateEditorInfoAttrs_makesBundleIfNull() {
final EditorInfo editorInfo = new EditorInfo();
editorInfo.extras = null;
@@ -848,40 +743,6 @@
}
@Test
- @SdkSuppress(maxSdkVersion = 18)
- public void testHandleDeleteSurroundingText_pre19() {
- final TestString testString = new TestString(EMOJI_SINGLE_CODEPOINT);
- final InputConnection inputConnection = mock(InputConnection.class);
- final Editable editable = spy(new SpannableStringBuilder(testString.toString()));
-
- Selection.setSelection(editable, testString.emojiEndIndex());
-
- reset(editable);
- reset(inputConnection);
- verifyNoMoreInteractions(editable);
- verifyNoMoreInteractions(inputConnection);
-
- // try backwards delete 1 character
- assertFalse(EmojiCompat.handleDeleteSurroundingText(inputConnection, editable,
- 1 /*beforeLength*/, 0 /*afterLength*/, false /*inCodePoints*/));
- }
-
- @Test
- @SdkSuppress(maxSdkVersion = 18)
- public void testOnKeyDown_pre19() {
- final TestString testString = new TestString(EMOJI_SINGLE_CODEPOINT);
- final Editable editable = spy(new SpannableStringBuilder(testString.toString()));
- Selection.setSelection(editable, testString.emojiEndIndex());
- final KeyEvent event = del();
-
- reset(editable);
- verifyNoMoreInteractions(editable);
-
- assertFalse(EmojiCompat.handleOnKeyDown(editable, event.getKeyCode(), event));
- }
-
- @Test
- @SdkSuppress(minSdkVersion = 19)
public void testUseEmojiAsDefaultStyle_whenEmojiInTheMiddle() {
final EmojiCompat.Config config = TestConfigBuilder.config().setReplaceAll(true);
EmojiCompat.reset(config);
@@ -894,7 +755,6 @@
}
@Test
- @SdkSuppress(minSdkVersion = 19)
public void testUseEmojiAsDefaultStyle_whenEmojiAtTheEnd() {
final EmojiCompat.Config config = TestConfigBuilder.config().setReplaceAll(true);
EmojiCompat.reset(config);
@@ -907,7 +767,6 @@
}
@Test
- @SdkSuppress(minSdkVersion = 19)
public void testUseEmojiAsDefaultStyle_noEmojisAdded_whenMarkedAsException() {
final String s = new TestString(CHAR_DEFAULT_TEXT_STYLE).toString();
final List<Integer> exceptions =
@@ -920,7 +779,6 @@
}
@Test
- @SdkSuppress(minSdkVersion = 19)
public void testUseEmojiAsDefaultStyle_emojisAdded_whenNotMarkedAsException() {
final String s = new TestString(CHAR_DEFAULT_TEXT_STYLE).toString();
final List<Integer> exceptions =
diff --git a/emoji/emoji/src/androidTest/java/androidx/emoji/text/EmojiSpanInstrumentationTest.java b/emoji/emoji/src/androidTest/java/androidx/emoji/text/EmojiSpanInstrumentationTest.java
index 4cc43d8..3b5ae18 100644
--- a/emoji/emoji/src/androidTest/java/androidx/emoji/text/EmojiSpanInstrumentationTest.java
+++ b/emoji/emoji/src/androidTest/java/androidx/emoji/text/EmojiSpanInstrumentationTest.java
@@ -33,7 +33,6 @@
import androidx.emoji.util.TestString;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
-import androidx.test.filters.SdkSuppress;
import androidx.test.platform.app.InstrumentationRegistry;
import org.junit.Before;
@@ -44,7 +43,6 @@
@LargeTest
@RunWith(AndroidJUnit4.class)
-@SdkSuppress(minSdkVersion = 19)
public class EmojiSpanInstrumentationTest {
@SuppressWarnings("deprecation")
diff --git a/emoji/emoji/src/androidTest/java/androidx/emoji/text/EmojiSpanTest.java b/emoji/emoji/src/androidTest/java/androidx/emoji/text/EmojiSpanTest.java
index 0b07a0f..3db5c29 100644
--- a/emoji/emoji/src/androidTest/java/androidx/emoji/text/EmojiSpanTest.java
+++ b/emoji/emoji/src/androidTest/java/androidx/emoji/text/EmojiSpanTest.java
@@ -31,7 +31,6 @@
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
-import androidx.test.filters.SdkSuppress;
import org.junit.Before;
import org.junit.Test;
@@ -41,7 +40,6 @@
@LargeTest
@RunWith(AndroidJUnit4.class)
-@SdkSuppress(minSdkVersion = 19)
public class EmojiSpanTest {
@Before
diff --git a/emoji/emoji/src/androidTest/java/androidx/emoji/text/FontRequestEmojiCompatConfigTest.java b/emoji/emoji/src/androidTest/java/androidx/emoji/text/FontRequestEmojiCompatConfigTest.java
index 5a65bc1..3035cf6 100644
--- a/emoji/emoji/src/androidTest/java/androidx/emoji/text/FontRequestEmojiCompatConfigTest.java
+++ b/emoji/emoji/src/androidTest/java/androidx/emoji/text/FontRequestEmojiCompatConfigTest.java
@@ -56,7 +56,6 @@
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
-import androidx.test.filters.SdkSuppress;
import org.junit.Before;
import org.junit.Test;
@@ -99,7 +98,6 @@
}
@Test
- @SdkSuppress(minSdkVersion = 19)
public void testLoad_whenGetFontThrowsException() throws NameNotFoundException {
final Exception exception = new RuntimeException();
doThrow(exception).when(mFontProviderHelper).fetchFonts(
@@ -114,7 +112,6 @@
}
@Test
- @SdkSuppress(minSdkVersion = 19)
public void testLoad_providerNotFound() throws NameNotFoundException {
doThrow(new NameNotFoundException()).when(mFontProviderHelper).fetchFonts(
any(Context.class), any(FontRequest.class));
@@ -131,14 +128,12 @@
}
@Test
- @SdkSuppress(minSdkVersion = 19)
public void testLoad_wrongCertificate() throws NameNotFoundException {
verifyLoaderOnFailedCalled(STATUS_WRONG_CERTIFICATES, null /* fonts */,
"fetchFonts failed (" + STATUS_WRONG_CERTIFICATES + ")");
}
@Test
- @SdkSuppress(minSdkVersion = 19)
public void testLoad_fontNotFound() throws NameNotFoundException {
verifyLoaderOnFailedCalled(STATUS_OK,
getTestFontInfoWithInvalidPath(RESULT_CODE_FONT_NOT_FOUND),
@@ -146,7 +141,6 @@
}
@Test
- @SdkSuppress(minSdkVersion = 19)
public void testLoad_fontUnavailable() throws NameNotFoundException {
verifyLoaderOnFailedCalled(STATUS_OK,
getTestFontInfoWithInvalidPath(RESULT_CODE_FONT_UNAVAILABLE),
@@ -154,7 +148,6 @@
}
@Test
- @SdkSuppress(minSdkVersion = 19)
public void testLoad_malformedQuery() throws NameNotFoundException {
verifyLoaderOnFailedCalled(STATUS_OK,
getTestFontInfoWithInvalidPath(RESULT_CODE_MALFORMED_QUERY),
@@ -162,21 +155,18 @@
}
@Test
- @SdkSuppress(minSdkVersion = 19)
public void testLoad_resultNotFound() throws NameNotFoundException {
verifyLoaderOnFailedCalled(STATUS_OK, new FontInfo[] {},
"fetchFonts failed (empty result)");
}
@Test
- @SdkSuppress(minSdkVersion = 19)
public void testLoad_nullFontInfo() throws NameNotFoundException {
verifyLoaderOnFailedCalled(STATUS_OK, null /* fonts */,
"fetchFonts failed (empty result)");
}
@Test
- @SdkSuppress(minSdkVersion = 19)
public void testLoad_cannotLoadTypeface() throws NameNotFoundException {
// getTestFontInfoWithInvalidPath returns FontInfo with invalid path to file.
verifyLoaderOnFailedCalled(STATUS_OK,
@@ -185,7 +175,6 @@
}
@Test
- @SdkSuppress(minSdkVersion = 19)
public void testLoad_success() throws IOException, NameNotFoundException {
final File file = loadFont(mContext, "NotoColorEmojiCompat.ttf");
final FontInfo[] fonts = new FontInfo[] {
@@ -204,7 +193,6 @@
}
@Test
- @SdkSuppress(minSdkVersion = 19)
public void testLoad_retryPolicy() throws IOException, NameNotFoundException {
final File file = loadFont(mContext, "NotoColorEmojiCompat.ttf");
final FontInfo[] fonts = new FontInfo[] {
@@ -226,7 +214,6 @@
}
@Test
- @SdkSuppress(minSdkVersion = 19)
public void testLoad_keepRetryingAndGiveUp() throws IOException, NameNotFoundException {
final File file = loadFont(mContext, "NotoColorEmojiCompat.ttf");
final FontInfo[] fonts = new FontInfo[] {
@@ -252,7 +239,6 @@
}
@Test
- @SdkSuppress(minSdkVersion = 19)
public void testLoad_keepRetryingAndFail() throws IOException, NameNotFoundException {
final File file = loadFont(mContext, "NotoColorEmojiCompat.ttf");
final Uri uri = Uri.fromFile(file);
@@ -309,7 +295,6 @@
}
@Test
- @SdkSuppress(minSdkVersion = 19)
public void testLoad_keepRetryingAndSuccess() throws IOException, NameNotFoundException {
final File file = loadFont(mContext, "NotoColorEmojiCompat.ttf");
final Uri uri = Uri.fromFile(file);
@@ -366,7 +351,6 @@
}
@Test
- @SdkSuppress(minSdkVersion = 19)
public void testLoad_ObserverNotifyAndSuccess() throws IOException, NameNotFoundException {
final File file = loadFont(mContext, "NotoColorEmojiCompat.ttf");
final Uri uri = Uri.fromFile(file);
@@ -422,7 +406,6 @@
}
@Test
- @SdkSuppress(minSdkVersion = 19)
public void testLoad_ObserverNotifyAndFail() throws IOException, NameNotFoundException {
final File file = loadFont(mContext, "NotoColorEmojiCompat.ttf");
final Uri uri = Uri.fromFile(file);
diff --git a/emoji/emoji/src/androidTest/java/androidx/emoji/text/HardDeleteTest.java b/emoji/emoji/src/androidTest/java/androidx/emoji/text/HardDeleteTest.java
index 614c6a9..cd6c449 100644
--- a/emoji/emoji/src/androidTest/java/androidx/emoji/text/HardDeleteTest.java
+++ b/emoji/emoji/src/androidTest/java/androidx/emoji/text/HardDeleteTest.java
@@ -41,7 +41,6 @@
import androidx.emoji.util.TestString;
import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SdkSuppress;
import androidx.test.filters.SmallTest;
import org.junit.Before;
@@ -51,7 +50,6 @@
@SmallTest
@RunWith(AndroidJUnit4.class)
-@SdkSuppress(minSdkVersion = 19)
public class HardDeleteTest {
private TestString mTestString;
diff --git a/emoji/emoji/src/androidTest/java/androidx/emoji/text/InitCallbackTest.java b/emoji/emoji/src/androidTest/java/androidx/emoji/text/InitCallbackTest.java
index 07d9d36..cc24436 100644
--- a/emoji/emoji/src/androidTest/java/androidx/emoji/text/InitCallbackTest.java
+++ b/emoji/emoji/src/androidTest/java/androidx/emoji/text/InitCallbackTest.java
@@ -25,7 +25,6 @@
import androidx.annotation.NonNull;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.MediumTest;
-import androidx.test.filters.SdkSuppress;
import androidx.test.platform.app.InstrumentationRegistry;
import org.junit.Test;
@@ -52,7 +51,6 @@
}
@Test
- @SdkSuppress(minSdkVersion = 19)
public void testRegisterInitCallback_callsFailCallback() {
final EmojiCompat.InitCallback initCallback1 = mock(EmojiCompat.InitCallback.class);
final EmojiCompat.InitCallback initCallback2 = mock(EmojiCompat.InitCallback.class);
@@ -72,7 +70,6 @@
}
@Test
- @SdkSuppress(minSdkVersion = 19)
public void testRegisterInitCallback_callsFailCallback_whenOnFailCalledByLoader() {
final EmojiCompat.InitCallback initCallback = mock(EmojiCompat.InitCallback.class);
final EmojiCompat.MetadataRepoLoader loader = new EmojiCompat.MetadataRepoLoader() {
@@ -91,7 +88,6 @@
}
@Test
- @SdkSuppress(minSdkVersion = 19)
public void testRegisterInitCallback_callsFailCallback_whenMetadataRepoIsNull() {
final EmojiCompat.InitCallback initCallback = mock(EmojiCompat.InitCallback.class);
final EmojiCompat.MetadataRepoLoader loader = new EmojiCompat.MetadataRepoLoader() {
@@ -110,7 +106,6 @@
}
@Test
- @SdkSuppress(minSdkVersion = 19)
public void testUnregisterInitCallback_doesNotInteractWithCallback()
throws InterruptedException {
// will be registered
diff --git a/emoji/emoji/src/androidTest/java/androidx/emoji/text/MetadataRepoTest.java b/emoji/emoji/src/androidTest/java/androidx/emoji/text/MetadataRepoTest.java
index 4be9a03..e992452 100644
--- a/emoji/emoji/src/androidTest/java/androidx/emoji/text/MetadataRepoTest.java
+++ b/emoji/emoji/src/androidTest/java/androidx/emoji/text/MetadataRepoTest.java
@@ -19,7 +19,6 @@
import static org.junit.Assert.assertSame;
import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SdkSuppress;
import androidx.test.filters.SmallTest;
import org.junit.Before;
@@ -28,7 +27,6 @@
@SmallTest
@RunWith(AndroidJUnit4.class)
-@SdkSuppress(minSdkVersion = 19)
public class MetadataRepoTest {
MetadataRepo mMetadataRepo;
diff --git a/emoji/emoji/src/androidTest/java/androidx/emoji/text/SoftDeleteTest.java b/emoji/emoji/src/androidTest/java/androidx/emoji/text/SoftDeleteTest.java
index 9676fc8..8f57c55 100644
--- a/emoji/emoji/src/androidTest/java/androidx/emoji/text/SoftDeleteTest.java
+++ b/emoji/emoji/src/androidTest/java/androidx/emoji/text/SoftDeleteTest.java
@@ -36,7 +36,6 @@
import androidx.emoji.util.Emoji;
import androidx.emoji.util.TestString;
import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SdkSuppress;
import androidx.test.filters.SmallTest;
import org.junit.Before;
@@ -46,7 +45,6 @@
@SmallTest
@RunWith(AndroidJUnit4.class)
-@SdkSuppress(minSdkVersion = 19)
public class SoftDeleteTest {
private InputConnection mInputConnection;
private TestString mTestString;
diff --git a/emoji/emoji/src/androidTest/java/androidx/emoji/text/UninitializedStateTest.java b/emoji/emoji/src/androidTest/java/androidx/emoji/text/UninitializedStateTest.java
index 9a3d61f..31b9f6a 100644
--- a/emoji/emoji/src/androidTest/java/androidx/emoji/text/UninitializedStateTest.java
+++ b/emoji/emoji/src/androidTest/java/androidx/emoji/text/UninitializedStateTest.java
@@ -16,7 +16,6 @@
package androidx.emoji.text;
import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SdkSuppress;
import androidx.test.filters.SmallTest;
import org.junit.After;
@@ -26,7 +25,6 @@
@SmallTest
@RunWith(AndroidJUnit4.class)
-@SdkSuppress(minSdkVersion = 19)
public class UninitializedStateTest {
private TestConfigBuilder.WaitingDataLoader mWaitingDataLoader;
diff --git a/emoji/emoji/src/androidTest/java/androidx/emoji/widget/EmojiEditTextHelperPre19Test.java b/emoji/emoji/src/androidTest/java/androidx/emoji/widget/EmojiEditTextHelperPre19Test.java
deleted file mode 100644
index 8f5dd1e..0000000
--- a/emoji/emoji/src/androidTest/java/androidx/emoji/widget/EmojiEditTextHelperPre19Test.java
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * Copyright (C) 2017 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.emoji.widget;
-
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertSame;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-
-import android.text.TextWatcher;
-import android.text.method.KeyListener;
-import android.view.inputmethod.EditorInfo;
-import android.view.inputmethod.InputConnection;
-import android.widget.EditText;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.LargeTest;
-import androidx.test.filters.SdkSuppress;
-import androidx.test.filters.SmallTest;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-@SdkSuppress(maxSdkVersion = 18)
-public class EmojiEditTextHelperPre19Test {
- EmojiEditTextHelper mEmojiEditTextHelper;
-
- @Before
- public void setup() {
- final EditText editText = mock(EditText.class);
- mEmojiEditTextHelper = new EmojiEditTextHelper(editText);
- verifyNoMoreInteractions(editText);
- }
-
- @Test
- public void testGetKeyListener_returnsSameKeyListener() {
- final KeyListener param = mock(KeyListener.class);
- final KeyListener keyListener = mEmojiEditTextHelper.getKeyListener(
- param);
-
- assertSame(param, keyListener);
- }
-
- @LargeTest
- @Test
- public void testGetOnCreateInputConnection_returnsSameInputConnection() {
- final InputConnection param = mock(InputConnection.class);
- final InputConnection inputConnection = mEmojiEditTextHelper.onCreateInputConnection(param,
- new EditorInfo());
-
- assertSame(param, inputConnection);
- }
-
- @Test
- public void testGetOnCreateInputConnection_withNullAttrs_returnsSameInputConnection() {
- final InputConnection param = mock(InputConnection.class);
- final InputConnection inputConnection = mEmojiEditTextHelper.onCreateInputConnection(param,
- null);
-
- assertSame(param, inputConnection);
- }
-
- @Test
- public void testGetOnCreateInputConnection_withNullInputConnection_returnsNull() {
- final InputConnection inputConnection = mEmojiEditTextHelper.onCreateInputConnection(null,
- new EditorInfo());
- assertNull(inputConnection);
- }
-
- @Test
- public void testDoesNotAttachTextWatcher() {
- final EditText editText = mock(EditText.class);
-
- mEmojiEditTextHelper = new EmojiEditTextHelper(editText);
-
- verify(editText, times(0)).addTextChangedListener(any(TextWatcher.class));
- }
-
-}
diff --git a/emoji/emoji/src/androidTest/java/androidx/emoji/widget/EmojiEditTextHelperTest.java b/emoji/emoji/src/androidTest/java/androidx/emoji/widget/EmojiEditTextHelperTest.java
index 4ff0979..f97415a 100644
--- a/emoji/emoji/src/androidTest/java/androidx/emoji/widget/EmojiEditTextHelperTest.java
+++ b/emoji/emoji/src/androidTest/java/androidx/emoji/widget/EmojiEditTextHelperTest.java
@@ -36,7 +36,6 @@
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
-import androidx.test.filters.SdkSuppress;
import org.junit.Before;
import org.junit.Test;
@@ -45,7 +44,6 @@
@LargeTest
@RunWith(AndroidJUnit4.class)
-@SdkSuppress(minSdkVersion = 19)
public class EmojiEditTextHelperTest {
EmojiEditTextHelper mEmojiEditTextHelper;
EditText mEditText;
diff --git a/emoji/emoji/src/androidTest/java/androidx/emoji/widget/EmojiEditTextTest.java b/emoji/emoji/src/androidTest/java/androidx/emoji/widget/EmojiEditTextTest.java
index 0ef6166..d0fa116 100644
--- a/emoji/emoji/src/androidTest/java/androidx/emoji/widget/EmojiEditTextTest.java
+++ b/emoji/emoji/src/androidTest/java/androidx/emoji/widget/EmojiEditTextTest.java
@@ -88,7 +88,6 @@
}
@Test
- @SdkSuppress(minSdkVersion = 19)
public void testSetMaxCount() {
final TestActivity activity = mActivityRule.getActivity();
final EmojiEditText editText = activity.findViewById(R.id.editTextWithMaxCount);
diff --git a/emoji/emoji/src/androidTest/java/androidx/emoji/widget/EmojiExtractTextLayoutTest.java b/emoji/emoji/src/androidTest/java/androidx/emoji/widget/EmojiExtractTextLayoutTest.java
index 9f9f404..8a06827 100644
--- a/emoji/emoji/src/androidTest/java/androidx/emoji/widget/EmojiExtractTextLayoutTest.java
+++ b/emoji/emoji/src/androidTest/java/androidx/emoji/widget/EmojiExtractTextLayoutTest.java
@@ -43,7 +43,6 @@
import androidx.test.annotation.UiThreadTest;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SdkSuppress;
import androidx.test.filters.SmallTest;
import org.junit.Before;
@@ -125,7 +124,6 @@
@Test
@UiThreadTest
- @SdkSuppress(minSdkVersion = 19)
public void testSetEmojiReplaceStrategyCallEmojiCompatWithCorrectStrategy() {
final Context context = ApplicationProvider.getApplicationContext();
diff --git a/emoji/emoji/src/androidTest/java/androidx/emoji/widget/EmojiInputConnectionTest.java b/emoji/emoji/src/androidTest/java/androidx/emoji/widget/EmojiInputConnectionTest.java
index 51ad68c..e80826c 100644
--- a/emoji/emoji/src/androidTest/java/androidx/emoji/widget/EmojiInputConnectionTest.java
+++ b/emoji/emoji/src/androidTest/java/androidx/emoji/widget/EmojiInputConnectionTest.java
@@ -54,7 +54,6 @@
@LargeTest
@RunWith(AndroidJUnit4.class)
-@SdkSuppress(minSdkVersion = 19)
public class EmojiInputConnectionTest {
private InputConnection mInputConnection;
diff --git a/emoji/emoji/src/androidTest/java/androidx/emoji/widget/EmojiInputFilterTest.java b/emoji/emoji/src/androidTest/java/androidx/emoji/widget/EmojiInputFilterTest.java
index 5130a65..f31b043 100644
--- a/emoji/emoji/src/androidTest/java/androidx/emoji/widget/EmojiInputFilterTest.java
+++ b/emoji/emoji/src/androidTest/java/androidx/emoji/widget/EmojiInputFilterTest.java
@@ -134,7 +134,6 @@
}
@Test
- @SdkSuppress(minSdkVersion = 19)
public void initCallback_doesntCrashWhenNotAttached() {
Context context = InstrumentationRegistry.getInstrumentation().getContext();
EditText editText = new EditText(context);
diff --git a/emoji/emoji/src/androidTest/java/androidx/emoji/widget/EmojiKeyListenerTest.java b/emoji/emoji/src/androidTest/java/androidx/emoji/widget/EmojiKeyListenerTest.java
index 817e524..e17215f 100644
--- a/emoji/emoji/src/androidTest/java/androidx/emoji/widget/EmojiKeyListenerTest.java
+++ b/emoji/emoji/src/androidTest/java/androidx/emoji/widget/EmojiKeyListenerTest.java
@@ -49,7 +49,6 @@
import androidx.emoji.text.TestConfigBuilder;
import androidx.emoji.util.TestString;
import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SdkSuppress;
import androidx.test.filters.SmallTest;
import org.junit.Before;
@@ -59,7 +58,6 @@
@SmallTest
@RunWith(AndroidJUnit4.class)
-@SdkSuppress(minSdkVersion = 19)
public class EmojiKeyListenerTest {
private KeyListener mKeyListener;
diff --git a/emoji/emoji/src/androidTest/java/androidx/emoji/widget/EmojiTextViewHelperPre19Test.java b/emoji/emoji/src/androidTest/java/androidx/emoji/widget/EmojiTextViewHelperPre19Test.java
deleted file mode 100644
index a1ec7ec..0000000
--- a/emoji/emoji/src/androidTest/java/androidx/emoji/widget/EmojiTextViewHelperPre19Test.java
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * Copyright (C) 2017 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.emoji.widget;
-
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertSame;
-import static org.mockito.Mockito.mock;
-
-import android.text.InputFilter;
-import android.text.method.TransformationMethod;
-import android.widget.TextView;
-
-import androidx.test.core.app.ApplicationProvider;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SdkSuppress;
-import androidx.test.filters.SmallTest;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-@SdkSuppress(maxSdkVersion = 18)
-public class EmojiTextViewHelperPre19Test {
- EmojiTextViewHelper mTextViewHelper;
- TextView mTextView;
-
- @Before
- public void setup() {
- mTextView = new TextView(ApplicationProvider.getApplicationContext());
- mTextViewHelper = new EmojiTextViewHelper(mTextView);
- }
-
- @Test
- public void testUpdateTransformationMethod_doesNotUpdateTransformationMethod() {
- final TransformationMethod tm = mock(TransformationMethod.class);
- mTextView.setTransformationMethod(tm);
-
- mTextViewHelper.updateTransformationMethod();
-
- assertSame(tm, mTextView.getTransformationMethod());
- }
-
- @Test
- public void testGetFilters_returnsSameFilters() {
- final InputFilter existingFilter = mock(InputFilter.class);
- final InputFilter[] filters = new InputFilter[]{existingFilter};
-
- final InputFilter[] newFilters = mTextViewHelper.getFilters(filters);
-
- assertSame(filters, newFilters);
- }
-
- @Test
- public void testGetTransformationMethod_returnSameTransformationMethod() {
- assertNull(mTextViewHelper.wrapTransformationMethod(null));
-
- final TransformationMethod tm = mock(TransformationMethod.class);
- assertSame(tm, mTextViewHelper.wrapTransformationMethod(tm));
- }
-
- @Test
- public void testSetAllCaps_doesNotUpdateTransformationMethod() {
- final TransformationMethod tm = mock(TransformationMethod.class);
- mTextView.setTransformationMethod(tm);
- mTextViewHelper.setAllCaps(true);
- assertSame(tm, mTextView.getTransformationMethod());
-
- mTextViewHelper.setAllCaps(false);
- assertSame(tm, mTextView.getTransformationMethod());
- }
-}
diff --git a/emoji/emoji/src/androidTest/java/androidx/emoji/widget/EmojiTextViewHelperTest.java b/emoji/emoji/src/androidTest/java/androidx/emoji/widget/EmojiTextViewHelperTest.java
index 70b4c36..248343e 100644
--- a/emoji/emoji/src/androidTest/java/androidx/emoji/widget/EmojiTextViewHelperTest.java
+++ b/emoji/emoji/src/androidTest/java/androidx/emoji/widget/EmojiTextViewHelperTest.java
@@ -32,7 +32,6 @@
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SdkSuppress;
import androidx.test.filters.SmallTest;
import org.junit.Before;
@@ -43,7 +42,6 @@
@SmallTest
@RunWith(AndroidJUnit4.class)
-@SdkSuppress(minSdkVersion = 19)
public class EmojiTextViewHelperTest {
EmojiTextViewHelper mTextViewHelper;
TextView mTextView;
diff --git a/emoji/emoji/src/androidTest/java/androidx/emoji/widget/EmojiTextWatcherTest.java b/emoji/emoji/src/androidTest/java/androidx/emoji/widget/EmojiTextWatcherTest.java
index 54d8ae1..d2dfcef 100644
--- a/emoji/emoji/src/androidTest/java/androidx/emoji/widget/EmojiTextWatcherTest.java
+++ b/emoji/emoji/src/androidTest/java/androidx/emoji/widget/EmojiTextWatcherTest.java
@@ -47,7 +47,6 @@
@SmallTest
@RunWith(AndroidJUnit4.class)
-@SdkSuppress(minSdkVersion = 19)
public class EmojiTextWatcherTest {
private EmojiTextWatcher mTextWatcher;
diff --git a/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentContainerViewTest.kt b/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentContainerViewTest.kt
index 3e65793..cb52138 100644
--- a/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentContainerViewTest.kt
+++ b/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentContainerViewTest.kt
@@ -103,7 +103,6 @@
assertThat(childFrag).isNotNull()
}
- @SdkSuppress(minSdkVersion = 18) // androidx.transition needs setLayoutTransition for API < 18
@Test
fun setLayoutTransitionUnsupported() {
val activity = activityRule.activity
@@ -122,20 +121,6 @@
}
}
- @SdkSuppress(maxSdkVersion = 17) // androidx.transition needs setLayoutTransition for API < 18
- @Test
- fun setLayoutTransitionAllowed() {
- val emptyLayoutTransition = LayoutTransition()
- emptyLayoutTransition.setAnimator(LayoutTransition.APPEARING, null)
- emptyLayoutTransition.setAnimator(LayoutTransition.CHANGE_APPEARING, null)
- emptyLayoutTransition.setAnimator(LayoutTransition.CHANGE_DISAPPEARING, null)
- emptyLayoutTransition.setAnimator(LayoutTransition.DISAPPEARING, null)
- emptyLayoutTransition.setAnimator(4 /*LayoutTransition.Changing*/, null)
-
- val containerView = FragmentContainerView(context)
- containerView.layoutTransition = emptyLayoutTransition
- }
-
// If view sets animateLayoutChanges to true, throw UnsupportedOperationException
@Test
fun animateLayoutChangesTrueUnsupported() {
diff --git a/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentTest.kt b/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentTest.kt
index 2313a6f..7d34ca7 100644
--- a/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentTest.kt
+++ b/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentTest.kt
@@ -26,7 +26,6 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import androidx.test.filters.MediumTest
-import androidx.test.filters.SdkSuppress
import androidx.test.filters.SmallTest
import androidx.test.platform.app.InstrumentationRegistry
import androidx.testutils.waitForExecution
@@ -101,7 +100,6 @@
@LargeTest
@Test
- @SdkSuppress(minSdkVersion = 16) // waitForHalfFadeIn requires API 16
fun testChildFragmentManagerGone() {
val activity = activityRule.activity
val fragmentA = FragmentA()
@@ -146,7 +144,6 @@
@LargeTest
@Test
- @SdkSuppress(minSdkVersion = 16) // waitForHalfFadeIn requires API 16
fun testRemoveUnrelatedDuringAnimation() {
val activity = activityRule.activity
val unrelatedFragment = StrictFragment()
diff --git a/gradle.properties b/gradle.properties
index b30720c..86c8736 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -82,3 +82,6 @@
# Properties we often want to toggle
ksp.version.check=false
+
+# Annotation processors discovery from compile classpath is deprecated
+kapt.include.compile.classpath=false
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index dc73106..d7c926c 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -85,7 +85,7 @@
androidToolsAnalyticsProtos = { module = "com.android.tools.analytics-library:protos", version.ref = "androidLint" }
androidKotlinMultiplatform = { module = "com.android.kotlin.multiplatform.library:com.android.kotlin.multiplatform.library.gradle.plugin", version.ref = "androidGradlePlugin" }
androidx-annotation = { module = "androidx.annotation:annotation", version.ref = "annotationVersion" }
-autoCommon = { module = "com.google.auto:auto-common", version = "0.11" }
+autoCommon = { module = "com.google.auto:auto-common", version = "1.2.1" }
atomicFu = { module = "org.jetbrains.kotlinx:atomicfu", version.ref = "atomicFu" }
atomicFuPluginz = { module = "org.jetbrains.kotlinx:atomicfu-gradle-plugin", version.ref = "atomicFu" }
autoServiceAnnotations = { module = "com.google.auto.service:auto-service-annotations", version.ref = "autoService" }
diff --git a/graphics/graphics-core/api/1.0.0-beta01.txt b/graphics/graphics-core/api/1.0.0-beta01.txt
new file mode 100644
index 0000000..e1b45be
--- /dev/null
+++ b/graphics/graphics-core/api/1.0.0-beta01.txt
@@ -0,0 +1,559 @@
+// Signature format: 4.0
+package androidx.graphics {
+
+ @RequiresApi(android.os.Build.VERSION_CODES.Q) public final class CanvasBufferedRenderer implements java.lang.AutoCloseable {
+ method public void close();
+ method public int getBufferFormat();
+ method public int getMaxBuffers();
+ method public long getUsageFlags();
+ method public boolean isClosed();
+ method public androidx.graphics.CanvasBufferedRenderer.RenderRequest obtainRenderRequest();
+ method public void releaseBuffer(android.hardware.HardwareBuffer hardwareBuffer, androidx.hardware.SyncFenceCompat? fence);
+ method public void setContentRoot(android.graphics.RenderNode renderNode);
+ method public void setLightSourceAlpha(float ambientShadowAlpha, float spotShadowAlpha);
+ method public void setLightSourceGeometry(float lightX, float lightY, float lightZ, float lightRadius);
+ property public final int bufferFormat;
+ property public final int maxBuffers;
+ property public final long usageFlags;
+ }
+
+ public static final class CanvasBufferedRenderer.Builder {
+ ctor public CanvasBufferedRenderer.Builder(int width, int height);
+ method public androidx.graphics.CanvasBufferedRenderer build();
+ method public androidx.graphics.CanvasBufferedRenderer.Builder setBufferFormat(int format);
+ method public androidx.graphics.CanvasBufferedRenderer.Builder setMaxBuffers(@IntRange(from=1L, to=64L) int numBuffers);
+ method public androidx.graphics.CanvasBufferedRenderer.Builder setUsageFlags(long usageFlags);
+ }
+
+ public final class CanvasBufferedRenderer.RenderRequest {
+ method public void draw(java.util.concurrent.Executor executor, androidx.core.util.Consumer<androidx.graphics.CanvasBufferedRenderer.RenderResult> callback);
+ method public androidx.graphics.CanvasBufferedRenderer.RenderRequest preserveContents(boolean preserve);
+ method public androidx.graphics.CanvasBufferedRenderer.RenderRequest setBufferTransform(int bufferTransform);
+ method public androidx.graphics.CanvasBufferedRenderer.RenderRequest setColorSpace(android.graphics.ColorSpace? colorSpace);
+ }
+
+ public static final class CanvasBufferedRenderer.RenderResult {
+ ctor public CanvasBufferedRenderer.RenderResult(android.hardware.HardwareBuffer buffer, androidx.hardware.SyncFenceCompat? mFence, int mStatus);
+ method public androidx.hardware.SyncFenceCompat? getFence();
+ method public android.hardware.HardwareBuffer getHardwareBuffer();
+ method public int getStatus();
+ property public final androidx.hardware.SyncFenceCompat? fence;
+ property public final android.hardware.HardwareBuffer hardwareBuffer;
+ property public final int status;
+ field public static final androidx.graphics.CanvasBufferedRenderer.RenderResult.Companion Companion;
+ field public static final int ERROR_UNKNOWN = 1; // 0x1
+ field public static final int SUCCESS = 0; // 0x0
+ }
+
+ public static final class CanvasBufferedRenderer.RenderResult.Companion {
+ }
+
+}
+
+package androidx.graphics.lowlatency {
+
+ public final class BufferInfo {
+ method public int getFrameBufferId();
+ method public int getHeight();
+ method public int getWidth();
+ property public final int frameBufferId;
+ property public final int height;
+ property public final int width;
+ }
+
+ @RequiresApi(android.os.Build.VERSION_CODES.Q) public final class CanvasFrontBufferedRenderer<T> {
+ ctor public CanvasFrontBufferedRenderer(android.view.SurfaceView surfaceView, androidx.graphics.lowlatency.CanvasFrontBufferedRenderer.Callback<T> callback);
+ ctor public CanvasFrontBufferedRenderer(android.view.SurfaceView surfaceView, androidx.graphics.lowlatency.CanvasFrontBufferedRenderer.Callback<T> callback, optional int bufferFormat);
+ method public void cancel();
+ method public void clear();
+ method public void commit();
+ method public int getBufferFormat();
+ method public android.graphics.ColorSpace getColorSpace();
+ method public boolean isValid();
+ method public void release(boolean cancelPending);
+ method public void release(boolean cancelPending, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onReleaseComplete);
+ method public void renderFrontBufferedLayer(T param);
+ method public void renderMultiBufferedLayer(java.util.Collection<? extends T> params);
+ method public void setColorSpace(android.graphics.ColorSpace);
+ property public final int bufferFormat;
+ property public final android.graphics.ColorSpace colorSpace;
+ }
+
+ @kotlin.jvm.JvmDefaultWithCompatibility public static interface CanvasFrontBufferedRenderer.Callback<T> {
+ method @WorkerThread public void onDrawFrontBufferedLayer(android.graphics.Canvas canvas, int bufferWidth, int bufferHeight, T param);
+ method @WorkerThread public void onDrawMultiBufferedLayer(android.graphics.Canvas canvas, int bufferWidth, int bufferHeight, java.util.Collection<? extends T> params);
+ method @WorkerThread public default void onFrontBufferedLayerRenderComplete(androidx.graphics.surface.SurfaceControlCompat frontBufferedLayerSurfaceControl, androidx.graphics.surface.SurfaceControlCompat.Transaction transaction);
+ method @WorkerThread public default void onMultiBufferedLayerRenderComplete(androidx.graphics.surface.SurfaceControlCompat frontBufferedLayerSurfaceControl, androidx.graphics.surface.SurfaceControlCompat multiBufferedLayerSurfaceControl, androidx.graphics.surface.SurfaceControlCompat.Transaction transaction);
+ }
+
+ public final class FrontBufferSyncStrategy implements androidx.graphics.opengl.SyncStrategy {
+ ctor public FrontBufferSyncStrategy(long usageFlags);
+ method @RequiresApi(android.os.Build.VERSION_CODES.KITKAT) public androidx.hardware.SyncFenceCompat? createSyncFence(androidx.graphics.opengl.egl.EGLSpec eglSpec);
+ method public boolean isVisible();
+ method public void setVisible(boolean);
+ property public final boolean isVisible;
+ }
+
+ @RequiresApi(android.os.Build.VERSION_CODES.Q) public final class GLFrontBufferedRenderer<T> {
+ ctor public GLFrontBufferedRenderer(android.view.SurfaceView surfaceView, androidx.graphics.lowlatency.GLFrontBufferedRenderer.Callback<T> callback);
+ ctor public GLFrontBufferedRenderer(android.view.SurfaceView surfaceView, androidx.graphics.lowlatency.GLFrontBufferedRenderer.Callback<T> callback, optional androidx.graphics.opengl.GLRenderer? glRenderer);
+ ctor public GLFrontBufferedRenderer(android.view.SurfaceView surfaceView, androidx.graphics.lowlatency.GLFrontBufferedRenderer.Callback<T> callback, optional androidx.graphics.opengl.GLRenderer? glRenderer, optional int bufferFormat);
+ method public void cancel();
+ method public void clear();
+ method public void commit();
+ method public void execute(Runnable runnable);
+ method public int getBufferFormat();
+ method public boolean isValid();
+ method public void release(boolean cancelPending);
+ method public void release(boolean cancelPending, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onReleaseComplete);
+ method public void renderFrontBufferedLayer(T param);
+ method public void renderMultiBufferedLayer(java.util.Collection<? extends T> params);
+ property public final int bufferFormat;
+ }
+
+ @kotlin.jvm.JvmDefaultWithCompatibility public static interface GLFrontBufferedRenderer.Callback<T> {
+ method @WorkerThread public void onDrawFrontBufferedLayer(androidx.graphics.opengl.egl.EGLManager eglManager, int width, int height, androidx.graphics.lowlatency.BufferInfo bufferInfo, float[] transform, T param);
+ method @WorkerThread public void onDrawMultiBufferedLayer(androidx.graphics.opengl.egl.EGLManager eglManager, int width, int height, androidx.graphics.lowlatency.BufferInfo bufferInfo, float[] transform, java.util.Collection<? extends T> params);
+ method @WorkerThread public default void onFrontBufferedLayerRenderComplete(androidx.graphics.surface.SurfaceControlCompat frontBufferedLayerSurfaceControl, androidx.graphics.surface.SurfaceControlCompat.Transaction transaction);
+ method @WorkerThread public default void onMultiBufferedLayerRenderComplete(androidx.graphics.surface.SurfaceControlCompat frontBufferedLayerSurfaceControl, androidx.graphics.surface.SurfaceControlCompat multiBufferedLayerSurfaceControl, androidx.graphics.surface.SurfaceControlCompat.Transaction transaction);
+ }
+
+ @RequiresApi(android.os.Build.VERSION_CODES.Q) public final class LowLatencyCanvasView extends android.view.ViewGroup {
+ ctor public LowLatencyCanvasView(android.content.Context context);
+ ctor public LowLatencyCanvasView(android.content.Context context, optional android.util.AttributeSet? attrs);
+ ctor public LowLatencyCanvasView(android.content.Context context, optional android.util.AttributeSet? attrs, optional int defStyle);
+ method public void cancel();
+ method public void clear();
+ method public void commit();
+ method public void execute(Runnable runnable);
+ method public void renderFrontBufferedLayer();
+ method public void setRenderCallback(androidx.graphics.lowlatency.LowLatencyCanvasView.Callback? callback);
+ }
+
+ @kotlin.jvm.JvmDefaultWithCompatibility public static interface LowLatencyCanvasView.Callback {
+ method @WorkerThread public void onDrawFrontBufferedLayer(android.graphics.Canvas canvas, int width, int height);
+ method @WorkerThread public default void onFrontBufferedLayerRenderComplete(androidx.graphics.surface.SurfaceControlCompat frontBufferedLayerSurfaceControl, androidx.graphics.surface.SurfaceControlCompat.Transaction transaction);
+ method @WorkerThread public void onRedrawRequested(android.graphics.Canvas canvas, int width, int height);
+ }
+
+}
+
+package androidx.graphics.opengl {
+
+ @RequiresApi(android.os.Build.VERSION_CODES.O) public final class FrameBuffer implements java.lang.AutoCloseable {
+ ctor public FrameBuffer(androidx.graphics.opengl.egl.EGLSpec egl, android.hardware.HardwareBuffer hardwareBuffer);
+ method public void close();
+ method public android.hardware.HardwareBuffer getHardwareBuffer();
+ method public boolean isClosed();
+ method public void makeCurrent();
+ property public final android.hardware.HardwareBuffer hardwareBuffer;
+ property public final boolean isClosed;
+ }
+
+ @RequiresApi(android.os.Build.VERSION_CODES.O) public final class FrameBufferRenderer implements androidx.graphics.opengl.GLRenderer.RenderCallback {
+ ctor public FrameBufferRenderer(androidx.graphics.opengl.FrameBufferRenderer.RenderCallback frameBufferRendererCallbacks, optional androidx.graphics.opengl.SyncStrategy syncStrategy);
+ method public void clear();
+ method public void onDrawFrame(androidx.graphics.opengl.egl.EGLManager eglManager);
+ }
+
+ public static interface FrameBufferRenderer.RenderCallback {
+ method public androidx.graphics.opengl.FrameBuffer obtainFrameBuffer(androidx.graphics.opengl.egl.EGLSpec egl);
+ method public void onDraw(androidx.graphics.opengl.egl.EGLManager eglManager);
+ method public void onDrawComplete(androidx.graphics.opengl.FrameBuffer frameBuffer, androidx.hardware.SyncFenceCompat? syncFenceCompat);
+ }
+
+ @RequiresApi(android.os.Build.VERSION_CODES.Q) public final class GLFrameBufferRenderer {
+ method public void execute(Runnable runnable);
+ method public int getBufferFormat();
+ method public androidx.graphics.opengl.GLRenderer getGLRenderer();
+ method public int getMaxBuffers();
+ method public androidx.graphics.opengl.SyncStrategy getSyncStrategy();
+ method public long getUsageFlags();
+ method public boolean isValid();
+ method public void release(boolean cancelPending);
+ method public void release(boolean cancelPending, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onReleaseCallback);
+ method public void render();
+ property public final int bufferFormat;
+ property public final androidx.graphics.opengl.GLRenderer glRenderer;
+ property public final int maxBuffers;
+ property public final androidx.graphics.opengl.SyncStrategy syncStrategy;
+ property public final long usageFlags;
+ }
+
+ public static final class GLFrameBufferRenderer.Builder {
+ ctor public GLFrameBufferRenderer.Builder(android.view.SurfaceView surfaceView, androidx.graphics.opengl.GLFrameBufferRenderer.Callback callback);
+ method public androidx.graphics.opengl.GLFrameBufferRenderer build();
+ method public androidx.graphics.opengl.GLFrameBufferRenderer.Builder setBufferFormat(int format);
+ method public androidx.graphics.opengl.GLFrameBufferRenderer.Builder setGLRenderer(androidx.graphics.opengl.GLRenderer? glRenderer);
+ method public androidx.graphics.opengl.GLFrameBufferRenderer.Builder setMaxBuffers(@IntRange(from=1L, to=64L) int numBuffers);
+ method public androidx.graphics.opengl.GLFrameBufferRenderer.Builder setSyncStrategy(androidx.graphics.opengl.SyncStrategy syncStrategy);
+ method public androidx.graphics.opengl.GLFrameBufferRenderer.Builder setUsageFlags(long usageFlags);
+ }
+
+ public static interface GLFrameBufferRenderer.Callback {
+ method @WorkerThread public default void onBufferReleased(androidx.graphics.opengl.FrameBuffer frameBuffer, androidx.hardware.SyncFenceCompat? releaseFence);
+ method @WorkerThread public default void onDrawComplete(androidx.graphics.surface.SurfaceControlCompat targetSurfaceControl, androidx.graphics.surface.SurfaceControlCompat.Transaction transaction, androidx.graphics.opengl.FrameBuffer frameBuffer, androidx.hardware.SyncFenceCompat? syncFence);
+ method @WorkerThread public void onDrawFrame(androidx.graphics.opengl.egl.EGLManager eglManager, int width, int height, androidx.graphics.lowlatency.BufferInfo bufferInfo, float[] transform);
+ }
+
+ public final class GLRenderer {
+ ctor public GLRenderer(optional kotlin.jvm.functions.Function0<? extends androidx.graphics.opengl.egl.EGLSpec> eglSpecFactory, optional kotlin.jvm.functions.Function1<? super androidx.graphics.opengl.egl.EGLManager,? extends android.opengl.EGLConfig> eglConfigFactory);
+ method public androidx.graphics.opengl.GLRenderer.RenderTarget attach(android.view.Surface surface, int width, int height, androidx.graphics.opengl.GLRenderer.RenderCallback renderer);
+ method public androidx.graphics.opengl.GLRenderer.RenderTarget attach(android.view.SurfaceView surfaceView, androidx.graphics.opengl.GLRenderer.RenderCallback renderer);
+ method public androidx.graphics.opengl.GLRenderer.RenderTarget attach(android.view.TextureView textureView, androidx.graphics.opengl.GLRenderer.RenderCallback renderer);
+ method public androidx.graphics.opengl.GLRenderer.RenderTarget createRenderTarget(int width, int height, androidx.graphics.opengl.GLRenderer.RenderCallback renderer);
+ method public void detach(androidx.graphics.opengl.GLRenderer.RenderTarget target, boolean cancelPending);
+ method public void detach(androidx.graphics.opengl.GLRenderer.RenderTarget target, boolean cancelPending, optional @WorkerThread kotlin.jvm.functions.Function1<? super androidx.graphics.opengl.GLRenderer.RenderTarget,kotlin.Unit>? onDetachComplete);
+ method public void execute(Runnable runnable);
+ method public boolean isRunning();
+ method public void registerEGLContextCallback(androidx.graphics.opengl.GLRenderer.EGLContextCallback callback);
+ method public void requestRender(androidx.graphics.opengl.GLRenderer.RenderTarget target);
+ method public void requestRender(androidx.graphics.opengl.GLRenderer.RenderTarget target, optional kotlin.jvm.functions.Function1<? super androidx.graphics.opengl.GLRenderer.RenderTarget,kotlin.Unit>? onRenderComplete);
+ method public void resize(androidx.graphics.opengl.GLRenderer.RenderTarget target, int width, int height);
+ method public void resize(androidx.graphics.opengl.GLRenderer.RenderTarget target, int width, int height, optional kotlin.jvm.functions.Function1<? super androidx.graphics.opengl.GLRenderer.RenderTarget,kotlin.Unit>? onResizeComplete);
+ method public void start();
+ method public void start(optional String name);
+ method public void stop(boolean cancelPending);
+ method public void stop(boolean cancelPending, optional kotlin.jvm.functions.Function1<? super androidx.graphics.opengl.GLRenderer,kotlin.Unit>? onStop);
+ method public void unregisterEGLContextCallback(androidx.graphics.opengl.GLRenderer.EGLContextCallback callback);
+ field public static final androidx.graphics.opengl.GLRenderer.Companion Companion;
+ }
+
+ public static final class GLRenderer.Companion {
+ }
+
+ public static interface GLRenderer.EGLContextCallback {
+ method @WorkerThread public void onEGLContextCreated(androidx.graphics.opengl.egl.EGLManager eglManager);
+ method @WorkerThread public void onEGLContextDestroyed(androidx.graphics.opengl.egl.EGLManager eglManager);
+ }
+
+ @kotlin.jvm.JvmDefaultWithCompatibility public static interface GLRenderer.RenderCallback {
+ method @WorkerThread public void onDrawFrame(androidx.graphics.opengl.egl.EGLManager eglManager);
+ method @WorkerThread public default android.opengl.EGLSurface? onSurfaceCreated(androidx.graphics.opengl.egl.EGLSpec spec, android.opengl.EGLConfig config, android.view.Surface surface, int width, int height);
+ }
+
+ public static final class GLRenderer.RenderTarget {
+ method public void detach(boolean cancelPending);
+ method public void detach(boolean cancelPending, optional kotlin.jvm.functions.Function1<? super androidx.graphics.opengl.GLRenderer.RenderTarget,kotlin.Unit>? onDetachComplete);
+ method public boolean isAttached();
+ method public void requestRender();
+ method public void requestRender(optional @WorkerThread kotlin.jvm.functions.Function1<? super androidx.graphics.opengl.GLRenderer.RenderTarget,kotlin.Unit>? onRenderComplete);
+ method public void resize(int width, int height);
+ method public void resize(int width, int height, optional @WorkerThread kotlin.jvm.functions.Function1<? super androidx.graphics.opengl.GLRenderer.RenderTarget,kotlin.Unit>? onResizeComplete);
+ }
+
+ public interface SyncStrategy {
+ method @RequiresApi(android.os.Build.VERSION_CODES.KITKAT) public androidx.hardware.SyncFenceCompat? createSyncFence(androidx.graphics.opengl.egl.EGLSpec eglSpec);
+ field public static final androidx.graphics.opengl.SyncStrategy ALWAYS;
+ field public static final androidx.graphics.opengl.SyncStrategy.Companion Companion;
+ }
+
+ public static final class SyncStrategy.Companion {
+ }
+
+}
+
+package androidx.graphics.opengl.egl {
+
+ public final class EGLConfigAttributes {
+ method public int[] toArray();
+ field public static final androidx.graphics.opengl.egl.EGLConfigAttributes.Companion Companion;
+ field public static final int EGL_COLOR_COMPONENT_TYPE_EXT = 13113; // 0x3339
+ field public static final int EGL_COLOR_COMPONENT_TYPE_FIXED_EXT = 13114; // 0x333a
+ field public static final int EGL_COLOR_COMPONENT_TYPE_FLOAT_EXT = 13115; // 0x333b
+ field public static final androidx.graphics.opengl.egl.EGLConfigAttributes RGBA_1010102;
+ field public static final androidx.graphics.opengl.egl.EGLConfigAttributes RGBA_8888;
+ field public static final androidx.graphics.opengl.egl.EGLConfigAttributes RGBA_F16;
+ }
+
+ public static final class EGLConfigAttributes.Builder {
+ method public androidx.graphics.opengl.egl.EGLConfigAttributes build();
+ method public void include(androidx.graphics.opengl.egl.EGLConfigAttributes attributes);
+ method public androidx.graphics.opengl.egl.EGLConfigAttributes.Builder setAttribute(int attribute, int value);
+ method @kotlin.jvm.JvmSynthetic public infix void to(int, int that);
+ }
+
+ public static final class EGLConfigAttributes.Companion {
+ }
+
+ public final class EGLConfigAttributesKt {
+ method @kotlin.jvm.JvmSynthetic public static inline androidx.graphics.opengl.egl.EGLConfigAttributes EGLConfigAttributes(kotlin.jvm.functions.Function1<? super androidx.graphics.opengl.egl.EGLConfigAttributes.Builder,kotlin.Unit> block);
+ }
+
+ public final class EGLException extends java.lang.RuntimeException {
+ ctor public EGLException(int error, optional String msg);
+ method public int getError();
+ method public String getMsg();
+ property public final int error;
+ property public String message;
+ property public final String msg;
+ }
+
+ public final class EGLManager {
+ ctor public EGLManager(optional androidx.graphics.opengl.egl.EGLSpec eglSpec);
+ method public android.opengl.EGLContext createContext(android.opengl.EGLConfig config);
+ method public android.opengl.EGLSurface getCurrentDrawSurface();
+ method public android.opengl.EGLSurface getCurrentReadSurface();
+ method public android.opengl.EGLSurface getDefaultSurface();
+ method public android.opengl.EGLConfig? getEGLConfig();
+ method public android.opengl.EGLContext? getEGLContext();
+ method public androidx.graphics.opengl.egl.EGLSpec getEGLSpec();
+ method public androidx.graphics.opengl.egl.EGLVersion getEGLVersion();
+ method public void initialize();
+ method public boolean isExtensionSupported(String extensionName);
+ method public android.opengl.EGLConfig? loadConfig(androidx.graphics.opengl.egl.EGLConfigAttributes configAttributes);
+ method public boolean makeCurrent(android.opengl.EGLSurface drawSurface);
+ method public boolean makeCurrent(android.opengl.EGLSurface drawSurface, optional android.opengl.EGLSurface readSurface);
+ method public void release();
+ method public void swapAndFlushBuffers();
+ property public final android.opengl.EGLSurface currentDrawSurface;
+ property public final android.opengl.EGLSurface currentReadSurface;
+ property public final android.opengl.EGLSurface defaultSurface;
+ property public final android.opengl.EGLConfig? eglConfig;
+ property public final android.opengl.EGLContext? eglContext;
+ property public final androidx.graphics.opengl.egl.EGLSpec eglSpec;
+ property public final androidx.graphics.opengl.egl.EGLVersion eglVersion;
+ field public static final androidx.graphics.opengl.egl.EGLManager.Companion Companion;
+ }
+
+ public static final class EGLManager.Companion {
+ }
+
+ @kotlin.jvm.JvmDefaultWithCompatibility public interface EGLSpec {
+ method public int eglClientWaitSyncKHR(androidx.opengl.EGLSyncKHR sync, int flags, long timeoutNanos);
+ method public android.opengl.EGLContext eglCreateContext(android.opengl.EGLConfig config);
+ method @RequiresApi(android.os.Build.VERSION_CODES.O) public androidx.opengl.EGLImageKHR? eglCreateImageFromHardwareBuffer(android.hardware.HardwareBuffer hardwareBuffer);
+ method public android.opengl.EGLSurface eglCreatePBufferSurface(android.opengl.EGLConfig config, androidx.graphics.opengl.egl.EGLConfigAttributes? configAttributes);
+ method public androidx.opengl.EGLSyncKHR? eglCreateSyncKHR(int type, androidx.graphics.opengl.egl.EGLConfigAttributes? attributes);
+ method public android.opengl.EGLSurface eglCreateWindowSurface(android.opengl.EGLConfig config, android.view.Surface surface, androidx.graphics.opengl.egl.EGLConfigAttributes? configAttributes);
+ method public void eglDestroyContext(android.opengl.EGLContext eglContext);
+ method public boolean eglDestroyImageKHR(androidx.opengl.EGLImageKHR image);
+ method public boolean eglDestroySurface(android.opengl.EGLSurface surface);
+ method public boolean eglDestroySyncKHR(androidx.opengl.EGLSyncKHR sync);
+ method public android.opengl.EGLSurface eglGetCurrentDrawSurface();
+ method public android.opengl.EGLSurface eglGetCurrentReadSurface();
+ method public int eglGetError();
+ method public boolean eglGetSyncAttribKHR(androidx.opengl.EGLSyncKHR sync, int attribute, int[] value, int offset);
+ method public androidx.graphics.opengl.egl.EGLVersion eglInitialize();
+ method public boolean eglMakeCurrent(android.opengl.EGLContext context, android.opengl.EGLSurface drawSurface, android.opengl.EGLSurface readSurface);
+ method public String eglQueryString(int nameId);
+ method public boolean eglQuerySurface(android.opengl.EGLSurface surface, int attribute, int[] result, int offset);
+ method public boolean eglSwapBuffers(android.opengl.EGLSurface surface);
+ method public default String getErrorMessage();
+ method public static String getStatusString(int error);
+ method public android.opengl.EGLConfig? loadConfig(androidx.graphics.opengl.egl.EGLConfigAttributes configAttributes);
+ field public static final androidx.graphics.opengl.egl.EGLSpec.Companion Companion;
+ field public static final androidx.graphics.opengl.egl.EGLSpec V14;
+ }
+
+ public static final class EGLSpec.Companion {
+ method public String getStatusString(int error);
+ }
+
+ public final class EGLVersion {
+ ctor public EGLVersion(int major, int minor);
+ method public int component1();
+ method public int component2();
+ method public androidx.graphics.opengl.egl.EGLVersion copy(int major, int minor);
+ method public int getMajor();
+ method public int getMinor();
+ property public final int major;
+ property public final int minor;
+ field public static final androidx.graphics.opengl.egl.EGLVersion.Companion Companion;
+ field public static final androidx.graphics.opengl.egl.EGLVersion Unknown;
+ field public static final androidx.graphics.opengl.egl.EGLVersion V14;
+ field public static final androidx.graphics.opengl.egl.EGLVersion V15;
+ }
+
+ public static final class EGLVersion.Companion {
+ }
+
+}
+
+package androidx.graphics.surface {
+
+ @RequiresApi(android.os.Build.VERSION_CODES.Q) public final class SurfaceControlCompat {
+ method public boolean isValid();
+ method public void release();
+ field public static final int BUFFER_TRANSFORM_IDENTITY = 0; // 0x0
+ field public static final int BUFFER_TRANSFORM_MIRROR_HORIZONTAL = 1; // 0x1
+ field public static final int BUFFER_TRANSFORM_MIRROR_VERTICAL = 2; // 0x2
+ field public static final int BUFFER_TRANSFORM_ROTATE_180 = 3; // 0x3
+ field public static final int BUFFER_TRANSFORM_ROTATE_270 = 7; // 0x7
+ field public static final int BUFFER_TRANSFORM_ROTATE_90 = 4; // 0x4
+ field public static final int CHANGE_FRAME_RATE_ALWAYS = 1; // 0x1
+ field public static final int CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS = 0; // 0x0
+ field public static final androidx.graphics.surface.SurfaceControlCompat.Companion Companion;
+ field public static final int FRAME_RATE_COMPATIBILITY_DEFAULT = 0; // 0x0
+ field public static final int FRAME_RATE_COMPATIBILITY_FIXED_SOURCE = 1; // 0x1
+ }
+
+ public static final class SurfaceControlCompat.Builder {
+ ctor public SurfaceControlCompat.Builder();
+ method public androidx.graphics.surface.SurfaceControlCompat build();
+ method public androidx.graphics.surface.SurfaceControlCompat.Builder setName(String name);
+ method public androidx.graphics.surface.SurfaceControlCompat.Builder setParent(android.view.SurfaceView surfaceView);
+ method public androidx.graphics.surface.SurfaceControlCompat.Builder setParent(androidx.graphics.surface.SurfaceControlCompat surfaceControl);
+ }
+
+ public static final class SurfaceControlCompat.Companion {
+ }
+
+ @RequiresApi(android.os.Build.VERSION_CODES.Q) public static final class SurfaceControlCompat.Transaction implements java.lang.AutoCloseable {
+ ctor public SurfaceControlCompat.Transaction();
+ method @RequiresApi(android.os.Build.VERSION_CODES.S) public androidx.graphics.surface.SurfaceControlCompat.Transaction addTransactionCommittedListener(java.util.concurrent.Executor executor, androidx.graphics.surface.SurfaceControlCompat.TransactionCommittedListener listener);
+ method public androidx.graphics.surface.SurfaceControlCompat.Transaction clearFrameRate(androidx.graphics.surface.SurfaceControlCompat surfaceControl);
+ method public void close();
+ method public void commit();
+ method @RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public void commitTransactionOnDraw(android.view.AttachedSurfaceControl attachedSurfaceControl);
+ method @RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public androidx.graphics.surface.SurfaceControlCompat.Transaction reparent(androidx.graphics.surface.SurfaceControlCompat surfaceControl, android.view.AttachedSurfaceControl attachedSurfaceControl);
+ method public androidx.graphics.surface.SurfaceControlCompat.Transaction reparent(androidx.graphics.surface.SurfaceControlCompat surfaceControl, androidx.graphics.surface.SurfaceControlCompat? newParent);
+ method public androidx.graphics.surface.SurfaceControlCompat.Transaction setAlpha(androidx.graphics.surface.SurfaceControlCompat surfaceControl, float alpha);
+ method public androidx.graphics.surface.SurfaceControlCompat.Transaction setBuffer(androidx.graphics.surface.SurfaceControlCompat surfaceControl, android.hardware.HardwareBuffer? buffer);
+ method public androidx.graphics.surface.SurfaceControlCompat.Transaction setBuffer(androidx.graphics.surface.SurfaceControlCompat surfaceControl, android.hardware.HardwareBuffer? buffer, optional androidx.hardware.SyncFenceCompat? fence);
+ method public androidx.graphics.surface.SurfaceControlCompat.Transaction setBuffer(androidx.graphics.surface.SurfaceControlCompat surfaceControl, android.hardware.HardwareBuffer? buffer, optional androidx.hardware.SyncFenceCompat? fence, optional kotlin.jvm.functions.Function1<? super androidx.hardware.SyncFenceCompat,kotlin.Unit>? releaseCallback);
+ method public androidx.graphics.surface.SurfaceControlCompat.Transaction setBufferTransform(androidx.graphics.surface.SurfaceControlCompat surfaceControl, int transformation);
+ method public androidx.graphics.surface.SurfaceControlCompat.Transaction setCrop(androidx.graphics.surface.SurfaceControlCompat surfaceControl, android.graphics.Rect? crop);
+ method public androidx.graphics.surface.SurfaceControlCompat.Transaction setDamageRegion(androidx.graphics.surface.SurfaceControlCompat surfaceControl, android.graphics.Region? region);
+ method public androidx.graphics.surface.SurfaceControlCompat.Transaction setDataSpace(androidx.graphics.surface.SurfaceControlCompat surfaceControl, int dataSpace);
+ method @RequiresApi(android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE) public androidx.graphics.surface.SurfaceControlCompat.Transaction setExtendedRangeBrightness(androidx.graphics.surface.SurfaceControlCompat surfaceControl, @FloatRange(from=1.0, fromInclusive=true) float currentBufferRatio, @FloatRange(from=1.0, fromInclusive=true) float desiredRatio);
+ method public androidx.graphics.surface.SurfaceControlCompat.Transaction setFrameRate(androidx.graphics.surface.SurfaceControlCompat surfaceControl, float frameRate, int compatibility, int changeFrameRateStrategy);
+ method public androidx.graphics.surface.SurfaceControlCompat.Transaction setLayer(androidx.graphics.surface.SurfaceControlCompat surfaceControl, int z);
+ method public androidx.graphics.surface.SurfaceControlCompat.Transaction setOpaque(androidx.graphics.surface.SurfaceControlCompat surfaceControl, boolean isOpaque);
+ method public androidx.graphics.surface.SurfaceControlCompat.Transaction setPosition(androidx.graphics.surface.SurfaceControlCompat surfaceControl, float x, float y);
+ method public androidx.graphics.surface.SurfaceControlCompat.Transaction setScale(androidx.graphics.surface.SurfaceControlCompat surfaceControl, float scaleX, float scaleY);
+ method public androidx.graphics.surface.SurfaceControlCompat.Transaction setVisibility(androidx.graphics.surface.SurfaceControlCompat surfaceControl, boolean visible);
+ }
+
+ public static interface SurfaceControlCompat.TransactionCommittedListener {
+ method public void onTransactionCommitted();
+ }
+
+}
+
+package androidx.hardware {
+
+ public final class DataSpace {
+ field public static final androidx.hardware.DataSpace.Companion Companion;
+ field public static final int DATASPACE_ADOBE_RGB = 151715840; // 0x90b0000
+ field public static final int DATASPACE_BT2020 = 147193856; // 0x8c60000
+ field public static final int DATASPACE_BT2020_HLG = 168165376; // 0xa060000
+ field public static final int DATASPACE_BT2020_PQ = 163971072; // 0x9c60000
+ field public static final int DATASPACE_BT601_525 = 281280512; // 0x10c40000
+ field public static final int DATASPACE_BT601_625 = 281149440; // 0x10c20000
+ field public static final int DATASPACE_BT709 = 281083904; // 0x10c10000
+ field public static final int DATASPACE_DCI_P3 = 155844608; // 0x94a0000
+ field public static final int DATASPACE_DEPTH = 4096; // 0x1000
+ field public static final int DATASPACE_DISPLAY_P3 = 143261696; // 0x88a0000
+ field public static final int DATASPACE_DYNAMIC_DEPTH = 4098; // 0x1002
+ field public static final int DATASPACE_HEIF = 4100; // 0x1004
+ field public static final int DATASPACE_JFIF = 146931712; // 0x8c20000
+ field public static final int DATASPACE_JPEG_R = 4101; // 0x1005
+ field public static final int DATASPACE_SCRGB = 411107328; // 0x18810000
+ field public static final int DATASPACE_SCRGB_LINEAR = 406913024; // 0x18410000
+ field public static final int DATASPACE_SRGB = 142671872; // 0x8810000
+ field public static final int DATASPACE_SRGB_LINEAR = 138477568; // 0x8410000
+ field public static final int DATASPACE_UNKNOWN = 0; // 0x0
+ field public static final int RANGE_EXTENDED = 402653184; // 0x18000000
+ field public static final int RANGE_FULL = 134217728; // 0x8000000
+ field public static final int RANGE_LIMITED = 268435456; // 0x10000000
+ field public static final int RANGE_UNSPECIFIED = 0; // 0x0
+ }
+
+ public static final class DataSpace.Companion {
+ }
+
+ @RequiresApi(android.os.Build.VERSION_CODES.KITKAT) public final class SyncFenceCompat implements java.lang.AutoCloseable {
+ method public boolean await(long timeoutNanos);
+ method public boolean awaitForever();
+ method public void close();
+ method public static androidx.hardware.SyncFenceCompat createNativeSyncFence();
+ method @RequiresApi(android.os.Build.VERSION_CODES.O) public long getSignalTimeNanos();
+ method public boolean isValid();
+ field public static final androidx.hardware.SyncFenceCompat.Companion Companion;
+ field public static final long SIGNAL_TIME_INVALID = -1L; // 0xffffffffffffffffL
+ field public static final long SIGNAL_TIME_PENDING = 9223372036854775807L; // 0x7fffffffffffffffL
+ }
+
+ public static final class SyncFenceCompat.Companion {
+ method public androidx.hardware.SyncFenceCompat createNativeSyncFence();
+ }
+
+}
+
+package androidx.opengl {
+
+ public final class EGLExt {
+ method public static int eglClientWaitSyncKHR(android.opengl.EGLDisplay eglDisplay, androidx.opengl.EGLSyncKHR sync, int flags, long timeoutNanos);
+ method @RequiresApi(android.os.Build.VERSION_CODES.O) public static androidx.opengl.EGLImageKHR? eglCreateImageFromHardwareBuffer(android.opengl.EGLDisplay eglDisplay, android.hardware.HardwareBuffer hardwareBuffer);
+ method public static androidx.opengl.EGLSyncKHR? eglCreateSyncKHR(android.opengl.EGLDisplay eglDisplay, int type, androidx.graphics.opengl.egl.EGLConfigAttributes? attributes);
+ method public static boolean eglDestroyImageKHR(android.opengl.EGLDisplay eglDisplay, androidx.opengl.EGLImageKHR image);
+ method public static boolean eglDestroySyncKHR(android.opengl.EGLDisplay eglDisplay, androidx.opengl.EGLSyncKHR eglSync);
+ method public static boolean eglGetSyncAttribKHR(android.opengl.EGLDisplay eglDisplay, androidx.opengl.EGLSyncKHR sync, int attribute, int[] value, int offset);
+ method public static void glEGLImageTargetTexture2DOES(int target, androidx.opengl.EGLImageKHR image);
+ method public static java.util.Set<java.lang.String> parseExtensions(String queryString);
+ field public static final androidx.opengl.EGLExt.Companion Companion;
+ field public static final String EGL_ANDROID_CLIENT_BUFFER = "EGL_ANDROID_get_native_client_buffer";
+ field public static final String EGL_ANDROID_IMAGE_NATIVE_BUFFER = "EGL_ANDROID_image_native_buffer";
+ field public static final String EGL_ANDROID_NATIVE_FENCE_SYNC = "EGL_ANDROID_native_fence_sync";
+ field public static final int EGL_CONDITION_SATISFIED_KHR = 12534; // 0x30f6
+ field public static final String EGL_EXT_BUFFER_AGE = "EGL_EXT_buffer_age";
+ field public static final String EGL_EXT_GL_COLORSPACE_BT2020_PQ = "EGL_EXT_gl_colorspace_bt2020_pq";
+ field public static final String EGL_EXT_GL_COLORSPACE_DISPLAY_P3_PASSTHROUGH = "EGL_EXT_gl_colorspace_display_p3_passthrough";
+ field public static final String EGL_EXT_GL_COLORSPACE_SCRGB = "EGL_EXT_gl_colorspace_scrgb";
+ field public static final String EGL_EXT_PIXEL_FORMAT_FLOAT = "EGL_EXT_pixel_format_float";
+ field public static final int EGL_FALSE = 0; // 0x0
+ field public static final long EGL_FOREVER_KHR = -1L; // 0xffffffffffffffffL
+ field public static final String EGL_IMG_CONTEXT_PRIORITY = "EGL_IMG_context_priority";
+ field public static final String EGL_KHR_FENCE_SYNC = "EGL_KHR_fence_sync";
+ field public static final String EGL_KHR_GL_COLORSPACE = "EGL_KHR_gl_colorspace";
+ field public static final String EGL_KHR_IMAGE = "EGL_KHR_image";
+ field public static final String EGL_KHR_IMAGE_BASE = "EGL_KHR_image_base";
+ field public static final String EGL_KHR_NO_CONFIG_CONTEXT = "EGL_KHR_no_config_context";
+ field public static final String EGL_KHR_PARTIAL_UPDATE = "EGL_KHR_partial_update";
+ field public static final String EGL_KHR_SURFACELESS_CONTEXT = "EGL_KHR_surfaceless_context";
+ field public static final String EGL_KHR_SWAP_BUFFERS_WITH_DAMAGE = "EGL_KHR_swap_buffers_with_damage";
+ field public static final String EGL_KHR_WAIT_SYNC = "EGL_KHR_wait_sync";
+ field public static final int EGL_SIGNALED_KHR = 12530; // 0x30f2
+ field public static final int EGL_SYNC_CONDITION_KHR = 12536; // 0x30f8
+ field public static final int EGL_SYNC_FENCE_KHR = 12537; // 0x30f9
+ field public static final int EGL_SYNC_FLUSH_COMMANDS_BIT_KHR = 1; // 0x1
+ field public static final int EGL_SYNC_NATIVE_FENCE_ANDROID = 12612; // 0x3144
+ field public static final int EGL_SYNC_PRIOR_COMMANDS_COMPLETE_KHR = 12528; // 0x30f0
+ field public static final int EGL_SYNC_STATUS_KHR = 12529; // 0x30f1
+ field public static final int EGL_SYNC_TYPE_KHR = 12535; // 0x30f7
+ field public static final int EGL_TIMEOUT_EXPIRED_KHR = 12533; // 0x30f5
+ field public static final int EGL_TRUE = 1; // 0x1
+ field public static final int EGL_UNSIGNALED_KHR = 12531; // 0x30f3
+ }
+
+ public static final class EGLExt.Companion {
+ method public int eglClientWaitSyncKHR(android.opengl.EGLDisplay eglDisplay, androidx.opengl.EGLSyncKHR sync, int flags, long timeoutNanos);
+ method @RequiresApi(android.os.Build.VERSION_CODES.O) public androidx.opengl.EGLImageKHR? eglCreateImageFromHardwareBuffer(android.opengl.EGLDisplay eglDisplay, android.hardware.HardwareBuffer hardwareBuffer);
+ method public androidx.opengl.EGLSyncKHR? eglCreateSyncKHR(android.opengl.EGLDisplay eglDisplay, int type, androidx.graphics.opengl.egl.EGLConfigAttributes? attributes);
+ method public boolean eglDestroyImageKHR(android.opengl.EGLDisplay eglDisplay, androidx.opengl.EGLImageKHR image);
+ method public boolean eglDestroySyncKHR(android.opengl.EGLDisplay eglDisplay, androidx.opengl.EGLSyncKHR eglSync);
+ method public boolean eglGetSyncAttribKHR(android.opengl.EGLDisplay eglDisplay, androidx.opengl.EGLSyncKHR sync, int attribute, int[] value, int offset);
+ method public void glEGLImageTargetTexture2DOES(int target, androidx.opengl.EGLImageKHR image);
+ method public java.util.Set<java.lang.String> parseExtensions(String queryString);
+ }
+
+ public interface EGLHandle {
+ method public long getNativeHandle();
+ property public abstract long nativeHandle;
+ }
+
+ public final class EGLImageKHR implements androidx.opengl.EGLHandle {
+ ctor public EGLImageKHR(long nativeHandle);
+ method public long getNativeHandle();
+ property public long nativeHandle;
+ }
+
+ public final class EGLSyncKHR implements androidx.opengl.EGLHandle {
+ ctor public EGLSyncKHR(long nativeHandle);
+ method public long getNativeHandle();
+ property public long nativeHandle;
+ }
+
+}
+
diff --git a/graphics/graphics-core/api/res-1.0.0-beta01.txt b/graphics/graphics-core/api/res-1.0.0-beta01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/graphics/graphics-core/api/res-1.0.0-beta01.txt
diff --git a/graphics/graphics-core/api/restricted_1.0.0-beta01.txt b/graphics/graphics-core/api/restricted_1.0.0-beta01.txt
new file mode 100644
index 0000000..74ac194
--- /dev/null
+++ b/graphics/graphics-core/api/restricted_1.0.0-beta01.txt
@@ -0,0 +1,560 @@
+// Signature format: 4.0
+package androidx.graphics {
+
+ @RequiresApi(android.os.Build.VERSION_CODES.Q) public final class CanvasBufferedRenderer implements java.lang.AutoCloseable {
+ method public void close();
+ method public int getBufferFormat();
+ method public int getMaxBuffers();
+ method public long getUsageFlags();
+ method public boolean isClosed();
+ method public androidx.graphics.CanvasBufferedRenderer.RenderRequest obtainRenderRequest();
+ method public void releaseBuffer(android.hardware.HardwareBuffer hardwareBuffer, androidx.hardware.SyncFenceCompat? fence);
+ method public void setContentRoot(android.graphics.RenderNode renderNode);
+ method public void setLightSourceAlpha(float ambientShadowAlpha, float spotShadowAlpha);
+ method public void setLightSourceGeometry(float lightX, float lightY, float lightZ, float lightRadius);
+ property public final int bufferFormat;
+ property public final int maxBuffers;
+ property public final long usageFlags;
+ }
+
+ public static final class CanvasBufferedRenderer.Builder {
+ ctor public CanvasBufferedRenderer.Builder(int width, int height);
+ method public androidx.graphics.CanvasBufferedRenderer build();
+ method public androidx.graphics.CanvasBufferedRenderer.Builder setBufferFormat(int format);
+ method public androidx.graphics.CanvasBufferedRenderer.Builder setMaxBuffers(@IntRange(from=1L, to=64L) int numBuffers);
+ method public androidx.graphics.CanvasBufferedRenderer.Builder setUsageFlags(long usageFlags);
+ }
+
+ public final class CanvasBufferedRenderer.RenderRequest {
+ method public void draw(java.util.concurrent.Executor executor, androidx.core.util.Consumer<androidx.graphics.CanvasBufferedRenderer.RenderResult> callback);
+ method public androidx.graphics.CanvasBufferedRenderer.RenderRequest preserveContents(boolean preserve);
+ method public androidx.graphics.CanvasBufferedRenderer.RenderRequest setBufferTransform(int bufferTransform);
+ method public androidx.graphics.CanvasBufferedRenderer.RenderRequest setColorSpace(android.graphics.ColorSpace? colorSpace);
+ }
+
+ public static final class CanvasBufferedRenderer.RenderResult {
+ ctor public CanvasBufferedRenderer.RenderResult(android.hardware.HardwareBuffer buffer, androidx.hardware.SyncFenceCompat? mFence, int mStatus);
+ method public androidx.hardware.SyncFenceCompat? getFence();
+ method public android.hardware.HardwareBuffer getHardwareBuffer();
+ method public int getStatus();
+ property public final androidx.hardware.SyncFenceCompat? fence;
+ property public final android.hardware.HardwareBuffer hardwareBuffer;
+ property public final int status;
+ field public static final androidx.graphics.CanvasBufferedRenderer.RenderResult.Companion Companion;
+ field public static final int ERROR_UNKNOWN = 1; // 0x1
+ field public static final int SUCCESS = 0; // 0x0
+ }
+
+ public static final class CanvasBufferedRenderer.RenderResult.Companion {
+ }
+
+}
+
+package androidx.graphics.lowlatency {
+
+ public final class BufferInfo {
+ method public int getFrameBufferId();
+ method public int getHeight();
+ method public int getWidth();
+ property public final int frameBufferId;
+ property public final int height;
+ property public final int width;
+ }
+
+ @RequiresApi(android.os.Build.VERSION_CODES.Q) public final class CanvasFrontBufferedRenderer<T> {
+ ctor public CanvasFrontBufferedRenderer(android.view.SurfaceView surfaceView, androidx.graphics.lowlatency.CanvasFrontBufferedRenderer.Callback<T> callback);
+ ctor public CanvasFrontBufferedRenderer(android.view.SurfaceView surfaceView, androidx.graphics.lowlatency.CanvasFrontBufferedRenderer.Callback<T> callback, optional int bufferFormat);
+ method public void cancel();
+ method public void clear();
+ method public void commit();
+ method public int getBufferFormat();
+ method public android.graphics.ColorSpace getColorSpace();
+ method public boolean isValid();
+ method public void release(boolean cancelPending);
+ method public void release(boolean cancelPending, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onReleaseComplete);
+ method public void renderFrontBufferedLayer(T param);
+ method public void renderMultiBufferedLayer(java.util.Collection<? extends T> params);
+ method public void setColorSpace(android.graphics.ColorSpace);
+ property public final int bufferFormat;
+ property public final android.graphics.ColorSpace colorSpace;
+ }
+
+ @kotlin.jvm.JvmDefaultWithCompatibility public static interface CanvasFrontBufferedRenderer.Callback<T> {
+ method @WorkerThread public void onDrawFrontBufferedLayer(android.graphics.Canvas canvas, int bufferWidth, int bufferHeight, T param);
+ method @WorkerThread public void onDrawMultiBufferedLayer(android.graphics.Canvas canvas, int bufferWidth, int bufferHeight, java.util.Collection<? extends T> params);
+ method @WorkerThread public default void onFrontBufferedLayerRenderComplete(androidx.graphics.surface.SurfaceControlCompat frontBufferedLayerSurfaceControl, androidx.graphics.surface.SurfaceControlCompat.Transaction transaction);
+ method @WorkerThread public default void onMultiBufferedLayerRenderComplete(androidx.graphics.surface.SurfaceControlCompat frontBufferedLayerSurfaceControl, androidx.graphics.surface.SurfaceControlCompat multiBufferedLayerSurfaceControl, androidx.graphics.surface.SurfaceControlCompat.Transaction transaction);
+ }
+
+ public final class FrontBufferSyncStrategy implements androidx.graphics.opengl.SyncStrategy {
+ ctor public FrontBufferSyncStrategy(long usageFlags);
+ method @RequiresApi(android.os.Build.VERSION_CODES.KITKAT) public androidx.hardware.SyncFenceCompat? createSyncFence(androidx.graphics.opengl.egl.EGLSpec eglSpec);
+ method public boolean isVisible();
+ method public void setVisible(boolean);
+ property public final boolean isVisible;
+ }
+
+ @RequiresApi(android.os.Build.VERSION_CODES.Q) public final class GLFrontBufferedRenderer<T> {
+ ctor public GLFrontBufferedRenderer(android.view.SurfaceView surfaceView, androidx.graphics.lowlatency.GLFrontBufferedRenderer.Callback<T> callback);
+ ctor public GLFrontBufferedRenderer(android.view.SurfaceView surfaceView, androidx.graphics.lowlatency.GLFrontBufferedRenderer.Callback<T> callback, optional androidx.graphics.opengl.GLRenderer? glRenderer);
+ ctor public GLFrontBufferedRenderer(android.view.SurfaceView surfaceView, androidx.graphics.lowlatency.GLFrontBufferedRenderer.Callback<T> callback, optional androidx.graphics.opengl.GLRenderer? glRenderer, optional int bufferFormat);
+ method public void cancel();
+ method public void clear();
+ method public void commit();
+ method public void execute(Runnable runnable);
+ method public int getBufferFormat();
+ method public boolean isValid();
+ method public void release(boolean cancelPending);
+ method public void release(boolean cancelPending, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onReleaseComplete);
+ method public void renderFrontBufferedLayer(T param);
+ method public void renderMultiBufferedLayer(java.util.Collection<? extends T> params);
+ property public final int bufferFormat;
+ }
+
+ @kotlin.jvm.JvmDefaultWithCompatibility public static interface GLFrontBufferedRenderer.Callback<T> {
+ method @WorkerThread public void onDrawFrontBufferedLayer(androidx.graphics.opengl.egl.EGLManager eglManager, int width, int height, androidx.graphics.lowlatency.BufferInfo bufferInfo, float[] transform, T param);
+ method @WorkerThread public void onDrawMultiBufferedLayer(androidx.graphics.opengl.egl.EGLManager eglManager, int width, int height, androidx.graphics.lowlatency.BufferInfo bufferInfo, float[] transform, java.util.Collection<? extends T> params);
+ method @WorkerThread public default void onFrontBufferedLayerRenderComplete(androidx.graphics.surface.SurfaceControlCompat frontBufferedLayerSurfaceControl, androidx.graphics.surface.SurfaceControlCompat.Transaction transaction);
+ method @WorkerThread public default void onMultiBufferedLayerRenderComplete(androidx.graphics.surface.SurfaceControlCompat frontBufferedLayerSurfaceControl, androidx.graphics.surface.SurfaceControlCompat multiBufferedLayerSurfaceControl, androidx.graphics.surface.SurfaceControlCompat.Transaction transaction);
+ }
+
+ @RequiresApi(android.os.Build.VERSION_CODES.Q) public final class LowLatencyCanvasView extends android.view.ViewGroup {
+ ctor public LowLatencyCanvasView(android.content.Context context);
+ ctor public LowLatencyCanvasView(android.content.Context context, optional android.util.AttributeSet? attrs);
+ ctor public LowLatencyCanvasView(android.content.Context context, optional android.util.AttributeSet? attrs, optional int defStyle);
+ method public void cancel();
+ method public void clear();
+ method public void commit();
+ method public void execute(Runnable runnable);
+ method public void renderFrontBufferedLayer();
+ method public void setRenderCallback(androidx.graphics.lowlatency.LowLatencyCanvasView.Callback? callback);
+ }
+
+ @kotlin.jvm.JvmDefaultWithCompatibility public static interface LowLatencyCanvasView.Callback {
+ method @WorkerThread public void onDrawFrontBufferedLayer(android.graphics.Canvas canvas, int width, int height);
+ method @WorkerThread public default void onFrontBufferedLayerRenderComplete(androidx.graphics.surface.SurfaceControlCompat frontBufferedLayerSurfaceControl, androidx.graphics.surface.SurfaceControlCompat.Transaction transaction);
+ method @WorkerThread public void onRedrawRequested(android.graphics.Canvas canvas, int width, int height);
+ }
+
+}
+
+package androidx.graphics.opengl {
+
+ @RequiresApi(android.os.Build.VERSION_CODES.O) public final class FrameBuffer implements java.lang.AutoCloseable {
+ ctor public FrameBuffer(androidx.graphics.opengl.egl.EGLSpec egl, android.hardware.HardwareBuffer hardwareBuffer);
+ method public void close();
+ method public android.hardware.HardwareBuffer getHardwareBuffer();
+ method public boolean isClosed();
+ method public void makeCurrent();
+ property public final android.hardware.HardwareBuffer hardwareBuffer;
+ property public final boolean isClosed;
+ }
+
+ @RequiresApi(android.os.Build.VERSION_CODES.O) public final class FrameBufferRenderer implements androidx.graphics.opengl.GLRenderer.RenderCallback {
+ ctor public FrameBufferRenderer(androidx.graphics.opengl.FrameBufferRenderer.RenderCallback frameBufferRendererCallbacks, optional androidx.graphics.opengl.SyncStrategy syncStrategy);
+ method public void clear();
+ method public void onDrawFrame(androidx.graphics.opengl.egl.EGLManager eglManager);
+ }
+
+ public static interface FrameBufferRenderer.RenderCallback {
+ method public androidx.graphics.opengl.FrameBuffer obtainFrameBuffer(androidx.graphics.opengl.egl.EGLSpec egl);
+ method public void onDraw(androidx.graphics.opengl.egl.EGLManager eglManager);
+ method public void onDrawComplete(androidx.graphics.opengl.FrameBuffer frameBuffer, androidx.hardware.SyncFenceCompat? syncFenceCompat);
+ }
+
+ @RequiresApi(android.os.Build.VERSION_CODES.Q) public final class GLFrameBufferRenderer {
+ method public void execute(Runnable runnable);
+ method public int getBufferFormat();
+ method public androidx.graphics.opengl.GLRenderer getGLRenderer();
+ method public int getMaxBuffers();
+ method public androidx.graphics.opengl.SyncStrategy getSyncStrategy();
+ method public long getUsageFlags();
+ method public boolean isValid();
+ method public void release(boolean cancelPending);
+ method public void release(boolean cancelPending, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onReleaseCallback);
+ method public void render();
+ property public final int bufferFormat;
+ property public final androidx.graphics.opengl.GLRenderer glRenderer;
+ property public final int maxBuffers;
+ property public final androidx.graphics.opengl.SyncStrategy syncStrategy;
+ property public final long usageFlags;
+ }
+
+ public static final class GLFrameBufferRenderer.Builder {
+ ctor public GLFrameBufferRenderer.Builder(android.view.SurfaceView surfaceView, androidx.graphics.opengl.GLFrameBufferRenderer.Callback callback);
+ method public androidx.graphics.opengl.GLFrameBufferRenderer build();
+ method public androidx.graphics.opengl.GLFrameBufferRenderer.Builder setBufferFormat(int format);
+ method public androidx.graphics.opengl.GLFrameBufferRenderer.Builder setGLRenderer(androidx.graphics.opengl.GLRenderer? glRenderer);
+ method public androidx.graphics.opengl.GLFrameBufferRenderer.Builder setMaxBuffers(@IntRange(from=1L, to=64L) int numBuffers);
+ method public androidx.graphics.opengl.GLFrameBufferRenderer.Builder setSyncStrategy(androidx.graphics.opengl.SyncStrategy syncStrategy);
+ method public androidx.graphics.opengl.GLFrameBufferRenderer.Builder setUsageFlags(long usageFlags);
+ }
+
+ public static interface GLFrameBufferRenderer.Callback {
+ method @WorkerThread public default void onBufferReleased(androidx.graphics.opengl.FrameBuffer frameBuffer, androidx.hardware.SyncFenceCompat? releaseFence);
+ method @WorkerThread public default void onDrawComplete(androidx.graphics.surface.SurfaceControlCompat targetSurfaceControl, androidx.graphics.surface.SurfaceControlCompat.Transaction transaction, androidx.graphics.opengl.FrameBuffer frameBuffer, androidx.hardware.SyncFenceCompat? syncFence);
+ method @WorkerThread public void onDrawFrame(androidx.graphics.opengl.egl.EGLManager eglManager, int width, int height, androidx.graphics.lowlatency.BufferInfo bufferInfo, float[] transform);
+ }
+
+ public final class GLRenderer {
+ ctor public GLRenderer(optional kotlin.jvm.functions.Function0<? extends androidx.graphics.opengl.egl.EGLSpec> eglSpecFactory, optional kotlin.jvm.functions.Function1<? super androidx.graphics.opengl.egl.EGLManager,? extends android.opengl.EGLConfig> eglConfigFactory);
+ method public androidx.graphics.opengl.GLRenderer.RenderTarget attach(android.view.Surface surface, int width, int height, androidx.graphics.opengl.GLRenderer.RenderCallback renderer);
+ method public androidx.graphics.opengl.GLRenderer.RenderTarget attach(android.view.SurfaceView surfaceView, androidx.graphics.opengl.GLRenderer.RenderCallback renderer);
+ method public androidx.graphics.opengl.GLRenderer.RenderTarget attach(android.view.TextureView textureView, androidx.graphics.opengl.GLRenderer.RenderCallback renderer);
+ method public androidx.graphics.opengl.GLRenderer.RenderTarget createRenderTarget(int width, int height, androidx.graphics.opengl.GLRenderer.RenderCallback renderer);
+ method public void detach(androidx.graphics.opengl.GLRenderer.RenderTarget target, boolean cancelPending);
+ method public void detach(androidx.graphics.opengl.GLRenderer.RenderTarget target, boolean cancelPending, optional @WorkerThread kotlin.jvm.functions.Function1<? super androidx.graphics.opengl.GLRenderer.RenderTarget,kotlin.Unit>? onDetachComplete);
+ method public void execute(Runnable runnable);
+ method public boolean isRunning();
+ method public void registerEGLContextCallback(androidx.graphics.opengl.GLRenderer.EGLContextCallback callback);
+ method public void requestRender(androidx.graphics.opengl.GLRenderer.RenderTarget target);
+ method public void requestRender(androidx.graphics.opengl.GLRenderer.RenderTarget target, optional kotlin.jvm.functions.Function1<? super androidx.graphics.opengl.GLRenderer.RenderTarget,kotlin.Unit>? onRenderComplete);
+ method public void resize(androidx.graphics.opengl.GLRenderer.RenderTarget target, int width, int height);
+ method public void resize(androidx.graphics.opengl.GLRenderer.RenderTarget target, int width, int height, optional kotlin.jvm.functions.Function1<? super androidx.graphics.opengl.GLRenderer.RenderTarget,kotlin.Unit>? onResizeComplete);
+ method public void start();
+ method public void start(optional String name);
+ method public void stop(boolean cancelPending);
+ method public void stop(boolean cancelPending, optional kotlin.jvm.functions.Function1<? super androidx.graphics.opengl.GLRenderer,kotlin.Unit>? onStop);
+ method public void unregisterEGLContextCallback(androidx.graphics.opengl.GLRenderer.EGLContextCallback callback);
+ field public static final androidx.graphics.opengl.GLRenderer.Companion Companion;
+ }
+
+ public static final class GLRenderer.Companion {
+ }
+
+ public static interface GLRenderer.EGLContextCallback {
+ method @WorkerThread public void onEGLContextCreated(androidx.graphics.opengl.egl.EGLManager eglManager);
+ method @WorkerThread public void onEGLContextDestroyed(androidx.graphics.opengl.egl.EGLManager eglManager);
+ }
+
+ @kotlin.jvm.JvmDefaultWithCompatibility public static interface GLRenderer.RenderCallback {
+ method @WorkerThread public void onDrawFrame(androidx.graphics.opengl.egl.EGLManager eglManager);
+ method @WorkerThread public default android.opengl.EGLSurface? onSurfaceCreated(androidx.graphics.opengl.egl.EGLSpec spec, android.opengl.EGLConfig config, android.view.Surface surface, int width, int height);
+ }
+
+ public static final class GLRenderer.RenderTarget {
+ method public void detach(boolean cancelPending);
+ method public void detach(boolean cancelPending, optional kotlin.jvm.functions.Function1<? super androidx.graphics.opengl.GLRenderer.RenderTarget,kotlin.Unit>? onDetachComplete);
+ method public boolean isAttached();
+ method public void requestRender();
+ method public void requestRender(optional @WorkerThread kotlin.jvm.functions.Function1<? super androidx.graphics.opengl.GLRenderer.RenderTarget,kotlin.Unit>? onRenderComplete);
+ method public void resize(int width, int height);
+ method public void resize(int width, int height, optional @WorkerThread kotlin.jvm.functions.Function1<? super androidx.graphics.opengl.GLRenderer.RenderTarget,kotlin.Unit>? onResizeComplete);
+ }
+
+ public interface SyncStrategy {
+ method @RequiresApi(android.os.Build.VERSION_CODES.KITKAT) public androidx.hardware.SyncFenceCompat? createSyncFence(androidx.graphics.opengl.egl.EGLSpec eglSpec);
+ field public static final androidx.graphics.opengl.SyncStrategy ALWAYS;
+ field public static final androidx.graphics.opengl.SyncStrategy.Companion Companion;
+ }
+
+ public static final class SyncStrategy.Companion {
+ }
+
+}
+
+package androidx.graphics.opengl.egl {
+
+ public final class EGLConfigAttributes {
+ method public int[] toArray();
+ field public static final androidx.graphics.opengl.egl.EGLConfigAttributes.Companion Companion;
+ field public static final int EGL_COLOR_COMPONENT_TYPE_EXT = 13113; // 0x3339
+ field public static final int EGL_COLOR_COMPONENT_TYPE_FIXED_EXT = 13114; // 0x333a
+ field public static final int EGL_COLOR_COMPONENT_TYPE_FLOAT_EXT = 13115; // 0x333b
+ field public static final androidx.graphics.opengl.egl.EGLConfigAttributes RGBA_1010102;
+ field public static final androidx.graphics.opengl.egl.EGLConfigAttributes RGBA_8888;
+ field public static final androidx.graphics.opengl.egl.EGLConfigAttributes RGBA_F16;
+ }
+
+ public static final class EGLConfigAttributes.Builder {
+ ctor @kotlin.PublishedApi internal EGLConfigAttributes.Builder();
+ method public androidx.graphics.opengl.egl.EGLConfigAttributes build();
+ method public void include(androidx.graphics.opengl.egl.EGLConfigAttributes attributes);
+ method public androidx.graphics.opengl.egl.EGLConfigAttributes.Builder setAttribute(int attribute, int value);
+ method @kotlin.jvm.JvmSynthetic public infix void to(int, int that);
+ }
+
+ public static final class EGLConfigAttributes.Companion {
+ }
+
+ public final class EGLConfigAttributesKt {
+ method @kotlin.jvm.JvmSynthetic public static inline androidx.graphics.opengl.egl.EGLConfigAttributes EGLConfigAttributes(kotlin.jvm.functions.Function1<? super androidx.graphics.opengl.egl.EGLConfigAttributes.Builder,kotlin.Unit> block);
+ }
+
+ public final class EGLException extends java.lang.RuntimeException {
+ ctor public EGLException(int error, optional String msg);
+ method public int getError();
+ method public String getMsg();
+ property public final int error;
+ property public String message;
+ property public final String msg;
+ }
+
+ public final class EGLManager {
+ ctor public EGLManager(optional androidx.graphics.opengl.egl.EGLSpec eglSpec);
+ method public android.opengl.EGLContext createContext(android.opengl.EGLConfig config);
+ method public android.opengl.EGLSurface getCurrentDrawSurface();
+ method public android.opengl.EGLSurface getCurrentReadSurface();
+ method public android.opengl.EGLSurface getDefaultSurface();
+ method public android.opengl.EGLConfig? getEGLConfig();
+ method public android.opengl.EGLContext? getEGLContext();
+ method public androidx.graphics.opengl.egl.EGLSpec getEGLSpec();
+ method public androidx.graphics.opengl.egl.EGLVersion getEGLVersion();
+ method public void initialize();
+ method public boolean isExtensionSupported(String extensionName);
+ method public android.opengl.EGLConfig? loadConfig(androidx.graphics.opengl.egl.EGLConfigAttributes configAttributes);
+ method public boolean makeCurrent(android.opengl.EGLSurface drawSurface);
+ method public boolean makeCurrent(android.opengl.EGLSurface drawSurface, optional android.opengl.EGLSurface readSurface);
+ method public void release();
+ method public void swapAndFlushBuffers();
+ property public final android.opengl.EGLSurface currentDrawSurface;
+ property public final android.opengl.EGLSurface currentReadSurface;
+ property public final android.opengl.EGLSurface defaultSurface;
+ property public final android.opengl.EGLConfig? eglConfig;
+ property public final android.opengl.EGLContext? eglContext;
+ property public final androidx.graphics.opengl.egl.EGLSpec eglSpec;
+ property public final androidx.graphics.opengl.egl.EGLVersion eglVersion;
+ field public static final androidx.graphics.opengl.egl.EGLManager.Companion Companion;
+ }
+
+ public static final class EGLManager.Companion {
+ }
+
+ @kotlin.jvm.JvmDefaultWithCompatibility public interface EGLSpec {
+ method public int eglClientWaitSyncKHR(androidx.opengl.EGLSyncKHR sync, int flags, long timeoutNanos);
+ method public android.opengl.EGLContext eglCreateContext(android.opengl.EGLConfig config);
+ method @RequiresApi(android.os.Build.VERSION_CODES.O) public androidx.opengl.EGLImageKHR? eglCreateImageFromHardwareBuffer(android.hardware.HardwareBuffer hardwareBuffer);
+ method public android.opengl.EGLSurface eglCreatePBufferSurface(android.opengl.EGLConfig config, androidx.graphics.opengl.egl.EGLConfigAttributes? configAttributes);
+ method public androidx.opengl.EGLSyncKHR? eglCreateSyncKHR(int type, androidx.graphics.opengl.egl.EGLConfigAttributes? attributes);
+ method public android.opengl.EGLSurface eglCreateWindowSurface(android.opengl.EGLConfig config, android.view.Surface surface, androidx.graphics.opengl.egl.EGLConfigAttributes? configAttributes);
+ method public void eglDestroyContext(android.opengl.EGLContext eglContext);
+ method public boolean eglDestroyImageKHR(androidx.opengl.EGLImageKHR image);
+ method public boolean eglDestroySurface(android.opengl.EGLSurface surface);
+ method public boolean eglDestroySyncKHR(androidx.opengl.EGLSyncKHR sync);
+ method public android.opengl.EGLSurface eglGetCurrentDrawSurface();
+ method public android.opengl.EGLSurface eglGetCurrentReadSurface();
+ method public int eglGetError();
+ method public boolean eglGetSyncAttribKHR(androidx.opengl.EGLSyncKHR sync, int attribute, int[] value, int offset);
+ method public androidx.graphics.opengl.egl.EGLVersion eglInitialize();
+ method public boolean eglMakeCurrent(android.opengl.EGLContext context, android.opengl.EGLSurface drawSurface, android.opengl.EGLSurface readSurface);
+ method public String eglQueryString(int nameId);
+ method public boolean eglQuerySurface(android.opengl.EGLSurface surface, int attribute, int[] result, int offset);
+ method public boolean eglSwapBuffers(android.opengl.EGLSurface surface);
+ method public default String getErrorMessage();
+ method public static String getStatusString(int error);
+ method public android.opengl.EGLConfig? loadConfig(androidx.graphics.opengl.egl.EGLConfigAttributes configAttributes);
+ field public static final androidx.graphics.opengl.egl.EGLSpec.Companion Companion;
+ field public static final androidx.graphics.opengl.egl.EGLSpec V14;
+ }
+
+ public static final class EGLSpec.Companion {
+ method public String getStatusString(int error);
+ }
+
+ public final class EGLVersion {
+ ctor public EGLVersion(int major, int minor);
+ method public int component1();
+ method public int component2();
+ method public androidx.graphics.opengl.egl.EGLVersion copy(int major, int minor);
+ method public int getMajor();
+ method public int getMinor();
+ property public final int major;
+ property public final int minor;
+ field public static final androidx.graphics.opengl.egl.EGLVersion.Companion Companion;
+ field public static final androidx.graphics.opengl.egl.EGLVersion Unknown;
+ field public static final androidx.graphics.opengl.egl.EGLVersion V14;
+ field public static final androidx.graphics.opengl.egl.EGLVersion V15;
+ }
+
+ public static final class EGLVersion.Companion {
+ }
+
+}
+
+package androidx.graphics.surface {
+
+ @RequiresApi(android.os.Build.VERSION_CODES.Q) public final class SurfaceControlCompat {
+ method public boolean isValid();
+ method public void release();
+ field public static final int BUFFER_TRANSFORM_IDENTITY = 0; // 0x0
+ field public static final int BUFFER_TRANSFORM_MIRROR_HORIZONTAL = 1; // 0x1
+ field public static final int BUFFER_TRANSFORM_MIRROR_VERTICAL = 2; // 0x2
+ field public static final int BUFFER_TRANSFORM_ROTATE_180 = 3; // 0x3
+ field public static final int BUFFER_TRANSFORM_ROTATE_270 = 7; // 0x7
+ field public static final int BUFFER_TRANSFORM_ROTATE_90 = 4; // 0x4
+ field public static final int CHANGE_FRAME_RATE_ALWAYS = 1; // 0x1
+ field public static final int CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS = 0; // 0x0
+ field public static final androidx.graphics.surface.SurfaceControlCompat.Companion Companion;
+ field public static final int FRAME_RATE_COMPATIBILITY_DEFAULT = 0; // 0x0
+ field public static final int FRAME_RATE_COMPATIBILITY_FIXED_SOURCE = 1; // 0x1
+ }
+
+ public static final class SurfaceControlCompat.Builder {
+ ctor public SurfaceControlCompat.Builder();
+ method public androidx.graphics.surface.SurfaceControlCompat build();
+ method public androidx.graphics.surface.SurfaceControlCompat.Builder setName(String name);
+ method public androidx.graphics.surface.SurfaceControlCompat.Builder setParent(android.view.SurfaceView surfaceView);
+ method public androidx.graphics.surface.SurfaceControlCompat.Builder setParent(androidx.graphics.surface.SurfaceControlCompat surfaceControl);
+ }
+
+ public static final class SurfaceControlCompat.Companion {
+ }
+
+ @RequiresApi(android.os.Build.VERSION_CODES.Q) public static final class SurfaceControlCompat.Transaction implements java.lang.AutoCloseable {
+ ctor public SurfaceControlCompat.Transaction();
+ method @RequiresApi(android.os.Build.VERSION_CODES.S) public androidx.graphics.surface.SurfaceControlCompat.Transaction addTransactionCommittedListener(java.util.concurrent.Executor executor, androidx.graphics.surface.SurfaceControlCompat.TransactionCommittedListener listener);
+ method public androidx.graphics.surface.SurfaceControlCompat.Transaction clearFrameRate(androidx.graphics.surface.SurfaceControlCompat surfaceControl);
+ method public void close();
+ method public void commit();
+ method @RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public void commitTransactionOnDraw(android.view.AttachedSurfaceControl attachedSurfaceControl);
+ method @RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public androidx.graphics.surface.SurfaceControlCompat.Transaction reparent(androidx.graphics.surface.SurfaceControlCompat surfaceControl, android.view.AttachedSurfaceControl attachedSurfaceControl);
+ method public androidx.graphics.surface.SurfaceControlCompat.Transaction reparent(androidx.graphics.surface.SurfaceControlCompat surfaceControl, androidx.graphics.surface.SurfaceControlCompat? newParent);
+ method public androidx.graphics.surface.SurfaceControlCompat.Transaction setAlpha(androidx.graphics.surface.SurfaceControlCompat surfaceControl, float alpha);
+ method public androidx.graphics.surface.SurfaceControlCompat.Transaction setBuffer(androidx.graphics.surface.SurfaceControlCompat surfaceControl, android.hardware.HardwareBuffer? buffer);
+ method public androidx.graphics.surface.SurfaceControlCompat.Transaction setBuffer(androidx.graphics.surface.SurfaceControlCompat surfaceControl, android.hardware.HardwareBuffer? buffer, optional androidx.hardware.SyncFenceCompat? fence);
+ method public androidx.graphics.surface.SurfaceControlCompat.Transaction setBuffer(androidx.graphics.surface.SurfaceControlCompat surfaceControl, android.hardware.HardwareBuffer? buffer, optional androidx.hardware.SyncFenceCompat? fence, optional kotlin.jvm.functions.Function1<? super androidx.hardware.SyncFenceCompat,kotlin.Unit>? releaseCallback);
+ method public androidx.graphics.surface.SurfaceControlCompat.Transaction setBufferTransform(androidx.graphics.surface.SurfaceControlCompat surfaceControl, int transformation);
+ method public androidx.graphics.surface.SurfaceControlCompat.Transaction setCrop(androidx.graphics.surface.SurfaceControlCompat surfaceControl, android.graphics.Rect? crop);
+ method public androidx.graphics.surface.SurfaceControlCompat.Transaction setDamageRegion(androidx.graphics.surface.SurfaceControlCompat surfaceControl, android.graphics.Region? region);
+ method public androidx.graphics.surface.SurfaceControlCompat.Transaction setDataSpace(androidx.graphics.surface.SurfaceControlCompat surfaceControl, int dataSpace);
+ method @RequiresApi(android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE) public androidx.graphics.surface.SurfaceControlCompat.Transaction setExtendedRangeBrightness(androidx.graphics.surface.SurfaceControlCompat surfaceControl, @FloatRange(from=1.0, fromInclusive=true) float currentBufferRatio, @FloatRange(from=1.0, fromInclusive=true) float desiredRatio);
+ method public androidx.graphics.surface.SurfaceControlCompat.Transaction setFrameRate(androidx.graphics.surface.SurfaceControlCompat surfaceControl, float frameRate, int compatibility, int changeFrameRateStrategy);
+ method public androidx.graphics.surface.SurfaceControlCompat.Transaction setLayer(androidx.graphics.surface.SurfaceControlCompat surfaceControl, int z);
+ method public androidx.graphics.surface.SurfaceControlCompat.Transaction setOpaque(androidx.graphics.surface.SurfaceControlCompat surfaceControl, boolean isOpaque);
+ method public androidx.graphics.surface.SurfaceControlCompat.Transaction setPosition(androidx.graphics.surface.SurfaceControlCompat surfaceControl, float x, float y);
+ method public androidx.graphics.surface.SurfaceControlCompat.Transaction setScale(androidx.graphics.surface.SurfaceControlCompat surfaceControl, float scaleX, float scaleY);
+ method public androidx.graphics.surface.SurfaceControlCompat.Transaction setVisibility(androidx.graphics.surface.SurfaceControlCompat surfaceControl, boolean visible);
+ }
+
+ public static interface SurfaceControlCompat.TransactionCommittedListener {
+ method public void onTransactionCommitted();
+ }
+
+}
+
+package androidx.hardware {
+
+ public final class DataSpace {
+ field public static final androidx.hardware.DataSpace.Companion Companion;
+ field public static final int DATASPACE_ADOBE_RGB = 151715840; // 0x90b0000
+ field public static final int DATASPACE_BT2020 = 147193856; // 0x8c60000
+ field public static final int DATASPACE_BT2020_HLG = 168165376; // 0xa060000
+ field public static final int DATASPACE_BT2020_PQ = 163971072; // 0x9c60000
+ field public static final int DATASPACE_BT601_525 = 281280512; // 0x10c40000
+ field public static final int DATASPACE_BT601_625 = 281149440; // 0x10c20000
+ field public static final int DATASPACE_BT709 = 281083904; // 0x10c10000
+ field public static final int DATASPACE_DCI_P3 = 155844608; // 0x94a0000
+ field public static final int DATASPACE_DEPTH = 4096; // 0x1000
+ field public static final int DATASPACE_DISPLAY_P3 = 143261696; // 0x88a0000
+ field public static final int DATASPACE_DYNAMIC_DEPTH = 4098; // 0x1002
+ field public static final int DATASPACE_HEIF = 4100; // 0x1004
+ field public static final int DATASPACE_JFIF = 146931712; // 0x8c20000
+ field public static final int DATASPACE_JPEG_R = 4101; // 0x1005
+ field public static final int DATASPACE_SCRGB = 411107328; // 0x18810000
+ field public static final int DATASPACE_SCRGB_LINEAR = 406913024; // 0x18410000
+ field public static final int DATASPACE_SRGB = 142671872; // 0x8810000
+ field public static final int DATASPACE_SRGB_LINEAR = 138477568; // 0x8410000
+ field public static final int DATASPACE_UNKNOWN = 0; // 0x0
+ field public static final int RANGE_EXTENDED = 402653184; // 0x18000000
+ field public static final int RANGE_FULL = 134217728; // 0x8000000
+ field public static final int RANGE_LIMITED = 268435456; // 0x10000000
+ field public static final int RANGE_UNSPECIFIED = 0; // 0x0
+ }
+
+ public static final class DataSpace.Companion {
+ }
+
+ @RequiresApi(android.os.Build.VERSION_CODES.KITKAT) public final class SyncFenceCompat implements java.lang.AutoCloseable {
+ method public boolean await(long timeoutNanos);
+ method public boolean awaitForever();
+ method public void close();
+ method public static androidx.hardware.SyncFenceCompat createNativeSyncFence();
+ method @RequiresApi(android.os.Build.VERSION_CODES.O) public long getSignalTimeNanos();
+ method public boolean isValid();
+ field public static final androidx.hardware.SyncFenceCompat.Companion Companion;
+ field public static final long SIGNAL_TIME_INVALID = -1L; // 0xffffffffffffffffL
+ field public static final long SIGNAL_TIME_PENDING = 9223372036854775807L; // 0x7fffffffffffffffL
+ }
+
+ public static final class SyncFenceCompat.Companion {
+ method public androidx.hardware.SyncFenceCompat createNativeSyncFence();
+ }
+
+}
+
+package androidx.opengl {
+
+ public final class EGLExt {
+ method public static int eglClientWaitSyncKHR(android.opengl.EGLDisplay eglDisplay, androidx.opengl.EGLSyncKHR sync, int flags, long timeoutNanos);
+ method @RequiresApi(android.os.Build.VERSION_CODES.O) public static androidx.opengl.EGLImageKHR? eglCreateImageFromHardwareBuffer(android.opengl.EGLDisplay eglDisplay, android.hardware.HardwareBuffer hardwareBuffer);
+ method public static androidx.opengl.EGLSyncKHR? eglCreateSyncKHR(android.opengl.EGLDisplay eglDisplay, int type, androidx.graphics.opengl.egl.EGLConfigAttributes? attributes);
+ method public static boolean eglDestroyImageKHR(android.opengl.EGLDisplay eglDisplay, androidx.opengl.EGLImageKHR image);
+ method public static boolean eglDestroySyncKHR(android.opengl.EGLDisplay eglDisplay, androidx.opengl.EGLSyncKHR eglSync);
+ method public static boolean eglGetSyncAttribKHR(android.opengl.EGLDisplay eglDisplay, androidx.opengl.EGLSyncKHR sync, int attribute, int[] value, int offset);
+ method public static void glEGLImageTargetTexture2DOES(int target, androidx.opengl.EGLImageKHR image);
+ method public static java.util.Set<java.lang.String> parseExtensions(String queryString);
+ field public static final androidx.opengl.EGLExt.Companion Companion;
+ field public static final String EGL_ANDROID_CLIENT_BUFFER = "EGL_ANDROID_get_native_client_buffer";
+ field public static final String EGL_ANDROID_IMAGE_NATIVE_BUFFER = "EGL_ANDROID_image_native_buffer";
+ field public static final String EGL_ANDROID_NATIVE_FENCE_SYNC = "EGL_ANDROID_native_fence_sync";
+ field public static final int EGL_CONDITION_SATISFIED_KHR = 12534; // 0x30f6
+ field public static final String EGL_EXT_BUFFER_AGE = "EGL_EXT_buffer_age";
+ field public static final String EGL_EXT_GL_COLORSPACE_BT2020_PQ = "EGL_EXT_gl_colorspace_bt2020_pq";
+ field public static final String EGL_EXT_GL_COLORSPACE_DISPLAY_P3_PASSTHROUGH = "EGL_EXT_gl_colorspace_display_p3_passthrough";
+ field public static final String EGL_EXT_GL_COLORSPACE_SCRGB = "EGL_EXT_gl_colorspace_scrgb";
+ field public static final String EGL_EXT_PIXEL_FORMAT_FLOAT = "EGL_EXT_pixel_format_float";
+ field public static final int EGL_FALSE = 0; // 0x0
+ field public static final long EGL_FOREVER_KHR = -1L; // 0xffffffffffffffffL
+ field public static final String EGL_IMG_CONTEXT_PRIORITY = "EGL_IMG_context_priority";
+ field public static final String EGL_KHR_FENCE_SYNC = "EGL_KHR_fence_sync";
+ field public static final String EGL_KHR_GL_COLORSPACE = "EGL_KHR_gl_colorspace";
+ field public static final String EGL_KHR_IMAGE = "EGL_KHR_image";
+ field public static final String EGL_KHR_IMAGE_BASE = "EGL_KHR_image_base";
+ field public static final String EGL_KHR_NO_CONFIG_CONTEXT = "EGL_KHR_no_config_context";
+ field public static final String EGL_KHR_PARTIAL_UPDATE = "EGL_KHR_partial_update";
+ field public static final String EGL_KHR_SURFACELESS_CONTEXT = "EGL_KHR_surfaceless_context";
+ field public static final String EGL_KHR_SWAP_BUFFERS_WITH_DAMAGE = "EGL_KHR_swap_buffers_with_damage";
+ field public static final String EGL_KHR_WAIT_SYNC = "EGL_KHR_wait_sync";
+ field public static final int EGL_SIGNALED_KHR = 12530; // 0x30f2
+ field public static final int EGL_SYNC_CONDITION_KHR = 12536; // 0x30f8
+ field public static final int EGL_SYNC_FENCE_KHR = 12537; // 0x30f9
+ field public static final int EGL_SYNC_FLUSH_COMMANDS_BIT_KHR = 1; // 0x1
+ field public static final int EGL_SYNC_NATIVE_FENCE_ANDROID = 12612; // 0x3144
+ field public static final int EGL_SYNC_PRIOR_COMMANDS_COMPLETE_KHR = 12528; // 0x30f0
+ field public static final int EGL_SYNC_STATUS_KHR = 12529; // 0x30f1
+ field public static final int EGL_SYNC_TYPE_KHR = 12535; // 0x30f7
+ field public static final int EGL_TIMEOUT_EXPIRED_KHR = 12533; // 0x30f5
+ field public static final int EGL_TRUE = 1; // 0x1
+ field public static final int EGL_UNSIGNALED_KHR = 12531; // 0x30f3
+ }
+
+ public static final class EGLExt.Companion {
+ method public int eglClientWaitSyncKHR(android.opengl.EGLDisplay eglDisplay, androidx.opengl.EGLSyncKHR sync, int flags, long timeoutNanos);
+ method @RequiresApi(android.os.Build.VERSION_CODES.O) public androidx.opengl.EGLImageKHR? eglCreateImageFromHardwareBuffer(android.opengl.EGLDisplay eglDisplay, android.hardware.HardwareBuffer hardwareBuffer);
+ method public androidx.opengl.EGLSyncKHR? eglCreateSyncKHR(android.opengl.EGLDisplay eglDisplay, int type, androidx.graphics.opengl.egl.EGLConfigAttributes? attributes);
+ method public boolean eglDestroyImageKHR(android.opengl.EGLDisplay eglDisplay, androidx.opengl.EGLImageKHR image);
+ method public boolean eglDestroySyncKHR(android.opengl.EGLDisplay eglDisplay, androidx.opengl.EGLSyncKHR eglSync);
+ method public boolean eglGetSyncAttribKHR(android.opengl.EGLDisplay eglDisplay, androidx.opengl.EGLSyncKHR sync, int attribute, int[] value, int offset);
+ method public void glEGLImageTargetTexture2DOES(int target, androidx.opengl.EGLImageKHR image);
+ method public java.util.Set<java.lang.String> parseExtensions(String queryString);
+ }
+
+ public interface EGLHandle {
+ method public long getNativeHandle();
+ property public abstract long nativeHandle;
+ }
+
+ public final class EGLImageKHR implements androidx.opengl.EGLHandle {
+ ctor public EGLImageKHR(long nativeHandle);
+ method public long getNativeHandle();
+ property public long nativeHandle;
+ }
+
+ public final class EGLSyncKHR implements androidx.opengl.EGLHandle {
+ ctor public EGLSyncKHR(long nativeHandle);
+ method public long getNativeHandle();
+ property public long nativeHandle;
+ }
+
+}
+
diff --git a/health/connect/connect-client/src/test/java/androidx/health/connect/client/HealthConnectClientTest.kt b/health/connect/connect-client/src/test/java/androidx/health/connect/client/HealthConnectClientTest.kt
index 95d9889..0299054 100644
--- a/health/connect/connect-client/src/test/java/androidx/health/connect/client/HealthConnectClientTest.kt
+++ b/health/connect/connect-client/src/test/java/androidx/health/connect/client/HealthConnectClientTest.kt
@@ -15,6 +15,7 @@
*/
package androidx.health.connect.client
+import android.app.Application
import android.content.ComponentName
import android.content.Context
import android.content.IntentFilter
@@ -27,6 +28,8 @@
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.google.common.truth.Truth.assertThat
+import kotlin.test.assertFailsWith
+import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertThrows
import org.junit.Before
import org.junit.Ignore
@@ -222,6 +225,29 @@
.isNotNull()
}
+ @Test
+ @Config(sdk = [Build.VERSION_CODES.TIRAMISU])
+ fun unbindableService_callThrowsIllegalStateException() {
+ installPackage(
+ context,
+ HealthConnectClient.DEFAULT_PROVIDER_PACKAGE_NAME,
+ versionCode = HealthConnectClient.DEFAULT_PROVIDER_MIN_VERSION_CODE,
+ enabled = true
+ )
+ installService(context, HealthConnectClient.DEFAULT_PROVIDER_PACKAGE_NAME)
+ shadowOf(context.applicationContext as Application)
+ .declareActionUnbindable(HealthDataService.ANDROID_HEALTH_PLATFORM_SERVICE_BIND_ACTION)
+
+ val permissionController =
+ HealthConnectClient.getOrCreate(context).permissionController
+ assertFailsWith<IllegalStateException> {
+ runBlocking {
+ // Simplest API call that requires no parameters
+ permissionController.getGrantedPermissions()
+ }
+ }
+ }
+
private fun installPackage(
context: Context,
packageName: String,
diff --git a/input/input-motionprediction/src/main/java/androidx/input/motionprediction/MotionEventPredictor.java b/input/input-motionprediction/src/main/java/androidx/input/motionprediction/MotionEventPredictor.java
index 6dcaaa3..3403d9bf 100644
--- a/input/input-motionprediction/src/main/java/androidx/input/motionprediction/MotionEventPredictor.java
+++ b/input/input-motionprediction/src/main/java/androidx/input/motionprediction/MotionEventPredictor.java
@@ -23,7 +23,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import androidx.input.motionprediction.common.SystemProperty;
+import androidx.input.motionprediction.common.Configuration;
import androidx.input.motionprediction.kalman.KalmanMotionEventPredictor;
import androidx.input.motionprediction.system.SystemMotionEventPredictor;
@@ -73,7 +73,7 @@
static MotionEventPredictor newInstance(@NonNull View view) {
Context context = view.getContext();
if (Build.VERSION.SDK_INT >= 34
- && SystemProperty.getBoolean("debug.input.prefer_system_prediction")) {
+ && Configuration.getInstance().preferSystemPrediction()) {
return SystemMotionEventPredictor.newInstance(context);
} else {
return new KalmanMotionEventPredictor(context);
diff --git a/input/input-motionprediction/src/main/java/androidx/input/motionprediction/common/Configuration.java b/input/input-motionprediction/src/main/java/androidx/input/motionprediction/common/Configuration.java
new file mode 100644
index 0000000..c67cfcf
--- /dev/null
+++ b/input/input-motionprediction/src/main/java/androidx/input/motionprediction/common/Configuration.java
@@ -0,0 +1,74 @@
+/*
+ * 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.input.motionprediction.common;
+
+import static androidx.annotation.RestrictTo.Scope.LIBRARY;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
+
+/**
+ */
+@RestrictTo(LIBRARY)
+public class Configuration {
+ private static volatile Configuration sInstance = null;
+ private static final Object sLock = new Object();
+
+ private boolean mPreferSystemPrediction;
+ private int mPredictionOffset;
+
+ /**
+ * Returns the configuration for prediction in this system.
+ *
+ * @return the prediction configuration
+ */
+ public static @NonNull Configuration getInstance() {
+ if (sInstance == null) {
+ synchronized (sLock) {
+ if (sInstance == null) {
+ sInstance = new Configuration();
+ }
+ }
+ }
+ return sInstance;
+ }
+
+ private Configuration() {
+ // This class is non-instantiable externally.
+ mPreferSystemPrediction = SystemProperty
+ .getBoolean("debug.input.androidx_prefer_system_prediction");
+ mPredictionOffset = SystemProperty.getInt("debug.input.androidx_prediction_offset");
+ }
+
+ /**
+ * Returns whether or not the library should prefer the system prediction when available
+ *
+ * @return true if the system prediction should be used when available
+ */
+ public boolean preferSystemPrediction() {
+ return mPreferSystemPrediction;
+ }
+
+ /**
+ * Returns the number of milliseconds to add to the computed prediction target
+ *
+ * @return number of additional milliseconds to predict
+ */
+ public int predictionOffset() {
+ return mPredictionOffset;
+ }
+}
diff --git a/input/input-motionprediction/src/main/java/androidx/input/motionprediction/common/PredictionEstimator.java b/input/input-motionprediction/src/main/java/androidx/input/motionprediction/common/PredictionEstimator.java
index 3376643..6bbd88c 100644
--- a/input/input-motionprediction/src/main/java/androidx/input/motionprediction/common/PredictionEstimator.java
+++ b/input/input-motionprediction/src/main/java/androidx/input/motionprediction/common/PredictionEstimator.java
@@ -41,9 +41,11 @@
private long mLastEventTime = -1;
private final float mFrameTimeMs;
+ private final int mOffsetMs;
public PredictionEstimator(@NonNull Context context) {
mFrameTimeMs = getFastestFrameTimeMs(context);
+ mOffsetMs = Configuration.getInstance().predictionOffset();
}
/** Records the needed information from the event to calculate the prediction. */
@@ -54,12 +56,12 @@
/** Return the estimated amount of prediction needed. */
public int estimate() {
if (mLastEventTime <= 0) {
- return (int) mFrameTimeMs;
+ return ((int) mFrameTimeMs) + mOffsetMs;
}
// The amount of prediction is the estimated amount of time it will take to land the
// information on the screen from now, plus the time since the last recorded MotionEvent
int estimatedMs = (int) (SystemClock.uptimeMillis() - mLastEventTime + mFrameTimeMs);
- return Math.min(MAX_PREDICTION_MS, estimatedMs);
+ return Math.min(MAX_PREDICTION_MS, estimatedMs + mOffsetMs);
}
private Display getDisplayForContext(Context context) {
diff --git a/input/input-motionprediction/src/main/java/androidx/input/motionprediction/common/SystemProperty.java b/input/input-motionprediction/src/main/java/androidx/input/motionprediction/common/SystemProperty.java
index 2899359..6375163 100644
--- a/input/input-motionprediction/src/main/java/androidx/input/motionprediction/common/SystemProperty.java
+++ b/input/input-motionprediction/src/main/java/androidx/input/motionprediction/common/SystemProperty.java
@@ -29,7 +29,8 @@
*/
@RestrictTo(LIBRARY)
public class SystemProperty {
- private static final boolean PROPERTY_DEFAULT = false;
+ private static final boolean BOOLEAN_PROPERTY_DEFAULT = false;
+ private static final int INT_PROPERTY_DEFAULT = 0;
private SystemProperty() {
// This class is non-instantiable.
@@ -50,12 +51,42 @@
String.class,
boolean.class);
@SuppressLint("BanUncheckedReflection")
- Boolean result = (Boolean) getMethod.invoke(systemProperties, name, PROPERTY_DEFAULT);
+ Boolean result = (Boolean) getMethod.invoke(
+ systemProperties,
+ name,
+ BOOLEAN_PROPERTY_DEFAULT);
if (result != null) {
return result.booleanValue();
}
} catch (Exception e) {
}
- return false;
+ return BOOLEAN_PROPERTY_DEFAULT;
+ }
+
+ /**
+ * Reads a system property and returns its integer value.
+ *
+ * @param name the name of the system property
+ * @return the integer value of the property if defined, zero otherwise
+ */
+ public static int getInt(@NonNull String name) {
+ try {
+ @SuppressLint("PrivateApi")
+ Class<?> systemProperties = Class.forName("android.os.SystemProperties");
+ Method getMethod = systemProperties.getMethod(
+ "getInt",
+ String.class,
+ int.class);
+ @SuppressLint("BanUncheckedReflection")
+ Integer result = (Integer) getMethod.invoke(
+ systemProperties,
+ name,
+ INT_PROPERTY_DEFAULT);
+ if (result != null) {
+ return result.intValue();
+ }
+ } catch (Exception e) {
+ }
+ return INT_PROPERTY_DEFAULT;
}
}
diff --git a/libraryversions.toml b/libraryversions.toml
index 7a171cb..26d314d 100644
--- a/libraryversions.toml
+++ b/libraryversions.toml
@@ -67,7 +67,7 @@
GLANCE_PREVIEW = "1.0.0-alpha06"
GLANCE_TEMPLATE = "1.0.0-alpha06"
GLANCE_WEAR_TILES = "1.0.0-alpha06"
-GRAPHICS_CORE = "1.0.0-alpha06"
+GRAPHICS_CORE = "1.0.0-beta01"
GRAPHICS_FILTERS = "1.0.0-alpha01"
GRAPHICS_PATH = "1.0.0-beta01"
GRAPHICS_SHAPES = "1.0.0-alpha03"
@@ -94,7 +94,7 @@
LIFECYCLE_EXTENSIONS = "2.2.0"
LOADER = "1.2.0-alpha01"
MEDIA = "1.7.0-rc01"
-MEDIA2 = "1.3.0-beta01"
+MEDIA2 = "1.3.0-rc01"
MEDIAROUTER = "1.7.0-beta01"
METRICS = "1.0.0-alpha05"
NAVIGATION = "2.8.0-alpha01"
@@ -146,7 +146,7 @@
VECTORDRAWABLE = "1.2.0-rc01"
VECTORDRAWABLE_ANIMATED = "1.2.0-rc01"
VECTORDRAWABLE_SEEKABLE = "1.0.0-rc01"
-VERSIONED_PARCELABLE = "1.2.0-beta01"
+VERSIONED_PARCELABLE = "1.2.0-rc01"
VIEWPAGER = "1.1.0-alpha02"
VIEWPAGER2 = "1.1.0-beta03"
WEAR = "1.4.0-alpha01"
diff --git a/media2/media2-session/src/androidTest/java/androidx/media2/session/MediaControllerTest.java b/media2/media2-session/src/androidTest/java/androidx/media2/session/MediaControllerTest.java
index 47e8b34..603f846 100644
--- a/media2/media2-session/src/androidTest/java/androidx/media2/session/MediaControllerTest.java
+++ b/media2/media2-session/src/androidTest/java/androidx/media2/session/MediaControllerTest.java
@@ -82,7 +82,7 @@
// TODO(jaewan): Implement host-side test so controller and session can run in different processes.
// TODO(jaewan): Fix flaky failure -- see MediaControllerImpl.getController()
// TODO(jaeawn): Revisit create/close session in the sHandler. It's no longer necessary.
-@SdkSuppress(maxSdkVersion = 32, minSdkVersion = 19) // b/244312419 and b/259936005
+@SdkSuppress(maxSdkVersion = 32) // b/244312419 and b/259936005
@RunWith(AndroidJUnit4.class)
@LargeTest
@FlakyTest
diff --git a/media2/media2-session/src/androidTest/java/androidx/media2/session/MediaController_SurfaceTest.java b/media2/media2-session/src/androidTest/java/androidx/media2/session/MediaController_SurfaceTest.java
index 6c602f0..7fa8fc8 100644
--- a/media2/media2-session/src/androidTest/java/androidx/media2/session/MediaController_SurfaceTest.java
+++ b/media2/media2-session/src/androidTest/java/androidx/media2/session/MediaController_SurfaceTest.java
@@ -47,7 +47,7 @@
/**
* Tests {@link MediaController#setSurface(Surface)}.
*/
-@SdkSuppress(maxSdkVersion = 32, minSdkVersion = 19) // b/244312419 and b/259936005
+@SdkSuppress(maxSdkVersion = 32) // b/244312419 and b/259936005
@RunWith(AndroidJUnit4.class)
@LargeTest
public class MediaController_SurfaceTest extends MediaSessionTestBase {
diff --git a/media2/media2-session/src/androidTest/java/androidx/media2/session/MediaSessionTest.java b/media2/media2-session/src/androidTest/java/androidx/media2/session/MediaSessionTest.java
index d4ba3c3..fff5512 100644
--- a/media2/media2-session/src/androidTest/java/androidx/media2/session/MediaSessionTest.java
+++ b/media2/media2-session/src/androidTest/java/androidx/media2/session/MediaSessionTest.java
@@ -71,7 +71,7 @@
/**
* Tests {@link MediaSession}.
*/
-@SdkSuppress(maxSdkVersion = 32, minSdkVersion = 19) // b/244312419 and b/259936005
+@SdkSuppress(maxSdkVersion = 32) // b/244312419 and b/259936005
@RunWith(AndroidJUnit4.class)
@LargeTest
public class MediaSessionTest extends MediaSessionTestBase {
diff --git a/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavControllerRouteTest.kt b/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavControllerRouteTest.kt
index e04dfa4..1ac6d52 100644
--- a/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavControllerRouteTest.kt
+++ b/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavControllerRouteTest.kt
@@ -44,7 +44,6 @@
import androidx.test.ext.truth.os.BundleSubject.assertThat
import androidx.test.filters.LargeTest
import androidx.test.filters.MediumTest
-import androidx.test.filters.SdkSuppress
import androidx.testutils.TestNavigator
import androidx.testutils.test
import com.google.common.truth.Truth.assertThat
@@ -1605,7 +1604,6 @@
@LargeTest
@Test
- @SdkSuppress(minSdkVersion = 17)
fun testNavigateViaImplicitDeepLink() {
val intent = Intent(
Intent.ACTION_VIEW,
diff --git a/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavControllerTest.kt b/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavControllerTest.kt
index c4df050..2bc9065 100644
--- a/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavControllerTest.kt
+++ b/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavControllerTest.kt
@@ -52,7 +52,6 @@
import androidx.test.ext.truth.os.BundleSubject.assertThat
import androidx.test.filters.LargeTest
import androidx.test.filters.MediumTest
-import androidx.test.filters.SdkSuppress
import androidx.testutils.TestNavigator
import androidx.testutils.test
import androidx.testutils.withActivity
@@ -1025,7 +1024,6 @@
@LargeTest
@Test
- @SdkSuppress(minSdkVersion = 17)
fun testNavigateViaImplicitDeepLink() {
val intent = Intent(
Intent.ACTION_VIEW,
@@ -1119,7 +1117,6 @@
@LargeTest
@Test
- @SdkSuppress(minSdkVersion = 17)
fun testExplicitDeepLinkNavigateUpOffOtherTaskStack() {
val navDeepLinkBuilder = NavDeepLinkBuilder(
ApplicationProvider.getApplicationContext()
@@ -1492,7 +1489,6 @@
@LargeTest
@Test
- @SdkSuppress(minSdkVersion = 17)
fun testExplicitDeepLinkSeparateNavGraph() {
val navDeepLinkBuilder = NavDeepLinkBuilder(
ApplicationProvider.getApplicationContext()
diff --git a/profileinstaller/profileinstaller/src/main/java/androidx/profileinstaller/ProfileTranscoder.java b/profileinstaller/profileinstaller/src/main/java/androidx/profileinstaller/ProfileTranscoder.java
index 700a737..adf0f86 100644
--- a/profileinstaller/profileinstaller/src/main/java/androidx/profileinstaller/ProfileTranscoder.java
+++ b/profileinstaller/profileinstaller/src/main/java/androidx/profileinstaller/ProfileTranscoder.java
@@ -38,7 +38,6 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
@@ -52,7 +51,6 @@
import java.util.Map;
import java.util.TreeMap;
-@RequiresApi(19)
class ProfileTranscoder {
private ProfileTranscoder() {
}
@@ -219,7 +217,7 @@
* (M|dex_map_size)
* type_index_diff[dex_map_size]
* where `M` stands for special encodings indicating missing types (kIsMissingTypesEncoding)
- * or memamorphic call (kIsMegamorphicEncoding) which both imply `dex_map_size == 0`.
+ * or megamorphic call (kIsMegamorphicEncoding) which both imply `dex_map_size == 0`.
*/
private static void writeProfileForS(
@NonNull OutputStream os,
@@ -371,7 +369,7 @@
// Method Flags
int methodFlags = computeMethodFlags(profile);
// Bitmap Contents
- byte[] bitmapContents = createMethodBitmapRegion(profile);
+ byte[] bitmapContents = createMethodBitmapRegionForS(methodFlags, profile);
// Methods with Inline Caches
byte[] methodRegionContents = createMethodsWithInlineCaches(profile);
// Profile Index
@@ -404,11 +402,12 @@
}
}
- private static byte[] createMethodBitmapRegion(
+ private static byte[] createMethodBitmapRegionForS(
+ int methodFlags,
@NonNull DexProfileData profile
) throws IOException {
try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
- writeMethodBitmap(out, profile);
+ writeMethodBitmapForS(out, methodFlags, profile);
return out.toByteArray();
}
}
@@ -543,8 +542,8 @@
}
/**
- * Create compressable body only for V0.1.0 v0.0.9.
- *
+ * Create compressible body only for V0.1.0 v0.0.9.
+ * <p>
* For 0.1.0 this will write header/header/header/body/body/body
* For 0.0.9 this will write header/body/header/body/header/body
*/
@@ -613,6 +612,12 @@
return roundUpToByte(methodBitmapBits) / SIZEOF_BYTE;
}
+ private static int getMethodBitmapStorageSizeForS(int methodFlags, int numMethodIds) {
+ int bits = Integer.bitCount(methodFlags & ~HOT);
+ int methodBitmapBits = bits * numMethodIds;
+ return roundUpToByte(methodBitmapBits) / SIZEOF_BYTE;
+ }
+
private static int roundUpToByte(int bits) {
return (bits + SIZEOF_BYTE - 1) & -SIZEOF_BYTE;
}
@@ -721,6 +726,34 @@
}
}
+ private static void writeMethodBitmapForS(
+ @NonNull OutputStream os,
+ int methodFlags,
+ @NonNull DexProfileData dexData
+ ) throws IOException {
+ int methodBitmapStorageSize = getMethodBitmapStorageSizeForS(
+ methodFlags, dexData.numMethodIds
+ );
+ byte[] bitmap = new byte[methodBitmapStorageSize];
+ for (Map.Entry<Integer, Integer> entry : dexData.methods.entrySet()) {
+ int methodIndex = entry.getKey();
+ int flagValue = entry.getValue();
+
+ if ((flagValue & methodFlags) == 0) {
+ continue;
+ }
+
+ if ((flagValue & STARTUP) != 0) {
+ setMethodBitmapBit(bitmap, STARTUP, methodIndex, dexData);
+ }
+
+ if ((flagValue & POST_STARTUP) != 0) {
+ setMethodBitmapBit(bitmap, POST_STARTUP, methodIndex, dexData);
+ }
+ }
+ os.write(bitmap);
+ }
+
/**
* Writes the methods flags as a bitmap to the output stream.
* @param os the destination OutputStream to write to
diff --git a/profileinstaller/profileinstaller/src/test/java/androidx/profileinstaller/ProfileTranscoderTests.java b/profileinstaller/profileinstaller/src/test/java/androidx/profileinstaller/ProfileTranscoderTests.java
index 23a4c10..a5e9240 100644
--- a/profileinstaller/profileinstaller/src/test/java/androidx/profileinstaller/ProfileTranscoderTests.java
+++ b/profileinstaller/profileinstaller/src/test/java/androidx/profileinstaller/ProfileTranscoderTests.java
@@ -65,7 +65,7 @@
@Test
public void testTranscodeForN() throws IOException {
assertGoldenTranscode(
- testFile("baseline.prof"),
+ testFile("baseline-p.prof"),
testFile("baseline-n.prof"),
ProfileVersion.V001_N
);
@@ -74,7 +74,7 @@
@Test
public void testTranscodeForO() throws IOException {
assertGoldenTranscode(
- testFile("baseline.prof"),
+ testFile("baseline-p.prof"),
testFile("baseline-o.prof"),
ProfileVersion.V005_O
);
@@ -83,7 +83,7 @@
@Test
public void testTranscodeForO_MR1() throws IOException {
assertGoldenTranscode(
- testFile("baseline.prof"),
+ testFile("baseline-p.prof"),
testFile("baseline-o-mr1.prof"),
ProfileVersion.V009_O_MR1
);
@@ -92,7 +92,7 @@
@Test
public void testTranscodeForP() throws IOException {
assertGoldenTranscode(
- testFile("baseline.prof"),
+ testFile("baseline-p.prof"),
testFile("baseline-p.prof"),
ProfileVersion.V010_P
);
@@ -110,6 +110,17 @@
}
@Test
+ public void testTranscodeForS_methodBitmapStorage() throws IOException {
+ assertGoldenTranscodeWithMeta(
+ testFile("katana/baseline-p.prof"),
+ testFile("katana/baseline-s.profm"),
+ testFile("katana/baseline-s.prof"),
+ ProfileVersion.V015_S,
+ "" /* apkName */
+ );
+ }
+
+ @Test
public void testMultidexTranscodeForO() throws IOException {
assertGoldenTranscode(
testFile("baseline-multidex.prof"),
diff --git a/profileinstaller/profileinstaller/src/test/test-data/katana/baseline-p.prof b/profileinstaller/profileinstaller/src/test/test-data/katana/baseline-p.prof
new file mode 100644
index 0000000..5633b92
--- /dev/null
+++ b/profileinstaller/profileinstaller/src/test/test-data/katana/baseline-p.prof
Binary files differ
diff --git a/profileinstaller/profileinstaller/src/test/test-data/katana/baseline-s.prof b/profileinstaller/profileinstaller/src/test/test-data/katana/baseline-s.prof
new file mode 100644
index 0000000..43ad67e
--- /dev/null
+++ b/profileinstaller/profileinstaller/src/test/test-data/katana/baseline-s.prof
Binary files differ
diff --git a/profileinstaller/profileinstaller/src/test/test-data/katana/baseline-s.profm b/profileinstaller/profileinstaller/src/test/test-data/katana/baseline-s.profm
new file mode 100644
index 0000000..596d8bc
--- /dev/null
+++ b/profileinstaller/profileinstaller/src/test/test-data/katana/baseline-s.profm
Binary files differ
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/WriteAheadLoggingKotlinTest.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/WriteAheadLoggingKotlinTest.kt
index 277f983..5c412ca 100644
--- a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/WriteAheadLoggingKotlinTest.kt
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/WriteAheadLoggingKotlinTest.kt
@@ -28,7 +28,6 @@
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
-import androidx.test.filters.SdkSuppress
import androidx.test.platform.app.InstrumentationRegistry
import java.util.concurrent.TimeUnit
import junit.framework.TestCase.assertEquals
@@ -48,7 +47,6 @@
@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(AndroidJUnit4::class)
@LargeTest
-@SdkSuppress(minSdkVersion = 16)
class WriteAheadLoggingKotlinTest {
@get:Rule
val countingTaskExecutorRule = CountingTaskExecutorRule()
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/ClearAllTablesTest.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/ClearAllTablesTest.java
index 4066106..c9e08d5 100644
--- a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/ClearAllTablesTest.java
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/ClearAllTablesTest.java
@@ -42,7 +42,6 @@
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.MediumTest;
-import androidx.test.filters.SdkSuppress;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
@@ -198,7 +197,6 @@
@Test
@MediumTest
- @SdkSuppress(minSdkVersion = 16)
public void clearsDataFromDiskWal() throws IOException {
clearsDataFromDisk(RoomDatabase.JournalMode.WRITE_AHEAD_LOGGING);
}
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/WriteAheadLoggingTest.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/WriteAheadLoggingTest.java
index 28be1c6..5acb92a 100644
--- a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/WriteAheadLoggingTest.java
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/WriteAheadLoggingTest.java
@@ -46,7 +46,6 @@
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.FlakyTest;
import androidx.test.filters.LargeTest;
-import androidx.test.filters.SdkSuppress;
import androidx.test.platform.app.InstrumentationRegistry;
import org.junit.After;
@@ -70,7 +69,6 @@
@RunWith(AndroidJUnit4.class)
@FlakyTest(bugId = 241095868)
@LargeTest
-@SdkSuppress(minSdkVersion = 16)
public class WriteAheadLoggingTest {
private static final String DATABASE_NAME = "wal.db";
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/AnnotationMirrorExt.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/AnnotationMirrorExt.kt
new file mode 100644
index 0000000..3b1368b
--- /dev/null
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/AnnotationMirrorExt.kt
@@ -0,0 +1,57 @@
+/*
+ * 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.room.compiler.processing.javac
+
+import com.google.auto.common.MoreElements
+import com.google.auto.common.MoreTypes
+import java.lang.annotation.Repeatable
+import javax.lang.model.element.AnnotationMirror
+import javax.lang.model.type.TypeKind
+import javax.lang.model.util.ElementFilter
+
+/** Returns true if the given [AnnotationMirror] represents a repeatable annotation. */
+internal fun AnnotationMirror.isRepeatable(): Boolean {
+ val valueType =
+ ElementFilter.methodsIn(MoreTypes.asTypeElement(annotationType).enclosedElements)
+ .singleOrNull { it.simpleName.toString() == "value" }
+ ?.returnType
+
+ // The contract of a repeatable annotation requires that the container annotation have a
+ // single "default" method that returns an array typed with the repeatable annotation type.
+ if (valueType == null || valueType.kind != TypeKind.ARRAY) {
+ return false
+ }
+ val componentType = MoreTypes.asArray(valueType).componentType
+ if (componentType.kind != TypeKind.DECLARED) {
+ return false
+ }
+ val componentElement = MoreTypes.asDeclared(componentType).asElement()
+
+ // Ideally we would read the value of the Repeatable annotation to get the container class
+ // type and check that it matches "this" type. However, there seems to be a KSP bug where
+ // the value of Repeatable is not present so the best we can do is check that all the nested
+ // members are annotated with repeatable.
+ // https://github.com/google/ksp/issues/358
+ return MoreElements.isAnnotationPresent(componentElement, Repeatable::class.java) ||
+ // The java and kotlin versions of Repeatable are not interchangeable.
+ // https://github.com/google/ksp/issues/459 asks whether the built in type
+ // mapper should convert them, but it may not be possible because there are
+ // differences to how they work (eg different parameters).
+ MoreElements.isAnnotationPresent(
+ componentElement, kotlin.annotation.Repeatable::class.java
+ )
+}
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacElement.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacElement.kt
index 0cb2cff..80e9569 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacElement.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacElement.kt
@@ -70,7 +70,16 @@
override fun getAllAnnotations(): List<XAnnotation> {
return element.annotationMirrors.map { mirror -> JavacAnnotation(env, mirror) }
.flatMap { annotation ->
- annotation.unwrapRepeatedAnnotationsFromContainer() ?: listOf(annotation)
+ // TODO(b/313473892): Checking if an annotation needs to be unwrapped can be
+ // expensive with the XProcessing API, especially if we don't really care about
+ // annotation values, so do a quick check on the AnnotationMirror first to decide
+ // if its repeatable. Remove this once we've optimized the general solution in
+ // unwrapRepeatedAnnotationsFromContainer()
+ if (annotation.mirror.isRepeatable()) {
+ annotation.unwrapRepeatedAnnotationsFromContainer() ?: listOf(annotation)
+ } else {
+ listOf(annotation)
+ }
}
}
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacType.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacType.kt
index 2d66b79..fc22ded 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacType.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacType.kt
@@ -62,13 +62,12 @@
}
override val typeElement by lazy {
- val element = try {
- MoreTypes.asTypeElement(typeMirror)
- } catch (notAnElement: IllegalArgumentException) {
- null
- }
- element?.let {
- env.wrapTypeElement(it)
+ env.delegate.typeUtils.asElement(typeMirror)?.let {
+ if (MoreElements.isType(it)) {
+ env.wrapTypeElement(MoreElements.asType(it))
+ } else {
+ null
+ }
}
}
@@ -112,7 +111,16 @@
JavacKmAnnotation(env, it)
} ?: typeMirror.annotationMirrors.map { mirror -> JavacAnnotation(env, mirror) }
.flatMap { annotation ->
- annotation.unwrapRepeatedAnnotationsFromContainer() ?: listOf(annotation)
+ // TODO(b/313473892): Checking if an annotation needs to be unwrapped can be
+ // expensive with the XProcessing API, especially if we don't really care about
+ // annotation values, so do a quick check on the AnnotationMirror first to decide
+ // if its repeatable. Remove this once we've optimized the general solution in
+ // unwrapRepeatedAnnotationsFromContainer()
+ if (annotation.mirror.isRepeatable()) {
+ annotation.unwrapRepeatedAnnotationsFromContainer() ?: listOf(annotation)
+ } else {
+ listOf(annotation)
+ }
}
}
diff --git a/room/room-guava/src/androidTest/java/androidx/room/guava/GuavaRoomTest.java b/room/room-guava/src/androidTest/java/androidx/room/guava/GuavaRoomTest.java
index 653a6b9..8ae5983 100644
--- a/room/room-guava/src/androidTest/java/androidx/room/guava/GuavaRoomTest.java
+++ b/room/room-guava/src/androidTest/java/androidx/room/guava/GuavaRoomTest.java
@@ -25,7 +25,6 @@
import androidx.room.InvalidationTracker;
import androidx.room.RoomDatabase;
import androidx.sqlite.db.SupportSQLiteOpenHelper;
-import androidx.test.filters.SdkSuppress;
import androidx.test.filters.SmallTest;
import com.google.common.util.concurrent.ListenableFuture;
@@ -38,7 +37,6 @@
public class GuavaRoomTest {
@Test
- @SdkSuppress(minSdkVersion = 16)
public void queryIsCancelled() {
Executor executor = runnable -> { /* nothing to do */ };
diff --git a/room/room-ktx/src/androidTest/java/androidx/room/CoroutineRoomCancellationTest.kt b/room/room-ktx/src/androidTest/java/androidx/room/CoroutineRoomCancellationTest.kt
index c3e1e03..2566a42 100644
--- a/room/room-ktx/src/androidTest/java/androidx/room/CoroutineRoomCancellationTest.kt
+++ b/room/room-ktx/src/androidTest/java/androidx/room/CoroutineRoomCancellationTest.kt
@@ -20,7 +20,6 @@
import android.os.CancellationSignal
import androidx.kruth.assertThat
import androidx.sqlite.db.SupportSQLiteOpenHelper
-import androidx.test.filters.SdkSuppress
import androidx.test.filters.SmallTest
import java.util.concurrent.Callable
import java.util.concurrent.CountDownLatch
@@ -38,7 +37,6 @@
import org.junit.Test
@SmallTest
-@SdkSuppress(minSdkVersion = 16)
class CoroutineRoomCancellationTest {
@OptIn(ExperimentalCoroutinesApi::class)
diff --git a/security/security-app-authenticator-testing/src/androidTest/java/androidx/security/app/authenticator/TestAppAuthenticatorBuilderTest.java b/security/security-app-authenticator-testing/src/androidTest/java/androidx/security/app/authenticator/TestAppAuthenticatorBuilderTest.java
index 83d27b6..d567ce2 100644
--- a/security/security-app-authenticator-testing/src/androidTest/java/androidx/security/app/authenticator/TestAppAuthenticatorBuilderTest.java
+++ b/security/security-app-authenticator-testing/src/androidTest/java/androidx/security/app/authenticator/TestAppAuthenticatorBuilderTest.java
@@ -294,6 +294,7 @@
public void callingAppIdentity_packageNotInstalled_returnsUnknownPackage() throws Exception {
// The TestAppAuthenticator can be configured to treat a package as uninstalled to verify
// scenarios where the package being queried is not available on the device.
+ final int packageUid = 10001;
AppAuthenticator appAuthenticatorFromResource =
mBuilderFromResource.setPackageNotInstalled(
DECLARED_PACKAGE1).setPackageNotInstalled(
@@ -305,20 +306,20 @@
assertEquals(AppAuthenticator.PERMISSION_DENIED_UNKNOWN_PACKAGE,
appAuthenticatorFromResource.checkCallingAppIdentity(
- DECLARED_PACKAGE1, TEST_PERMISSION));
+ DECLARED_PACKAGE1, TEST_PERMISSION, packageUid));
assertEquals(AppAuthenticator.PERMISSION_DENIED_UNKNOWN_PACKAGE,
appAuthenticatorFromInputStream.checkCallingAppIdentity(
- DECLARED_PACKAGE1, TEST_PERMISSION));
+ DECLARED_PACKAGE1, TEST_PERMISSION, packageUid));
assertEquals(AppAuthenticator.SIGNATURE_NO_MATCH,
appAuthenticatorFromResource.checkAppIdentity(EXPECTED_IDENTITY_PACKAGE));
assertEquals(AppAuthenticator.SIGNATURE_NO_MATCH,
appAuthenticatorFromInputStream.checkAppIdentity(EXPECTED_IDENTITY_PACKAGE));
assertThrows(SecurityException.class, () ->
appAuthenticatorFromResource.enforceCallingAppIdentity(
- DECLARED_PACKAGE1, TEST_PERMISSION));
+ DECLARED_PACKAGE1, TEST_PERMISSION, packageUid));
assertThrows(SecurityException.class, () ->
appAuthenticatorFromInputStream.enforceCallingAppIdentity(
- DECLARED_PACKAGE1, TEST_PERMISSION));
+ DECLARED_PACKAGE1, TEST_PERMISSION, packageUid));
assertThrows(SecurityException.class, () ->
appAuthenticatorFromResource.enforceAppIdentity(EXPECTED_IDENTITY_PACKAGE));
assertThrows(SecurityException.class, () ->
diff --git a/security/security-app-authenticator/api/current.txt b/security/security-app-authenticator/api/current.txt
index adb40b6..3a3da2b 100644
--- a/security/security-app-authenticator/api/current.txt
+++ b/security/security-app-authenticator/api/current.txt
@@ -4,11 +4,13 @@
public class AppAuthenticator {
method public int checkAppIdentity(String);
method public int checkCallingAppIdentity(String, String);
+ method public int checkCallingAppIdentity(String, String, int);
method public int checkCallingAppIdentity(String, String, int, int);
method public static androidx.security.app.authenticator.AppAuthenticator createFromInputStream(android.content.Context, java.io.InputStream) throws androidx.security.app.authenticator.AppAuthenticatorXmlException, java.io.IOException;
method public static androidx.security.app.authenticator.AppAuthenticator createFromResource(android.content.Context, @XmlRes int) throws androidx.security.app.authenticator.AppAuthenticatorXmlException, java.io.IOException;
method public void enforceAppIdentity(String);
method public void enforceCallingAppIdentity(String, String);
+ method public void enforceCallingAppIdentity(String, String, int);
method public void enforceCallingAppIdentity(String, String, int, int);
field public static final int PERMISSION_DENIED_NO_MATCH = -3; // 0xfffffffd
field public static final int PERMISSION_DENIED_PACKAGE_UID_MISMATCH = -5; // 0xfffffffb
diff --git a/security/security-app-authenticator/api/restricted_current.txt b/security/security-app-authenticator/api/restricted_current.txt
index adb40b6..3a3da2b 100644
--- a/security/security-app-authenticator/api/restricted_current.txt
+++ b/security/security-app-authenticator/api/restricted_current.txt
@@ -4,11 +4,13 @@
public class AppAuthenticator {
method public int checkAppIdentity(String);
method public int checkCallingAppIdentity(String, String);
+ method public int checkCallingAppIdentity(String, String, int);
method public int checkCallingAppIdentity(String, String, int, int);
method public static androidx.security.app.authenticator.AppAuthenticator createFromInputStream(android.content.Context, java.io.InputStream) throws androidx.security.app.authenticator.AppAuthenticatorXmlException, java.io.IOException;
method public static androidx.security.app.authenticator.AppAuthenticator createFromResource(android.content.Context, @XmlRes int) throws androidx.security.app.authenticator.AppAuthenticatorXmlException, java.io.IOException;
method public void enforceAppIdentity(String);
method public void enforceCallingAppIdentity(String, String);
+ method public void enforceCallingAppIdentity(String, String, int);
method public void enforceCallingAppIdentity(String, String, int, int);
field public static final int PERMISSION_DENIED_NO_MATCH = -3; // 0xfffffffd
field public static final int PERMISSION_DENIED_PACKAGE_UID_MISMATCH = -5; // 0xfffffffb
diff --git a/security/security-app-authenticator/src/main/java/androidx/security/app/authenticator/AppAuthenticator.java b/security/security-app-authenticator/src/main/java/androidx/security/app/authenticator/AppAuthenticator.java
index b547cff..b926493 100644
--- a/security/security-app-authenticator/src/main/java/androidx/security/app/authenticator/AppAuthenticator.java
+++ b/security/security-app-authenticator/src/main/java/androidx/security/app/authenticator/AppAuthenticator.java
@@ -16,7 +16,12 @@
package androidx.security.app.authenticator;
+import static android.os.Process.INVALID_PID;
+import static android.os.Process.INVALID_UID;
+
+import android.app.Activity;
import android.content.Context;
+import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.os.Binder;
@@ -205,10 +210,12 @@
* Enforces the specified {@code packageName} has the expected signing identity for the
* provided {@code permission}.
*
- * <p>This method should be used when verifying the identity of a calling process of an IPC.
- * This is the same as calling {@link #enforceCallingAppIdentity(String, String, int, int)} with
- * the pid and uid returned by {@link Binder#getCallingPid()} and
- * {@link Binder#getCallingUid()}.
+ * <p>This method should be used for verifying the identity of a package when the UID / PID
+ * is not available. For instance, this should be used within an activity that was started
+ * with {@link Activity#startActivityForResult(Intent, int)} where the package name is
+ * available from {@link Activity#getCallingPackage()}. For instances where the calling
+ * UID is available but the calling PID is not available,
+ * ({@link #checkCallingAppIdentity(String, String, int)} should be preferred.
*
* @param packageName the name of the package to be verified
* @param permission the name of the permission as specified in the XML from which to verify the
@@ -217,15 +224,36 @@
* for the permission
*/
public void enforceCallingAppIdentity(@NonNull String packageName, @NonNull String permission) {
- enforceCallingAppIdentity(packageName, permission,
- mAppAuthenticatorUtils.getCallingPid(), mAppAuthenticatorUtils.getCallingUid());
+ enforceCallingAppIdentity(packageName, permission, INVALID_PID, INVALID_UID);
+ }
+
+ /**
+ * Enforces the specified {@code packageName} belongs to the provided {@code uid}
+ * and has the expected signing identity for the {@code permission}.
+ *
+ * <p>This method should be used for verifying the identity of a package when the UID is
+ * available but the PID is not. For instance, this should be used within an activity that
+ * is started with {@link android.app.ActivityOptions#setShareIdentityEnabled(boolean)} set to
+ * true; the package name would then be accessible via {@link Activity#getLaunchedFromPackage()}
+ * and the UID from {@link Activity#getLaunchedFromUid()}.
+ *
+ * @param packageName the name of the package to be verified
+ * @param permission the name of the permission as specified in the XML from which to verify the
+ * package / signing identity
+ * @param uid the expected uid of the package
+ * @throws SecurityException if the uid does not belong to the specified package, or if the
+ * signing identity of the package does not match that defined for the permission
+ */
+ public void enforceCallingAppIdentity(@NonNull String packageName, @NonNull String permission,
+ int uid) {
+ enforceCallingAppIdentity(packageName, permission, INVALID_PID, uid);
}
/**
* Enforces the specified {@code packageName} belongs to the provided {@code pid} / {@code uid}
* and has the expected signing identity for the {@code permission}.
*
- * <p>This method should be used when verifying the identity of a calling process of an IPC.
+ * <p>This method should be used for verifying the identity of a calling process of an IPC.
*
* @param packageName the name of the package to be verified
* @param permission the name of the permission as specified in the XML from which to verify the
@@ -248,10 +276,12 @@
* Checks the specified {@code packageName} has the expected signing identity for the
* provided {@code permission}.
*
- * <p>This method should be used when verifying the identity of a calling process of an IPC.
- * This is the same as calling {@link #checkCallingAppIdentity(String, String, int, int)} with
- * the pid and uid returned by {@link Binder#getCallingPid()} and
- * {@link Binder#getCallingUid()}.
+ * <p>This method should be used for verifying the identity of a package when the UID / PID
+ * is not available. For instance, this should be used within an activity that was started
+ * with {@link Activity#startActivityForResult(Intent, int)} where the package name is
+ * available from {@link Activity#getCallingPackage()}. For instances where the calling
+ * UID is available but the calling PID is not available,
+ * ({@link #checkCallingAppIdentity(String, String, int)} should be preferred.
*
* @param packageName the name of the package to be verified
* @param permission the name of the permission as specified in the XML from which to verify the
@@ -262,20 +292,46 @@
* the expected signing identity for the provided {@code permission},<br>
* {@link #PERMISSION_DENIED_UNKNOWN_PACKAGE} if the specified {@code packageName} does not
* exist on the device,<br>
- * {@link #PERMISSION_DENIED_PACKAGE_UID_MISMATCH} if the uid as returned from
- * {@link Binder#getCallingUid()} does not match the uid assigned to the package
*/
@AppIdentityPermissionResult
public int checkCallingAppIdentity(@NonNull String packageName, @NonNull String permission) {
- return checkCallingAppIdentity(packageName, permission,
- mAppAuthenticatorUtils.getCallingPid(), mAppAuthenticatorUtils.getCallingUid());
+ return checkCallingAppIdentity(packageName, permission, INVALID_PID, INVALID_UID);
}
/**
- * Checks the specified {@code packageName} has the expected signing identity for the
- * provided {@code permission}.
+ * Checks the specified {@code packageName} running under {@code uid} has the expected
+ * signing identity for the provided {@code permission}.
*
- * <p>This method should be used when verifying the identity of a calling process of an IPC.
+ * <p>This method should be used for verifying the identity of a package when the UID is
+ * available but the PID is not. For instance, this should be used within an activity that
+ * is started with {@link android.app.ActivityOptions#setShareIdentityEnabled(boolean)} set to
+ * true; the package name would then be accessible via {@link Activity#getLaunchedFromPackage()}
+ * and the UID from {@link Activity#getLaunchedFromUid()}.
+ *
+ * @param packageName the name of the package to be verified
+ * @param permission the name of the permission as specified in the XML from which to verify the
+ * package / signing identity
+ * @param uid the expected uid of the package
+ * @return {@link #PERMISSION_GRANTED} if the specified {@code packageName} has the expected
+ * signing identity for the provided {@code permission},<br>
+ * {@link #PERMISSION_DENIED_NO_MATCH} if the specified {@code packageName} does not have
+ * the expected signing identity for the provided {@code permission},<br>
+ * {@link #PERMISSION_DENIED_UNKNOWN_PACKAGE} if the specified {@code packageName} does not
+ * exist on the device,<br>
+ * {@link #PERMISSION_DENIED_PACKAGE_UID_MISMATCH} if the specified {@code uid} does not
+ * match the uid assigned to the package
+ */
+ @AppIdentityPermissionResult
+ public int checkCallingAppIdentity(@NonNull String packageName, @NonNull String permission,
+ int uid) {
+ return checkCallingAppIdentity(packageName, permission, INVALID_PID, uid);
+ }
+
+ /**
+ * Checks the specified {@code packageName} running with {@code pid} and {@code uid} has the
+ * expected signing identity for the provided {@code permission}.
+ *
+ * <p>This method should be used for verifying the identity of a calling process of an IPC.
*
* @param packageName the name of the package to be verified
* @param permission the name of the permission as specified in the XML from which to verify the
@@ -314,18 +370,21 @@
String permission,
int pid,
int uid) {
- // First verify that the UID of the calling package matches the specified value.
- int packageUid;
- try {
- packageUid = mAppAuthenticatorUtils.getUidForPackage(packageName);
- } catch (PackageManager.NameNotFoundException e) {
- return AppAuthenticatorResult.create(PERMISSION_DENIED_UNKNOWN_PACKAGE,
- "The app " + packageName + " was not found on the device");
- }
- if (packageUid != uid) {
- return AppAuthenticatorResult.create(PERMISSION_DENIED_PACKAGE_UID_MISMATCH,
- "The expected UID, " + uid + ", of the app " + packageName
- + " does not match the actual UID, " + packageUid);
+ // If a valid UID is provided, verify that the UID of the calling package matches the
+ // specified value.
+ if (uid != INVALID_UID) {
+ int packageUid;
+ try {
+ packageUid = mAppAuthenticatorUtils.getUidForPackage(packageName);
+ } catch (PackageManager.NameNotFoundException e) {
+ return AppAuthenticatorResult.create(PERMISSION_DENIED_UNKNOWN_PACKAGE,
+ "The app " + packageName + " was not found on the device");
+ }
+ if (packageUid != uid) {
+ return AppAuthenticatorResult.create(PERMISSION_DENIED_PACKAGE_UID_MISMATCH,
+ "The expected UID, " + uid + ", of the app " + packageName
+ + " does not match the actual UID, " + packageUid);
+ }
}
if (mAppSignatureVerifier.verifySigningIdentity(packageName, permission)) {
return AppAuthenticatorResult.create(PERMISSION_GRANTED, null);
diff --git a/security/security-app-authenticator/src/test/java/androidx/security/app/authenticator/AppAuthenticatorTest.java b/security/security-app-authenticator/src/test/java/androidx/security/app/authenticator/AppAuthenticatorTest.java
index 1e89cdf..1ec1ede 100644
--- a/security/security-app-authenticator/src/test/java/androidx/security/app/authenticator/AppAuthenticatorTest.java
+++ b/security/security-app-authenticator/src/test/java/androidx/security/app/authenticator/AppAuthenticatorTest.java
@@ -83,6 +83,7 @@
// tests, no assertion is required as making it past this statement without a
// SecurityException indicates the test was successful.
mAppAuthenticator.enforceCallingAppIdentity(TEST_PACKAGE, TEST_PERMISSION);
+ mAppAuthenticator.enforceCallingAppIdentity(TEST_PACKAGE, TEST_PERMISSION, TEST_UID);
mAppAuthenticator.enforceCallingAppIdentity(TEST_PACKAGE, TEST_PERMISSION, TEST_PID,
TEST_UID);
}
@@ -99,6 +100,9 @@
() -> mAppAuthenticator.enforceCallingAppIdentity(TEST_PACKAGE, TEST_PERMISSION));
assertThrows(SecurityException.class,
() -> mAppAuthenticator.enforceCallingAppIdentity(TEST_PACKAGE, TEST_PERMISSION,
+ TEST_UID));
+ assertThrows(SecurityException.class,
+ () -> mAppAuthenticator.enforceCallingAppIdentity(TEST_PACKAGE, TEST_PERMISSION,
TEST_PID, TEST_UID));
}
@@ -112,7 +116,8 @@
when(mMockAppAuthenticatorUtils.getUidForPackage(TEST_PACKAGE)).thenReturn(23456);
assertThrows(SecurityException.class,
- () -> mAppAuthenticator.enforceCallingAppIdentity(TEST_PACKAGE, TEST_PERMISSION));
+ () -> mAppAuthenticator.enforceCallingAppIdentity(TEST_PACKAGE, TEST_PERMISSION,
+ TEST_UID));
assertThrows(SecurityException.class,
() -> mAppAuthenticator.enforceCallingAppIdentity(TEST_PACKAGE, TEST_PERMISSION,
TEST_PID, TEST_UID));
@@ -123,13 +128,20 @@
// If the specified package does not exist on the device then enforceCallingAppIdentity
// should receive a NameNotFoundException when checking for the UID; this should result
// in a SecurityException.
+ // true is returned here to ensure the test fails for the expected reason; a return value
+ // of false would still result in a SecurityException thrown by the method under test
+ // since any failures during an enforce call result in this exception. Returning true
+ // allows the method under test to return a successful response if package verification
+ // is incorrectly skipped, allowing the test to ensure that the SecurityException is the
+ // expected result from the package not existing on the system.
when(mMockAppSignatureVerifier.verifySigningIdentity(TEST_PACKAGE,
TEST_PERMISSION)).thenReturn(true);
when(mMockAppAuthenticatorUtils.getUidForPackage(TEST_PACKAGE)).thenThrow(
PackageManager.NameNotFoundException.class);
assertThrows(SecurityException.class,
- () -> mAppAuthenticator.enforceCallingAppIdentity(TEST_PACKAGE, TEST_PERMISSION));
+ () -> mAppAuthenticator.enforceCallingAppIdentity(TEST_PACKAGE, TEST_PERMISSION,
+ TEST_UID));
assertThrows(SecurityException.class,
() -> mAppAuthenticator.enforceCallingAppIdentity(TEST_PACKAGE, TEST_PERMISSION,
TEST_PID, TEST_UID));
@@ -145,6 +157,8 @@
assertEquals(AppAuthenticator.PERMISSION_GRANTED,
mAppAuthenticator.checkCallingAppIdentity(TEST_PACKAGE, TEST_PERMISSION));
assertEquals(AppAuthenticator.PERMISSION_GRANTED,
+ mAppAuthenticator.checkCallingAppIdentity(TEST_PACKAGE, TEST_PERMISSION, TEST_UID));
+ assertEquals(AppAuthenticator.PERMISSION_GRANTED,
mAppAuthenticator.checkCallingAppIdentity(
TEST_PACKAGE, TEST_PERMISSION, TEST_PID, TEST_UID));
}
@@ -160,6 +174,8 @@
assertEquals(AppAuthenticator.PERMISSION_DENIED_NO_MATCH,
mAppAuthenticator.checkCallingAppIdentity(TEST_PACKAGE, TEST_PERMISSION));
assertEquals(AppAuthenticator.PERMISSION_DENIED_NO_MATCH,
+ mAppAuthenticator.checkCallingAppIdentity(TEST_PACKAGE, TEST_PERMISSION, TEST_UID));
+ assertEquals(AppAuthenticator.PERMISSION_DENIED_NO_MATCH,
mAppAuthenticator.checkCallingAppIdentity(
TEST_PACKAGE, TEST_PERMISSION, TEST_PID, TEST_UID));
}
@@ -174,7 +190,7 @@
when(mMockAppAuthenticatorUtils.getUidForPackage(TEST_PACKAGE)).thenReturn(23456);
assertEquals(AppAuthenticator.PERMISSION_DENIED_PACKAGE_UID_MISMATCH,
- mAppAuthenticator.checkCallingAppIdentity(TEST_PACKAGE, TEST_PERMISSION));
+ mAppAuthenticator.checkCallingAppIdentity(TEST_PACKAGE, TEST_PERMISSION), TEST_UID);
assertEquals(AppAuthenticator.PERMISSION_DENIED_PACKAGE_UID_MISMATCH,
mAppAuthenticator.checkCallingAppIdentity(
TEST_PACKAGE, TEST_PERMISSION, TEST_PID, TEST_UID));
@@ -191,7 +207,7 @@
PackageManager.NameNotFoundException.class);
assertEquals(AppAuthenticator.PERMISSION_DENIED_UNKNOWN_PACKAGE,
- mAppAuthenticator.checkCallingAppIdentity(TEST_PACKAGE, TEST_PERMISSION));
+ mAppAuthenticator.checkCallingAppIdentity(TEST_PACKAGE, TEST_PERMISSION, TEST_UID));
assertEquals(AppAuthenticator.PERMISSION_DENIED_UNKNOWN_PACKAGE,
mAppAuthenticator.checkCallingAppIdentity(
TEST_PACKAGE, TEST_PERMISSION, TEST_PID, TEST_UID));
diff --git a/slice/slice-benchmark/src/androidTest/java/androidx/slice/SliceSerializeMetrics.java b/slice/slice-benchmark/src/androidTest/java/androidx/slice/SliceSerializeMetrics.java
index cc3df0a..dc3c37d 100644
--- a/slice/slice-benchmark/src/androidTest/java/androidx/slice/SliceSerializeMetrics.java
+++ b/slice/slice-benchmark/src/androidTest/java/androidx/slice/SliceSerializeMetrics.java
@@ -40,7 +40,6 @@
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
-import androidx.test.filters.SdkSuppress;
import androidx.test.platform.app.InstrumentationRegistry;
import org.junit.Rule;
@@ -58,7 +57,6 @@
@RunWith(AndroidJUnit4.class)
@LargeTest
-@SdkSuppress(minSdkVersion = 19)
@SuppressWarnings("deprecation")
public class SliceSerializeMetrics {
diff --git a/slice/slice-benchmark/src/androidTest/java/androidx/slice/SliceViewMetrics.java b/slice/slice-benchmark/src/androidTest/java/androidx/slice/SliceViewMetrics.java
index b892033..a7ca517 100644
--- a/slice/slice-benchmark/src/androidTest/java/androidx/slice/SliceViewMetrics.java
+++ b/slice/slice-benchmark/src/androidTest/java/androidx/slice/SliceViewMetrics.java
@@ -25,7 +25,6 @@
import androidx.slice.widget.SliceView;
import androidx.test.annotation.UiThreadTest;
import androidx.test.core.app.ApplicationProvider;
-import androidx.test.filters.SdkSuppress;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
@@ -38,7 +37,6 @@
@RunWith(Parameterized.class)
@SmallTest
-@SdkSuppress(minSdkVersion = 19)
@SuppressWarnings("deprecation")
public class SliceViewMetrics {
diff --git a/slice/slice-builders-ktx/src/androidTest/java/androidx/slice/builders/SliceBuildersKtxTest.kt b/slice/slice-builders-ktx/src/androidTest/java/androidx/slice/builders/SliceBuildersKtxTest.kt
index 6faebd4..71dae27 100644
--- a/slice/slice-builders-ktx/src/androidTest/java/androidx/slice/builders/SliceBuildersKtxTest.kt
+++ b/slice/slice-builders-ktx/src/androidTest/java/androidx/slice/builders/SliceBuildersKtxTest.kt
@@ -27,11 +27,9 @@
import androidx.slice.builders.ktx.test.R
import androidx.test.core.app.ApplicationProvider
import androidx.test.filters.MediumTest
-import androidx.test.filters.SdkSuppress
import org.junit.Assert.assertEquals
import org.junit.Test
-@SdkSuppress(minSdkVersion = 19)
@MediumTest
@Suppress("DEPRECATION")
class SliceBuildersKtxTest {
diff --git a/slice/slice-core/src/androidTest/java/androidx/slice/SliceItemTest.java b/slice/slice-core/src/androidTest/java/androidx/slice/SliceItemTest.java
index 6fd9163..11a2a158 100644
--- a/slice/slice-core/src/androidTest/java/androidx/slice/SliceItemTest.java
+++ b/slice/slice-core/src/androidTest/java/androidx/slice/SliceItemTest.java
@@ -25,7 +25,6 @@
import android.text.style.StyleSpan;
import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SdkSuppress;
import androidx.test.filters.SmallTest;
import org.junit.Test;
@@ -33,7 +32,6 @@
@RunWith(AndroidJUnit4.class)
@SmallTest
-@SdkSuppress(minSdkVersion = 19)
public class SliceItemTest {
@Test
diff --git a/slice/slice-core/src/androidTest/java/androidx/slice/SliceTest.java b/slice/slice-core/src/androidTest/java/androidx/slice/SliceTest.java
index 31f39d5..690edd4 100644
--- a/slice/slice-core/src/androidTest/java/androidx/slice/SliceTest.java
+++ b/slice/slice-core/src/androidTest/java/androidx/slice/SliceTest.java
@@ -51,7 +51,6 @@
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
-import androidx.test.filters.SdkSuppress;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -63,7 +62,6 @@
@RunWith(AndroidJUnit4.class)
@LargeTest
-@SdkSuppress(minSdkVersion = 19)
public class SliceTest {
public static boolean sFlag = false;
diff --git a/slice/slice-core/src/androidTest/java/androidx/slice/SliceTestProvider.java b/slice/slice-core/src/androidTest/java/androidx/slice/SliceTestProvider.java
index 3e1949f..a6a41fc 100644
--- a/slice/slice-core/src/androidTest/java/androidx/slice/SliceTestProvider.java
+++ b/slice/slice-core/src/androidTest/java/androidx/slice/SliceTestProvider.java
@@ -34,9 +34,7 @@
import androidx.core.graphics.drawable.IconCompat;
import androidx.slice.Slice.Builder;
import androidx.slice.core.test.R;
-import androidx.test.filters.SdkSuppress;
-@SdkSuppress(minSdkVersion = 19)
public class SliceTestProvider extends androidx.slice.SliceProvider {
@Override
diff --git a/slice/slice-core/src/androidTest/java/androidx/slice/compat/CompatPermissionManagerTest.java b/slice/slice-core/src/androidTest/java/androidx/slice/compat/CompatPermissionManagerTest.java
index 583cb44..ad385d8 100644
--- a/slice/slice-core/src/androidTest/java/androidx/slice/compat/CompatPermissionManagerTest.java
+++ b/slice/slice-core/src/androidTest/java/androidx/slice/compat/CompatPermissionManagerTest.java
@@ -38,7 +38,6 @@
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.FlakyTest;
import androidx.test.filters.LargeTest;
-import androidx.test.filters.SdkSuppress;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -47,7 +46,6 @@
@RunWith(AndroidJUnit4.class)
@LargeTest
-@SdkSuppress(minSdkVersion = 19)
public class CompatPermissionManagerTest {
private final Context mContext = ApplicationProvider.getApplicationContext();
diff --git a/slice/slice-core/src/androidTest/java/androidx/slice/compat/CompatPinnedListTest.java b/slice/slice-core/src/androidTest/java/androidx/slice/compat/CompatPinnedListTest.java
index b0bff92..0654fb0 100644
--- a/slice/slice-core/src/androidTest/java/androidx/slice/compat/CompatPinnedListTest.java
+++ b/slice/slice-core/src/androidTest/java/androidx/slice/compat/CompatPinnedListTest.java
@@ -29,7 +29,6 @@
import androidx.slice.SliceSpec;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SdkSuppress;
import androidx.test.filters.SmallTest;
import junit.framework.AssertionFailedError;
@@ -45,7 +44,6 @@
@RunWith(AndroidJUnit4.class)
@SmallTest
-@SdkSuppress(minSdkVersion = 19)
public class CompatPinnedListTest {
private final Context mContext = ApplicationProvider.getApplicationContext();
diff --git a/slice/slice-core/src/androidTest/java/androidx/slice/compat/SliceProviderCompatTest.java b/slice/slice-core/src/androidTest/java/androidx/slice/compat/SliceProviderCompatTest.java
index 882d888..a0ca18a 100644
--- a/slice/slice-core/src/androidTest/java/androidx/slice/compat/SliceProviderCompatTest.java
+++ b/slice/slice-core/src/androidTest/java/androidx/slice/compat/SliceProviderCompatTest.java
@@ -43,7 +43,6 @@
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
-import androidx.test.filters.SdkSuppress;
import org.junit.Before;
import org.junit.Test;
@@ -53,7 +52,6 @@
@RunWith(AndroidJUnit4.class)
@LargeTest
-@SdkSuppress(minSdkVersion = 19)
public class SliceProviderCompatTest {
private static final String AUTHORITY = "my.authority";
diff --git a/slice/slice-view/src/androidTest/java/androidx/slice/SliceBuilderTest.java b/slice/slice-view/src/androidTest/java/androidx/slice/SliceBuilderTest.java
index dd4f25e..b24f98d 100644
--- a/slice/slice-view/src/androidTest/java/androidx/slice/SliceBuilderTest.java
+++ b/slice/slice-view/src/androidTest/java/androidx/slice/SliceBuilderTest.java
@@ -41,7 +41,6 @@
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
-import androidx.test.filters.SdkSuppress;
import org.junit.Before;
import org.junit.Test;
@@ -53,7 +52,6 @@
*/
@RunWith(AndroidJUnit4.class)
@LargeTest
-@SdkSuppress(minSdkVersion = 19)
public class SliceBuilderTest {
private final Context mContext = ApplicationProvider.getApplicationContext();
diff --git a/slice/slice-view/src/androidTest/java/androidx/slice/SliceMetadataTest.java b/slice/slice-view/src/androidTest/java/androidx/slice/SliceMetadataTest.java
index 90a0c13..639c98b 100644
--- a/slice/slice-view/src/androidTest/java/androidx/slice/SliceMetadataTest.java
+++ b/slice/slice-view/src/androidTest/java/androidx/slice/SliceMetadataTest.java
@@ -56,7 +56,6 @@
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.MediumTest;
-import androidx.test.filters.SdkSuppress;
import org.junit.Assert;
import org.junit.Before;
@@ -75,7 +74,6 @@
*/
@RunWith(AndroidJUnit4.class)
@MediumTest
-@SdkSuppress(minSdkVersion = 19)
public class SliceMetadataTest {
private final Context mContext = ApplicationProvider.getApplicationContext();
diff --git a/slice/slice-view/src/androidTest/java/androidx/slice/SliceViewManagerTest.java b/slice/slice-view/src/androidTest/java/androidx/slice/SliceViewManagerTest.java
index ec1a3eb..92ca772 100644
--- a/slice/slice-view/src/androidTest/java/androidx/slice/SliceViewManagerTest.java
+++ b/slice/slice-view/src/androidTest/java/androidx/slice/SliceViewManagerTest.java
@@ -57,7 +57,6 @@
@RunWith(AndroidJUnit4.class)
@LargeTest
-@SdkSuppress(minSdkVersion = 19)
public class SliceViewManagerTest {
private final Context mContext = ApplicationProvider.getApplicationContext();
@@ -111,7 +110,7 @@
}
@Test
- @SdkSuppress(minSdkVersion = 19, maxSdkVersion = 33) // b/262909049: Failing on SDK 34
+ @SdkSuppress(maxSdkVersion = 33) // b/262909049: Failing on SDK 34
public void testPinList() {
if (Build.VERSION.SDK_INT == 33 && !"REL".equals(Build.VERSION.CODENAME)) {
return; // b/262909049: Do not run this test on pre-release Android U.
@@ -140,7 +139,7 @@
}
}
- @SdkSuppress(minSdkVersion = 19, maxSdkVersion = 27)
+ @SdkSuppress(maxSdkVersion = 27)
@Test
public void testCallback() {
Uri uri = new Uri.Builder()
@@ -162,7 +161,7 @@
verify(callback, timeout(2000)).onSliceUpdated(any(Slice.class));
}
- @SdkSuppress(minSdkVersion = 19, maxSdkVersion = 27)
+ @SdkSuppress(maxSdkVersion = 27)
@Test
public void testPinnedSpecs() {
Uri uri = new Uri.Builder()
diff --git a/slice/slice-view/src/androidTest/java/androidx/slice/SliceXmlTest.java b/slice/slice-view/src/androidTest/java/androidx/slice/SliceXmlTest.java
index df563ae..b728500 100644
--- a/slice/slice-view/src/androidTest/java/androidx/slice/SliceXmlTest.java
+++ b/slice/slice-view/src/androidTest/java/androidx/slice/SliceXmlTest.java
@@ -44,7 +44,6 @@
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.MediumTest;
-import androidx.test.filters.SdkSuppress;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -57,7 +56,6 @@
@RunWith(AndroidJUnit4.class)
@MediumTest
-@SdkSuppress(minSdkVersion = 19)
public class SliceXmlTest {
private final Context mContext = ApplicationProvider.getApplicationContext();
diff --git a/slice/slice-view/src/androidTest/java/androidx/slice/render/RenderTest.java b/slice/slice-view/src/androidTest/java/androidx/slice/render/RenderTest.java
index e41a676..5231c51 100644
--- a/slice/slice-view/src/androidTest/java/androidx/slice/render/RenderTest.java
+++ b/slice/slice-view/src/androidTest/java/androidx/slice/render/RenderTest.java
@@ -27,7 +27,6 @@
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
-import androidx.test.filters.SdkSuppress;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.rule.ActivityTestRule;
import androidx.test.rule.GrantPermissionRule;
@@ -46,7 +45,6 @@
@LargeTest
@RunWith(AndroidJUnit4.class)
-@SdkSuppress(minSdkVersion = 19)
public class RenderTest {
@Rule
diff --git a/slice/slice-view/src/androidTest/java/androidx/slice/widget/CachedSliceLiveDataTest.java b/slice/slice-view/src/androidTest/java/androidx/slice/widget/CachedSliceLiveDataTest.java
index b34ad92..80db96e 100644
--- a/slice/slice-view/src/androidTest/java/androidx/slice/widget/CachedSliceLiveDataTest.java
+++ b/slice/slice-view/src/androidTest/java/androidx/slice/widget/CachedSliceLiveDataTest.java
@@ -41,7 +41,6 @@
import androidx.slice.SliceViewManager.SliceCallback;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SdkSuppress;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
@@ -60,7 +59,6 @@
@SuppressWarnings("unchecked")
@RunWith(AndroidJUnit4.class)
@SmallTest
-@SdkSuppress(minSdkVersion = 19)
public class CachedSliceLiveDataTest {
private static final Uri URI = Uri.parse("content://test/something");
diff --git a/slice/slice-view/src/androidTest/java/androidx/slice/widget/SliceLiveDataTest.java b/slice/slice-view/src/androidTest/java/androidx/slice/widget/SliceLiveDataTest.java
index fd48b3b..6b93bf3 100644
--- a/slice/slice-view/src/androidTest/java/androidx/slice/widget/SliceLiveDataTest.java
+++ b/slice/slice-view/src/androidTest/java/androidx/slice/widget/SliceLiveDataTest.java
@@ -45,7 +45,6 @@
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.MediumTest;
-import androidx.test.filters.SdkSuppress;
import androidx.test.platform.app.InstrumentationRegistry;
import org.junit.After;
@@ -63,7 +62,6 @@
@SuppressWarnings("unchecked")
@RunWith(AndroidJUnit4.class)
@MediumTest
-@SdkSuppress(minSdkVersion = 19)
public class SliceLiveDataTest {
private static final Uri URI = Uri.parse("content://test/something");
diff --git a/slice/slice-view/src/androidTest/java/androidx/slice/widget/SliceStyleTest.java b/slice/slice-view/src/androidTest/java/androidx/slice/widget/SliceStyleTest.java
index c39e53f..4445b5e 100644
--- a/slice/slice-view/src/androidTest/java/androidx/slice/widget/SliceStyleTest.java
+++ b/slice/slice-view/src/androidTest/java/androidx/slice/widget/SliceStyleTest.java
@@ -17,7 +17,6 @@
package androidx.slice.widget;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertTrue;
import android.content.Context;
@@ -29,7 +28,6 @@
import androidx.slice.view.test.R;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SdkSuppress;
import androidx.test.filters.SmallTest;
import org.junit.Before;
@@ -40,7 +38,6 @@
/** Tests for {@link SliceView}. */
@RunWith(AndroidJUnit4.class)
@SmallTest
-@SdkSuppress(minSdkVersion = 19)
public class SliceStyleTest {
private final Context mContext = ApplicationProvider.getApplicationContext();
diff --git a/slice/slice-view/src/androidTest/java/androidx/slice/widget/SliceViewTest.java b/slice/slice-view/src/androidTest/java/androidx/slice/widget/SliceViewTest.java
index f56036f..93b295c 100644
--- a/slice/slice-view/src/androidTest/java/androidx/slice/widget/SliceViewTest.java
+++ b/slice/slice-view/src/androidTest/java/androidx/slice/widget/SliceViewTest.java
@@ -48,7 +48,6 @@
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.MediumTest;
-import androidx.test.filters.SdkSuppress;
import androidx.test.platform.app.InstrumentationRegistry;
import org.junit.Before;
@@ -64,7 +63,6 @@
*/
@RunWith(AndroidJUnit4.class)
@MediumTest
-@SdkSuppress(minSdkVersion = 19)
public class SliceViewTest {
private final Context mContext = ApplicationProvider.getApplicationContext();
diff --git a/sqliteMultiplatform/sqlite-driver-unbundled/src/androidMain/kotlin/androidx/sqliteMultiplatform/unbundled/NativeLibraryLoader.kt b/sqliteMultiplatform/sqlite-driver-unbundled/src/androidMain/kotlin/androidx/sqliteMultiplatform/unbundled/NativeLibraryLoader.android.kt
similarity index 100%
rename from sqliteMultiplatform/sqlite-driver-unbundled/src/androidMain/kotlin/androidx/sqliteMultiplatform/unbundled/NativeLibraryLoader.kt
rename to sqliteMultiplatform/sqlite-driver-unbundled/src/androidMain/kotlin/androidx/sqliteMultiplatform/unbundled/NativeLibraryLoader.android.kt
diff --git a/sqliteMultiplatform/sqlite-driver-unbundled/src/jvmMain/kotlin/androidx/sqliteMultiplatform/unbundled/NativeLibraryLoader.kt b/sqliteMultiplatform/sqlite-driver-unbundled/src/jvmMain/kotlin/androidx/sqliteMultiplatform/unbundled/NativeLibraryLoader.jvm.kt
similarity index 100%
rename from sqliteMultiplatform/sqlite-driver-unbundled/src/jvmMain/kotlin/androidx/sqliteMultiplatform/unbundled/NativeLibraryLoader.kt
rename to sqliteMultiplatform/sqlite-driver-unbundled/src/jvmMain/kotlin/androidx/sqliteMultiplatform/unbundled/NativeLibraryLoader.jvm.kt
diff --git a/transition/transition-ktx/src/androidTest/java/androidx/transition/TransitionTest.kt b/transition/transition-ktx/src/androidTest/java/androidx/transition/TransitionTest.kt
index 0f42fdc..24d6649 100644
--- a/transition/transition-ktx/src/androidTest/java/androidx/transition/TransitionTest.kt
+++ b/transition/transition-ktx/src/androidTest/java/androidx/transition/TransitionTest.kt
@@ -21,7 +21,6 @@
import android.widget.ImageView
import androidx.test.filters.FlakyTest
import androidx.test.filters.MediumTest
-import androidx.test.filters.SdkSuppress
import androidx.test.platform.app.InstrumentationRegistry
import androidx.transition.ktx.test.R
import java.util.concurrent.CountDownLatch
@@ -32,7 +31,6 @@
import org.junit.Rule
import org.junit.Test
-@SdkSuppress(minSdkVersion = 19)
@MediumTest
class TransitionTest {
@Suppress("DEPRECATION")
diff --git a/transition/transition/src/androidTest/java/androidx/transition/ChangeClipBoundsTest.java b/transition/transition/src/androidTest/java/androidx/transition/ChangeClipBoundsTest.java
index 5ba6b75..b1d6ce8 100644
--- a/transition/transition/src/androidTest/java/androidx/transition/ChangeClipBoundsTest.java
+++ b/transition/transition/src/androidTest/java/androidx/transition/ChangeClipBoundsTest.java
@@ -50,7 +50,6 @@
return new ChangeClipBounds();
}
- @SdkSuppress(minSdkVersion = 18)
@Test
public void testChangeClipBounds() throws Throwable {
final View redSquare = spy(new View(rule.getActivity()));
diff --git a/viewpager2/viewpager2/src/androidTest/java/androidx/viewpager2/widget/AccessibilityTest.kt b/viewpager2/viewpager2/src/androidTest/java/androidx/viewpager2/widget/AccessibilityTest.kt
index f3ba3a4..2abc406 100644
--- a/viewpager2/viewpager2/src/androidTest/java/androidx/viewpager2/widget/AccessibilityTest.kt
+++ b/viewpager2/viewpager2/src/androidTest/java/androidx/viewpager2/widget/AccessibilityTest.kt
@@ -21,7 +21,6 @@
import androidx.core.view.ViewCompat
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat
import androidx.test.filters.LargeTest
-import androidx.test.filters.SdkSuppress
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import androidx.testutils.LocaleTestUtils
@@ -71,7 +70,6 @@
}
@Test
- @SdkSuppress(minSdkVersion = 16)
fun test_onPerformPageAction() {
setUpTest(config.orientation).apply {
setAdapterSync(viewAdapterProvider.provider(stringSequence(6)))
@@ -102,13 +100,11 @@
}
@Test
- @SdkSuppress(minSdkVersion = 19)
fun test_collectionInfo() {
test_collectionInfo(6)
}
@Test
- @SdkSuppress(minSdkVersion = 19)
fun test_collectionInfo_zeroItems() {
test_collectionInfo(0)
}
@@ -139,7 +135,6 @@
}
@Test
- @SdkSuppress(minSdkVersion = 19)
fun test_collectionItemInfo() {
setUpTest(config.orientation).apply {
setAdapterSync(viewAdapterProvider.provider(stringSequence(6)))
diff --git a/viewpager2/viewpager2/src/androidTest/java/androidx/viewpager2/widget/CanScrollTest.kt b/viewpager2/viewpager2/src/androidTest/java/androidx/viewpager2/widget/CanScrollTest.kt
index 2dfbb83..6576efe 100644
--- a/viewpager2/viewpager2/src/androidTest/java/androidx/viewpager2/widget/CanScrollTest.kt
+++ b/viewpager2/viewpager2/src/androidTest/java/androidx/viewpager2/widget/CanScrollTest.kt
@@ -18,7 +18,6 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
-import androidx.test.filters.SdkSuppress
import androidx.testutils.LocaleTestUtils
import androidx.viewpager2.widget.ViewPager2.ORIENTATION_HORIZONTAL
import androidx.viewpager2.widget.ViewPager2.ORIENTATION_VERTICAL
@@ -77,7 +76,6 @@
}
@Test
- @SdkSuppress(minSdkVersion = 17)
fun test_canScrollHorizontallyVertically_horizontal_rtl() {
// given RTL locale
localeUtil.resetLocale()
diff --git a/viewpager2/viewpager2/src/androidTest/java/androidx/viewpager2/widget/FakeDragTest.kt b/viewpager2/viewpager2/src/androidTest/java/androidx/viewpager2/widget/FakeDragTest.kt
index fc58747..074d046 100644
--- a/viewpager2/viewpager2/src/androidTest/java/androidx/viewpager2/widget/FakeDragTest.kt
+++ b/viewpager2/viewpager2/src/androidTest/java/androidx/viewpager2/widget/FakeDragTest.kt
@@ -26,7 +26,6 @@
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD
import androidx.core.view.animation.PathInterpolatorCompat
import androidx.test.filters.LargeTest
-import androidx.test.filters.SdkSuppress
import androidx.testutils.LocaleTestUtils
import androidx.viewpager2.widget.BaseTest.Context.SwipeMethod
import androidx.viewpager2.widget.FakeDragTest.Event.OnPageScrollStateChangedEvent
@@ -343,7 +342,6 @@
* onPageScrollStateChanged(0)
*/
@Test
- @SdkSuppress(minSdkVersion = 16)
fun test_performA11yActionDuringFakeDrag() {
startManualDragDuringFakeDrag(.9f, 1000, interpolator = fastDecelerateInterpolator) {
test.runOnUiThreadSync {
diff --git a/viewpager2/viewpager2/src/androidTest/java/androidx/viewpager2/widget/TransientStateFragmentTest.kt b/viewpager2/viewpager2/src/androidTest/java/androidx/viewpager2/widget/TransientStateFragmentTest.kt
index f9e4951..e680a22 100644
--- a/viewpager2/viewpager2/src/androidTest/java/androidx/viewpager2/widget/TransientStateFragmentTest.kt
+++ b/viewpager2/viewpager2/src/androidTest/java/androidx/viewpager2/widget/TransientStateFragmentTest.kt
@@ -22,7 +22,6 @@
import androidx.fragment.app.FragmentManager
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
-import androidx.test.filters.SdkSuppress
import androidx.viewpager2.widget.ViewPager2.ORIENTATION_HORIZONTAL
import java.util.concurrent.TimeUnit.MILLISECONDS
import org.junit.Test
@@ -41,7 +40,6 @@
private val timeoutMs = 3000L
@Test
- @SdkSuppress(minSdkVersion = 16) /** [View.setHasTransientState] was introduced in API 16 */
fun test_swipeBetweenPages() {
setUpTest(orientation).apply {
val expectedValues = stringSequence(totalPages)
@@ -67,7 +65,6 @@
private fun createTransientStateCallback(): FragmentManager.FragmentLifecycleCallbacks {
return object : FragmentManager.FragmentLifecycleCallbacks() {
- @SdkSuppress(minSdkVersion = 16)
override fun onFragmentViewCreated(
fm: FragmentManager,
f: Fragment,
diff --git a/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/BasicSwipeToDismissBox.kt b/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/BasicSwipeToDismissBox.kt
index df92362a..2b087b8 100644
--- a/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/BasicSwipeToDismissBox.kt
+++ b/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/BasicSwipeToDismissBox.kt
@@ -197,10 +197,16 @@
Canvas(Modifier.fillMaxSize()) {
val color = if (isBackground) {
backgroundScrimColor
- .copy(alpha = MAX_BACKGROUND_SCRIM_ALPHA * (1 - progress))
+ .copy(
+ alpha = (MAX_BACKGROUND_SCRIM_ALPHA * (1 - progress))
+ .coerceIn(0f, 1f)
+ )
} else {
contentScrimColor
- .copy(alpha = min(MAX_CONTENT_SCRIM_ALPHA, progress / 2f))
+ .copy(
+ alpha = min(MAX_CONTENT_SCRIM_ALPHA, progress / 2f)
+ .coerceIn(0f, 1f)
+ )
}
drawRect(color = color)
}
diff --git a/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/DynamicDataBiTransformNode.java b/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/DynamicDataBiTransformNode.java
index 16df6ce..2b3ecda 100644
--- a/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/DynamicDataBiTransformNode.java
+++ b/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/DynamicDataBiTransformNode.java
@@ -153,7 +153,11 @@
mDownstream.onInvalidated();
} else {
O result = mTransformer.apply(lhs, rhs);
- mDownstream.onData(result);
+ if (result == null) {
+ mDownstream.onInvalidated();
+ } else {
+ mDownstream.onData(result);
+ }
}
}
}
diff --git a/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/FloatNodes.java b/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/FloatNodes.java
index 3ca2138..fb45b99 100644
--- a/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/FloatNodes.java
+++ b/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/FloatNodes.java
@@ -23,6 +23,7 @@
import androidx.annotation.UiThread;
import androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat;
import androidx.wear.protolayout.expression.proto.AnimationParameterProto.AnimationSpec;
+import androidx.wear.protolayout.expression.proto.DynamicProto;
import androidx.wear.protolayout.expression.proto.DynamicProto.AnimatableFixedFloat;
import androidx.wear.protolayout.expression.proto.DynamicProto.ArithmeticFloatOp;
import androidx.wear.protolayout.expression.proto.DynamicProto.StateFloatSource;
@@ -35,12 +36,12 @@
/** Dynamic float node that has a fixed value. */
static class FixedFloatNode implements DynamicDataSourceNode<Float> {
- private final float mValue;
+ @Nullable private final Float mValue;
private final DynamicTypeValueReceiverWithPreUpdate<Float> mDownstream;
FixedFloatNode(
FixedFloat protoNode, DynamicTypeValueReceiverWithPreUpdate<Float> downstream) {
- this.mValue = protoNode.getValue();
+ this.mValue = getValidValueOrNull(protoNode.getValue());
this.mDownstream = downstream;
}
@@ -53,7 +54,7 @@
@Override
@UiThread
public void init() {
- if (Float.isNaN(mValue)) {
+ if (mValue == null) {
mDownstream.onInvalidated();
} else {
mDownstream.onData(mValue);
@@ -75,7 +76,7 @@
dataStore,
StateSourceNode.<DynamicFloat>createKey(
protoNode.getSourceNamespace(), protoNode.getSourceKey()),
- se -> se.getFloatVal().getValue(),
+ se -> getValidValueOrNull(se.getFloatVal().getValue()),
downstream);
}
}
@@ -89,32 +90,36 @@
DynamicTypeValueReceiverWithPreUpdate<Float> downstream) {
super(
downstream,
- (lhs, rhs) -> {
- try {
- switch (protoNode.getOperationType()) {
- case ARITHMETIC_OP_TYPE_UNDEFINED:
- case UNRECOGNIZED:
- Log.e(TAG, "Unknown operation type in ArithmeticFloatNode");
- return Float.NaN;
- case ARITHMETIC_OP_TYPE_ADD:
- return lhs + rhs;
- case ARITHMETIC_OP_TYPE_SUBTRACT:
- return lhs - rhs;
- case ARITHMETIC_OP_TYPE_MULTIPLY:
- return lhs * rhs;
- case ARITHMETIC_OP_TYPE_DIVIDE:
- return lhs / rhs;
- case ARITHMETIC_OP_TYPE_MODULO:
- return lhs % rhs;
- }
- } catch (ArithmeticException ex) {
- Log.e(TAG, "ArithmeticException in ArithmeticFloatNode", ex);
- return Float.NaN;
- }
+ (lhs, rhs) ->
+ getValidValueOrNull(
+ computeResult(protoNode.getOperationType(), lhs, rhs)));
+ }
+ private static float computeResult(
+ DynamicProto.ArithmeticOpType opType, Float lhs, Float rhs) {
+ try {
+ switch (opType) {
+ case ARITHMETIC_OP_TYPE_UNDEFINED:
+ case UNRECOGNIZED:
Log.e(TAG, "Unknown operation type in ArithmeticFloatNode");
return Float.NaN;
- });
+ case ARITHMETIC_OP_TYPE_ADD:
+ return lhs + rhs;
+ case ARITHMETIC_OP_TYPE_SUBTRACT:
+ return lhs - rhs;
+ case ARITHMETIC_OP_TYPE_MULTIPLY:
+ return lhs * rhs;
+ case ARITHMETIC_OP_TYPE_DIVIDE:
+ return lhs / rhs;
+ case ARITHMETIC_OP_TYPE_MODULO:
+ return lhs % rhs;
+ }
+ } catch (ArithmeticException ex) {
+ Log.e(TAG, "ArithmeticException in ArithmeticFloatNode", ex);
+ return Float.NaN;
+ }
+ Log.e(TAG, "Unknown operation type in ArithmeticFloatNode");
+ return Float.NaN;
}
}
@@ -154,8 +159,13 @@
@Override
@UiThread
public void init() {
- mQuotaAwareAnimator.setFloatValues(mProtoNode.getFromValue(), mProtoNode.getToValue());
- startOrSkipAnimator();
+ if (isValid(mProtoNode.getFromValue()) && isValid(mProtoNode.getToValue())) {
+ mQuotaAwareAnimator.setFloatValues(
+ mProtoNode.getFromValue(), mProtoNode.getToValue());
+ startOrSkipAnimator();
+ } else {
+ mDownstream.onInvalidated();
+ }
}
@Override
@@ -237,4 +247,13 @@
return mInputCallback;
}
}
+
+ private static boolean isValid(Float value) {
+ return value != null && Float.isFinite(value);
+ }
+
+ @Nullable
+ private static Float getValidValueOrNull(Float value) {
+ return isValid(value) ? value : null;
+ }
}
diff --git a/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/Int32Nodes.java b/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/Int32Nodes.java
index dc035e0..b43d388 100644
--- a/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/Int32Nodes.java
+++ b/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/Int32Nodes.java
@@ -150,7 +150,7 @@
}
} catch (ArithmeticException ex) {
Log.e(TAG, "ArithmeticException in ArithmeticInt32Node", ex);
- return 0;
+ return null;
}
Log.e(TAG, "Unknown operation type in ArithmeticInt32Node");
diff --git a/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/StateSourceNode.java b/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/StateSourceNode.java
index dc0c723..98da705 100644
--- a/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/StateSourceNode.java
+++ b/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/StateSourceNode.java
@@ -79,7 +79,11 @@
@Override
public void onData(@NonNull DynamicDataValue newData) {
T actualValue = mStateExtractor.apply(newData);
- mDownstream.onData(actualValue);
+ if (actualValue == null) {
+ this.onInvalidated();
+ } else {
+ mDownstream.onData(actualValue);
+ }
}
@Override
diff --git a/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/FloatNodeTest.java b/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/FloatNodeTest.java
index 43ff081..27ea07b 100644
--- a/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/FloatNodeTest.java
+++ b/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/FloatNodeTest.java
@@ -68,21 +68,58 @@
@RunWith(AndroidJUnit4.class)
public class FloatNodeTest {
private static final AppDataKey<DynamicFloat> KEY_FOO = new AppDataKey<>("foo");
- @Rule
- public MockitoRule mMockitoRule = MockitoJUnit.rule();
+ @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();
@Mock private PlatformDataProvider mMockDataProvider;
+
@Test
- public void fixedFloatNodesTest() {
+ public void fixedFloatNodesTest_invalidateNotCalled() {
List<Float> results = new ArrayList<>();
+ List<Boolean> invalidList = new ArrayList<>();
float testValue = 6.6f;
FixedFloat protoNode = FixedFloat.newBuilder().setValue(testValue).build();
- FixedFloatNode node = new FixedFloatNode(protoNode, new AddToListCallback<>(results));
+ FixedFloatNode node =
+ new FixedFloatNode(protoNode, new AddToListCallback<>(results, invalidList));
node.preInit();
node.init();
assertThat(results).containsExactly(testValue);
+ assertThat(invalidList).isEmpty();
+ }
+
+ @Test
+ public void fixedFloatNodes_receiveNaN_invalidate() {
+ List<Float> results = new ArrayList<>();
+ List<Boolean> invalidList = new ArrayList<>();
+ AddToListCallback<Float> addToListCallback = new AddToListCallback<>(results, invalidList);
+ float testValue = Float.NaN;
+
+ FixedFloat protoNode = FixedFloat.newBuilder().setValue(testValue).build();
+ FixedFloatNode node = new FixedFloatNode(protoNode, addToListCallback);
+
+ node.preInit();
+ node.init();
+
+ assertThat(results).isEmpty();
+ assertThat(invalidList).containsExactly(true);
+ }
+
+ @Test
+ public void fixedFloatNodes_receiveInfinite_invalidate() {
+ List<Float> results = new ArrayList<>();
+ List<Boolean> invalidList = new ArrayList<>();
+ AddToListCallback<Float> addToListCallback = new AddToListCallback<>(results, invalidList);
+ float testValue = Float.POSITIVE_INFINITY;
+
+ FixedFloat protoNode = FixedFloat.newBuilder().setValue(testValue).build();
+ FixedFloatNode node = new FixedFloatNode(protoNode, addToListCallback);
+
+ node.preInit();
+ node.init();
+
+ assertThat(results).isEmpty();
+ assertThat(invalidList).containsExactly(true);
}
@Test
@@ -108,6 +145,58 @@
}
@Test
+ public void stateFloatSourceNode_receiveNaN_invalidate() {
+ List<Float> results = new ArrayList<>();
+ List<Boolean> invalidList = new ArrayList<>();
+
+ float testValue = Float.NaN;
+ StateStore oss =
+ new StateStore(
+ ImmutableMap.of(
+ KEY_FOO,
+ DynamicDataValue.newBuilder()
+ .setFloatVal(FixedFloat.newBuilder().setValue(testValue))
+ .build()));
+
+ StateFloatSource protoNode = StateFloatSource.newBuilder().setSourceKey("foo").build();
+ StateFloatSourceNode node =
+ new StateFloatSourceNode(
+ oss, protoNode, new AddToListCallback<>(results, invalidList));
+
+ node.preInit();
+ node.init();
+
+ assertThat(results).isEmpty();
+ assertThat(invalidList).containsExactly(true);
+ }
+
+ @Test
+ public void stateFloatSourceNode_receiveInfinite_invalidate() {
+ List<Float> results = new ArrayList<>();
+ List<Boolean> invalidList = new ArrayList<>();
+
+ float testValue = Float.POSITIVE_INFINITY;
+ StateStore oss =
+ new StateStore(
+ ImmutableMap.of(
+ KEY_FOO,
+ DynamicDataValue.newBuilder()
+ .setFloatVal(FixedFloat.newBuilder().setValue(testValue))
+ .build()));
+
+ StateFloatSource protoNode = StateFloatSource.newBuilder().setSourceKey("foo").build();
+ StateFloatSourceNode node =
+ new StateFloatSourceNode(
+ oss, protoNode, new AddToListCallback<>(results, invalidList));
+
+ node.preInit();
+ node.init();
+
+ assertThat(results).isEmpty();
+ assertThat(invalidList).containsExactly(true);
+ }
+
+ @Test
public void stateFloatSourceNode_updatesWithStateChanges() {
List<Float> results = new ArrayList<>();
float oldValue = 6.5f;
@@ -141,22 +230,17 @@
@Test
public void stateFloatSource_canSubscribeToHeartRateUpdates() {
- PlatformDataStore platformDataStore = new PlatformDataStore(
- Collections.singletonMap(
- HEART_RATE_BPM,
- mMockDataProvider));
+ PlatformDataStore platformDataStore =
+ new PlatformDataStore(Collections.singletonMap(HEART_RATE_BPM, mMockDataProvider));
StateFloatSource dailyStepsSource =
StateFloatSource.newBuilder()
.setSourceKey(HEART_RATE_BPM.getKey())
- .setSourceNamespace(
- HEART_RATE_BPM.getNamespace())
+ .setSourceNamespace(HEART_RATE_BPM.getNamespace())
.build();
List<Float> results = new ArrayList<>();
StateFloatSourceNode dailyStepsSourceNode =
new StateFloatSourceNode(
- platformDataStore,
- dailyStepsSource,
- new AddToListCallback<>(results));
+ platformDataStore, dailyStepsSource, new AddToListCallback<>(results));
dailyStepsSourceNode.preInit();
dailyStepsSourceNode.init();
@@ -261,6 +345,66 @@
}
@Test
+ public void arithmeticFloat_resultIsNaN_invalidate() {
+ List<Float> results = new ArrayList<>();
+ List<Boolean> invalidList = new ArrayList<>();
+
+ ArithmeticFloatOp protoNode =
+ ArithmeticFloatOp.newBuilder()
+ .setOperationType(ArithmeticOpType.ARITHMETIC_OP_TYPE_DIVIDE)
+ .build();
+
+ ArithmeticFloatNode node =
+ new ArithmeticFloatNode(protoNode, new AddToListCallback<>(results, invalidList));
+
+ float numerator = 0f;
+ FixedFloat lhsProtoNode = FixedFloat.newBuilder().setValue(numerator).build();
+ FixedFloatNode lhsNode = new FixedFloatNode(lhsProtoNode, node.getLhsIncomingCallback());
+
+ float denominator = 0f;
+ FixedFloat rhsProtoNode = FixedFloat.newBuilder().setValue(denominator).build();
+ FixedFloatNode rhsNode = new FixedFloatNode(rhsProtoNode, node.getRhsIncomingCallback());
+ lhsNode.preInit();
+ rhsNode.preInit();
+
+ lhsNode.init();
+ rhsNode.init();
+
+ assertThat(results).isEmpty();
+ assertThat(invalidList).containsExactly(true);
+ }
+
+ @Test
+ public void arithmeticFloat_resultIsInfinite_invalidate() {
+ List<Float> results = new ArrayList<>();
+ List<Boolean> invalidList = new ArrayList<>();
+
+ ArithmeticFloatOp protoNode =
+ ArithmeticFloatOp.newBuilder()
+ .setOperationType(ArithmeticOpType.ARITHMETIC_OP_TYPE_DIVIDE)
+ .build();
+
+ ArithmeticFloatNode node =
+ new ArithmeticFloatNode(protoNode, new AddToListCallback<>(results, invalidList));
+
+ float numerator = 1f;
+ FixedFloat lhsProtoNode = FixedFloat.newBuilder().setValue(numerator).build();
+ FixedFloatNode lhsNode = new FixedFloatNode(lhsProtoNode, node.getLhsIncomingCallback());
+
+ float denominator = 0;
+ FixedFloat rhsProtoNode = FixedFloat.newBuilder().setValue(denominator).build();
+ FixedFloatNode rhsNode = new FixedFloatNode(rhsProtoNode, node.getRhsIncomingCallback());
+ lhsNode.preInit();
+ rhsNode.preInit();
+
+ lhsNode.init();
+ rhsNode.init();
+
+ assertThat(results).isEmpty();
+ assertThat(invalidList).containsExactly(true);
+ }
+
+ @Test
public void int32ToFloatTest() {
List<Float> results = new ArrayList<>();
Int32ToFloatNode node = new Int32ToFloatNode(new AddToListCallback<>(results));
diff --git a/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/Int32NodesTest.java b/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/Int32NodesTest.java
index 1e32dc5..0adc166 100644
--- a/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/Int32NodesTest.java
+++ b/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/Int32NodesTest.java
@@ -36,6 +36,7 @@
import androidx.wear.protolayout.expression.PlatformDataValues;
import androidx.wear.protolayout.expression.PlatformHealthSources;
import androidx.wear.protolayout.expression.pipeline.Int32Nodes.AnimatableFixedInt32Node;
+import androidx.wear.protolayout.expression.pipeline.Int32Nodes.ArithmeticInt32Node;
import androidx.wear.protolayout.expression.pipeline.Int32Nodes.DynamicAnimatedInt32Node;
import androidx.wear.protolayout.expression.pipeline.Int32Nodes.FixedInt32Node;
import androidx.wear.protolayout.expression.pipeline.Int32Nodes.GetDurationPartOpNode;
@@ -43,7 +44,9 @@
import androidx.wear.protolayout.expression.pipeline.Int32Nodes.StateInt32SourceNode;
import androidx.wear.protolayout.expression.proto.AnimationParameterProto.AnimationSpec;
import androidx.wear.protolayout.expression.proto.DynamicDataProto.DynamicDataValue;
+import androidx.wear.protolayout.expression.proto.DynamicProto;
import androidx.wear.protolayout.expression.proto.DynamicProto.AnimatableFixedInt32;
+import androidx.wear.protolayout.expression.proto.DynamicProto.ArithmeticOpType;
import androidx.wear.protolayout.expression.proto.DynamicProto.DurationPartType;
import androidx.wear.protolayout.expression.proto.DynamicProto.GetDurationPartOp;
import androidx.wear.protolayout.expression.proto.DynamicProto.GetZonedDateTimePartOp;
@@ -96,6 +99,66 @@
}
@Test
+ public void testArithmeticOperation_validResult_invalidateNotCalled() {
+ List<Integer> results = new ArrayList<>();
+ List<Boolean> invalidList = new ArrayList<>();
+
+ DynamicProto.ArithmeticInt32Op protoNode =
+ DynamicProto.ArithmeticInt32Op.newBuilder()
+ .setOperationType(ArithmeticOpType.ARITHMETIC_OP_TYPE_DIVIDE)
+ .build();
+
+ ArithmeticInt32Node node =
+ new ArithmeticInt32Node(protoNode, new AddToListCallback<>(results, invalidList));
+
+ int numerator = 4;
+ FixedInt32 lhsProtoNode = FixedInt32.newBuilder().setValue(numerator).build();
+ FixedInt32Node lhsNode = new FixedInt32Node(lhsProtoNode, node.getLhsIncomingCallback());
+
+ int denominator = 2;
+ FixedInt32 rhsProtoNode = FixedInt32.newBuilder().setValue(denominator).build();
+ FixedInt32Node rhsNode = new FixedInt32Node(rhsProtoNode, node.getRhsIncomingCallback());
+ lhsNode.preInit();
+ rhsNode.preInit();
+
+ lhsNode.init();
+ rhsNode.init();
+
+ assertThat(results).containsExactly(2);
+ assertThat(invalidList).isEmpty();
+ }
+
+ @Test
+ public void testArithmeticOperation_arithmeticExceptionThrown_invalidate() {
+ List<Integer> results = new ArrayList<>();
+ List<Boolean> invalidList = new ArrayList<>();
+
+ DynamicProto.ArithmeticInt32Op protoNode =
+ DynamicProto.ArithmeticInt32Op.newBuilder()
+ .setOperationType(ArithmeticOpType.ARITHMETIC_OP_TYPE_DIVIDE)
+ .build();
+
+ ArithmeticInt32Node node =
+ new ArithmeticInt32Node(protoNode, new AddToListCallback<>(results, invalidList));
+
+ int numerator = 0;
+ FixedInt32 lhsProtoNode = FixedInt32.newBuilder().setValue(numerator).build();
+ FixedInt32Node lhsNode = new FixedInt32Node(lhsProtoNode, node.getLhsIncomingCallback());
+
+ int denominator = 0;
+ FixedInt32 rhsProtoNode = FixedInt32.newBuilder().setValue(denominator).build();
+ FixedInt32Node rhsNode = new FixedInt32Node(rhsProtoNode, node.getRhsIncomingCallback());
+ lhsNode.preInit();
+ rhsNode.preInit();
+
+ lhsNode.init();
+ rhsNode.init();
+
+ assertThat(results).isEmpty();
+ assertThat(invalidList).containsExactly(true);
+ }
+
+ @Test
public void testGetDurationPartOpNode_positiveDuration() {
// Equivalent to 1day and 10h:17m:36s
diff --git a/wear/protolayout/protolayout/api/current.txt b/wear/protolayout/protolayout/api/current.txt
index 9176f20..4bc6a05 100644
--- a/wear/protolayout/protolayout/api/current.txt
+++ b/wear/protolayout/protolayout/api/current.txt
@@ -569,7 +569,7 @@
method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public androidx.wear.protolayout.LayoutElementBuilders.FontStyle.Builder setItalic(boolean);
method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public androidx.wear.protolayout.LayoutElementBuilders.FontStyle.Builder setLetterSpacing(androidx.wear.protolayout.DimensionBuilders.EmProp);
method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public androidx.wear.protolayout.LayoutElementBuilders.FontStyle.Builder setSize(androidx.wear.protolayout.DimensionBuilders.SpProp);
- method @SuppressCompatibility @androidx.wear.protolayout.expression.ProtoLayoutExperimental @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=300) public androidx.wear.protolayout.LayoutElementBuilders.FontStyle.Builder setSizes(androidx.wear.protolayout.DimensionBuilders.SpProp!...);
+ method @SuppressCompatibility @androidx.wear.protolayout.expression.ProtoLayoutExperimental @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=300) public androidx.wear.protolayout.LayoutElementBuilders.FontStyle.Builder setSizes(@Dimension(unit=androidx.annotation.Dimension.SP) @IntRange(from=1) int...);
method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public androidx.wear.protolayout.LayoutElementBuilders.FontStyle.Builder setUnderline(androidx.wear.protolayout.TypeBuilders.BoolProp);
method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public androidx.wear.protolayout.LayoutElementBuilders.FontStyle.Builder setUnderline(boolean);
method @SuppressCompatibility @androidx.wear.protolayout.expression.ProtoLayoutExperimental @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public androidx.wear.protolayout.LayoutElementBuilders.FontStyle.Builder setVariant(androidx.wear.protolayout.LayoutElementBuilders.FontVariantProp);
diff --git a/wear/protolayout/protolayout/api/restricted_current.txt b/wear/protolayout/protolayout/api/restricted_current.txt
index 9176f20..4bc6a05 100644
--- a/wear/protolayout/protolayout/api/restricted_current.txt
+++ b/wear/protolayout/protolayout/api/restricted_current.txt
@@ -569,7 +569,7 @@
method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public androidx.wear.protolayout.LayoutElementBuilders.FontStyle.Builder setItalic(boolean);
method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public androidx.wear.protolayout.LayoutElementBuilders.FontStyle.Builder setLetterSpacing(androidx.wear.protolayout.DimensionBuilders.EmProp);
method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public androidx.wear.protolayout.LayoutElementBuilders.FontStyle.Builder setSize(androidx.wear.protolayout.DimensionBuilders.SpProp);
- method @SuppressCompatibility @androidx.wear.protolayout.expression.ProtoLayoutExperimental @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=300) public androidx.wear.protolayout.LayoutElementBuilders.FontStyle.Builder setSizes(androidx.wear.protolayout.DimensionBuilders.SpProp!...);
+ method @SuppressCompatibility @androidx.wear.protolayout.expression.ProtoLayoutExperimental @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=300) public androidx.wear.protolayout.LayoutElementBuilders.FontStyle.Builder setSizes(@Dimension(unit=androidx.annotation.Dimension.SP) @IntRange(from=1) int...);
method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public androidx.wear.protolayout.LayoutElementBuilders.FontStyle.Builder setUnderline(androidx.wear.protolayout.TypeBuilders.BoolProp);
method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public androidx.wear.protolayout.LayoutElementBuilders.FontStyle.Builder setUnderline(boolean);
method @SuppressCompatibility @androidx.wear.protolayout.expression.ProtoLayoutExperimental @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public androidx.wear.protolayout.LayoutElementBuilders.FontStyle.Builder setVariant(androidx.wear.protolayout.LayoutElementBuilders.FontVariantProp);
diff --git a/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/LayoutElementBuilders.java b/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/LayoutElementBuilders.java
index f60ea72..83ec93f 100644
--- a/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/LayoutElementBuilders.java
+++ b/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/LayoutElementBuilders.java
@@ -16,10 +16,12 @@
package androidx.wear.protolayout;
+import static androidx.wear.protolayout.DimensionBuilders.sp;
import static androidx.wear.protolayout.expression.Preconditions.checkNotNull;
import android.annotation.SuppressLint;
+import androidx.annotation.Dimension;
import androidx.annotation.IntDef;
import androidx.annotation.IntRange;
import androidx.annotation.NonNull;
@@ -843,15 +845,15 @@
* words, the largest size from the specified preset sizes that can fit the most text
* within the parent bounds will be used.
*
- * <p>The specified sizes don't have to be sorted, but they need to contain at least one
- * positive value. The maximum number of sizes used is limited to 10.
+ * <p>The specified sizes don't have to be sorted, but they need to contain only
+ * positive values. The maximum number of sizes used is limited to 10.
*
* <p>Note that, if multiple sizes are set, the parent of the {@link Text} element this
* corresponds to shouldn't have its width and height set to wrapped, as it can lead to
* unexpected results.
*
* <p>If this {@link FontStyle} is set to any other element besides {@link Text} or that
- * {@link Text} element has dynamic field, only the last added size will be use.
+ * {@link Text} element has dynamic field, only the last added size will be used.
*
* <p>Any previously added values with this method or {@link #setSize} will be cleared.
*
@@ -860,28 +862,32 @@
* values to automatically scale text. Renderers who don't support this version will use
* the last size among multiple values.
*
- * @throws IllegalArgumentException if the number of available sizes is larger than 10.
+ * @throws IllegalArgumentException if the number of available sizes is larger than 10
+ * or one of the sizes is not a positive value.
*/
@RequiresSchemaVersion(major = 1, minor = 300)
@NonNull
@ProtoLayoutExperimental
- public Builder setSizes(@NonNull SpProp... sizes) {
+ public Builder setSizes(
+ @NonNull @IntRange(from = 1) @Dimension(unit = Dimension.SP) int... sizes) {
if (sizes.length > TEXT_SIZES_LIMIT) {
throw new IllegalArgumentException(
"Number of available sizes of the font style can't be larger than 10.");
}
mImpl.clearSize();
- for (SpProp size : sizes) {
- if (size.getValue() <= 0) {
+ for (int size : sizes) {
+ if (size <= 0) {
throw new IllegalArgumentException(
"Available sizes of the font style must contain only positive "
+ "value.");
}
- mImpl.addSize(size.toProto());
+ SpProp spPropSize = sp(size);
+ mImpl.addSize(spPropSize.toProto());
mFingerprint.recordPropertyUpdate(
- 1, checkNotNull(size.getFingerprint()).aggregateValueAsInt());
+ 1,
+ checkNotNull(spPropSize.getFingerprint()).aggregateValueAsInt());
}
return this;
}
@@ -5791,7 +5797,7 @@
public static FontStyle.Builder display1(@NonNull DeviceParameters deviceParameters) {
return new FontStyle.Builder()
.setWeight(FONT_WEIGHT_BOLD)
- .setSize(DimensionBuilders.sp(isLargeScreen(deviceParameters) ? 54 : 50));
+ .setSize(sp(isLargeScreen(deviceParameters) ? 54 : 50));
}
/**
@@ -5805,7 +5811,7 @@
public static FontStyle.Builder display2(@NonNull DeviceParameters deviceParameters) {
return new FontStyle.Builder()
.setWeight(FONT_WEIGHT_BOLD)
- .setSize(DimensionBuilders.sp(isLargeScreen(deviceParameters) ? 44 : 40));
+ .setSize(sp(isLargeScreen(deviceParameters) ? 44 : 40));
}
/**
@@ -5819,7 +5825,7 @@
public static FontStyle.Builder display3(@NonNull DeviceParameters deviceParameters) {
return new FontStyle.Builder()
.setWeight(FONT_WEIGHT_BOLD)
- .setSize(DimensionBuilders.sp(isLargeScreen(deviceParameters) ? 34 : 30));
+ .setSize(sp(isLargeScreen(deviceParameters) ? 34 : 30));
}
/**
@@ -5833,7 +5839,7 @@
public static FontStyle.Builder title1(@NonNull DeviceParameters deviceParameters) {
return new FontStyle.Builder()
.setWeight(FONT_WEIGHT_BOLD)
- .setSize(DimensionBuilders.sp(isLargeScreen(deviceParameters) ? 26 : 24));
+ .setSize(sp(isLargeScreen(deviceParameters) ? 26 : 24));
}
/**
@@ -5847,7 +5853,7 @@
public static FontStyle.Builder title2(@NonNull DeviceParameters deviceParameters) {
return new FontStyle.Builder()
.setWeight(FONT_WEIGHT_BOLD)
- .setSize(DimensionBuilders.sp(isLargeScreen(deviceParameters) ? 22 : 20));
+ .setSize(sp(isLargeScreen(deviceParameters) ? 22 : 20));
}
/**
@@ -5861,7 +5867,7 @@
public static FontStyle.Builder title3(@NonNull DeviceParameters deviceParameters) {
return new FontStyle.Builder()
.setWeight(FONT_WEIGHT_BOLD)
- .setSize(DimensionBuilders.sp(isLargeScreen(deviceParameters) ? 18 : 16));
+ .setSize(sp(isLargeScreen(deviceParameters) ? 18 : 16));
}
/**
@@ -5874,7 +5880,7 @@
@Deprecated
public static FontStyle.Builder body1(@NonNull DeviceParameters deviceParameters) {
return new FontStyle.Builder()
- .setSize(DimensionBuilders.sp(isLargeScreen(deviceParameters) ? 18 : 16));
+ .setSize(sp(isLargeScreen(deviceParameters) ? 18 : 16));
}
/**
@@ -5887,7 +5893,7 @@
@Deprecated
public static FontStyle.Builder body2(@NonNull DeviceParameters deviceParameters) {
return new FontStyle.Builder()
- .setSize(DimensionBuilders.sp(isLargeScreen(deviceParameters) ? 16 : 14));
+ .setSize(sp(isLargeScreen(deviceParameters) ? 16 : 14));
}
/**
@@ -5901,7 +5907,7 @@
public static FontStyle.Builder button(@NonNull DeviceParameters deviceParameters) {
return new FontStyle.Builder()
.setWeight(FONT_WEIGHT_BOLD)
- .setSize(DimensionBuilders.sp(isLargeScreen(deviceParameters) ? 16 : 14));
+ .setSize(sp(isLargeScreen(deviceParameters) ? 16 : 14));
}
/**
@@ -5914,7 +5920,7 @@
@Deprecated
public static FontStyle.Builder caption1(@NonNull DeviceParameters deviceParameters) {
return new FontStyle.Builder()
- .setSize(DimensionBuilders.sp(isLargeScreen(deviceParameters) ? 16 : 14));
+ .setSize(sp(isLargeScreen(deviceParameters) ? 16 : 14));
}
/**
@@ -5927,7 +5933,7 @@
@Deprecated
public static FontStyle.Builder caption2(@NonNull DeviceParameters deviceParameters) {
return new FontStyle.Builder()
- .setSize(DimensionBuilders.sp(isLargeScreen(deviceParameters) ? 14 : 12));
+ .setSize(sp(isLargeScreen(deviceParameters) ? 14 : 12));
}
private FontStyles() {}
diff --git a/wear/protolayout/protolayout/src/test/java/androidx/wear/protolayout/LayoutElementBuildersTest.java b/wear/protolayout/protolayout/src/test/java/androidx/wear/protolayout/LayoutElementBuildersTest.java
index c4eb4e1..a8352c8 100644
--- a/wear/protolayout/protolayout/src/test/java/androidx/wear/protolayout/LayoutElementBuildersTest.java
+++ b/wear/protolayout/protolayout/src/test/java/androidx/wear/protolayout/LayoutElementBuildersTest.java
@@ -323,7 +323,7 @@
int[] expectedSizes = {size1, size2, lastSize};
LayoutElementBuilders.FontStyle fontStyle =
new LayoutElementBuilders.FontStyle.Builder()
- .setSizes(sp(size1), sp(size2), sp(lastSize))
+ .setSizes(size1, size2, lastSize)
.build();
LayoutElementProto.FontStyle fontStyleProto = fontStyle.toProto();
@@ -367,7 +367,7 @@
LayoutElementBuilders.FontStyle fontStyle =
new LayoutElementBuilders.FontStyle.Builder()
.setSize(sp(20))
- .setSizes(sp(size1), sp(size2))
+ .setSizes(size1, size2)
.build();
LayoutElementProto.FontStyle fontStyleProto = fontStyle.toProto();
@@ -385,21 +385,30 @@
@Test
public void testFontStyleSetSize_tooManySizes_throws() {
- DimensionBuilders.SpProp[] sizes =
- new DimensionBuilders.SpProp
- [LayoutElementBuilders.FontStyle.Builder.TEXT_SIZES_LIMIT + 1];
+ int[] sizes =
+ new int[LayoutElementBuilders.FontStyle.Builder.TEXT_SIZES_LIMIT + 1];
assertThrows(
IllegalArgumentException.class,
() -> new LayoutElementBuilders.FontStyle.Builder().setSizes(sizes).build());
}
@Test
- public void testFontStyleSetSize_allNegativeOrZero_throws() {
+ public void testFontStyleSetSize_atLeastOneNegative_throws() {
assertThrows(
IllegalArgumentException.class,
() ->
new LayoutElementBuilders.FontStyle.Builder()
- .setSizes(sp(-1), sp(0))
+ .setSizes(-1, 5, 1)
+ .build());
+ }
+
+ @Test
+ public void testFontStyleSetSize_atLeastOneZero_throws() {
+ assertThrows(
+ IllegalArgumentException.class,
+ () ->
+ new LayoutElementBuilders.FontStyle.Builder()
+ .setSizes(1, 2, 0)
.build());
}
diff --git a/work/work-runtime/src/androidTest/java/androidx/work/impl/constraints/trackers/NetworkStateTrackerTest.java b/work/work-runtime/src/androidTest/java/androidx/work/impl/constraints/trackers/NetworkStateTrackerTest.java
index afbaf5b..7b4f8c3 100644
--- a/work/work-runtime/src/androidTest/java/androidx/work/impl/constraints/trackers/NetworkStateTrackerTest.java
+++ b/work/work-runtime/src/androidTest/java/androidx/work/impl/constraints/trackers/NetworkStateTrackerTest.java
@@ -65,16 +65,6 @@
@Test
@SmallTest
- @SdkSuppress(maxSdkVersion = 15)
- public void testGetInitialState_nullNetworkInfoSdk15() {
- // API < 16 conservatively treats null networkInfo as metered.
- NetworkState expectedState = new NetworkState(false, false, true, false);
- assertThat(mTracker.readSystemState(), is(expectedState));
- }
-
- @Test
- @SmallTest
- @SdkSuppress(minSdkVersion = 16)
public void testGetInitialState_nullNetworkInfo() {
NetworkState expectedState = new NetworkState(false, false, false, false);
assertThat(mTracker.readSystemState(), is(expectedState));
diff --git a/work/work-runtime/src/androidTest/java/androidx/work/impl/utils/WorkForegroundUpdaterTest.kt b/work/work-runtime/src/androidTest/java/androidx/work/impl/utils/WorkForegroundUpdaterTest.kt
index d7e25ac..5e34fba 100644
--- a/work/work-runtime/src/androidTest/java/androidx/work/impl/utils/WorkForegroundUpdaterTest.kt
+++ b/work/work-runtime/src/androidTest/java/androidx/work/impl/utils/WorkForegroundUpdaterTest.kt
@@ -68,7 +68,7 @@
@Test(expected = IllegalStateException::class)
@MediumTest
// Mockito tries to class load android.os.CancellationSignal which is only available on API >= 16
- @SdkSuppress(minSdkVersion = 16, maxSdkVersion = 29)
+ @SdkSuppress(maxSdkVersion = 29)
public fun setForeground_whenWorkReplaced() {
val foregroundUpdater =
WorkForegroundUpdater(mDatabase, mForegroundProcessor, mTaskExecutor)
@@ -83,7 +83,7 @@
@Test(expected = IllegalStateException::class)
@MediumTest
// Mockito tries to class load android.os.CancellationSignal which is only available on API >= 16
- @SdkSuppress(minSdkVersion = 16, maxSdkVersion = 29)
+ @SdkSuppress(maxSdkVersion = 29)
public fun setForeground_whenWorkFinished() {
`when`(mWorkSpecDao.getState(anyString())).thenReturn(WorkInfo.State.SUCCEEDED)
val foregroundUpdater =
diff --git a/work/work-runtime/src/androidTest/java/androidx/work/impl/utils/WorkProgressUpdaterTest.kt b/work/work-runtime/src/androidTest/java/androidx/work/impl/utils/WorkProgressUpdaterTest.kt
index eaf7b7b..b812e9e 100644
--- a/work/work-runtime/src/androidTest/java/androidx/work/impl/utils/WorkProgressUpdaterTest.kt
+++ b/work/work-runtime/src/androidTest/java/androidx/work/impl/utils/WorkProgressUpdaterTest.kt
@@ -19,7 +19,6 @@
import android.content.Context
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
-import androidx.test.filters.SdkSuppress
import androidx.work.Data
import androidx.work.WorkInfo
import androidx.work.impl.WorkDatabase
@@ -36,7 +35,6 @@
@RunWith(AndroidJUnit4::class)
// Mockito tries to class load android.os.CancellationSignal which is only available on API >= 16
-@SdkSuppress(minSdkVersion = 16)
class WorkProgressUpdaterTest {
private lateinit var mContext: Context
private lateinit var mDatabase: WorkDatabase