Merge "Merge remoteBuildCache.gradle into settings.gradle" into androidx-main
diff --git a/annotation/annotation-experimental-lint/integration-tests/build.gradle b/annotation/annotation-experimental-lint/integration-tests/build.gradle
index 83a3c34..6e4b361 100644
--- a/annotation/annotation-experimental-lint/integration-tests/build.gradle
+++ b/annotation/annotation-experimental-lint/integration-tests/build.gradle
@@ -26,6 +26,10 @@
id("kotlin-android")
}
+android {
+ namespace "androidx.annotation.experimental.lint.integrationtests"
+}
+
dependencies {
implementation(libs.kotlinStdlib)
implementation(project(":annotation:annotation-experimental"))
diff --git a/annotation/annotation-experimental-lint/integration-tests/src/main/AndroidManifest.xml b/annotation/annotation-experimental-lint/integration-tests/src/main/AndroidManifest.xml
deleted file mode 100644
index ed18b0e..0000000
--- a/annotation/annotation-experimental-lint/integration-tests/src/main/AndroidManifest.xml
+++ /dev/null
@@ -1,17 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright 2019 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.
--->
-<manifest package="androidx.annotation.experimental.lint.integrationtests" />
diff --git a/camera/camera-camera2/lint-baseline.xml b/camera/camera-camera2/lint-baseline.xml
index 756bf4f..8b2b44e 100644
--- a/camera/camera-camera2/lint-baseline.xml
+++ b/camera/camera-camera2/lint-baseline.xml
@@ -130,33 +130,6 @@
<issue
id="UnsafeOptInUsageError"
message="This declaration is opt-in and its usage should be marked with
'@androidx.camera.camera2.interop.ExperimentalCamera2Interop' or '@OptIn(markerClass = androidx.camera.camera2.interop.ExperimentalCamera2Interop.class)'"
- errorLine1=" builder.setCaptureRequestOption(CaptureRequest.SCALER_CROP_REGION,"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/camera/camera2/internal/CropRegionZoomImpl.java"/>
- </issue>
-
- <issue
- id="UnsafeOptInUsageError"
- message="This declaration is opt-in and its usage should be marked with
'@androidx.camera.camera2.interop.ExperimentalCamera2Interop' or '@OptIn(markerClass = androidx.camera.camera2.interop.ExperimentalCamera2Interop.class)'"
- errorLine1=" builder.setCaptureRequestOption(CaptureRequest.SCALER_CROP_REGION,"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/camera/camera2/internal/CropRegionZoomImpl.java"/>
- </issue>
-
- <issue
- id="UnsafeOptInUsageError"
- message="This declaration is opt-in and its usage should be marked with
'@androidx.camera.camera2.interop.ExperimentalCamera2Interop' or '@OptIn(markerClass = androidx.camera.camera2.interop.ExperimentalCamera2Interop.class)'"
- errorLine1=" mCurrentCropRect);"
- errorLine2=" ~~~~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/camera/camera2/internal/CropRegionZoomImpl.java"/>
- </issue>
-
- <issue
- id="UnsafeOptInUsageError"
- message="This declaration is opt-in and its usage should be marked with
'@androidx.camera.camera2.interop.ExperimentalCamera2Interop' or '@OptIn(markerClass = androidx.camera.camera2.interop.ExperimentalCamera2Interop.class)'"
errorLine1=" configBuilder.setCaptureRequestOption(CaptureRequest.CONTROL_AE_EXPOSURE_COMPENSATION,"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
<location
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/CropRegionZoomImpl.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/CropRegionZoomImpl.java
index 4ec3c82..812a230 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/CropRegionZoomImpl.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/CropRegionZoomImpl.java
@@ -22,9 +22,11 @@
import android.hardware.camera2.TotalCaptureResult;
import androidx.annotation.NonNull;
+import androidx.annotation.OptIn;
import androidx.annotation.RequiresApi;
import androidx.camera.camera2.impl.Camera2ImplConfig;
import androidx.camera.camera2.internal.compat.CameraCharacteristicsCompat;
+import androidx.camera.camera2.interop.ExperimentalCamera2Interop;
import androidx.camera.core.CameraControl;
import androidx.concurrent.futures.CallbackToFutureAdapter;
import androidx.core.util.Preconditions;
@@ -61,11 +63,11 @@
return maxZoom;
}
+ @OptIn(markerClass = ExperimentalCamera2Interop.class)
@Override
public void addRequestOption(@NonNull Camera2ImplConfig.Builder builder) {
if (mCurrentCropRect != null) {
- builder.setCaptureRequestOption(CaptureRequest.SCALER_CROP_REGION,
- mCurrentCropRect);
+ builder.setCaptureRequestOption(CaptureRequest.SCALER_CROP_REGION, mCurrentCropRect);
}
}
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/ExposureControl.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/ExposureControl.java
index dcc344d..0dd44c2 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/ExposureControl.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/ExposureControl.java
@@ -23,10 +23,12 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.annotation.OptIn;
import androidx.annotation.RequiresApi;
import androidx.camera.camera2.impl.Camera2ImplConfig;
import androidx.camera.camera2.internal.annotation.CameraExecutor;
import androidx.camera.camera2.internal.compat.CameraCharacteristicsCompat;
+import androidx.camera.camera2.interop.ExperimentalCamera2Interop;
import androidx.camera.core.CameraControl;
import androidx.camera.core.ExposureState;
import androidx.camera.core.impl.annotation.ExecutedBy;
@@ -125,6 +127,7 @@
* to the shared options. It applies to all repeating requests and single requests.
*/
@ExecutedBy("mExecutor")
+ @OptIn(markerClass = ExperimentalCamera2Interop.class)
void setCaptureRequestOption(@NonNull Camera2ImplConfig.Builder configBuilder) {
configBuilder.setCaptureRequestOption(CaptureRequest.CONTROL_AE_EXPOSURE_COMPENSATION,
mExposureStateImpl.getExposureCompensationIndex());
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/workaround/ImageCapturePixelHDRPlus.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/workaround/ImageCapturePixelHDRPlus.java
index 7334bfb..2067ca8 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/workaround/ImageCapturePixelHDRPlus.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/workaround/ImageCapturePixelHDRPlus.java
@@ -20,10 +20,12 @@
import android.hardware.camera2.CaptureRequest;
import androidx.annotation.NonNull;
+import androidx.annotation.OptIn;
import androidx.annotation.RequiresApi;
import androidx.camera.camera2.impl.Camera2ImplConfig;
import androidx.camera.camera2.internal.compat.quirk.DeviceQuirks;
import androidx.camera.camera2.internal.compat.quirk.ImageCapturePixelHDRPlusQuirk;
+import androidx.camera.camera2.interop.ExperimentalCamera2Interop;
import androidx.camera.core.ImageCapture;
/**
@@ -31,6 +33,7 @@
*
* @see ImageCapturePixelHDRPlusQuirk
*/
+@OptIn(markerClass = ExperimentalCamera2Interop.class)
@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
public class ImageCapturePixelHDRPlus {
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/workaround/PreviewPixelHDRnet.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/workaround/PreviewPixelHDRnet.java
index 6a001a8..0465d6d 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/workaround/PreviewPixelHDRnet.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/workaround/PreviewPixelHDRnet.java
@@ -19,10 +19,12 @@
import android.hardware.camera2.CaptureRequest;
import androidx.annotation.NonNull;
+import androidx.annotation.OptIn;
import androidx.annotation.RequiresApi;
import androidx.camera.camera2.impl.Camera2ImplConfig;
import androidx.camera.camera2.internal.compat.quirk.DeviceQuirks;
import androidx.camera.camera2.internal.compat.quirk.PreviewPixelHDRnetQuirk;
+import androidx.camera.camera2.interop.ExperimentalCamera2Interop;
import androidx.camera.core.impl.SessionConfig;
/**
@@ -39,6 +41,7 @@
/**
* Turns on WYSIWYG viewfinder on Pixel devices
*/
+ @OptIn(markerClass = ExperimentalCamera2Interop.class)
public static void setHDRnet(@NonNull SessionConfig.Builder sessionBuilder) {
final PreviewPixelHDRnetQuirk quirk = DeviceQuirks.get(PreviewPixelHDRnetQuirk.class);
if (quirk == null) {
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/AndroidImageProxy.java b/camera/camera-core/src/main/java/androidx/camera/core/AndroidImageProxy.java
index 2cba273..b321d30 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/AndroidImageProxy.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/AndroidImageProxy.java
@@ -20,7 +20,6 @@
import android.graphics.Rect;
import android.media.Image;
-import androidx.annotation.GuardedBy;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
@@ -31,10 +30,8 @@
/** An {@link ImageProxy} which wraps around an {@link Image}. */
@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
final class AndroidImageProxy implements ImageProxy {
- @GuardedBy("this")
private final Image mImage;
- @GuardedBy("this")
private final PlaneProxy[] mPlanes;
private final ImageInfo mImageInfo;
@@ -66,45 +63,44 @@
}
@Override
- public synchronized void close() {
+ public void close() {
mImage.close();
}
@Override
@NonNull
- public synchronized Rect getCropRect() {
+ public Rect getCropRect() {
return mImage.getCropRect();
}
@Override
- public synchronized void setCropRect(@Nullable Rect rect) {
+ public void setCropRect(@Nullable Rect rect) {
mImage.setCropRect(rect);
}
@Override
- public synchronized int getFormat() {
+ public int getFormat() {
return mImage.getFormat();
}
@Override
- public synchronized int getHeight() {
+ public int getHeight() {
return mImage.getHeight();
}
@Override
- public synchronized int getWidth() {
+ public int getWidth() {
return mImage.getWidth();
}
@Override
@NonNull
- public synchronized ImageProxy.PlaneProxy[] getPlanes() {
+ public ImageProxy.PlaneProxy[] getPlanes() {
return mPlanes;
}
/** An {@link ImageProxy.PlaneProxy} which wraps around an {@link Image.Plane}. */
private static final class PlaneProxy implements ImageProxy.PlaneProxy {
- @GuardedBy("this")
private final Image.Plane mPlane;
PlaneProxy(Image.Plane plane) {
@@ -112,18 +108,18 @@
}
@Override
- public synchronized int getRowStride() {
+ public int getRowStride() {
return mPlane.getRowStride();
}
@Override
- public synchronized int getPixelStride() {
+ public int getPixelStride() {
return mPlane.getPixelStride();
}
@Override
@NonNull
- public synchronized ByteBuffer getBuffer() {
+ public ByteBuffer getBuffer() {
return mPlane.getBuffer();
}
}
@@ -136,7 +132,7 @@
@Override
@ExperimentalGetImage
- public synchronized Image getImage() {
+ public Image getImage() {
return mImage;
}
}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/ForwardingImageProxy.java b/camera/camera-core/src/main/java/androidx/camera/core/ForwardingImageProxy.java
index 672dd35..03a638e 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/ForwardingImageProxy.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/ForwardingImageProxy.java
@@ -38,17 +38,17 @@
*/
@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
abstract class ForwardingImageProxy implements ImageProxy {
- @GuardedBy("this")
+ private final Object mLock = new Object();
+
protected final ImageProxy mImage;
- @GuardedBy("this")
+ @GuardedBy("mLock")
private final Set<OnImageCloseListener> mOnImageCloseListeners = new HashSet<>();
/**
* Creates a new instance which wraps the given image.
*
* @param image to wrap
- * @return new {@link AndroidImageProxy} instance
*/
protected ForwardingImageProxy(ImageProxy image) {
mImage = image;
@@ -56,53 +56,51 @@
@Override
public void close() {
- synchronized (this) {
- mImage.close();
- }
+ mImage.close();
notifyOnImageCloseListeners();
}
@Override
@NonNull
- public synchronized Rect getCropRect() {
+ public Rect getCropRect() {
return mImage.getCropRect();
}
@Override
- public synchronized void setCropRect(@Nullable Rect rect) {
+ public void setCropRect(@Nullable Rect rect) {
mImage.setCropRect(rect);
}
@Override
- public synchronized int getFormat() {
+ public int getFormat() {
return mImage.getFormat();
}
@Override
- public synchronized int getHeight() {
+ public int getHeight() {
return mImage.getHeight();
}
@Override
- public synchronized int getWidth() {
+ public int getWidth() {
return mImage.getWidth();
}
@Override
@NonNull
- public synchronized ImageProxy.PlaneProxy[] getPlanes() {
+ public ImageProxy.PlaneProxy[] getPlanes() {
return mImage.getPlanes();
}
@Override
@NonNull
- public synchronized ImageInfo getImageInfo() {
+ public ImageInfo getImageInfo() {
return mImage.getImageInfo();
}
@Override
@ExperimentalGetImage
- public synchronized Image getImage() {
+ public Image getImage() {
return mImage.getImage();
}
@@ -111,14 +109,16 @@
*
* @param listener to add
*/
- synchronized void addOnImageCloseListener(OnImageCloseListener listener) {
- mOnImageCloseListeners.add(listener);
+ void addOnImageCloseListener(OnImageCloseListener listener) {
+ synchronized (mLock) {
+ mOnImageCloseListeners.add(listener);
+ }
}
/** Notifies the listeners that this image has been closed. */
protected void notifyOnImageCloseListeners() {
Set<OnImageCloseListener> onImageCloseListeners;
- synchronized (this) {
+ synchronized (mLock) {
// Make a copy for thread safety. We want to synchronize the access for member variables
// but not the actual callbacks to avoid a deadlock between ForwardingImageProxy and
// QueuedImageReaderProxy. go/deadlock-in-sharedimagereaderproxy
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/ImageAnalysis.java b/camera/camera-core/src/main/java/androidx/camera/core/ImageAnalysis.java
index b7caa7b..27c5d88 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/ImageAnalysis.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/ImageAnalysis.java
@@ -163,6 +163,10 @@
* <p>All {@link ImageProxy} sent to {@link Analyzer#analyze(ImageProxy)} will have
* format {@link android.graphics.PixelFormat#RGBA_8888}
*
+ * <p>The output order is a single-plane with the order of R, G, B, A in increasing byte index
+ * in the {@link java.nio.ByteBuffer}. The {@link java.nio.ByteBuffer} is retrieved from
+ * {@link ImageProxy.PlaneProxy#getBuffer()}.
+ *
* @see Builder#setOutputImageFormat(int)
*/
public static final int OUTPUT_IMAGE_FORMAT_RGBA_8888 = 2;
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/ImageProxyDownsampler.java b/camera/camera-core/src/main/java/androidx/camera/core/ImageProxyDownsampler.java
index b7a2eb9..7bf8d92 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/ImageProxyDownsampler.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/ImageProxyDownsampler.java
@@ -237,18 +237,18 @@
}
@Override
- public synchronized int getWidth() {
+ public int getWidth() {
return mDownsampledWidth;
}
@Override
- public synchronized int getHeight() {
+ public int getHeight() {
return mDownsampledHeight;
}
@Override
@NonNull
- public synchronized PlaneProxy[] getPlanes() {
+ public PlaneProxy[] getPlanes() {
return mDownsampledPlanes;
}
}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/SettableImageProxy.java b/camera/camera-core/src/main/java/androidx/camera/core/SettableImageProxy.java
index 3c45ed6..99e7789 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/SettableImageProxy.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/SettableImageProxy.java
@@ -19,6 +19,7 @@
import android.graphics.Rect;
import android.util.Size;
+import androidx.annotation.GuardedBy;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
@@ -27,9 +28,11 @@
* An {@link ImageProxy} which overwrites the {@link ImageInfo}.
*/
@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
-final class SettableImageProxy extends ForwardingImageProxy{
+final class SettableImageProxy extends ForwardingImageProxy {
+ private final Object mLock = new Object();
private final ImageInfo mImageInfo;
+ @GuardedBy("mLock")
@Nullable
private Rect mCropRect;
@@ -67,36 +70,39 @@
@NonNull
@Override
- public synchronized Rect getCropRect() {
- if (mCropRect == null) {
- return new Rect(0, 0, getWidth(), getHeight());
- } else {
- return new Rect(mCropRect); // return a copy
+ public Rect getCropRect() {
+ synchronized (mLock) {
+ if (mCropRect == null) {
+ return new Rect(0, 0, getWidth(), getHeight());
+ } else {
+ return new Rect(mCropRect); // return a copy
+ }
}
}
@Override
- public synchronized void setCropRect(@Nullable Rect cropRect) {
+ public void setCropRect(@Nullable Rect cropRect) {
if (cropRect != null) {
cropRect = new Rect(cropRect); // make a copy
if (!cropRect.intersect(0, 0, getWidth(), getHeight())) {
cropRect.setEmpty();
}
}
- mCropRect = cropRect;
+ synchronized (mLock) {
+ mCropRect = cropRect;
+ }
}
@Override
- public synchronized int getWidth() {
+ public int getWidth() {
return mWidth;
}
@Override
- public synchronized int getHeight() {
+ public int getHeight() {
return mHeight;
}
- @SuppressWarnings("UnsynchronizedOverridesSynchronized")
@Override
@NonNull
public ImageInfo getImageInfo() {
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/SingleCloseImageProxy.java b/camera/camera-core/src/main/java/androidx/camera/core/SingleCloseImageProxy.java
index a6ddb1e..241e803 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/SingleCloseImageProxy.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/SingleCloseImageProxy.java
@@ -16,14 +16,14 @@
package androidx.camera.core;
-import androidx.annotation.GuardedBy;
import androidx.annotation.RequiresApi;
+import java.util.concurrent.atomic.AtomicBoolean;
+
/** A {@link ImageProxy} which filters out redundant calls to {@link #close()}. */
@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
final class SingleCloseImageProxy extends ForwardingImageProxy {
- @GuardedBy("this")
- private boolean mClosed = false;
+ private final AtomicBoolean mClosed = new AtomicBoolean(false);
/**
* Creates a new instances which wraps the given image.
@@ -36,9 +36,8 @@
}
@Override
- public synchronized void close() {
- if (!mClosed) {
- mClosed = true;
+ public void close() {
+ if (!mClosed.getAndSet(true)) {
super.close();
}
}
diff --git a/camera/camera-effects/build.gradle b/camera/camera-effects/build.gradle
index e82c7eb..ce77c26 100644
--- a/camera/camera-effects/build.gradle
+++ b/camera/camera-effects/build.gradle
@@ -29,6 +29,7 @@
multiDexEnabled = true
}
testOptions.unitTests.includeAndroidResources = true
+ namespace "androidx.camera.effects"
}
androidx {
name = "Jetpack Camera Effects Library"
diff --git a/camera/camera-effects/src/main/AndroidManifest.xml b/camera/camera-effects/src/main/AndroidManifest.xml
deleted file mode 100644
index f0be73e..0000000
--- a/camera/camera-effects/src/main/AndroidManifest.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- 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.
- -->
-
-<manifest package="androidx.camera.effects"/>
\ No newline at end of file
diff --git a/camera/integration-tests/diagnosetestapp/src/main/AndroidManifest.xml b/camera/integration-tests/diagnosetestapp/src/main/AndroidManifest.xml
index 40312a1..83f7ba3 100644
--- a/camera/integration-tests/diagnosetestapp/src/main/AndroidManifest.xml
+++ b/camera/integration-tests/diagnosetestapp/src/main/AndroidManifest.xml
@@ -15,8 +15,7 @@
limitations under the License.
-->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="androidx.camera.integration.diagnose">>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
diff --git a/car/app/app-samples/navigation/common/src/main/AndroidManifest.xml b/car/app/app-samples/navigation/common/src/main/AndroidManifest.xml
index fff984f..0786374 100644
--- a/car/app/app-samples/navigation/common/src/main/AndroidManifest.xml
+++ b/car/app/app-samples/navigation/common/src/main/AndroidManifest.xml
@@ -14,8 +14,7 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<manifest package="androidx.car.app.sample.navigation.common"
- xmlns:android="http://schemas.android.com/apk/res/android">
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/DrawTextDemo.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/DrawTextDemo.kt
new file mode 100644
index 0000000..aaae8f1
--- /dev/null
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/DrawTextDemo.kt
@@ -0,0 +1,323 @@
+/*
+ * 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.
+ */
+@file:OptIn(ExperimentalTextApi::class)
+
+package androidx.compose.foundation.demos.text
+
+import androidx.compose.animation.animateColor
+import androidx.compose.animation.core.RepeatMode
+import androidx.compose.animation.core.infiniteRepeatable
+import androidx.compose.animation.core.rememberInfiniteTransition
+import androidx.compose.animation.core.tween
+import androidx.compose.foundation.Canvas
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.material.Checkbox
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.derivedStateOf
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateListOf
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.runtime.snapshotFlow
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.graphics.Brush
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.drawscope.translate
+import androidx.compose.ui.layout.layout
+import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.text.ExperimentalTextApi
+import androidx.compose.ui.text.SpanStyle
+import androidx.compose.ui.text.TextLayoutResult
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.buildAnnotatedString
+import androidx.compose.ui.text.drawText
+import androidx.compose.ui.text.rememberTextMeasurer
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.text.withStyle
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.dp
+import kotlin.math.roundToLong
+import kotlin.system.measureNanoTime
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.withIndex
+
+@Preview
+@Composable
+fun DrawTextDemo() {
+ LazyColumn {
+ item {
+ TagLine(tag = "Draw text string")
+ DrawTextString()
+ }
+ item {
+ TagLine(tag = "Draw text long string")
+ DrawTextLongString()
+ }
+ item {
+ TagLine(tag = "Draw text AnnotatedString")
+ DrawTextAnnotatedString()
+ }
+ item {
+ TagLine(tag = "DrawText measure")
+ DrawTextMeasure()
+ }
+
+ item {
+ TagLine(tag = "DrawText and animate color")
+ DrawTextAndAnimateColor()
+ }
+ item {
+ TagLine(tag = "DrawText measure and animate color")
+ DrawTextMeasureAndAnimateColor()
+ }
+ }
+}
+
+@Composable
+fun DrawTextString() {
+ val textMeasurer = rememberTextMeasurer()
+ Canvas(
+ Modifier
+ .fillMaxWidth()
+ .height(100.dp)
+ ) {
+ drawRect(brush = Brush.linearGradient(RainbowColors))
+ val padding = 16.dp.toPx()
+
+ drawText(
+ textMeasurer,
+ text = "Hello, World!",
+ topLeft = Offset(padding, padding),
+ style = TextStyle(fontSize = fontSize8)
+ )
+ }
+}
+
+@Composable
+fun DrawTextLongString() {
+ val textMeasurer = rememberTextMeasurer()
+ Canvas(
+ Modifier
+ .fillMaxWidth()
+ .height(100.dp)
+ ) {
+ drawRect(color = Color.Gray)
+ val padding = 16.dp.toPx()
+
+ drawText(
+ textMeasurer,
+ text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin fringilla " +
+ "laoreet aliquam. Aliquam ut nisl aliquet, laoreet tellus quis, sagittis enim. " +
+ "Sed sem dolor, tempus blandit purus suscipit, convallis tincidunt purus. Donec " +
+ "mattis placerat arcu sed consectetur. Pellentesque eu turpis lacus.",
+ topLeft = Offset(padding, padding),
+ style = TextStyle(fontSize = fontSize6),
+ overflow = TextOverflow.Visible,
+ size = IntSize((size.width - 2 * padding).toInt(), (size.height - 2 * padding).toInt())
+ )
+ }
+}
+
+@Composable
+fun DrawTextAnnotatedString() {
+ val textMeasurer = rememberTextMeasurer()
+ val text = remember {
+ buildAnnotatedString {
+ append("Hello World\n")
+ withStyle(
+ SpanStyle(brush = Brush.linearGradient(colors = RainbowColors))
+ ) {
+ append("Hello World")
+ }
+ append("\nHello World")
+ }
+ }
+ Canvas(
+ Modifier
+ .fillMaxWidth()
+ .height(100.dp)
+ ) {
+ drawRect(brush = Brush.linearGradient(RainbowColors))
+ val padding = 16.dp.toPx()
+
+ translate(padding, padding) {
+ drawText(textMeasurer, text, style = TextStyle(fontSize = fontSize6))
+ }
+ }
+}
+
+@Composable
+fun DrawTextMeasure() {
+ val textMeasurer = rememberTextMeasurer()
+ var textLayoutResult by remember { mutableStateOf<TextLayoutResult?>(null) }
+
+ Canvas(
+ Modifier
+ .fillMaxWidth()
+ .height(100.dp)
+ .layout { measurable, constraints ->
+ val placeable = measurable.measure(constraints)
+ textLayoutResult = textMeasurer.measure(
+ AnnotatedString("Hello, World!"),
+ style = TextStyle(fontSize = fontSize8)
+ )
+ layout(placeable.width, placeable.height) {
+ placeable.placeRelative(0, 0)
+ }
+ }) {
+ drawRect(brush = Brush.linearGradient(RainbowColors))
+ val padding = 16.dp.toPx()
+
+ textLayoutResult?.let { drawText(it, topLeft = Offset(padding, padding)) }
+ }
+}
+
+@Composable
+fun DrawTextAndAnimateColor() {
+ val infiniteTransition = rememberInfiniteTransition()
+ val color by infiniteTransition.animateColor(
+ initialValue = Color.Red,
+ targetValue = Color.Blue,
+ animationSpec = infiniteRepeatable(tween(3000), RepeatMode.Reverse)
+ )
+
+ var skipCache by remember { mutableStateOf(false) }
+ val textMeasurer = rememberTextMeasurer(cacheSize = if (skipCache) 0 else 16)
+
+ val totalMeasurer = remember(skipCache) { AverageDurationMeasurer() }
+ val averageTotalDuration by totalMeasurer.averageDurationFlow.collectAsState(0L)
+
+ Column {
+ Text("Average total duration: $averageTotalDuration ns")
+ Row(verticalAlignment = Alignment.CenterVertically) {
+ Checkbox(checked = skipCache, onCheckedChange = { skipCache = it })
+ Text(text = "Skip Cache")
+ }
+ Canvas(
+ Modifier
+ .fillMaxWidth()
+ .height(100.dp)) {
+ drawRect(brush = Brush.linearGradient(RainbowColors))
+ val padding = 16.dp.toPx()
+
+ val duration = measureNanoTime {
+ drawText(
+ textMeasurer,
+ text = AnnotatedString("Hello, World!"),
+ style = TextStyle(color = color, fontSize = fontSize8),
+ topLeft = Offset(padding, padding)
+ )
+ }
+ totalMeasurer.addMeasure(duration)
+ }
+ }
+}
+
+@Composable
+fun DrawTextMeasureAndAnimateColor() {
+ val textMeasurer = rememberTextMeasurer()
+ var textLayoutResult by remember { mutableStateOf<TextLayoutResult?>(null) }
+ val infiniteTransition = rememberInfiniteTransition()
+ val color by infiniteTransition.animateColor(
+ initialValue = Color.Red,
+ targetValue = Color.Blue,
+ animationSpec = infiniteRepeatable(tween(3000), RepeatMode.Reverse)
+ )
+
+ var skipCache by remember { mutableStateOf(false) }
+ val layoutMeasurer = remember(skipCache) { AverageDurationMeasurer() }
+ val drawMeasurer = remember(skipCache) { AverageDurationMeasurer() }
+
+ val averageLayoutDuration by layoutMeasurer.averageDurationFlow.collectAsState(0L)
+ val averageDrawDuration by drawMeasurer.averageDurationFlow.collectAsState(0L)
+
+ Column {
+ Text("Average layout duration: $averageLayoutDuration ns")
+ Text("Average draw duration: $averageDrawDuration ns")
+ Text("Average total duration: ${averageLayoutDuration + averageDrawDuration} ns")
+ Row(verticalAlignment = Alignment.CenterVertically) {
+ Checkbox(checked = skipCache, onCheckedChange = { skipCache = it })
+ Text(text = "Skip Cache")
+ }
+ Canvas(
+ Modifier
+ .fillMaxWidth()
+ .height(100.dp)
+ .layout { measurable, constraints ->
+ val placeable = measurable.measure(constraints)
+ val duration = measureNanoTime {
+ textLayoutResult = textMeasurer.measure(
+ AnnotatedString("Hello, World!"),
+ style = TextStyle(fontSize = fontSize8),
+ skipCache = skipCache
+ )
+ }
+ layoutMeasurer.addMeasure(duration)
+ layout(placeable.width, placeable.height) {
+ placeable.placeRelative(0, 0)
+ }
+ }) {
+ drawRect(brush = Brush.linearGradient(RainbowColors))
+ val padding = 16.dp.toPx()
+
+ textLayoutResult?.let {
+ val duration = measureNanoTime {
+ drawText(it, topLeft = Offset(padding, padding), color = color)
+ }
+ drawMeasurer.addMeasure(duration)
+ }
+ }
+ }
+}
+
+class AverageDurationMeasurer(private val capacity: Int = 600 /*60 fps * 10 seconds*/) {
+ private val values = mutableStateListOf<Long>()
+
+ fun addMeasure(duration: Long) {
+ values.add(duration)
+ while (values.size > capacity) {
+ values.removeFirst()
+ }
+ }
+
+ val current = derivedStateOf {
+ if (values.isEmpty()) 0L else values.average().roundToLong()
+ }
+
+ val averageDurationFlow = snapshotFlow { current.value }.withIndex().map { (index, value) ->
+ if (index % 60 == 0) value else null
+ }.filterNotNull()
+}
+
+private val RainbowColors = listOf(
+ Color(0xff9c4f96),
+ Color(0xffff6355),
+ Color(0xfffba949),
+ Color(0xfffae442),
+ Color(0xff8bd448),
+ Color(0xff2aa8f2)
+)
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/TextDemos.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/TextDemos.kt
index 898b9cd..d510c3b 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/TextDemos.kt
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/TextDemos.kt
@@ -23,6 +23,7 @@
"Text",
listOf(
ComposableDemo("Static text") { TextDemo() },
+ ComposableDemo("Canvas") { DrawTextDemo() },
ComposableDemo("Brush") { TextBrushDemo() },
ComposableDemo("Ellipsize") { EllipsizeDemo() },
ComposableDemo("Typeface") { TypefaceDemo() },
@@ -62,4 +63,4 @@
),
ComposableDemo("Text Accessibility") { TextAccessibilityDemo() }
)
-)
\ No newline at end of file
+)
diff --git a/compose/material3/material3/api/current.txt b/compose/material3/material3/api/current.txt
index 2764de3..581e3db 100644
--- a/compose/material3/material3/api/current.txt
+++ b/compose/material3/material3/api/current.txt
@@ -85,6 +85,7 @@
method @androidx.compose.runtime.Composable public androidx.compose.material3.ButtonElevation elevatedButtonElevation(optional float defaultElevation, optional float pressedElevation, optional float focusedElevation, optional float hoveredElevation, optional float disabledElevation);
method @androidx.compose.runtime.Composable public androidx.compose.material3.ButtonColors filledTonalButtonColors(optional long containerColor, optional long contentColor, optional long disabledContainerColor, optional long disabledContentColor);
method @androidx.compose.runtime.Composable public androidx.compose.material3.ButtonElevation filledTonalButtonElevation(optional float defaultElevation, optional float pressedElevation, optional float focusedElevation, optional float hoveredElevation, optional float disabledElevation);
+ method public androidx.compose.foundation.layout.PaddingValues getButtonWithIconContentPadding();
method public androidx.compose.foundation.layout.PaddingValues getContentPadding();
method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getElevatedShape();
method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getFilledTonalShape();
@@ -99,6 +100,7 @@
method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getTextShape();
method @androidx.compose.runtime.Composable public androidx.compose.material3.ButtonColors outlinedButtonColors(optional long containerColor, optional long contentColor, optional long disabledContainerColor, optional long disabledContentColor);
method @androidx.compose.runtime.Composable public androidx.compose.material3.ButtonColors textButtonColors(optional long containerColor, optional long contentColor, optional long disabledContainerColor, optional long disabledContentColor);
+ property public final androidx.compose.foundation.layout.PaddingValues ButtonWithIconContentPadding;
property public final androidx.compose.foundation.layout.PaddingValues ContentPadding;
property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape ElevatedShape;
property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape FilledTonalShape;
@@ -451,8 +453,6 @@
}
public final class OutlinedTextFieldKt {
- method @androidx.compose.runtime.Composable public static void OutlinedTextField(String value, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional boolean readOnly, optional androidx.compose.ui.text.TextStyle textStyle, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional boolean isError, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional androidx.compose.foundation.text.KeyboardActions keyboardActions, optional boolean singleLine, optional int maxLines, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.TextFieldColors colors);
- method @androidx.compose.runtime.Composable public static void OutlinedTextField(androidx.compose.ui.text.input.TextFieldValue value, kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.input.TextFieldValue,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional boolean readOnly, optional androidx.compose.ui.text.TextStyle textStyle, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional boolean isError, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional androidx.compose.foundation.text.KeyboardActions keyboardActions, optional boolean singleLine, optional int maxLines, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.TextFieldColors colors);
}
public final class ProgressIndicatorDefaults {
@@ -664,35 +664,6 @@
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);
}
- @androidx.compose.runtime.Stable public interface TextFieldColors {
- method @androidx.compose.runtime.Composable public androidx.compose.runtime.State<androidx.compose.ui.graphics.Color> containerColor(boolean enabled);
- method @androidx.compose.runtime.Composable public androidx.compose.runtime.State<androidx.compose.ui.graphics.Color> cursorColor(boolean isError);
- method @androidx.compose.runtime.Composable public androidx.compose.runtime.State<androidx.compose.ui.graphics.Color> indicatorColor(boolean enabled, boolean isError, androidx.compose.foundation.interaction.InteractionSource interactionSource);
- method @androidx.compose.runtime.Composable public androidx.compose.runtime.State<androidx.compose.ui.graphics.Color> labelColor(boolean enabled, boolean isError, androidx.compose.foundation.interaction.InteractionSource interactionSource);
- method @androidx.compose.runtime.Composable public androidx.compose.runtime.State<androidx.compose.ui.graphics.Color> leadingIconColor(boolean enabled, boolean isError, androidx.compose.foundation.interaction.InteractionSource interactionSource);
- method @androidx.compose.runtime.Composable public androidx.compose.runtime.State<androidx.compose.ui.graphics.Color> placeholderColor(boolean enabled);
- method @androidx.compose.runtime.Composable public androidx.compose.runtime.State<androidx.compose.ui.graphics.Color> textColor(boolean enabled);
- method @androidx.compose.runtime.Composable public androidx.compose.runtime.State<androidx.compose.ui.graphics.Color> trailingIconColor(boolean enabled, boolean isError, androidx.compose.foundation.interaction.InteractionSource interactionSource);
- }
-
- @androidx.compose.runtime.Immutable public final class TextFieldDefaults {
- method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getFilledShape();
- method public float getFocusedBorderThickness();
- method public float getMinHeight();
- method public float getMinWidth();
- method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getOutlinedShape();
- method public float getUnfocusedBorderThickness();
- method @androidx.compose.runtime.Composable public androidx.compose.material3.TextFieldColors outlinedTextFieldColors(optional long textColor, optional long disabledTextColor, optional long containerColor, optional long cursorColor, optional long errorCursorColor, optional long focusedBorderColor, optional long unfocusedBorderColor, optional long disabledBorderColor, optional long errorBorderColor, optional long focusedLeadingIconColor, optional long unfocusedLeadingIconColor, optional long disabledLeadingIconColor, optional long errorLeadingIconColor, optional long focusedTrailingIconColor, optional long unfocusedTrailingIconColor, optional long disabledTrailingIconColor, optional long errorTrailingIconColor, optional long focusedLabelColor, optional long unfocusedLabelColor, optional long disabledLabelColor, optional long errorLabelColor, optional long placeholderColor, optional long disabledPlaceholderColor);
- method @androidx.compose.runtime.Composable public androidx.compose.material3.TextFieldColors textFieldColors(optional long textColor, optional long disabledTextColor, optional long containerColor, optional long cursorColor, optional long errorCursorColor, optional long focusedIndicatorColor, optional long unfocusedIndicatorColor, optional long disabledIndicatorColor, optional long errorIndicatorColor, optional long focusedLeadingIconColor, optional long unfocusedLeadingIconColor, optional long disabledLeadingIconColor, optional long errorLeadingIconColor, optional long focusedTrailingIconColor, optional long unfocusedTrailingIconColor, optional long disabledTrailingIconColor, optional long errorTrailingIconColor, optional long focusedLabelColor, optional long unfocusedLabelColor, optional long disabledLabelColor, optional long errorLabelColor, optional long placeholderColor, optional long disabledPlaceholderColor);
- property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape FilledShape;
- property public final float FocusedBorderThickness;
- property public final float MinHeight;
- property public final float MinWidth;
- property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape OutlinedShape;
- property public final float UnfocusedBorderThickness;
- field public static final androidx.compose.material3.TextFieldDefaults INSTANCE;
- }
-
public final class TextFieldDefaultsKt {
}
@@ -700,8 +671,6 @@
}
public final class TextFieldKt {
- method @androidx.compose.runtime.Composable public static void TextField(String value, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional boolean readOnly, optional androidx.compose.ui.text.TextStyle textStyle, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional boolean isError, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional androidx.compose.foundation.text.KeyboardActions keyboardActions, optional boolean singleLine, optional int maxLines, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.TextFieldColors colors);
- method @androidx.compose.runtime.Composable public static void TextField(androidx.compose.ui.text.input.TextFieldValue value, kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.input.TextFieldValue,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional boolean readOnly, optional androidx.compose.ui.text.TextStyle textStyle, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional boolean isError, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional androidx.compose.foundation.text.KeyboardActions keyboardActions, optional boolean singleLine, optional int maxLines, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.TextFieldColors colors);
}
public final class TextKt {
diff --git a/compose/material3/material3/api/public_plus_experimental_current.txt b/compose/material3/material3/api/public_plus_experimental_current.txt
index 44e6450..77cfe24 100644
--- a/compose/material3/material3/api/public_plus_experimental_current.txt
+++ b/compose/material3/material3/api/public_plus_experimental_current.txt
@@ -100,6 +100,7 @@
method @androidx.compose.runtime.Composable public androidx.compose.material3.ButtonElevation elevatedButtonElevation(optional float defaultElevation, optional float pressedElevation, optional float focusedElevation, optional float hoveredElevation, optional float disabledElevation);
method @androidx.compose.runtime.Composable public androidx.compose.material3.ButtonColors filledTonalButtonColors(optional long containerColor, optional long contentColor, optional long disabledContainerColor, optional long disabledContentColor);
method @androidx.compose.runtime.Composable public androidx.compose.material3.ButtonElevation filledTonalButtonElevation(optional float defaultElevation, optional float pressedElevation, optional float focusedElevation, optional float hoveredElevation, optional float disabledElevation);
+ method public androidx.compose.foundation.layout.PaddingValues getButtonWithIconContentPadding();
method public androidx.compose.foundation.layout.PaddingValues getContentPadding();
method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getElevatedShape();
method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getFilledTonalShape();
@@ -114,6 +115,7 @@
method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getTextShape();
method @androidx.compose.runtime.Composable public androidx.compose.material3.ButtonColors outlinedButtonColors(optional long containerColor, optional long contentColor, optional long disabledContainerColor, optional long disabledContentColor);
method @androidx.compose.runtime.Composable public androidx.compose.material3.ButtonColors textButtonColors(optional long containerColor, optional long contentColor, optional long disabledContainerColor, optional long disabledContentColor);
+ property public final androidx.compose.foundation.layout.PaddingValues ButtonWithIconContentPadding;
property public final androidx.compose.foundation.layout.PaddingValues ContentPadding;
property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape ElevatedShape;
property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape FilledTonalShape;
@@ -628,8 +630,8 @@
}
public final class OutlinedTextFieldKt {
- method @androidx.compose.runtime.Composable public static void OutlinedTextField(String value, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional boolean readOnly, optional androidx.compose.ui.text.TextStyle textStyle, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional boolean isError, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional androidx.compose.foundation.text.KeyboardActions keyboardActions, optional boolean singleLine, optional int maxLines, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.TextFieldColors colors);
- method @androidx.compose.runtime.Composable public static void OutlinedTextField(androidx.compose.ui.text.input.TextFieldValue value, kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.input.TextFieldValue,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional boolean readOnly, optional androidx.compose.ui.text.TextStyle textStyle, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional boolean isError, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional androidx.compose.foundation.text.KeyboardActions keyboardActions, optional boolean singleLine, optional int maxLines, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.TextFieldColors colors);
+ method @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void OutlinedTextField(String value, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional boolean readOnly, optional androidx.compose.ui.text.TextStyle textStyle, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional boolean isError, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional androidx.compose.foundation.text.KeyboardActions keyboardActions, optional boolean singleLine, optional int maxLines, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.TextFieldColors colors);
+ method @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void OutlinedTextField(androidx.compose.ui.text.input.TextFieldValue value, kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.input.TextFieldValue,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional boolean readOnly, optional androidx.compose.ui.text.TextStyle textStyle, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional boolean isError, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional androidx.compose.foundation.text.KeyboardActions keyboardActions, optional boolean singleLine, optional int maxLines, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.TextFieldColors colors);
}
public final class ProgressIndicatorDefaults {
@@ -879,7 +881,7 @@
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);
}
- @androidx.compose.runtime.Stable public interface TextFieldColors {
+ @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Stable public interface TextFieldColors {
method @androidx.compose.runtime.Composable public androidx.compose.runtime.State<androidx.compose.ui.graphics.Color> containerColor(boolean enabled);
method @androidx.compose.runtime.Composable public androidx.compose.runtime.State<androidx.compose.ui.graphics.Color> cursorColor(boolean isError);
method @androidx.compose.runtime.Composable public androidx.compose.runtime.State<androidx.compose.ui.graphics.Color> indicatorColor(boolean enabled, boolean isError, androidx.compose.foundation.interaction.InteractionSource interactionSource);
@@ -890,7 +892,7 @@
method @androidx.compose.runtime.Composable public androidx.compose.runtime.State<androidx.compose.ui.graphics.Color> trailingIconColor(boolean enabled, boolean isError, androidx.compose.foundation.interaction.InteractionSource interactionSource);
}
- @androidx.compose.runtime.Immutable public final class TextFieldDefaults {
+ @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Immutable public final class TextFieldDefaults {
method @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public void BorderBox(boolean enabled, boolean isError, androidx.compose.foundation.interaction.InteractionSource interactionSource, androidx.compose.material3.TextFieldColors colors, optional androidx.compose.ui.graphics.Shape shape, optional float focusedBorderThickness, optional float unfocusedBorderThickness);
method @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public void OutlinedTextFieldDecorationBox(String value, kotlin.jvm.functions.Function0<kotlin.Unit> innerTextField, boolean enabled, boolean singleLine, androidx.compose.ui.text.input.VisualTransformation visualTransformation, androidx.compose.foundation.interaction.InteractionSource interactionSource, optional boolean isError, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional androidx.compose.material3.TextFieldColors colors, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional kotlin.jvm.functions.Function0<kotlin.Unit> border);
method @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public void TextFieldDecorationBox(String value, kotlin.jvm.functions.Function0<kotlin.Unit> innerTextField, boolean enabled, boolean singleLine, androidx.compose.ui.text.input.VisualTransformation visualTransformation, androidx.compose.foundation.interaction.InteractionSource interactionSource, optional boolean isError, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional androidx.compose.material3.TextFieldColors colors, optional androidx.compose.foundation.layout.PaddingValues contentPadding);
@@ -901,9 +903,9 @@
method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getOutlinedShape();
method public float getUnfocusedBorderThickness();
method @androidx.compose.material3.ExperimentalMaterial3Api public androidx.compose.ui.Modifier indicatorLine(androidx.compose.ui.Modifier, boolean enabled, boolean isError, androidx.compose.foundation.interaction.InteractionSource interactionSource, androidx.compose.material3.TextFieldColors colors, optional float focusedIndicatorLineThickness, optional float unfocusedIndicatorLineThickness);
- method @androidx.compose.runtime.Composable public androidx.compose.material3.TextFieldColors outlinedTextFieldColors(optional long textColor, optional long disabledTextColor, optional long containerColor, optional long cursorColor, optional long errorCursorColor, optional long focusedBorderColor, optional long unfocusedBorderColor, optional long disabledBorderColor, optional long errorBorderColor, optional long focusedLeadingIconColor, optional long unfocusedLeadingIconColor, optional long disabledLeadingIconColor, optional long errorLeadingIconColor, optional long focusedTrailingIconColor, optional long unfocusedTrailingIconColor, optional long disabledTrailingIconColor, optional long errorTrailingIconColor, optional long focusedLabelColor, optional long unfocusedLabelColor, optional long disabledLabelColor, optional long errorLabelColor, optional long placeholderColor, optional long disabledPlaceholderColor);
+ method @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public androidx.compose.material3.TextFieldColors outlinedTextFieldColors(optional long textColor, optional long disabledTextColor, optional long containerColor, optional long cursorColor, optional long errorCursorColor, optional long focusedBorderColor, optional long unfocusedBorderColor, optional long disabledBorderColor, optional long errorBorderColor, optional long focusedLeadingIconColor, optional long unfocusedLeadingIconColor, optional long disabledLeadingIconColor, optional long errorLeadingIconColor, optional long focusedTrailingIconColor, optional long unfocusedTrailingIconColor, optional long disabledTrailingIconColor, optional long errorTrailingIconColor, optional long focusedLabelColor, optional long unfocusedLabelColor, optional long disabledLabelColor, optional long errorLabelColor, optional long placeholderColor, optional long disabledPlaceholderColor);
method @androidx.compose.material3.ExperimentalMaterial3Api public androidx.compose.foundation.layout.PaddingValues outlinedTextFieldPadding(optional float start, optional float top, optional float end, optional float bottom);
- method @androidx.compose.runtime.Composable public androidx.compose.material3.TextFieldColors textFieldColors(optional long textColor, optional long disabledTextColor, optional long containerColor, optional long cursorColor, optional long errorCursorColor, optional long focusedIndicatorColor, optional long unfocusedIndicatorColor, optional long disabledIndicatorColor, optional long errorIndicatorColor, optional long focusedLeadingIconColor, optional long unfocusedLeadingIconColor, optional long disabledLeadingIconColor, optional long errorLeadingIconColor, optional long focusedTrailingIconColor, optional long unfocusedTrailingIconColor, optional long disabledTrailingIconColor, optional long errorTrailingIconColor, optional long focusedLabelColor, optional long unfocusedLabelColor, optional long disabledLabelColor, optional long errorLabelColor, optional long placeholderColor, optional long disabledPlaceholderColor);
+ method @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public androidx.compose.material3.TextFieldColors textFieldColors(optional long textColor, optional long disabledTextColor, optional long containerColor, optional long cursorColor, optional long errorCursorColor, optional long focusedIndicatorColor, optional long unfocusedIndicatorColor, optional long disabledIndicatorColor, optional long errorIndicatorColor, optional long focusedLeadingIconColor, optional long unfocusedLeadingIconColor, optional long disabledLeadingIconColor, optional long errorLeadingIconColor, optional long focusedTrailingIconColor, optional long unfocusedTrailingIconColor, optional long disabledTrailingIconColor, optional long errorTrailingIconColor, optional long focusedLabelColor, optional long unfocusedLabelColor, optional long disabledLabelColor, optional long errorLabelColor, optional long placeholderColor, optional long disabledPlaceholderColor);
method @androidx.compose.material3.ExperimentalMaterial3Api public androidx.compose.foundation.layout.PaddingValues textFieldWithLabelPadding(optional float start, optional float end, optional float top, optional float bottom);
method @androidx.compose.material3.ExperimentalMaterial3Api public androidx.compose.foundation.layout.PaddingValues textFieldWithoutLabelPadding(optional float start, optional float top, optional float end, optional float bottom);
property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape FilledShape;
@@ -922,8 +924,8 @@
}
public final class TextFieldKt {
- method @androidx.compose.runtime.Composable public static void TextField(String value, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional boolean readOnly, optional androidx.compose.ui.text.TextStyle textStyle, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional boolean isError, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional androidx.compose.foundation.text.KeyboardActions keyboardActions, optional boolean singleLine, optional int maxLines, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.TextFieldColors colors);
- method @androidx.compose.runtime.Composable public static void TextField(androidx.compose.ui.text.input.TextFieldValue value, kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.input.TextFieldValue,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional boolean readOnly, optional androidx.compose.ui.text.TextStyle textStyle, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional boolean isError, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional androidx.compose.foundation.text.KeyboardActions keyboardActions, optional boolean singleLine, optional int maxLines, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.TextFieldColors colors);
+ method @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void TextField(String value, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional boolean readOnly, optional androidx.compose.ui.text.TextStyle textStyle, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional boolean isError, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional androidx.compose.foundation.text.KeyboardActions keyboardActions, optional boolean singleLine, optional int maxLines, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.TextFieldColors colors);
+ method @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void TextField(androidx.compose.ui.text.input.TextFieldValue value, kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.input.TextFieldValue,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional boolean readOnly, optional androidx.compose.ui.text.TextStyle textStyle, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional boolean isError, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional androidx.compose.foundation.text.KeyboardActions keyboardActions, optional boolean singleLine, optional int maxLines, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.TextFieldColors colors);
}
public final class TextKt {
diff --git a/compose/material3/material3/api/restricted_current.txt b/compose/material3/material3/api/restricted_current.txt
index 2764de3..581e3db 100644
--- a/compose/material3/material3/api/restricted_current.txt
+++ b/compose/material3/material3/api/restricted_current.txt
@@ -85,6 +85,7 @@
method @androidx.compose.runtime.Composable public androidx.compose.material3.ButtonElevation elevatedButtonElevation(optional float defaultElevation, optional float pressedElevation, optional float focusedElevation, optional float hoveredElevation, optional float disabledElevation);
method @androidx.compose.runtime.Composable public androidx.compose.material3.ButtonColors filledTonalButtonColors(optional long containerColor, optional long contentColor, optional long disabledContainerColor, optional long disabledContentColor);
method @androidx.compose.runtime.Composable public androidx.compose.material3.ButtonElevation filledTonalButtonElevation(optional float defaultElevation, optional float pressedElevation, optional float focusedElevation, optional float hoveredElevation, optional float disabledElevation);
+ method public androidx.compose.foundation.layout.PaddingValues getButtonWithIconContentPadding();
method public androidx.compose.foundation.layout.PaddingValues getContentPadding();
method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getElevatedShape();
method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getFilledTonalShape();
@@ -99,6 +100,7 @@
method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getTextShape();
method @androidx.compose.runtime.Composable public androidx.compose.material3.ButtonColors outlinedButtonColors(optional long containerColor, optional long contentColor, optional long disabledContainerColor, optional long disabledContentColor);
method @androidx.compose.runtime.Composable public androidx.compose.material3.ButtonColors textButtonColors(optional long containerColor, optional long contentColor, optional long disabledContainerColor, optional long disabledContentColor);
+ property public final androidx.compose.foundation.layout.PaddingValues ButtonWithIconContentPadding;
property public final androidx.compose.foundation.layout.PaddingValues ContentPadding;
property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape ElevatedShape;
property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape FilledTonalShape;
@@ -451,8 +453,6 @@
}
public final class OutlinedTextFieldKt {
- method @androidx.compose.runtime.Composable public static void OutlinedTextField(String value, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional boolean readOnly, optional androidx.compose.ui.text.TextStyle textStyle, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional boolean isError, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional androidx.compose.foundation.text.KeyboardActions keyboardActions, optional boolean singleLine, optional int maxLines, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.TextFieldColors colors);
- method @androidx.compose.runtime.Composable public static void OutlinedTextField(androidx.compose.ui.text.input.TextFieldValue value, kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.input.TextFieldValue,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional boolean readOnly, optional androidx.compose.ui.text.TextStyle textStyle, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional boolean isError, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional androidx.compose.foundation.text.KeyboardActions keyboardActions, optional boolean singleLine, optional int maxLines, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.TextFieldColors colors);
}
public final class ProgressIndicatorDefaults {
@@ -664,35 +664,6 @@
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);
}
- @androidx.compose.runtime.Stable public interface TextFieldColors {
- method @androidx.compose.runtime.Composable public androidx.compose.runtime.State<androidx.compose.ui.graphics.Color> containerColor(boolean enabled);
- method @androidx.compose.runtime.Composable public androidx.compose.runtime.State<androidx.compose.ui.graphics.Color> cursorColor(boolean isError);
- method @androidx.compose.runtime.Composable public androidx.compose.runtime.State<androidx.compose.ui.graphics.Color> indicatorColor(boolean enabled, boolean isError, androidx.compose.foundation.interaction.InteractionSource interactionSource);
- method @androidx.compose.runtime.Composable public androidx.compose.runtime.State<androidx.compose.ui.graphics.Color> labelColor(boolean enabled, boolean isError, androidx.compose.foundation.interaction.InteractionSource interactionSource);
- method @androidx.compose.runtime.Composable public androidx.compose.runtime.State<androidx.compose.ui.graphics.Color> leadingIconColor(boolean enabled, boolean isError, androidx.compose.foundation.interaction.InteractionSource interactionSource);
- method @androidx.compose.runtime.Composable public androidx.compose.runtime.State<androidx.compose.ui.graphics.Color> placeholderColor(boolean enabled);
- method @androidx.compose.runtime.Composable public androidx.compose.runtime.State<androidx.compose.ui.graphics.Color> textColor(boolean enabled);
- method @androidx.compose.runtime.Composable public androidx.compose.runtime.State<androidx.compose.ui.graphics.Color> trailingIconColor(boolean enabled, boolean isError, androidx.compose.foundation.interaction.InteractionSource interactionSource);
- }
-
- @androidx.compose.runtime.Immutable public final class TextFieldDefaults {
- method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getFilledShape();
- method public float getFocusedBorderThickness();
- method public float getMinHeight();
- method public float getMinWidth();
- method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getOutlinedShape();
- method public float getUnfocusedBorderThickness();
- method @androidx.compose.runtime.Composable public androidx.compose.material3.TextFieldColors outlinedTextFieldColors(optional long textColor, optional long disabledTextColor, optional long containerColor, optional long cursorColor, optional long errorCursorColor, optional long focusedBorderColor, optional long unfocusedBorderColor, optional long disabledBorderColor, optional long errorBorderColor, optional long focusedLeadingIconColor, optional long unfocusedLeadingIconColor, optional long disabledLeadingIconColor, optional long errorLeadingIconColor, optional long focusedTrailingIconColor, optional long unfocusedTrailingIconColor, optional long disabledTrailingIconColor, optional long errorTrailingIconColor, optional long focusedLabelColor, optional long unfocusedLabelColor, optional long disabledLabelColor, optional long errorLabelColor, optional long placeholderColor, optional long disabledPlaceholderColor);
- method @androidx.compose.runtime.Composable public androidx.compose.material3.TextFieldColors textFieldColors(optional long textColor, optional long disabledTextColor, optional long containerColor, optional long cursorColor, optional long errorCursorColor, optional long focusedIndicatorColor, optional long unfocusedIndicatorColor, optional long disabledIndicatorColor, optional long errorIndicatorColor, optional long focusedLeadingIconColor, optional long unfocusedLeadingIconColor, optional long disabledLeadingIconColor, optional long errorLeadingIconColor, optional long focusedTrailingIconColor, optional long unfocusedTrailingIconColor, optional long disabledTrailingIconColor, optional long errorTrailingIconColor, optional long focusedLabelColor, optional long unfocusedLabelColor, optional long disabledLabelColor, optional long errorLabelColor, optional long placeholderColor, optional long disabledPlaceholderColor);
- property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape FilledShape;
- property public final float FocusedBorderThickness;
- property public final float MinHeight;
- property public final float MinWidth;
- property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape OutlinedShape;
- property public final float UnfocusedBorderThickness;
- field public static final androidx.compose.material3.TextFieldDefaults INSTANCE;
- }
-
public final class TextFieldDefaultsKt {
}
@@ -700,8 +671,6 @@
}
public final class TextFieldKt {
- method @androidx.compose.runtime.Composable public static void TextField(String value, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional boolean readOnly, optional androidx.compose.ui.text.TextStyle textStyle, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional boolean isError, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional androidx.compose.foundation.text.KeyboardActions keyboardActions, optional boolean singleLine, optional int maxLines, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.TextFieldColors colors);
- method @androidx.compose.runtime.Composable public static void TextField(androidx.compose.ui.text.input.TextFieldValue value, kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.input.TextFieldValue,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional boolean readOnly, optional androidx.compose.ui.text.TextStyle textStyle, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional boolean isError, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional androidx.compose.foundation.text.KeyboardActions keyboardActions, optional boolean singleLine, optional int maxLines, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.TextFieldColors colors);
}
public final class TextKt {
diff --git a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/ButtonSamples.kt b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/ButtonSamples.kt
index d9a844e..220c10f 100644
--- a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/ButtonSamples.kt
+++ b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/ButtonSamples.kt
@@ -65,7 +65,10 @@
@Sampled
@Composable
fun ButtonWithIconSample() {
- Button(onClick = { /* Do something! */ }) {
+ Button(
+ onClick = { /* Do something! */ },
+ contentPadding = ButtonDefaults.ButtonWithIconContentPadding
+ ) {
Icon(
Icons.Filled.Favorite,
contentDescription = "Localized description",
diff --git a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/TextFieldSamples.kt b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/TextFieldSamples.kt
index 78a0b68..34d1fe9 100644
--- a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/TextFieldSamples.kt
+++ b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/TextFieldSamples.kt
@@ -14,6 +14,8 @@
* limitations under the License.
*/
+@file:OptIn(ExperimentalMaterial3Api::class)
+
package androidx.compose.material3.samples
import androidx.annotation.Sampled
diff --git a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/OutlinedTextFieldScreenshotTest.kt b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/OutlinedTextFieldScreenshotTest.kt
index 08e2e1c..4b6409e 100644
--- a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/OutlinedTextFieldScreenshotTest.kt
+++ b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/OutlinedTextFieldScreenshotTest.kt
@@ -53,6 +53,7 @@
import org.junit.Test
import org.junit.runner.RunWith
+@OptIn(ExperimentalMaterial3Api::class)
@LargeTest
@RunWith(AndroidJUnit4::class)
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
diff --git a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/OutlinedTextFieldTest.kt b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/OutlinedTextFieldTest.kt
index 6ee82f7..f7edf54 100644
--- a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/OutlinedTextFieldTest.kt
+++ b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/OutlinedTextFieldTest.kt
@@ -92,6 +92,7 @@
import org.junit.Test
import org.junit.runner.RunWith
+@OptIn(ExperimentalMaterial3Api::class)
@MediumTest
@RunWith(AndroidJUnit4::class)
class OutlinedTextFieldTest {
diff --git a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/TextFieldScreenshotTest.kt b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/TextFieldScreenshotTest.kt
index ee57b40..997cb9d 100644
--- a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/TextFieldScreenshotTest.kt
+++ b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/TextFieldScreenshotTest.kt
@@ -52,6 +52,7 @@
import org.junit.Test
import org.junit.runner.RunWith
+@OptIn(ExperimentalMaterial3Api::class)
@LargeTest
@RunWith(AndroidJUnit4::class)
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
diff --git a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/TextFieldTest.kt b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/TextFieldTest.kt
index ac806d9..6da32cb 100644
--- a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/TextFieldTest.kt
+++ b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/TextFieldTest.kt
@@ -111,6 +111,7 @@
import org.junit.Test
import org.junit.runner.RunWith
+@OptIn(ExperimentalMaterial3Api::class)
@MediumTest
@RunWith(AndroidJUnit4::class)
class TextFieldTest {
diff --git a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/TextSelectionColorsScreenshotTest.kt b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/TextSelectionColorsScreenshotTest.kt
index 5a1a8d9..22fe7bf 100644
--- a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/TextSelectionColorsScreenshotTest.kt
+++ b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/TextSelectionColorsScreenshotTest.kt
@@ -217,6 +217,7 @@
private fun FilledTextFieldTestContent(colorScheme: ColorScheme) {
MaterialTheme(colorScheme) {
Surface(Modifier.testTag(Tag)) {
+ @OptIn(ExperimentalMaterial3Api::class)
TextField(
value = TextFieldText,
onValueChange = {},
@@ -230,6 +231,7 @@
private fun OutlinedTextFieldTestContent(colorScheme: ColorScheme) {
MaterialTheme(colorScheme) {
Surface(Modifier.testTag(Tag)) {
+ @OptIn(ExperimentalMaterial3Api::class)
OutlinedTextField(
value = TextFieldText,
onValueChange = {},
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Button.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Button.kt
index 6c39046..a0b2199 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Button.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Button.kt
@@ -460,6 +460,7 @@
* [OutlinedButton] buttons.
*
* - See [TextButtonContentPadding] for content padding used by [TextButton].
+ * - See [ButtonWithIconContentPadding] for content padding used by [Button] that contains [Icon].
*/
// TODO(b/201343537): Use tokens.
val ContentPadding =
@@ -470,6 +471,17 @@
bottom = ButtonVerticalPadding
)
+ private val ButtonWithIconHorizontalStartPadding = 16.dp
+
+ /** The default content padding used by [Button] that contains an [Icon]. */
+ val ButtonWithIconContentPadding =
+ PaddingValues(
+ start = ButtonWithIconHorizontalStartPadding,
+ top = ButtonVerticalPadding,
+ end = ButtonHorizontalPadding,
+ bottom = ButtonVerticalPadding
+ )
+
// TODO(b/201344013): Make sure these values stay up to date until replaced with tokens.
private val TextButtonHorizontalPadding = 12.dp
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/OutlinedTextField.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/OutlinedTextField.kt
index bbdde55..68fdeca 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/OutlinedTextField.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/OutlinedTextField.kt
@@ -125,6 +125,7 @@
* @param colors [TextFieldColors] that will be used to resolve the colors used for this text field
* in different states. See [TextFieldDefaults.outlinedTextFieldColors].
*/
+@ExperimentalMaterial3Api
@Composable
fun OutlinedTextField(
value: String,
@@ -267,6 +268,7 @@
* @param colors [TextFieldColors] that will be used to resolve the colors used for this text field
* in different states. See [TextFieldDefaults.outlinedTextFieldColors].
*/
+@ExperimentalMaterial3Api
@Composable
fun OutlinedTextField(
value: TextFieldValue,
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TextField.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TextField.kt
index 6fa1036c..f92aec7 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TextField.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TextField.kt
@@ -151,6 +151,7 @@
* @param colors [TextFieldColors] that will be used to resolve the colors used for this text field
* in different states. See [TextFieldDefaults.textFieldColors].
*/
+@ExperimentalMaterial3Api
@Composable
fun TextField(
value: String,
@@ -283,6 +284,7 @@
* @param colors [TextFieldColors] that will be used to resolve the colors used for this text field
* in different states. See [TextFieldDefaults.textFieldColors].
*/
+@ExperimentalMaterial3Api
@Composable
fun TextField(
value: TextFieldValue,
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TextFieldDefaults.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TextFieldDefaults.kt
index 5d66ca1..3d65897 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TextFieldDefaults.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TextFieldDefaults.kt
@@ -56,6 +56,7 @@
* See [TextFieldDefaults.outlinedTextFieldColors] for the default colors used in
* [OutlinedTextField].
*/
+@ExperimentalMaterial3Api
@Stable
interface TextFieldColors {
/**
@@ -154,6 +155,7 @@
/**
* Contains the default values used by [TextField] and [OutlinedTextField].
*/
+@ExperimentalMaterial3Api
@Immutable
object TextFieldDefaults {
/** Default shape for an outlined text field. */
@@ -337,6 +339,7 @@
* @param placeholderColor the placeholder color for this text field
* @param disabledPlaceholderColor the placeholder color for this text field when disabled
*/
+ @ExperimentalMaterial3Api
@Composable
fun textFieldColors(
textColor: Color = FilledTextFieldTokens.InputColor.toColor(),
@@ -424,6 +427,7 @@
* @param placeholderColor the placeholder color for this text field
* @param disabledPlaceholderColor the placeholder color for this text field when disabled
*/
+ @ExperimentalMaterial3Api
@Composable
fun outlinedTextFieldColors(
textColor: Color = OutlinedTextFieldTokens.InputColor.toColor(),
@@ -670,6 +674,7 @@
}
}
+@OptIn(ExperimentalMaterial3Api::class)
@Immutable
private class DefaultTextFieldColors(
private val textColor: Color,
@@ -852,6 +857,7 @@
}
}
+@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun animateBorderStrokeAsState(
enabled: Boolean,
diff --git a/compose/runtime/runtime-tracing/build.gradle b/compose/runtime/runtime-tracing/build.gradle
index 242290f..c12bfd6 100644
--- a/compose/runtime/runtime-tracing/build.gradle
+++ b/compose/runtime/runtime-tracing/build.gradle
@@ -28,6 +28,7 @@
defaultConfig {
minSdkVersion 21
}
+ namespace "androidx.compose.runtime.tracing"
}
dependencies {
diff --git a/compose/runtime/runtime-tracing/src/androidTest/AndroidManifest.xml b/compose/runtime/runtime-tracing/src/androidTest/AndroidManifest.xml
deleted file mode 100644
index 0b0410b..0000000
--- a/compose/runtime/runtime-tracing/src/androidTest/AndroidManifest.xml
+++ /dev/null
@@ -1,36 +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.
- -->
-
-<!--
- ~ Copyright (C) 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.
- -->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="androidx.compose.runtime.tracing.test">
-
-</manifest>
diff --git a/compose/runtime/runtime-tracing/src/main/AndroidManifest.xml b/compose/runtime/runtime-tracing/src/main/AndroidManifest.xml
index b3f90e5..280bb2f 100644
--- a/compose/runtime/runtime-tracing/src/main/AndroidManifest.xml
+++ b/compose/runtime/runtime-tracing/src/main/AndroidManifest.xml
@@ -31,8 +31,7 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- package="androidx.compose.runtime.tracing">
+ xmlns:tools="http://schemas.android.com/tools">
<application>
<provider
diff --git a/compose/ui/ui-test/api/public_plus_experimental_current.txt b/compose/ui/ui-test/api/public_plus_experimental_current.txt
index 6f8155d..23641cd 100644
--- a/compose/ui/ui-test/api/public_plus_experimental_current.txt
+++ b/compose/ui/ui-test/api/public_plus_experimental_current.txt
@@ -266,15 +266,11 @@
method @androidx.compose.ui.test.ExperimentalTestApi public static boolean isFnDown(androidx.compose.ui.test.KeyInjectionScope);
method @androidx.compose.ui.test.ExperimentalTestApi public static boolean isMetaDown(androidx.compose.ui.test.KeyInjectionScope);
method @androidx.compose.ui.test.ExperimentalTestApi public static boolean isShiftDown(androidx.compose.ui.test.KeyInjectionScope);
- method @androidx.compose.ui.test.ExperimentalTestApi public static void keysDown(androidx.compose.ui.test.KeyInjectionScope, java.util.List<androidx.compose.ui.input.key.Key> keys, optional long pauseDurationMillis);
- method @androidx.compose.ui.test.ExperimentalTestApi public static void keysUp(androidx.compose.ui.test.KeyInjectionScope, java.util.List<androidx.compose.ui.input.key.Key> keys, optional long pauseDurationMillis);
method @androidx.compose.ui.test.ExperimentalTestApi public static void pressKey(androidx.compose.ui.test.KeyInjectionScope, long key, optional long pressDurationMillis);
- method @androidx.compose.ui.test.ExperimentalTestApi public static void pressKey(androidx.compose.ui.test.KeyInjectionScope, long key, int times, optional long pressDurationMillis, optional long pauseDurationMillis);
- method @androidx.compose.ui.test.ExperimentalTestApi public static void pressKeys(androidx.compose.ui.test.KeyInjectionScope, java.util.List<androidx.compose.ui.input.key.Key> keys, optional long pressDurationMillis, optional long pauseDurationMillis);
- method @androidx.compose.ui.test.ExperimentalTestApi public static void withKeyDown(androidx.compose.ui.test.KeyInjectionScope, long key, optional long pauseDurationMillis, kotlin.jvm.functions.Function1<? super androidx.compose.ui.test.KeyInjectionScope,kotlin.Unit> block);
- method @androidx.compose.ui.test.ExperimentalTestApi public static void withKeyToggled(androidx.compose.ui.test.KeyInjectionScope, long key, optional long pauseDurationMillis, kotlin.jvm.functions.Function1<? super androidx.compose.ui.test.KeyInjectionScope,kotlin.Unit> block);
- method @androidx.compose.ui.test.ExperimentalTestApi public static void withKeysDown(androidx.compose.ui.test.KeyInjectionScope, java.util.List<androidx.compose.ui.input.key.Key> keys, optional long pauseDurationMillis, kotlin.jvm.functions.Function1<? super androidx.compose.ui.test.KeyInjectionScope,kotlin.Unit> block);
- method @androidx.compose.ui.test.ExperimentalTestApi public static void withKeysToggled(androidx.compose.ui.test.KeyInjectionScope, java.util.List<androidx.compose.ui.input.key.Key> keys, optional long pauseDurationMillis, kotlin.jvm.functions.Function1<? super androidx.compose.ui.test.KeyInjectionScope,kotlin.Unit> block);
+ method @androidx.compose.ui.test.ExperimentalTestApi public static void withKeyDown(androidx.compose.ui.test.KeyInjectionScope, long key, kotlin.jvm.functions.Function1<? super androidx.compose.ui.test.KeyInjectionScope,kotlin.Unit> block);
+ method @androidx.compose.ui.test.ExperimentalTestApi public static void withKeyToggled(androidx.compose.ui.test.KeyInjectionScope, long key, kotlin.jvm.functions.Function1<? super androidx.compose.ui.test.KeyInjectionScope,kotlin.Unit> block);
+ method @androidx.compose.ui.test.ExperimentalTestApi public static void withKeysDown(androidx.compose.ui.test.KeyInjectionScope, java.util.List<androidx.compose.ui.input.key.Key> keys, kotlin.jvm.functions.Function1<? super androidx.compose.ui.test.KeyInjectionScope,kotlin.Unit> block);
+ method @androidx.compose.ui.test.ExperimentalTestApi public static void withKeysToggled(androidx.compose.ui.test.KeyInjectionScope, java.util.List<androidx.compose.ui.input.key.Key> keys, kotlin.jvm.functions.Function1<? super androidx.compose.ui.test.KeyInjectionScope,kotlin.Unit> block);
}
public final class KeyInputHelpersKt {
diff --git a/compose/ui/ui-test/src/androidAndroidTest/kotlin/androidx/compose/ui/test/injectionscope/key/KeyDownTest.kt b/compose/ui/ui-test/src/androidAndroidTest/kotlin/androidx/compose/ui/test/injectionscope/key/KeyDownTest.kt
index b8bf432..6d6d75a 100644
--- a/compose/ui/ui-test/src/androidAndroidTest/kotlin/androidx/compose/ui/test/injectionscope/key/KeyDownTest.kt
+++ b/compose/ui/ui-test/src/androidAndroidTest/kotlin/androidx/compose/ui/test/injectionscope/key/KeyDownTest.kt
@@ -24,7 +24,6 @@
import androidx.compose.ui.test.injectionscope.key.Common.assertTyped
import androidx.compose.ui.test.injectionscope.key.Common.performKeyInput
import androidx.compose.ui.test.junit4.createComposeRule
-import androidx.compose.ui.test.keysDown
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.util.TestTextField
@@ -108,22 +107,4 @@
}
rule.assertTyped(":")
}
-
- @Test
- fun downedKeys_areDown() {
- rule.performKeyInput {
- keysDown(listOf(Key.A, Key.Enter))
- assertTrue(isKeyDown(Key.A))
- assertTrue(isKeyDown(Key.Enter))
- }
- }
-
- @Test
- fun duplicates_inKeysDown_throwIllegalStateException() {
- expectError<IllegalArgumentException>(
- expectedMessage = "List of keys must not contain any duplicates."
- ) {
- rule.performKeyInput { keysDown(listOf(Key.A, Key.A)) }
- }
- }
}
diff --git a/compose/ui/ui-test/src/androidAndroidTest/kotlin/androidx/compose/ui/test/injectionscope/key/KeyPressTest.kt b/compose/ui/ui-test/src/androidAndroidTest/kotlin/androidx/compose/ui/test/injectionscope/key/KeyPressTest.kt
index f4bd25d..913e47d 100644
--- a/compose/ui/ui-test/src/androidAndroidTest/kotlin/androidx/compose/ui/test/injectionscope/key/KeyPressTest.kt
+++ b/compose/ui/ui-test/src/androidAndroidTest/kotlin/androidx/compose/ui/test/injectionscope/key/KeyPressTest.kt
@@ -18,7 +18,6 @@
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.input.key.Key
-import androidx.compose.ui.input.key.nativeKeyCode
import androidx.compose.ui.test.ExperimentalTestApi
import androidx.compose.ui.test.KeyInjectionScope
import androidx.compose.ui.test.injectionscope.key.Common.assertTyped
@@ -27,7 +26,6 @@
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.pressKey
-import androidx.compose.ui.test.pressKeys
import androidx.compose.ui.test.util.TestTextField
import androidx.compose.ui.test.util.TestTextField.Tag
import androidx.test.filters.FlakyTest
@@ -37,7 +35,7 @@
import org.junit.Test
/**
- * Tests if [KeyInjectionScope.pressKey] and [KeyInjectionScope.pressKeys] work.
+ * Tests if [KeyInjectionScope.pressKey] works.
*/
@LargeTest
@OptIn(ExperimentalComposeUiApi::class, ExperimentalTestApi::class)
@@ -72,14 +70,7 @@
rule.assertTyped("abb")
}
- @Test
- fun typeAlphabet_withPressKeys() {
- rule.performKeyInput {
- pressKeys((Key.A.nativeKeyCode..Key.Z.nativeKeyCode).map { Key(it) }.toList())
- }
- rule.assertTyped(('a'..'z').joinToString(separator = ""))
- }
-
+ @FlakyTest(bugId = 236864049)
@Test
fun pressingNumberKeys_typesNumberChars() {
rule.performKeyInput { pressKey(Key.One) }
@@ -87,15 +78,6 @@
rule.assertTyped("12")
}
- @FlakyTest(bugId = 236950171)
- @Test
- fun pressKeyMultipleTimes_pressesKey_correctNumberOfTimes() {
- rule.performKeyInput {
- pressKey(Key.A, 10)
- }
- rule.assertTyped((1..10).joinToString(separator = "") { "a" })
- }
-
@Test
fun pressingBackspace_deletesLastCharacter() {
rule.performKeyInput {
diff --git a/compose/ui/ui-test/src/androidAndroidTest/kotlin/androidx/compose/ui/test/injectionscope/key/KeyUpTest.kt b/compose/ui/ui-test/src/androidAndroidTest/kotlin/androidx/compose/ui/test/injectionscope/key/KeyUpTest.kt
index 25d6c3dc..4ded126 100644
--- a/compose/ui/ui-test/src/androidAndroidTest/kotlin/androidx/compose/ui/test/injectionscope/key/KeyUpTest.kt
+++ b/compose/ui/ui-test/src/androidAndroidTest/kotlin/androidx/compose/ui/test/injectionscope/key/KeyUpTest.kt
@@ -24,7 +24,6 @@
import androidx.compose.ui.test.injectionscope.key.Common.assertTyped
import androidx.compose.ui.test.injectionscope.key.Common.performKeyInput
import androidx.compose.ui.test.junit4.createComposeRule
-import androidx.compose.ui.test.keysUp
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.util.TestTextField
@@ -91,27 +90,4 @@
}
rule.assertTyped("a")
}
-
- @Test
- fun keysAreUp_after_keysUp() {
- rule.performKeyInput {
- keyDown(Key.A)
- keyDown(Key.Enter)
- }
- rule.performKeyInput {
- keysUp(listOf(Key.A, Key.Enter))
- assertFalse(isKeyDown(Key.A))
- assertFalse(isKeyDown(Key.Enter))
- }
- }
-
- @Test
- fun duplicates_inKeysDown_throwIllegalStateException() {
- rule.performKeyInput { keyDown(Key.A) }
- expectError<IllegalArgumentException>(
- expectedMessage = "List of keys must not contain any duplicates."
- ) {
- rule.performKeyInput { keysUp(listOf(Key.A, Key.A)) }
- }
- }
}
diff --git a/compose/ui/ui-test/src/androidAndroidTest/kotlin/androidx/compose/ui/test/injectionscope/key/LockKeysTest.kt b/compose/ui/ui-test/src/androidAndroidTest/kotlin/androidx/compose/ui/test/injectionscope/key/LockKeysTest.kt
index 659bc55..5cad927 100644
--- a/compose/ui/ui-test/src/androidAndroidTest/kotlin/androidx/compose/ui/test/injectionscope/key/LockKeysTest.kt
+++ b/compose/ui/ui-test/src/androidAndroidTest/kotlin/androidx/compose/ui/test/injectionscope/key/LockKeysTest.kt
@@ -26,7 +26,6 @@
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.pressKey
-import androidx.compose.ui.test.pressKeys
import androidx.compose.ui.test.util.TestTextField
import androidx.compose.ui.test.util.TestTextField.Tag
import androidx.compose.ui.test.withKeyToggled
@@ -91,7 +90,10 @@
fun lettersTyped_withCapsLockOn_areUppercase() {
rule.performKeyInput {
pressKey(Key.A)
- withKeyToggled(Key.CapsLock) { pressKeys(listOf(Key.A, Key.B)) }
+ withKeyToggled(Key.CapsLock) {
+ pressKey(Key.A)
+ pressKey(Key.B)
+ }
pressKey(Key.B)
}
@@ -102,10 +104,14 @@
fun withKeyToggled_turnsCapsLockOff_ifCapsLockAlreadyOn() {
rule.performKeyInput {
pressKey(Key.CapsLock)
- pressKeys(listOf(Key.A, Key.B))
- withKeyToggled(Key.CapsLock) { pressKeys(listOf(Key.A, Key.B)) }
- pressKeys(listOf(Key.A, Key.B))
- }
+ pressKey(Key.A)
+ pressKey(Key.B)
+ withKeyToggled(Key.CapsLock) {
+ pressKey(Key.A)
+ pressKey(Key.B)
+ }
+ pressKey(Key.A)
+ pressKey(Key.B) }
rule.assertTyped("ABabAB")
}
@@ -114,7 +120,10 @@
fun numPadKeysPressed_withNumLockToggled_areNumbers() {
rule.performKeyInput {
pressKey(Key.NumPad0)
- withKeyToggled(Key.NumLock) { pressKeys(listOf(Key.NumPad1, Key.NumPad0)) }
+ withKeyToggled(Key.NumLock) {
+ pressKey(Key.NumPad1)
+ pressKey(Key.NumPad0)
+ }
pressKey(Key.NumPad1)
}
@@ -125,8 +134,11 @@
fun withKeyToggled_turnsNumLockOff_ifNumLockAlreadyOn() {
rule.performKeyInput {
pressKey(Key.NumLock)
- pressKeys(listOf(Key.NumPad0, Key.NumPad1))
- withKeyToggled(Key.NumLock) { pressKey(Key.NumPad0) }
+ pressKey(Key.NumPad0)
+ pressKey(Key.NumPad1)
+ withKeyToggled(Key.NumLock) {
+ pressKey(Key.NumPad0)
+ }
}
rule.assertTyped("01")
diff --git a/compose/ui/ui-test/src/androidAndroidTest/kotlin/androidx/compose/ui/test/injectionscope/key/MetaKeysTest.kt b/compose/ui/ui-test/src/androidAndroidTest/kotlin/androidx/compose/ui/test/injectionscope/key/MetaKeysTest.kt
index 729ea87..ccdf060 100644
--- a/compose/ui/ui-test/src/androidAndroidTest/kotlin/androidx/compose/ui/test/injectionscope/key/MetaKeysTest.kt
+++ b/compose/ui/ui-test/src/androidAndroidTest/kotlin/androidx/compose/ui/test/injectionscope/key/MetaKeysTest.kt
@@ -32,7 +32,6 @@
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.pressKey
-import androidx.compose.ui.test.pressKeys
import androidx.compose.ui.test.util.TestTextField
import androidx.compose.ui.test.util.TestTextField.Tag
import androidx.compose.ui.test.withKeyDown
@@ -114,7 +113,10 @@
fun lettersTyped_withShiftDown_areUppercase() {
rule.performKeyInput {
pressKey(Key.A)
- withKeyDown(Key.ShiftLeft) { pressKeys(listOf(Key.A, Key.B)) }
+ withKeyDown(Key.ShiftLeft) {
+ pressKey(Key.A)
+ pressKey(Key.B)
+ }
pressKey(Key.B)
}
rule.assertTyped("aABb")
diff --git a/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/KeyInjectionScope.kt b/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/KeyInjectionScope.kt
index c0758f4..3350296 100644
--- a/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/KeyInjectionScope.kt
+++ b/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/KeyInjectionScope.kt
@@ -136,55 +136,6 @@
}
/**
- * Depresses each of the given [keys] sequentially, with [pauseDurationMillis] milliseconds
- * separating each successive call to [KeyInjectionScope.keyDown].
- *
- * If [keys] contains any duplicate elements, an [IllegalArgumentException] will be thrown.
- * If any of the given [keys] is already down, an [IllegalStateException] will be thrown.
- *
- * @param keys The keys to be depressed.
- * @param pauseDurationMillis The duration separating each key down event in milliseconds.
- */
-// TODO(b/234011835): Refactor this and all functions that take List<Keys> to use vararg instead.
-@ExperimentalTestApi
-fun KeyInjectionScope.keysDown(
- keys: List<Key>,
- pauseDurationMillis: Long = DefaultPauseDurationBetweenKeyPressesMillis
-) {
- require(keys.size == keys.distinct().size) {
- "List of keys must not contain any duplicates."
- }
- keys.forEachIndexed { idx: Int, key: Key ->
- if (idx != 0) advanceEventTime(pauseDurationMillis)
- keyDown(key)
- }
-}
-
-/**
- * Releases each of the given [keys] sequentially, with [pauseDurationMillis] milliseconds
- * separating each successive call to [KeyInjectionScope.keyUp].
- *
- * If [keys] contains any duplicate elements, an [IllegalArgumentException] will be thrown.
- * If any of the given [keys] is not down, an [IllegalStateException] will be thrown.
- *
- * @param keys The keys to be released.
- * @param pauseDurationMillis The duration separating each key up event in milliseconds.
- */
-@ExperimentalTestApi
-fun KeyInjectionScope.keysUp(
- keys: List<Key>,
- pauseDurationMillis: Long = DefaultPauseDurationBetweenKeyPressesMillis
-) {
- require(keys.size == keys.distinct().size) {
- "List of keys must not contain any duplicates."
- }
- keys.forEachIndexed { idx: Int, key: Key ->
- if (idx != 0) advanceEventTime(pauseDurationMillis)
- keyUp(key)
- }
-}
-
-/**
* Holds down the given [key] for the given [pressDurationMillis] by sending a key down event,
* advancing the event time and sending a key up event.
*
@@ -204,144 +155,70 @@
}
/**
- * Presses the given [key] the given number of [times], for [pressDurationMillis] milliseconds each
- * time. Pauses for [pauseDurationMillis] milliseconds in between each key press.
- *
- * If the given [key] is already down an [IllegalStateException] is thrown.
- *
- * @param key The key to be pressed.
- * @param times The number of times to press the given key.
- * @param pressDurationMillis The length of time for which to hold each key press.
- * @param pauseDurationMillis The duration of the pause in between presses.
- */
-@ExperimentalTestApi
-fun KeyInjectionScope.pressKey(
- key: Key,
- times: Int,
- pressDurationMillis: Long = DefaultKeyPressDurationMillis,
- pauseDurationMillis: Long = DefaultPauseDurationBetweenKeyPressesMillis
-) = (0 until times).forEach { idx ->
- if (idx != 0) advanceEventTime(pauseDurationMillis)
- pressKey(key, pressDurationMillis)
-}
-
-/**
- * Holds down the key each of the given [keys] for the given [pressDurationMillis] in sequence, with
- * [pauseDurationMillis] milliseconds between each press.
- *
- * If one of the keys is already down, an [IllegalStateException] will be thrown.
- *
- * @param keys The list of keys to be pressed down.
- * @param pressDurationMillis Duration of press in milliseconds.
- * @param pauseDurationMillis The duration between presses.
- */
-@ExperimentalTestApi
-fun KeyInjectionScope.pressKeys(
- keys: List<Key>,
- pressDurationMillis: Long = DefaultKeyPressDurationMillis,
- pauseDurationMillis: Long = DefaultPauseDurationBetweenKeyPressesMillis
-) = keys.forEachIndexed { idx: Int, key: Key ->
- if (idx != 0) advanceEventTime(pauseDurationMillis)
- pressKey(key, pressDurationMillis)
-}
-
-/**
* Executes the keyboard sequence specified in the given [block], whilst holding down the
- * given [key]. This key must not be used within the [block]. Waits for [pauseDurationMillis]
- * milliseconds after pressing the [key] down before it injects the [block]. Waits for the same
- * duration after injecting the [block] before it releases the [key].
+ * given [key]. This key must not be used within the [block].
*
* If the given [key] is already down, an [IllegalStateException] will be thrown.
*
* @param key The key to be held down during injection of the [block].
- * @param pauseDurationMillis The pause after the initial key down and before the final key up.
* @param block Sequence of KeyInjectionScope methods to be injected with the given key down.
*/
@ExperimentalTestApi
-fun KeyInjectionScope.withKeyDown(
- key: Key,
- pauseDurationMillis: Long = DefaultPauseDurationBetweenKeyPressesMillis,
- block: KeyInjectionScope.() -> Unit
-) {
+fun KeyInjectionScope.withKeyDown(key: Key, block: KeyInjectionScope.() -> Unit) {
keyDown(key)
- advanceEventTime(pauseDurationMillis)
block.invoke(this)
- advanceEventTime(pauseDurationMillis)
keyUp(key)
}
/**
* Executes the keyboard sequence specified in the given [block], whilst holding down the each of
- * the given [keys]. These keys must not be used within the [block]. Waits for [pauseDurationMillis]
- * milliseconds in between each key down and each key up event.
+ * the given [keys]. Each of the [keys] will be pressed down and released simultaneously.
+ * These keys must not be used within the [block].
*
- * If [keys] contains any duplicate elements, an [IllegalArgumentException] will be thrown.
* If any of the given [keys] are already down, an [IllegalStateException] will be thrown.
*
* @param keys List of keys to be held down during injection of the [block].
- * @param pauseDurationMillis The pause in milliseconds between each key down and key up event.
* @param block Sequence of KeyInjectionScope methods to be injected with the given keys down.
*/
@ExperimentalTestApi
-fun KeyInjectionScope.withKeysDown(
- keys: List<Key>,
- pauseDurationMillis: Long = DefaultPauseDurationBetweenKeyPressesMillis,
- block: KeyInjectionScope.() -> Unit
-) {
- keysDown(keys, pauseDurationMillis)
- advanceEventTime(pauseDurationMillis)
+// TODO(b/234011835): Refactor this and all functions that take List<Keys> to use vararg instead.
+fun KeyInjectionScope.withKeysDown(keys: List<Key>, block: KeyInjectionScope.() -> Unit) {
+ keys.forEach { keyDown(it) }
block.invoke(this)
- advanceEventTime(pauseDurationMillis)
- keysUp(keys, pauseDurationMillis)
+ keys.forEach { keyUp(it) }
}
/**
* Executes the keyboard sequence specified in the given [block], in between presses to the
* given [key]. This key can also be used within the [block], as long as it is not down at the end
- * of the block. There will be [pauseDurationMillis] milliseconds after the initial press and before
- * the final press.
+ * of the block.
*
* If the given [key] is already down, an [IllegalStateException] will be thrown.
*
* @param key The key to be toggled around the injection of the [block].
- * @param pauseDurationMillis The pause after the initial and before the final key presses.
* @param block Sequence of KeyInjectionScope methods to be injected with the given key down.
*/
@ExperimentalTestApi
-fun KeyInjectionScope.withKeyToggled(
- key: Key,
- pauseDurationMillis: Long = DefaultPauseDurationBetweenKeyPressesMillis,
- block: KeyInjectionScope.() -> Unit
-) {
+fun KeyInjectionScope.withKeyToggled(key: Key, block: KeyInjectionScope.() -> Unit) {
pressKey(key)
- advanceEventTime(pauseDurationMillis)
block.invoke(this)
- advanceEventTime(pauseDurationMillis)
pressKey(key)
}
/**
* Executes the keyboard sequence specified in the given [block], in between presses to the
- * given [keys]. These keys can also be used within the [block], as long as they are not down at
- * the end of the block. There will be [pauseDurationMillis] milliseconds after the initial press
- * and before the final press.
+ * given [keys]. Each of the [keys] will be toggled simultaneously.These keys can also be used
+ * within the [block], as long as they are not down at the end of the block.
*
* If any of the given [keys] are already down, an [IllegalStateException] will be thrown.
*
* @param keys The keys to be toggled around the injection of the [block].
- * @param pauseDurationMillis The pause after the initial and before the final key presses.
* @param block Sequence of KeyInjectionScope methods to be injected with the given keys down.
*/
@ExperimentalTestApi
-fun KeyInjectionScope.withKeysToggled(
- keys: List<Key>,
- pauseDurationMillis: Long = DefaultPauseDurationBetweenKeyPressesMillis,
- block: KeyInjectionScope.() -> Unit
-) {
+fun KeyInjectionScope.withKeysToggled(keys: List<Key>, block: KeyInjectionScope.() -> Unit) {
pressKeys(keys)
- advanceEventTime(pauseDurationMillis)
block.invoke(this)
- advanceEventTime(pauseDurationMillis)
pressKeys(keys)
}
@@ -404,3 +281,18 @@
@Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
val KeyInjectionScope.isShiftDown: Boolean
get() = isKeyDown(Key.ShiftLeft) || isKeyDown(Key.ShiftRight)
+
+/**
+ * Holds down the key each of the given [keys] for [DefaultKeyPressDurationMillis] in sequence, with
+ * [DefaultPauseDurationBetweenKeyPressesMillis] between each press.
+ *
+ * If one of the keys is already down, an [IllegalStateException] will be thrown.
+ *
+ * @param keys The list of keys to be pressed down.
+ */
+@ExperimentalTestApi
+private fun KeyInjectionScope.pressKeys(keys: List<Key>) =
+ keys.forEachIndexed { idx: Int, key: Key ->
+ if (idx != 0) advanceEventTime(DefaultPauseDurationBetweenKeyPressesMillis)
+ pressKey(key, DefaultKeyPressDurationMillis)
+ }
diff --git a/compose/ui/ui-text/api/current.txt b/compose/ui/ui-text/api/current.txt
index 4ac58e5..124c06b 100644
--- a/compose/ui/ui-text/api/current.txt
+++ b/compose/ui/ui-text/api/current.txt
@@ -398,11 +398,17 @@
property public final long size;
}
+ public final class TextMeasurerKt {
+ }
+
public final class TextPainter {
method public void paint(androidx.compose.ui.graphics.Canvas canvas, androidx.compose.ui.text.TextLayoutResult textLayoutResult);
field public static final androidx.compose.ui.text.TextPainter INSTANCE;
}
+ public final class TextPainterKt {
+ }
+
@androidx.compose.runtime.Immutable @kotlin.jvm.JvmInline public final value class TextRange {
method public operator boolean contains(long other);
method public operator boolean contains(int offset);
diff --git a/compose/ui/ui-text/api/public_plus_experimental_current.txt b/compose/ui/ui-text/api/public_plus_experimental_current.txt
index ca406ac..fed7f60 100644
--- a/compose/ui/ui-text/api/public_plus_experimental_current.txt
+++ b/compose/ui/ui-text/api/public_plus_experimental_current.txt
@@ -464,11 +464,26 @@
property public final long size;
}
+ @androidx.compose.runtime.Immutable @androidx.compose.ui.text.ExperimentalTextApi public final class TextMeasurer {
+ ctor public TextMeasurer(androidx.compose.ui.text.font.FontFamily.Resolver fallbackFontFamilyResolver, androidx.compose.ui.unit.Density fallbackDensity, androidx.compose.ui.unit.LayoutDirection fallbackLayoutDirection, optional int cacheSize);
+ method @androidx.compose.runtime.Stable public androidx.compose.ui.text.TextLayoutResult measure(androidx.compose.ui.text.AnnotatedString text, optional androidx.compose.ui.text.TextStyle style, optional int overflow, optional boolean softWrap, optional int maxLines, optional java.util.List<androidx.compose.ui.text.AnnotatedString.Range<androidx.compose.ui.text.Placeholder>> placeholders, optional long size, optional androidx.compose.ui.unit.LayoutDirection layoutDirection, optional androidx.compose.ui.unit.Density density, optional androidx.compose.ui.text.font.FontFamily.Resolver fontFamilyResolver, optional boolean skipCache);
+ }
+
+ public final class TextMeasurerKt {
+ }
+
public final class TextPainter {
method public void paint(androidx.compose.ui.graphics.Canvas canvas, androidx.compose.ui.text.TextLayoutResult textLayoutResult);
field public static final androidx.compose.ui.text.TextPainter INSTANCE;
}
+ public final class TextPainterKt {
+ method @androidx.compose.ui.text.ExperimentalTextApi public static void drawText(androidx.compose.ui.graphics.drawscope.DrawScope, androidx.compose.ui.text.TextMeasurer textMeasurer, androidx.compose.ui.text.AnnotatedString text, optional long topLeft, optional androidx.compose.ui.text.TextStyle style, optional int overflow, optional boolean softWrap, optional int maxLines, optional java.util.List<androidx.compose.ui.text.AnnotatedString.Range<androidx.compose.ui.text.Placeholder>> placeholders, optional long size);
+ method @androidx.compose.ui.text.ExperimentalTextApi public static void drawText(androidx.compose.ui.graphics.drawscope.DrawScope, androidx.compose.ui.text.TextMeasurer textMeasurer, String text, optional long topLeft, optional androidx.compose.ui.text.TextStyle style, optional int overflow, optional boolean softWrap, optional int maxLines, optional long size);
+ method @androidx.compose.ui.text.ExperimentalTextApi public static void drawText(androidx.compose.ui.graphics.drawscope.DrawScope, androidx.compose.ui.text.TextLayoutResult textLayoutResult, optional long color, optional long topLeft, optional float alpha, optional androidx.compose.ui.graphics.Shadow? shadow, optional androidx.compose.ui.text.style.TextDecoration? textDecoration);
+ method @androidx.compose.ui.text.ExperimentalTextApi public static void drawText(androidx.compose.ui.graphics.drawscope.DrawScope, androidx.compose.ui.text.TextLayoutResult textLayoutResult, androidx.compose.ui.graphics.Brush brush, optional long topLeft, optional float alpha, optional androidx.compose.ui.graphics.Shadow? shadow, optional androidx.compose.ui.text.style.TextDecoration? textDecoration);
+ }
+
@androidx.compose.runtime.Immutable @kotlin.jvm.JvmInline public final value class TextRange {
method public operator boolean contains(long other);
method public operator boolean contains(int offset);
diff --git a/compose/ui/ui-text/api/restricted_current.txt b/compose/ui/ui-text/api/restricted_current.txt
index 4ac58e5..124c06b 100644
--- a/compose/ui/ui-text/api/restricted_current.txt
+++ b/compose/ui/ui-text/api/restricted_current.txt
@@ -398,11 +398,17 @@
property public final long size;
}
+ public final class TextMeasurerKt {
+ }
+
public final class TextPainter {
method public void paint(androidx.compose.ui.graphics.Canvas canvas, androidx.compose.ui.text.TextLayoutResult textLayoutResult);
field public static final androidx.compose.ui.text.TextPainter INSTANCE;
}
+ public final class TextPainterKt {
+ }
+
@androidx.compose.runtime.Immutable @kotlin.jvm.JvmInline public final value class TextRange {
method public operator boolean contains(long other);
method public operator boolean contains(int offset);
diff --git a/compose/ui/ui-text/benchmark/src/androidTest/java/androidx/compose/ui/text/benchmark/TextMeasurerBenchmark.kt b/compose/ui/ui-text/benchmark/src/androidTest/java/androidx/compose/ui/text/benchmark/TextMeasurerBenchmark.kt
new file mode 100644
index 0000000..c72e982
--- /dev/null
+++ b/compose/ui/ui-text/benchmark/src/androidTest/java/androidx/compose/ui/text/benchmark/TextMeasurerBenchmark.kt
@@ -0,0 +1,133 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.ui.text.benchmark
+
+import android.content.Context
+import android.util.TypedValue
+import androidx.benchmark.junit4.BenchmarkRule
+import androidx.benchmark.junit4.measureRepeated
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.text.ExperimentalTextApi
+import androidx.compose.ui.text.TextMeasurer
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.createFontFamilyResolver
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.LayoutDirection
+import androidx.compose.ui.unit.sp
+import androidx.test.filters.LargeTest
+import androidx.test.platform.app.InstrumentationRegistry
+import kotlin.math.roundToInt
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+@OptIn(ExperimentalTextApi::class)
+@LargeTest
+@RunWith(Parameterized::class)
+class TextMeasurerBenchmark(
+ private val textLength: Int,
+ private val textType: TextType,
+ alphabet: Alphabet
+) {
+ companion object {
+ @JvmStatic
+ @Parameterized.Parameters(name = "length={0} type={1} alphabet={2}")
+ fun initParameters(): List<Array<Any>> = cartesian(
+ arrayOf(8, 32, 128, 512),
+ arrayOf(TextType.PlainText, TextType.StyledText),
+ arrayOf(Alphabet.Latin, Alphabet.Cjk)
+ )
+ }
+
+ @get:Rule
+ val benchmarkRule = BenchmarkRule()
+
+ @get:Rule
+ val textBenchmarkRule = TextBenchmarkTestRule(alphabet)
+
+ private lateinit var instrumentationContext: Context
+
+ // Width initialized in setup().
+ private var width: Int = 0
+ private val fontSize = textBenchmarkRule.fontSizeSp.sp
+
+ @Before
+ fun setup() {
+ instrumentationContext = InstrumentationRegistry.getInstrumentation().context
+ width = TypedValue.applyDimension(
+ TypedValue.COMPLEX_UNIT_DIP,
+ textBenchmarkRule.widthDp,
+ instrumentationContext.resources.displayMetrics
+ ).roundToInt()
+ }
+
+ private fun text(textGenerator: RandomTextGenerator): AnnotatedString {
+ val text = textGenerator.nextParagraph(textLength)
+ val spanStyles = if (textType == TextType.StyledText) {
+ textGenerator.createStyles(text)
+ } else {
+ listOf()
+ }
+ return AnnotatedString(text = text, spanStyles = spanStyles)
+ }
+
+ @OptIn(ExperimentalTextApi::class)
+ @Test
+ fun text_measurer_no_cache() {
+ textBenchmarkRule.generator { textGenerator ->
+ val textMeasurer = TextMeasurer(
+ fallbackFontFamilyResolver = createFontFamilyResolver(instrumentationContext),
+ fallbackDensity = Density(instrumentationContext),
+ fallbackLayoutDirection = LayoutDirection.Ltr,
+ cacheSize = 0
+ )
+ val text = text(textGenerator)
+ benchmarkRule.measureRepeated {
+ textMeasurer.measure(
+ text,
+ style = TextStyle(color = Color.Red, fontSize = fontSize),
+ size = IntSize(width, Int.MAX_VALUE)
+ )
+ }
+ }
+ }
+
+ @OptIn(ExperimentalTextApi::class)
+ @Test
+ fun text_measurer_cached() {
+ textBenchmarkRule.generator { textGenerator ->
+ val textMeasurer = TextMeasurer(
+ fallbackFontFamilyResolver = createFontFamilyResolver(instrumentationContext),
+ fallbackDensity = Density(instrumentationContext),
+ fallbackLayoutDirection = LayoutDirection.Ltr,
+ cacheSize = 16
+ )
+ val text = text(textGenerator)
+ benchmarkRule.measureRepeated {
+ textMeasurer.measure(
+ text,
+ style = TextStyle(color = Color.Red, fontSize = fontSize),
+ size = IntSize(width, Int.MAX_VALUE)
+ )
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/compose/ui/ui-text/samples/src/main/java/androidx/compose/ui/text/samples/DrawTextSamples.kt b/compose/ui/ui-text/samples/src/main/java/androidx/compose/ui/text/samples/DrawTextSamples.kt
new file mode 100644
index 0000000..e655120
--- /dev/null
+++ b/compose/ui/ui-text/samples/src/main/java/androidx/compose/ui/text/samples/DrawTextSamples.kt
@@ -0,0 +1,66 @@
+/*
+ * 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.compose.ui.text.samples
+
+import androidx.annotation.Sampled
+import androidx.compose.foundation.Canvas
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.layout
+import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.text.ExperimentalTextApi
+import androidx.compose.ui.text.TextLayoutResult
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.drawText
+import androidx.compose.ui.text.rememberTextMeasurer
+import androidx.compose.ui.unit.sp
+
+@OptIn(ExperimentalTextApi::class)
+@Sampled
+@Composable
+fun DrawTextLayoutResultSample() {
+ val textMeasurer = rememberTextMeasurer()
+ var textLayoutResult by remember {
+ mutableStateOf<TextLayoutResult?>(null)
+ }
+
+ Canvas(
+ Modifier.fillMaxSize()
+ .layout { measurable, constraints ->
+ val placeable = measurable.measure(constraints)
+ // TextLayout can be done any time prior to its use in draw, including in a
+ // background thread.
+ // In this sample, text layout is done in compose layout. This way the layout call
+ // can be restarted when async font loading completes due to the fact that
+ // `.measure` call is executed in `.layout`.
+ textLayoutResult = textMeasurer.measure(
+ text = AnnotatedString("Hello ".repeat(2)),
+ style = TextStyle(fontSize = 35.sp)
+ )
+ layout(placeable.width, placeable.height) {
+ placeable.placeRelative(0, 0)
+ }
+ }) {
+ // This happens during draw phase.
+ textLayoutResult?.let { drawText(it) }
+ }
+}
diff --git a/compose/ui/ui-text/src/androidAndroidTest/kotlin/androidx/compose/ui/text/CacheTextLayoutInputTest.kt b/compose/ui/ui-text/src/androidAndroidTest/kotlin/androidx/compose/ui/text/CacheTextLayoutInputTest.kt
new file mode 100644
index 0000000..d377ab3
--- /dev/null
+++ b/compose/ui/ui-text/src/androidAndroidTest/kotlin/androidx/compose/ui/text/CacheTextLayoutInputTest.kt
@@ -0,0 +1,230 @@
+/*
+ * 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.
+ */
+
+@file:OptIn(ExperimentalTextApi::class)
+
+package androidx.compose.ui.text
+
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.Shadow
+import androidx.compose.ui.graphics.SolidColor
+import androidx.compose.ui.text.font.FontFamily
+import androidx.compose.ui.text.font.createFontFamilyResolver
+import androidx.compose.ui.text.style.TextDecoration
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.LayoutDirection
+import androidx.compose.ui.unit.sp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class CacheTextLayoutInputTest {
+ private val context = InstrumentationRegistry.getInstrumentation().context
+ private val fontFamilyResolver = createFontFamilyResolver(context)
+
+ @Test
+ fun default_ctor_should_be_equal() {
+ val input1 = cacheTextLayoutInput()
+ val input2 = cacheTextLayoutInput()
+
+ assertThat(input1.hashCode()).isEqualTo(input2.hashCode())
+ assertThat(input1).isEqualTo(input2)
+ }
+
+ @Test
+ fun text_should_differ() {
+ val input1 = cacheTextLayoutInput(AnnotatedString("Hello"))
+ val input2 = cacheTextLayoutInput(AnnotatedString("Hello, World"))
+
+ assertThat(input1.hashCode()).isNotEqualTo(input2.hashCode())
+ assertThat(input1).isNotEqualTo(input2)
+ }
+
+ @Test
+ fun placeholders_should_differ() {
+ val input1 = cacheTextLayoutInput(placeholders = listOf(AnnotatedString.Range(
+ Placeholder(20.sp, 20.sp, PlaceholderVerticalAlign.AboveBaseline), 0, 1
+ )))
+ val input2 = cacheTextLayoutInput(placeholders = listOf(AnnotatedString.Range(
+ Placeholder(20.sp, 20.sp, PlaceholderVerticalAlign.AboveBaseline), 1, 2
+ )))
+
+ assertThat(input1.hashCode()).isNotEqualTo(input2.hashCode())
+ assertThat(input1).isNotEqualTo(input2)
+ }
+
+ @Test
+ fun maxLines_should_differ() {
+ val input1 = cacheTextLayoutInput(maxLines = 1)
+ val input2 = cacheTextLayoutInput(maxLines = 2)
+
+ assertThat(input1.hashCode()).isNotEqualTo(input2.hashCode())
+ assertThat(input1).isNotEqualTo(input2)
+ }
+
+ @Test
+ fun softWrap_should_differ() {
+ val input1 = cacheTextLayoutInput(softWrap = true)
+ val input2 = cacheTextLayoutInput(softWrap = false)
+
+ assertThat(input1.hashCode()).isNotEqualTo(input2.hashCode())
+ assertThat(input1).isNotEqualTo(input2)
+ }
+
+ @Test
+ fun overflow_should_differ() {
+ val input1 = cacheTextLayoutInput(overflow = TextOverflow.Visible)
+ val input2 = cacheTextLayoutInput(overflow = TextOverflow.Ellipsis)
+
+ assertThat(input1.hashCode()).isNotEqualTo(input2.hashCode())
+ assertThat(input1).isNotEqualTo(input2)
+ }
+
+ @Test
+ fun density_should_differ() {
+ val input1 = cacheTextLayoutInput(density = Density(1f))
+ val input2 = cacheTextLayoutInput(density = Density(1.5f))
+
+ assertThat(input1.hashCode()).isNotEqualTo(input2.hashCode())
+ assertThat(input1).isNotEqualTo(input2)
+ }
+
+ @Test
+ fun layoutDirection_should_differ() {
+ val input1 = cacheTextLayoutInput(layoutDirection = LayoutDirection.Ltr)
+ val input2 = cacheTextLayoutInput(layoutDirection = LayoutDirection.Rtl)
+
+ assertThat(input1.hashCode()).isNotEqualTo(input2.hashCode())
+ assertThat(input1).isNotEqualTo(input2)
+ }
+
+ @Test
+ fun fontFamilyResolver_should_differ() {
+ // FontFamilyResolver only checks for instance equality.
+ val input1 = cacheTextLayoutInput(fontFamilyResolver = createFontFamilyResolver(context))
+ val input2 = cacheTextLayoutInput(fontFamilyResolver = createFontFamilyResolver(context))
+
+ assertThat(input1.hashCode()).isNotEqualTo(input2.hashCode())
+ assertThat(input1).isNotEqualTo(input2)
+ }
+
+ @Test
+ fun constraints_maxWidth_should_differ() {
+ val input1 = cacheTextLayoutInput(constraints = Constraints(maxWidth = 100))
+ val input2 = cacheTextLayoutInput(constraints = Constraints(maxWidth = 200))
+
+ assertThat(input1.hashCode()).isNotEqualTo(input2.hashCode())
+ assertThat(input1).isNotEqualTo(input2)
+ }
+
+ @Test
+ fun constraints_maxHeight_should_differ() {
+ val input1 = cacheTextLayoutInput(constraints = Constraints(maxHeight = 100))
+ val input2 = cacheTextLayoutInput(constraints = Constraints(maxHeight = 200))
+
+ assertThat(input1.hashCode()).isNotEqualTo(input2.hashCode())
+ assertThat(input1).isNotEqualTo(input2)
+ }
+
+ @Test
+ fun color_should_not_differ() {
+ val input1 = cacheTextLayoutInput(style = TextStyle(color = Color.Red))
+ val input2 = cacheTextLayoutInput(style = TextStyle(color = Color.Blue))
+
+ assertThat(input1.hashCode()).isEqualTo(input2.hashCode())
+ assertThat(input1).isEqualTo(input2)
+ }
+
+ @Test
+ fun brush_should_not_differ() {
+ val input1 = cacheTextLayoutInput(style = TextStyle(color = Color.Red))
+ val input2 = cacheTextLayoutInput(style = TextStyle(brush = SolidColor(Color.Blue)))
+
+ assertThat(input1.hashCode()).isEqualTo(input2.hashCode())
+ assertThat(input1).isEqualTo(input2)
+ }
+
+ @Test
+ fun shadow_should_not_differ() {
+ val input1 = cacheTextLayoutInput(
+ style = TextStyle(shadow = Shadow(Color.Red, Offset(10f, 10f)))
+ )
+ val input2 = cacheTextLayoutInput(
+ style = TextStyle(shadow = Shadow(Color.Red, Offset(12f, 12f)))
+ )
+
+ assertThat(input1.hashCode()).isEqualTo(input2.hashCode())
+ assertThat(input1).isEqualTo(input2)
+ }
+
+ @Test
+ fun textDecoration_should_not_differ() {
+ val input1 = cacheTextLayoutInput(
+ style = TextStyle(textDecoration = TextDecoration.Underline)
+ )
+ val input2 = cacheTextLayoutInput(
+ style = TextStyle(textDecoration = TextDecoration.LineThrough)
+ )
+
+ assertThat(input1.hashCode()).isEqualTo(input2.hashCode())
+ assertThat(input1).isEqualTo(input2)
+ }
+
+ @Test
+ fun minConstraints_should_not_differ() {
+ val input1 = cacheTextLayoutInput(constraints = Constraints(minWidth = 10, minHeight = 20))
+ val input2 = cacheTextLayoutInput(constraints = Constraints(minWidth = 20, minHeight = 10))
+
+ assertThat(input1.hashCode()).isEqualTo(input2.hashCode())
+ assertThat(input1).isEqualTo(input2)
+ }
+
+ private fun cacheTextLayoutInput(
+ text: AnnotatedString = AnnotatedString("Hello"),
+ style: TextStyle = TextStyle.Default,
+ placeholders: List<AnnotatedString.Range<Placeholder>> = emptyList(),
+ maxLines: Int = Int.MAX_VALUE,
+ softWrap: Boolean = true,
+ overflow: TextOverflow = TextOverflow.Clip,
+ density: Density = Density(context),
+ layoutDirection: LayoutDirection = LayoutDirection.Ltr,
+ fontFamilyResolver: FontFamily.Resolver = this.fontFamilyResolver,
+ constraints: Constraints = Constraints()
+ ): CacheTextLayoutInput {
+ return CacheTextLayoutInput(
+ TextLayoutInput(
+ text = text,
+ style = style,
+ placeholders = placeholders,
+ maxLines = maxLines,
+ softWrap = softWrap,
+ overflow = overflow,
+ density = density,
+ layoutDirection = layoutDirection,
+ fontFamilyResolver = fontFamilyResolver,
+ constraints = constraints
+ )
+ )
+ }
+}
\ No newline at end of file
diff --git a/compose/ui/ui-text/src/androidAndroidTest/kotlin/androidx/compose/ui/text/TextLayoutCacheTest.kt b/compose/ui/ui-text/src/androidAndroidTest/kotlin/androidx/compose/ui/text/TextLayoutCacheTest.kt
new file mode 100644
index 0000000..43cbe84
--- /dev/null
+++ b/compose/ui/ui-text/src/androidAndroidTest/kotlin/androidx/compose/ui/text/TextLayoutCacheTest.kt
@@ -0,0 +1,324 @@
+/*
+ * 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.
+ */
+
+@file:OptIn(ExperimentalTextApi::class)
+
+package androidx.compose.ui.text
+
+import androidx.compose.ui.graphics.Brush
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.Shadow
+import androidx.compose.ui.text.font.FontFamily
+import androidx.compose.ui.text.font.createFontFamilyResolver
+import androidx.compose.ui.text.font.toFontFamily
+import androidx.compose.ui.text.style.TextDecoration
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.LayoutDirection
+import androidx.compose.ui.unit.sp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry
+import com.google.common.truth.Truth
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class TextLayoutCacheTest {
+ private val fontFamilyMeasureFont = FontTestData.BASIC_MEASURE_FONT.toFontFamily()
+ private val context = InstrumentationRegistry.getInstrumentation().context
+ private val fontFamilyResolver = createFontFamilyResolver(context)
+ private val defaultDensity = Density(density = 1f)
+
+ @Test(expected = IllegalArgumentException::class)
+ fun capacity_cannot_be_zero() {
+ TextLayoutCache(0)
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun capacity_cannot_be_negative() {
+ TextLayoutCache(-2)
+ }
+
+ @Test
+ fun exactInput_shouldReturnTheSameResult() {
+ val textLayoutCache = TextLayoutCache(16)
+ val textLayoutInput = textLayoutInput(
+ text = AnnotatedString("Hello"),
+ style = TextStyle(color = Color.Red)
+ )
+
+ val textLayoutResult = layoutText(textLayoutInput)
+ textLayoutCache.put(textLayoutInput, textLayoutResult)
+
+ Truth.assertThat(textLayoutCache.get(textLayoutInput)).isEqualTo(textLayoutResult)
+ }
+
+ @Test
+ fun colorChange_shouldReturnFromCache() {
+ val textLayoutCache = TextLayoutCache(16)
+ val firstInput = textLayoutInput(
+ text = AnnotatedString("Hello"),
+ style = TextStyle(color = Color.Red)
+ )
+
+ val secondInput = textLayoutInput(
+ text = AnnotatedString("Hello"),
+ style = TextStyle(color = Color.Blue)
+ )
+
+ val textLayoutResult = layoutText(firstInput)
+ textLayoutCache.put(firstInput, textLayoutResult)
+
+ Truth.assertThat(textLayoutCache.get(secondInput)).isEqualTo(textLayoutResult)
+ }
+
+ @Test
+ fun brushChange_shouldReturnFromCache() {
+ val textLayoutCache = TextLayoutCache(16)
+ val firstInput = textLayoutInput(
+ text = AnnotatedString("Hello"),
+ style = TextStyle(color = Color.Red)
+ )
+
+ val secondInput = textLayoutInput(
+ text = AnnotatedString("Hello"),
+ style = TextStyle(brush = Brush.linearGradient(listOf(Color.Blue, Color.Red)))
+ )
+
+ val textLayoutResult = layoutText(firstInput)
+ textLayoutCache.put(firstInput, textLayoutResult)
+
+ Truth.assertThat(textLayoutCache.get(secondInput)).isEqualTo(textLayoutResult)
+ }
+
+ @Test
+ fun shadowChange_shouldReturnFromCache() {
+ val textLayoutCache = TextLayoutCache(16)
+ val firstInput = textLayoutInput(
+ text = AnnotatedString("Hello"),
+ style = TextStyle(shadow = Shadow(color = Color.Red))
+ )
+
+ val secondInput = textLayoutInput(
+ text = AnnotatedString("Hello"),
+ style = TextStyle(shadow = Shadow(color = Color.Blue))
+ )
+
+ val textLayoutResult = layoutText(firstInput)
+ textLayoutCache.put(firstInput, textLayoutResult)
+
+ Truth.assertThat(textLayoutCache.get(secondInput)).isEqualTo(textLayoutResult)
+ }
+
+ @Test
+ fun textDecorationChange_shouldReturnFromCache() {
+ val textLayoutCache = TextLayoutCache(16)
+ val firstInput = textLayoutInput(
+ text = AnnotatedString("Hello"),
+ style = TextStyle(textDecoration = TextDecoration.LineThrough)
+ )
+
+ val secondInput = textLayoutInput(
+ text = AnnotatedString("Hello"),
+ style = TextStyle(textDecoration = TextDecoration.Underline)
+ )
+
+ val textLayoutResult = layoutText(firstInput)
+ textLayoutCache.put(firstInput, textLayoutResult)
+
+ Truth.assertThat(textLayoutCache.get(secondInput)).isEqualTo(textLayoutResult)
+ }
+
+ @Test
+ fun constraintsMinChanges_shouldReturnFromCache() {
+ val textLayoutCache = TextLayoutCache(16)
+ val firstInput = textLayoutInput(
+ text = AnnotatedString("Hello"),
+ style = TextStyle(color = Color.Red),
+ constraints = Constraints(minWidth = 20, maxWidth = 200)
+ )
+
+ val secondInput = textLayoutInput(
+ text = AnnotatedString("Hello"),
+ style = TextStyle(color = Color.Red),
+ constraints = Constraints(minWidth = 60, maxWidth = 200)
+ )
+
+ val textLayoutResult = layoutText(firstInput)
+ textLayoutCache.put(firstInput, textLayoutResult)
+
+ Truth.assertThat(textLayoutCache.get(secondInput)).isEqualTo(textLayoutResult)
+ }
+
+ @Test
+ fun textChanges_shouldReturnNull() {
+ val textLayoutCache = TextLayoutCache(16)
+ val firstInput = textLayoutInput(text = AnnotatedString("Hello World"))
+
+ val secondInput = textLayoutInput(text = AnnotatedString("Hello World!"))
+
+ val textLayoutResult = layoutText(firstInput)
+ textLayoutCache.put(firstInput, textLayoutResult)
+
+ Truth.assertThat(textLayoutCache.get(secondInput)).isNull()
+ }
+
+ @Test
+ fun fontSizeChange_shouldReturnNull() {
+ val textLayoutCache = TextLayoutCache(16)
+ val firstInput = textLayoutInput(
+ text = AnnotatedString("Hello"),
+ style = TextStyle(color = Color.Red, fontSize = 14.sp)
+ )
+
+ val secondInput = textLayoutInput(
+ text = AnnotatedString("Hello"),
+ style = TextStyle(color = Color.Red, fontSize = 18.sp)
+ )
+
+ val textLayoutResult = layoutText(firstInput)
+ textLayoutCache.put(firstInput, textLayoutResult)
+
+ Truth.assertThat(textLayoutCache.get(secondInput)).isNull()
+ }
+
+ @Test
+ fun densityChange_shouldReturnNull() {
+ val textLayoutCache = TextLayoutCache(16)
+ val firstInput = textLayoutInput(
+ text = AnnotatedString("Hello")
+ )
+
+ val secondInput = textLayoutInput(
+ text = AnnotatedString("Hello"),
+ density = Density(2f)
+ )
+
+ val textLayoutResult = layoutText(firstInput)
+ textLayoutCache.put(firstInput, textLayoutResult)
+
+ Truth.assertThat(textLayoutCache.get(secondInput)).isNull()
+ }
+
+ @Test
+ fun layoutDirectionChange_shouldReturnNull() {
+ val textLayoutCache = TextLayoutCache(16)
+ val firstInput = textLayoutInput(
+ text = AnnotatedString("Hello"),
+ layoutDirection = LayoutDirection.Ltr
+ )
+
+ val secondInput = textLayoutInput(
+ text = AnnotatedString("Hello"),
+ layoutDirection = LayoutDirection.Rtl
+ )
+
+ val textLayoutResult = layoutText(firstInput)
+ textLayoutCache.put(firstInput, textLayoutResult)
+
+ Truth.assertThat(textLayoutCache.get(secondInput)).isNull()
+ }
+
+ @Test
+ fun constraintsMaxChanges_shouldReturnNull() {
+ val textLayoutCache = TextLayoutCache(16)
+ val firstInput = textLayoutInput(
+ text = AnnotatedString("Hello"),
+ style = TextStyle(color = Color.Red),
+ constraints = Constraints(minWidth = 20, maxWidth = 200)
+ )
+
+ val secondInput = textLayoutInput(
+ text = AnnotatedString("Hello"),
+ style = TextStyle(color = Color.Red),
+ constraints = Constraints(minWidth = 20, maxWidth = 250)
+ )
+
+ val textLayoutResult = layoutText(firstInput)
+ textLayoutCache.put(firstInput, textLayoutResult)
+
+ Truth.assertThat(textLayoutCache.get(secondInput)).isNull()
+ }
+
+ @Test
+ fun cacheShouldEvict_leastRecentlyUsedLayout() {
+ val textLayoutCache = TextLayoutCache(2)
+ val firstInput = textLayoutInput(text = AnnotatedString("1"))
+ val secondInput = textLayoutInput(text = AnnotatedString("2"))
+ val thirdInput = textLayoutInput(text = AnnotatedString("3"))
+
+ val firstLayout = layoutText(firstInput)
+ val secondLayout = layoutText(secondInput)
+ val thirdLayout = layoutText(thirdInput)
+
+ textLayoutCache.put(firstInput, firstLayout)
+ textLayoutCache.put(secondInput, secondLayout)
+ textLayoutCache.get(firstInput)
+ textLayoutCache.put(thirdInput, thirdLayout)
+
+ Truth.assertThat(textLayoutCache.get(firstInput)).isNotNull()
+ Truth.assertThat(textLayoutCache.get(secondInput)).isNull()
+ Truth.assertThat(textLayoutCache.get(thirdInput)).isNotNull()
+ }
+
+ private fun textLayoutInput(
+ text: AnnotatedString,
+ style: TextStyle = TextStyle.Default,
+ placeholders: List<AnnotatedString.Range<Placeholder>> = emptyList(),
+ maxLines: Int = Int.MAX_VALUE,
+ softWrap: Boolean = true,
+ overflow: TextOverflow = TextOverflow.Clip,
+ density: Density = this.defaultDensity,
+ layoutDirection: LayoutDirection = LayoutDirection.Ltr,
+ fontFamilyResolver: FontFamily.Resolver = this.fontFamilyResolver,
+ constraints: Constraints = Constraints()
+ ): TextLayoutInput {
+ return TextLayoutInput(
+ text = text,
+ style = style.merge(TextStyle(fontFamily = fontFamilyMeasureFont)),
+ placeholders = placeholders,
+ maxLines = maxLines,
+ softWrap = softWrap,
+ overflow = overflow,
+ density = density,
+ layoutDirection = layoutDirection,
+ fontFamilyResolver = fontFamilyResolver,
+ constraints = constraints
+ )
+ }
+
+ private fun layoutText(textLayoutInput: TextLayoutInput) = with(textLayoutInput) {
+ val measurer = TextMeasurer(
+ fontFamilyResolver,
+ density,
+ layoutDirection,
+ 0
+ )
+ measurer.measure(
+ text = text,
+ style = style,
+ overflow = overflow,
+ softWrap = softWrap,
+ maxLines = maxLines,
+ placeholders = placeholders,
+ size = IntSize(constraints.maxWidth, constraints.maxHeight),
+ )
+ }
+}
\ No newline at end of file
diff --git a/compose/ui/ui-text/src/androidAndroidTest/kotlin/androidx/compose/ui/text/TextMeasurerTest.kt b/compose/ui/ui-text/src/androidAndroidTest/kotlin/androidx/compose/ui/text/TextMeasurerTest.kt
new file mode 100644
index 0000000..db5ab99
--- /dev/null
+++ b/compose/ui/ui-text/src/androidAndroidTest/kotlin/androidx/compose/ui/text/TextMeasurerTest.kt
@@ -0,0 +1,337 @@
+/*
+ * 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.
+ */
+
+@file:OptIn(ExperimentalTextApi::class)
+
+package androidx.compose.ui.text
+
+import androidx.compose.ui.graphics.Brush
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.Shadow
+import androidx.compose.ui.text.font.FontFamily
+import androidx.compose.ui.text.font.createFontFamilyResolver
+import androidx.compose.ui.text.font.toFontFamily
+import androidx.compose.ui.text.style.TextDecoration
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.LayoutDirection
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class TextMeasurerTest {
+ private val fontFamilyMeasureFont = FontTestData.BASIC_MEASURE_FONT.toFontFamily()
+ private val context = InstrumentationRegistry.getInstrumentation().context
+ private val fontFamilyResolver = createFontFamilyResolver(context)
+ private val defaultDensity = Density(density = 1f)
+ private val layoutDirection = LayoutDirection.Ltr
+
+ private val longText = AnnotatedString(
+ "Lorem ipsum dolor sit amet, consectetur " +
+ "adipiscing elit. Curabitur augue leo, finibus vitae felis ac, pretium condimentum " +
+ "augue. Nullam non libero sed lectus aliquet venenatis non at purus. Fusce id arcu " +
+ "eu mauris pulvinar laoreet."
+ )
+
+ private val multiLineText = AnnotatedString("Lorem\nipsum\ndolor\nsit\namet")
+
+ @Test
+ fun width_shouldMatter_ifSoftwrapIsEnabled() {
+ val textLayoutResult = layoutText(
+ textLayoutInput(
+ text = longText,
+ softWrap = true,
+ constraints = Constraints(maxWidth = 200)
+ )
+ )
+
+ assertThat(textLayoutResult.multiParagraph.width).isEqualTo(200)
+ }
+
+ @Test
+ fun width_shouldMatter_ifSoftwrapIsDisabled_butOverflowIsEllipsis() {
+ val textLayoutResult = layoutText(
+ textLayoutInput(
+ text = longText,
+ softWrap = false,
+ overflow = TextOverflow.Ellipsis,
+ constraints = Constraints(maxWidth = 200)
+ )
+ )
+
+ assertThat(textLayoutResult.multiParagraph.width).isEqualTo(200)
+ }
+
+ @Test
+ fun width_shouldBeMaxIntrinsicWidth_ifSoftwrapIsDisabled_andOverflowIsClip() {
+ val textLayoutResult = layoutText(
+ textLayoutInput(
+ text = longText,
+ softWrap = false,
+ overflow = TextOverflow.Clip,
+ constraints = Constraints(maxWidth = 200)
+ )
+ )
+
+ val intrinsics = multiParagraphIntrinsics(text = longText)
+
+ assertThat(textLayoutResult.multiParagraph.width).isEqualTo(intrinsics.maxIntrinsicWidth)
+ }
+
+ @Test
+ fun width_shouldBeMaxIntrinsicWidth_ifSoftwrapIsDisabled_andOverflowIsVisible() {
+ val textLayoutResult = layoutText(
+ textLayoutInput(
+ text = longText,
+ softWrap = false,
+ overflow = TextOverflow.Clip,
+ constraints = Constraints(maxWidth = 200)
+ )
+ )
+
+ val intrinsics = multiParagraphIntrinsics(text = longText)
+
+ assertThat(textLayoutResult.multiParagraph.width).isEqualTo(intrinsics.maxIntrinsicWidth)
+ }
+
+ @Test
+ fun overwriteMaxLines_ifSoftwrapIsDisabled_andTextOverflowIsEllipsis() {
+ val textLayoutResult = layoutText(
+ textLayoutInput(
+ text = multiLineText,
+ softWrap = false,
+ overflow = TextOverflow.Ellipsis
+ )
+ )
+
+ assertThat(textLayoutResult.multiParagraph.lineCount).isEqualTo(1)
+ }
+
+ @Test
+ fun dontOverwriteMaxLines_ifSoftwrapIsEnabled() {
+ val textLayoutResult = layoutText(
+ textLayoutInput(
+ text = multiLineText,
+ softWrap = true,
+ overflow = TextOverflow.Ellipsis
+ )
+ )
+
+ assertThat(textLayoutResult.multiParagraph.lineCount).isEqualTo(5)
+ }
+
+ @Test
+ fun disabledSoftwrap_andOverflowClip_shouldConstrainLayoutSize() {
+ val textLayoutResult = layoutText(
+ textLayoutInput(
+ text = longText,
+ softWrap = false,
+ overflow = TextOverflow.Clip,
+ constraints = Constraints(maxWidth = 200)
+ )
+ )
+
+ assertThat(textLayoutResult.multiParagraph.width).isNotEqualTo(200f)
+ assertThat(textLayoutResult.size.width).isEqualTo(200)
+ }
+
+ @Test
+ fun disabledSoftwrap_andOverflowVisible_shouldConstrainLayoutSize() {
+ val textLayoutResult = layoutText(
+ textLayoutInput(
+ text = longText,
+ softWrap = false,
+ overflow = TextOverflow.Clip,
+ constraints = Constraints(maxWidth = 200)
+ )
+ )
+
+ assertThat(textLayoutResult.multiParagraph.width).isNotEqualTo(200f)
+ assertThat(textLayoutResult.size.width).isEqualTo(200)
+ }
+
+ @Test
+ fun colorShouldChangeInResult_whenCacheIsActive() {
+ val textMeasurer = textMeasurer(cacheSize = 8)
+ val firstTextLayout = layoutText(
+ textLayoutInput(
+ text = longText,
+ style = TextStyle(color = Color.Red)
+ ), textMeasurer
+ )
+
+ val secondTextLayout = layoutText(
+ textLayoutInput(
+ text = longText,
+ style = TextStyle(color = Color.Blue)
+ ), textMeasurer
+ )
+
+ assertThat(firstTextLayout.multiParagraph).isSameInstanceAs(secondTextLayout.multiParagraph)
+ assertThat(firstTextLayout.layoutInput.style.color).isEqualTo(Color.Red)
+ assertThat(secondTextLayout.layoutInput.style.color).isEqualTo(Color.Blue)
+ }
+
+ @Test
+ fun brushShouldChangeInResult_whenCacheIsActive() {
+ val textMeasurer = textMeasurer(cacheSize = 8)
+ val firstTextLayout = layoutText(
+ textLayoutInput(
+ text = longText,
+ style = TextStyle(brush = Brush.linearGradient(listOf(Color.Red, Color.Blue)))
+ ), textMeasurer
+ )
+
+ val secondTextLayout = layoutText(
+ textLayoutInput(
+ text = longText,
+ style = TextStyle(brush = Brush.linearGradient(listOf(Color.Green, Color.Yellow)))
+ ), textMeasurer
+ )
+
+ assertThat(firstTextLayout.multiParagraph).isSameInstanceAs(secondTextLayout.multiParagraph)
+ assertThat(firstTextLayout.layoutInput.style.brush)
+ .isEqualTo(Brush.linearGradient(listOf(Color.Red, Color.Blue)))
+ assertThat(secondTextLayout.layoutInput.style.brush)
+ .isEqualTo(Brush.linearGradient(listOf(Color.Green, Color.Yellow)))
+ }
+
+ @Test
+ fun shadowShouldChangeInResult_whenCacheIsActive() {
+ val textMeasurer = textMeasurer(cacheSize = 8)
+ val firstTextLayout = layoutText(
+ textLayoutInput(
+ text = longText,
+ style = TextStyle(shadow = Shadow(Color.Red))
+ ), textMeasurer
+ )
+
+ val secondTextLayout = layoutText(
+ textLayoutInput(
+ text = longText,
+ style = TextStyle(shadow = Shadow(Color.Blue))
+ ), textMeasurer
+ )
+
+ assertThat(firstTextLayout.multiParagraph).isSameInstanceAs(secondTextLayout.multiParagraph)
+ assertThat(firstTextLayout.layoutInput.style.shadow).isEqualTo(Shadow(Color.Red))
+ assertThat(secondTextLayout.layoutInput.style.shadow).isEqualTo(Shadow(Color.Blue))
+ }
+
+ @Test
+ fun textDecorationShouldChangeInResult_whenCacheIsActive() {
+ val textMeasurer = textMeasurer(cacheSize = 8)
+ val firstTextLayout = layoutText(
+ textLayoutInput(
+ text = longText,
+ style = TextStyle(textDecoration = TextDecoration.Underline)
+ ), textMeasurer
+ )
+
+ val secondTextLayout = layoutText(
+ textLayoutInput(
+ text = longText,
+ style = TextStyle(textDecoration = TextDecoration.LineThrough)
+ ), textMeasurer
+ )
+
+ assertThat(firstTextLayout.multiParagraph).isSameInstanceAs(secondTextLayout.multiParagraph)
+ assertThat(firstTextLayout.layoutInput.style.textDecoration)
+ .isEqualTo(TextDecoration.Underline)
+ assertThat(secondTextLayout.layoutInput.style.textDecoration)
+ .isEqualTo(TextDecoration.LineThrough)
+ }
+
+ private fun textLayoutInput(
+ text: AnnotatedString = AnnotatedString("Hello"),
+ style: TextStyle = TextStyle.Default,
+ placeholders: List<AnnotatedString.Range<Placeholder>> = emptyList(),
+ maxLines: Int = Int.MAX_VALUE,
+ softWrap: Boolean = true,
+ overflow: TextOverflow = TextOverflow.Clip,
+ density: Density = this.defaultDensity,
+ layoutDirection: LayoutDirection = this.layoutDirection,
+ fontFamilyResolver: FontFamily.Resolver = this.fontFamilyResolver,
+ constraints: Constraints = Constraints()
+ ): TextLayoutInput {
+ return TextLayoutInput(
+ text = text,
+ style = style.merge(TextStyle(fontFamily = fontFamilyMeasureFont)),
+ placeholders = placeholders,
+ maxLines = maxLines,
+ softWrap = softWrap,
+ overflow = overflow,
+ density = density,
+ layoutDirection = layoutDirection,
+ fontFamilyResolver = fontFamilyResolver,
+ constraints = constraints
+ )
+ }
+
+ private fun multiParagraphIntrinsics(
+ text: AnnotatedString = AnnotatedString("Hello"),
+ style: TextStyle = TextStyle.Default,
+ placeholders: List<AnnotatedString.Range<Placeholder>> = emptyList(),
+ density: Density = this.defaultDensity,
+ layoutDirection: LayoutDirection = this.layoutDirection,
+ fontFamilyResolver: FontFamily.Resolver = this.fontFamilyResolver
+ ): MultiParagraphIntrinsics {
+ return MultiParagraphIntrinsics(
+ annotatedString = text,
+ style = resolveDefaults(
+ style.merge(TextStyle(fontFamily = fontFamilyMeasureFont)),
+ layoutDirection
+ ),
+ placeholders = placeholders,
+ density = density,
+ fontFamilyResolver = fontFamilyResolver
+ )
+ }
+
+ private fun textMeasurer(
+ fontFamilyResolver: FontFamily.Resolver = this.fontFamilyResolver,
+ density: Density = this.defaultDensity,
+ layoutDirection: LayoutDirection = this.layoutDirection,
+ cacheSize: Int = 0
+ ): TextMeasurer = TextMeasurer(
+ fontFamilyResolver,
+ density,
+ layoutDirection,
+ cacheSize
+ )
+
+ private fun layoutText(
+ textLayoutInput: TextLayoutInput,
+ textMeasurer: TextMeasurer? = null
+ ) = with(textLayoutInput) {
+ (textMeasurer ?: textMeasurer()).measure(
+ text = text,
+ style = style,
+ overflow = overflow,
+ softWrap = softWrap,
+ maxLines = maxLines,
+ placeholders = placeholders,
+ size = IntSize(constraints.maxWidth, constraints.maxHeight),
+ )
+ }
+}
\ No newline at end of file
diff --git a/compose/ui/ui-text/src/androidAndroidTest/kotlin/androidx/compose/ui/text/TextPainterTest.kt b/compose/ui/ui-text/src/androidAndroidTest/kotlin/androidx/compose/ui/text/TextPainterTest.kt
new file mode 100644
index 0000000..ea43dc0
--- /dev/null
+++ b/compose/ui/ui-text/src/androidAndroidTest/kotlin/androidx/compose/ui/text/TextPainterTest.kt
@@ -0,0 +1,366 @@
+/*
+ * 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.
+ */
+
+@file:OptIn(ExperimentalTextApi::class)
+
+package androidx.compose.ui.text
+
+import android.graphics.Bitmap
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.graphics.Brush
+import androidx.compose.ui.graphics.Canvas
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.asImageBitmap
+import androidx.compose.ui.graphics.drawscope.CanvasDrawScope
+import androidx.compose.ui.graphics.drawscope.DrawScope
+import androidx.compose.ui.text.font.FontFamily
+import androidx.compose.ui.text.font.createFontFamilyResolver
+import androidx.compose.ui.text.font.toFontFamily
+import androidx.compose.ui.text.matchers.assertThat
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.LayoutDirection
+import androidx.compose.ui.unit.sp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import androidx.test.platform.app.InstrumentationRegistry
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@MediumTest
+class TextPainterTest {
+
+ private val fontFamilyMeasureFont = FontTestData.BASIC_MEASURE_FONT.toFontFamily()
+ private val context = InstrumentationRegistry.getInstrumentation().context
+ private val fontFamilyResolver = createFontFamilyResolver(context)
+ private var defaultDensity = Density(density = 1f)
+ private var layoutDirection = LayoutDirection.Ltr
+
+ private val longText = AnnotatedString(
+ "Lorem ipsum dolor sit amet, consectetur " +
+ "adipiscing elit. Curabitur augue leo, finibus vitae felis ac, pretium condimentum " +
+ "augue. Nullam non libero sed lectus aliquet venenatis non at purus. Fusce id arcu " +
+ "eu mauris pulvinar laoreet."
+ )
+
+ @Test
+ fun drawTextWithMeasurer_shouldBeEqualTo_drawTextLayoutResult() {
+ val measurer = textMeasurer()
+ val textLayoutResult = measurer.measure(
+ text = longText,
+ style = TextStyle(fontFamily = fontFamilyMeasureFont, fontSize = 20.sp),
+ size = IntSize(400, 400)
+ )
+
+ val bitmap = draw {
+ drawText(textLayoutResult)
+ }
+ val bitmap2 = draw {
+ drawText(
+ measurer,
+ text = longText,
+ style = TextStyle(fontFamily = fontFamilyMeasureFont, fontSize = 20.sp),
+ size = IntSize(400, 400)
+ )
+ }
+
+ assertThat(bitmap).isEqualToBitmap(bitmap2)
+ }
+
+ @Test
+ fun textMeasurerCache_shouldNotAffectTheResult_forColor() {
+ val measurer = textMeasurer(cacheSize = 8)
+
+ val bitmap = draw {
+ drawText(
+ textMeasurer = measurer,
+ text = longText,
+ style = TextStyle(
+ color = Color.Red,
+ fontFamily = fontFamilyMeasureFont,
+ fontSize = 20.sp
+ ),
+ size = IntSize(400, 400)
+ )
+ }
+ val bitmap2 = draw {
+ drawText(
+ textMeasurer = measurer,
+ text = longText,
+ style = TextStyle(
+ color = Color.Blue,
+ fontFamily = fontFamilyMeasureFont,
+ fontSize = 20.sp
+ ),
+ size = IntSize(400, 400)
+ )
+ }
+
+ assertThat(bitmap).isNotEqualToBitmap(bitmap2)
+ }
+
+ @Test
+ fun textMeasurerCache_shouldNotAffectTheResult_forFontSize() {
+ val measurer = textMeasurer(cacheSize = 8)
+
+ val bitmap = draw {
+ drawText(
+ textMeasurer = measurer,
+ text = longText,
+ style = TextStyle(fontFamily = fontFamilyMeasureFont, fontSize = 20.sp),
+ size = IntSize(400, 400)
+ )
+ }
+ val bitmap2 = draw {
+ drawText(
+ textMeasurer = measurer,
+ text = longText,
+ style = TextStyle(fontFamily = fontFamilyMeasureFont, fontSize = 24.sp),
+ size = IntSize(400, 400)
+ )
+ }
+
+ assertThat(bitmap).isNotEqualToBitmap(bitmap2)
+ }
+
+ @Test
+ fun drawTextLayout_shouldChangeColor() {
+ val measurer = textMeasurer()
+ val textLayoutResultRed = measurer.measure(
+ text = longText,
+ style = TextStyle(
+ color = Color.Red,
+ fontFamily = fontFamilyMeasureFont,
+ fontSize = 20.sp
+ ),
+ size = IntSize(400, 400)
+ )
+
+ val textLayoutResultBlue = measurer.measure(
+ text = longText,
+ style = TextStyle(
+ color = Color.Blue,
+ fontFamily = fontFamilyMeasureFont,
+ fontSize = 20.sp
+ ),
+ size = IntSize(400, 400)
+ )
+
+ val bitmap = draw {
+ drawText(textLayoutResultRed, color = Color.Blue)
+ }
+ val bitmap2 = draw {
+ drawText(textLayoutResultBlue)
+ }
+
+ assertThat(bitmap).isEqualToBitmap(bitmap2)
+ }
+
+ @Test
+ fun drawTextLayout_shouldChangeAlphaColor() {
+ val measurer = textMeasurer()
+ val textLayoutResultOpaque = measurer.measure(
+ text = longText,
+ style = TextStyle(
+ color = Color.Red,
+ fontFamily = fontFamilyMeasureFont,
+ fontSize = 20.sp
+ ),
+ size = IntSize(400, 400)
+ )
+
+ val textLayoutResultHalfOpaque = measurer.measure(
+ text = longText,
+ style = TextStyle(
+ color = Color.Red.copy(alpha = 0.5f),
+ fontFamily = fontFamilyMeasureFont,
+ fontSize = 20.sp
+ ),
+ size = IntSize(400, 400)
+ )
+
+ val bitmap = draw {
+ drawText(textLayoutResultOpaque, alpha = 0.5f)
+ }
+ val bitmap2 = draw {
+ drawText(textLayoutResultHalfOpaque)
+ }
+
+ assertThat(bitmap).isEqualToBitmap(bitmap2)
+ }
+
+ @Test
+ fun drawTextLayout_shouldChangeBrush() {
+ val rbBrush = Brush.radialGradient(listOf(Color.Red, Color.Blue))
+ val gyBrush = Brush.radialGradient(listOf(Color.Green, Color.Yellow))
+ val measurer = textMeasurer()
+ val textLayoutResultRB = measurer.measure(
+ text = longText,
+ style = TextStyle(
+ brush = rbBrush,
+ fontFamily = fontFamilyMeasureFont,
+ fontSize = 20.sp
+ ),
+ size = IntSize(400, 400)
+ )
+
+ val textLayoutResultGY = measurer.measure(
+ text = longText,
+ style = TextStyle(
+ brush = gyBrush,
+ fontFamily = fontFamilyMeasureFont,
+ fontSize = 20.sp
+ ),
+ size = IntSize(400, 400)
+ )
+
+ val bitmap = draw {
+ drawText(textLayoutResultRB, brush = gyBrush)
+ }
+ val bitmap2 = draw {
+ drawText(textLayoutResultGY)
+ }
+
+ assertThat(bitmap).isEqualToBitmap(bitmap2)
+ }
+
+ @Test
+ fun drawTextLayout_shouldChangeAlphaForBrush() {
+ val rbBrush = Brush.radialGradient(listOf(Color.Red, Color.Blue))
+ val measurer = textMeasurer()
+ val textLayoutResultOpaque = measurer.measure(
+ text = longText,
+ style = TextStyle(
+ brush = rbBrush,
+ alpha = 1f,
+ fontFamily = fontFamilyMeasureFont,
+ fontSize = 20.sp
+ ),
+ size = IntSize(400, 400)
+ )
+
+ val textLayoutResultHalfOpaque = measurer.measure(
+ text = longText,
+ style = TextStyle(
+ brush = rbBrush,
+ alpha = 0.5f,
+ fontFamily = fontFamilyMeasureFont,
+ fontSize = 20.sp
+ ),
+ size = IntSize(400, 400)
+ )
+
+ val bitmap = draw {
+ drawText(textLayoutResultOpaque, alpha = 0.5f)
+ }
+ val bitmap2 = draw {
+ drawText(textLayoutResultHalfOpaque)
+ }
+
+ assertThat(bitmap).isEqualToBitmap(bitmap2)
+ }
+
+ @Test
+ fun textMeasurerDraw_isConstrainedTo_canvasSizeByDefault() {
+ val measurer = textMeasurer()
+ // constrain the width, height is ignored
+ val textLayoutResult = measurer.measure(
+ text = longText,
+ style = TextStyle(
+ fontFamily = fontFamilyMeasureFont,
+ fontSize = 20.sp
+ ),
+ size = IntSize(200, 4000)
+ )
+
+ val bitmap = draw(200f, 4000f) {
+ drawText(textLayoutResult)
+ }
+ val bitmap2 = draw(200f, 4000f) {
+ drawText(measurer, longText, style = TextStyle(
+ fontFamily = fontFamilyMeasureFont,
+ fontSize = 20.sp
+ ))
+ }
+
+ assertThat(bitmap).isEqualToBitmap(bitmap2)
+ }
+
+ @Test
+ fun textMeasurerDraw_usesCanvasDensity_ByDefault() {
+ val measurer = textMeasurer()
+ // constrain the width, height is ignored
+ val textLayoutResult = measurer.measure(
+ text = longText,
+ style = TextStyle(
+ fontFamily = fontFamilyMeasureFont,
+ fontSize = 20.sp
+ ),
+ density = Density(4f),
+ size = IntSize(1000, 1000)
+ )
+
+ val bitmap = draw {
+ drawText(textLayoutResult)
+ }
+
+ defaultDensity = Density(4f)
+ val bitmap2 = draw {
+ drawText(measurer, longText, style = TextStyle(
+ fontFamily = fontFamilyMeasureFont,
+ fontSize = 20.sp
+ ))
+ }
+
+ assertThat(bitmap).isEqualToBitmap(bitmap2)
+ }
+
+ private fun textMeasurer(
+ fontFamilyResolver: FontFamily.Resolver = this.fontFamilyResolver,
+ density: Density = this.defaultDensity,
+ layoutDirection: LayoutDirection = this.layoutDirection,
+ cacheSize: Int = 0
+ ): TextMeasurer = TextMeasurer(
+ fontFamilyResolver,
+ density,
+ layoutDirection,
+ cacheSize
+ )
+
+ fun draw(
+ width: Float = 1000f,
+ height: Float = 1000f,
+ block: DrawScope.() -> Unit
+ ): Bitmap {
+ val size = Size(width, height)
+ val bitmap = Bitmap.createBitmap(
+ size.width.toIntPx(),
+ size.height.toIntPx(),
+ Bitmap.Config.ARGB_8888
+ )
+ val canvas = Canvas(bitmap.asImageBitmap())
+ val drawScope = CanvasDrawScope()
+ drawScope.draw(
+ defaultDensity,
+ layoutDirection,
+ canvas,
+ size,
+ block
+ )
+ return bitmap
+ }
+}
\ No newline at end of file
diff --git a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/SpanStyle.kt b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/SpanStyle.kt
index a886de7..9dcd322 100644
--- a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/SpanStyle.kt
+++ b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/SpanStyle.kt
@@ -546,6 +546,23 @@
}
@OptIn(ExperimentalTextApi::class)
+ internal fun hashCodeLayoutAffectingAttributes(): Int {
+ var result = fontSize.hashCode()
+ result = 31 * result + (fontWeight?.hashCode() ?: 0)
+ result = 31 * result + (fontStyle?.hashCode() ?: 0)
+ result = 31 * result + (fontSynthesis?.hashCode() ?: 0)
+ result = 31 * result + (fontFamily?.hashCode() ?: 0)
+ result = 31 * result + (fontFeatureSettings?.hashCode() ?: 0)
+ result = 31 * result + letterSpacing.hashCode()
+ result = 31 * result + (baselineShift?.hashCode() ?: 0)
+ result = 31 * result + (textGeometricTransform?.hashCode() ?: 0)
+ result = 31 * result + (localeList?.hashCode() ?: 0)
+ result = 31 * result + background.hashCode()
+ result = 31 * result + (platformStyle?.hashCode() ?: 0)
+ return result
+ }
+
+ @OptIn(ExperimentalTextApi::class)
override fun toString(): String {
return "SpanStyle(" +
"color=$color, " +
diff --git a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/TextMeasurer.kt b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/TextMeasurer.kt
new file mode 100644
index 0000000..e540b13
--- /dev/null
+++ b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/TextMeasurer.kt
@@ -0,0 +1,337 @@
+/*
+ * Copyright 2019 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.ui.text
+
+import androidx.compose.runtime.Immutable
+import androidx.compose.runtime.Stable
+import androidx.compose.ui.text.caches.LruCache
+import androidx.compose.ui.text.font.FontFamily
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.LayoutDirection
+import androidx.compose.ui.unit.constrain
+import kotlin.math.ceil
+
+/**
+ * Use cases that converge to this number;
+ * - Static text is drawn on canvas for legend and labels.
+ * - Text toggles between enumerated states bold, italic.
+ * - Multiple texts drawn but only their colors are animated.
+ *
+ * If text layout is always called with different inputs, this number is a good stopping point so
+ * that cache does not becomes unnecessarily large and miss penalty stays low. Of course developers
+ * should be aware that in a use case like that the cache should explicitly be disabled.
+ */
+private val DefaultCacheSize = 8
+
+/**
+ * TextMeasurer is responsible for measuring a text in its entirety so that it's ready to be drawn.
+ *
+ * Text layout is a computationally expensive task. Therefore, this class holds an internal LRU
+ * Cache of layout input and output pairs to optimize the repeated measure calls that use the same
+ * input parameters.
+ *
+ * Although most input parameters have a direct influence on layout, some parameters like color,
+ * brush, and shadow can be ignored during layout and set at the end. Using TextMeasurer with
+ * appropriate [cacheSize] should provide significant improvements while animating
+ * non-layout-affecting attributes like color.
+ *
+ * Moreover, if there is a need to render multiple static texts, you can provide the number of texts
+ * by [cacheSize] and their layouts should be cached for repeating calls. Be careful that even a
+ * slight change in input parameters like fontSize, maxLines, an additional character in text would
+ * create a distinct set of input parameters. As a result, a new layout would be calculated and a
+ * new set of input and output pair would be placed in LRU Cache, possibly evicting an earlier
+ * result.
+ *
+ * [FontFamily.Resolver], [LayoutDirection], and [Density] are required parameters to construct a
+ * text layout but they have no safe fallbacks outside of composition. These parameters must be
+ * provided during the construction of a [TextMeasurer] to be used as default values when they
+ * are skipped in [TextMeasurer.measure] call.
+ *
+ * @param fallbackFontFamilyResolver to be used to load fonts given in [TextStyle] and [SpanStyle]s
+ * in [AnnotatedString].
+ * @param fallbackLayoutDirection layout direction of the measurement environment.
+ * @param fallbackDensity density of the measurement environment. Density controls the scaling
+ * factor for fonts.
+ * @param cacheSize Capacity of internal cache inside TextMeasurer. Value of this parameter highly
+ * depends on the consumer use case. Provide a cache size that is in line with how many distinct
+ * text layouts are going to be calculated by this measurer repeatedly. If you are animating font
+ * attributes, or any other layout affecting input, cache can be skipped because most measure calls
+ * would miss the cache.
+ */
+@ExperimentalTextApi
+@Immutable
+class TextMeasurer constructor(
+ private val fallbackFontFamilyResolver: FontFamily.Resolver,
+ private val fallbackDensity: Density,
+ private val fallbackLayoutDirection: LayoutDirection,
+ private val cacheSize: Int = DefaultCacheSize
+) {
+ private val textLayoutCache: TextLayoutCache? = if (cacheSize > 0) {
+ TextLayoutCache(cacheSize)
+ } else null
+
+ /**
+ * Creates a [TextLayoutResult] according to given parameters.
+ *
+ * This function supports laying out text that consists of multiple paragraphs, includes
+ * placeholders, wraps around soft line breaks, and might overflow outside the specified size.
+ *
+ * Most parameters for text affect the final text layout. One pixel change in [size] can
+ * displace a word to another line which would cause a chain reaction that completely changes
+ * how text is rendered. On the other hand, some attributes only play a role when drawing the
+ * created text layout. For example text layout can be created completely in black color but we
+ * can apply [TextStyle.color] later in draw phase. This also means that animating text color
+ * shouldn't invalidate text layout.
+ *
+ * Thus, [textLayoutCache] helps in the process of converting a set of text layout inputs to
+ * a text layout while ignoring non-layout-affecting attributes. Iterative calls that use the
+ * same input parameters should benefit from substantial performance improvements.
+ *
+ * @param text the text to be laid out
+ * @param style the [TextStyle] to be applied to the whole text
+ * @param overflow How visual overflow should be handled.
+ * @param softWrap Whether the text should break at soft line breaks. If false, the glyphs in
+ * the text will be positioned as if there was unlimited horizontal space. If [softWrap] is
+ * false, [overflow] and TextAlign may have unexpected effects.
+ * @param maxLines An optional maximum number of lines for the text to span, wrapping if
+ * necessary. If the text exceeds the given number of lines, it will be truncated according to
+ * [overflow] and [softWrap]. If it is not null, then it must be greater than zero.
+ * @param placeholders a list of [Placeholder]s that specify ranges of text which will be
+ * skipped during layout and replaced with [Placeholder]. It's required that the range of each
+ * [Placeholder] doesn't cross paragraph boundary, otherwise [IllegalArgumentException] is
+ * thrown.
+ * @param size how wide and tall the text is allowed to be. [IntSize.width]
+ * will define the width of the text. [IntSize.height] helps defining the
+ * number of lines that fit if [softWrap] is enabled and [overflow] is [TextOverflow.Ellipsis].
+ * @param layoutDirection layout direction of the measurement environment. If not specified,
+ * defaults to the value that was given during initialization of this [TextMeasurer].
+ * @param density density of the measurement environment. If not specified, defaults to
+ * the value that was given during initialization of this [TextMeasurer].
+ * @param fontFamilyResolver to be used to load the font given in [SpanStyle]s. If not
+ * specified, defaults to the value that was given during initialization of this [TextMeasurer].
+ * @param skipCache Disables cache optimization if it is passed as true.
+ */
+ @Stable
+ fun measure(
+ text: AnnotatedString,
+ style: TextStyle = TextStyle.Default,
+ overflow: TextOverflow = TextOverflow.Clip,
+ softWrap: Boolean = true,
+ maxLines: Int = Int.MAX_VALUE,
+ placeholders: List<AnnotatedString.Range<Placeholder>> = emptyList(),
+ size: IntSize = IntSize(Int.MAX_VALUE, Int.MAX_VALUE),
+ layoutDirection: LayoutDirection = this.fallbackLayoutDirection,
+ density: Density = this.fallbackDensity,
+ fontFamilyResolver: FontFamily.Resolver = this.fallbackFontFamilyResolver,
+ skipCache: Boolean = false
+ ): TextLayoutResult {
+ val constraints = Constraints(maxWidth = size.width, maxHeight = size.height)
+ val requestedTextLayoutInput = TextLayoutInput(
+ text,
+ style,
+ placeholders,
+ maxLines,
+ softWrap,
+ overflow,
+ density,
+ layoutDirection,
+ fontFamilyResolver,
+ constraints
+ )
+
+ val cacheResult = if (!skipCache && textLayoutCache != null) {
+ textLayoutCache.get(requestedTextLayoutInput)
+ } else null
+
+ return if (cacheResult != null) {
+ cacheResult.copy(
+ layoutInput = requestedTextLayoutInput,
+ size = constraints.constrain(
+ IntSize(
+ cacheResult.multiParagraph.width.ceilToInt(),
+ cacheResult.multiParagraph.height.ceilToInt()
+ )
+ )
+ )
+ } else {
+ layout(requestedTextLayoutInput).also {
+ textLayoutCache?.put(requestedTextLayoutInput, it)
+ }
+ }
+ }
+
+ internal companion object {
+ /**
+ * Computes the visual position of the glyphs for painting the text.
+ *
+ * The text will layout with a width that's as close to its max intrinsic width as possible
+ * while still being greater than or equal to `minWidth` and less than or equal to
+ * `maxWidth`.
+ */
+ private fun layout(
+ textLayoutInput: TextLayoutInput
+ ): TextLayoutResult = with(textLayoutInput) {
+ val nonNullIntrinsics = MultiParagraphIntrinsics(
+ annotatedString = text,
+ style = resolveDefaults(style, layoutDirection),
+ density = density,
+ fontFamilyResolver = fontFamilyResolver,
+ placeholders = placeholders
+ )
+
+ val minWidth = constraints.minWidth
+ val widthMatters = softWrap ||
+ overflow == TextOverflow.Ellipsis
+ val maxWidth = if (widthMatters && constraints.hasBoundedWidth) {
+ constraints.maxWidth
+ } else {
+ Constraints.Infinity
+ }
+
+ // This is a fallback behavior because native text layout doesn't support multiple
+ // ellipsis in one text layout.
+ // When softWrap is turned off and overflow is ellipsis, it's expected that each line
+ // that exceeds maxWidth will be ellipsized.
+ // For example,
+ // input text:
+ // "AAAA\nAAAA"
+ // maxWidth:
+ // 3 * fontSize that only allow 3 characters to be displayed each line.
+ // expected output:
+ // AA…
+ // AA…
+ // Here we assume there won't be any '\n' character when softWrap is false. And make
+ // maxLines 1 to implement the similar behavior.
+ val overwriteMaxLines = !softWrap &&
+ overflow == TextOverflow.Ellipsis
+ val finalMaxLines = if (overwriteMaxLines) 1 else maxLines
+
+ // if minWidth == maxWidth the width is fixed.
+ // therefore we can pass that value to our paragraph and use it
+ // if minWidth != maxWidth there is a range
+ // then we should check if the max intrinsic width is in this range to decide the
+ // width to be passed to Paragraph
+ // if max intrinsic width is between minWidth and maxWidth
+ // we can use it to layout
+ // else if max intrinsic width is greater than maxWidth, we can only use maxWidth
+ // else if max intrinsic width is less than minWidth, we should use minWidth
+ val width = if (minWidth == maxWidth) {
+ maxWidth
+ } else {
+ nonNullIntrinsics.maxIntrinsicWidth.ceilToInt().coerceIn(minWidth, maxWidth)
+ }
+
+ val multiParagraph = MultiParagraph(
+ intrinsics = nonNullIntrinsics,
+ constraints = Constraints(maxWidth = width, maxHeight = constraints.maxHeight),
+ // This is a fallback behavior for ellipsis. Native
+ maxLines = finalMaxLines,
+ ellipsis = overflow == TextOverflow.Ellipsis
+ )
+
+ return TextLayoutResult(
+ layoutInput = textLayoutInput,
+ multiParagraph = multiParagraph,
+ size = constraints.constrain(
+ IntSize(
+ ceil(multiParagraph.width).toInt(),
+ ceil(multiParagraph.height).toInt()
+ )
+ )
+ )
+ }
+ }
+}
+
+/**
+ * Keeps an LRU layout cache of TextLayoutInput, TextLayoutResult pairs. Any non-layout affecting
+ * change in TextLayoutInput (color, brush, shadow, TextDecoration) is ignored by this cache.
+ *
+ * @param capacity Maximum initial size of LRU cache.
+ *
+ * @throws IllegalArgumentException if capacity is not a positive integer.
+ */
+internal class TextLayoutCache(capacity: Int = DefaultCacheSize) {
+ private val lruCache = LruCache<CacheTextLayoutInput, TextLayoutResult>(capacity)
+
+ fun get(key: TextLayoutInput): TextLayoutResult? {
+ val resultFromCache = lruCache.get(CacheTextLayoutInput(key)) ?: return null
+
+ if (resultFromCache.multiParagraph.intrinsics.hasStaleResolvedFonts) {
+ // one of the resolved fonts has updated, and this MeasuredText is no longer valid for
+ // measure or display
+ return null
+ }
+
+ return resultFromCache
+ }
+
+ fun put(key: TextLayoutInput, value: TextLayoutResult): TextLayoutResult? {
+ return lruCache.put(CacheTextLayoutInput(key), value)
+ }
+
+ fun remove(key: TextLayoutInput): TextLayoutResult? {
+ return lruCache.remove(CacheTextLayoutInput(key))
+ }
+}
+
+/**
+ * Provides custom hashCode and equals function that are only interested in layout affecting
+ * attributes in TextLayoutInput. Used as a key in [TextLayoutCache].
+ */
+@Immutable
+internal class CacheTextLayoutInput(val textLayoutInput: TextLayoutInput) {
+
+ override fun hashCode(): Int = with(textLayoutInput) {
+ var result = text.hashCode()
+ result = 31 * result + style.hashCodeLayoutAffectingAttributes()
+ result = 31 * result + placeholders.hashCode()
+ result = 31 * result + maxLines
+ result = 31 * result + softWrap.hashCode()
+ result = 31 * result + overflow.hashCode()
+ result = 31 * result + density.hashCode()
+ result = 31 * result + layoutDirection.hashCode()
+ result = 31 * result + fontFamilyResolver.hashCode()
+ result = 31 * result + constraints.maxWidth.hashCode()
+ result = 31 * result + constraints.maxHeight.hashCode()
+ return result
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other !is CacheTextLayoutInput) return false
+
+ with(textLayoutInput) {
+ if (text != other.textLayoutInput.text) return false
+ if (!style.hasSameLayoutAffectingAttributes(other.textLayoutInput.style)) return false
+ if (placeholders != other.textLayoutInput.placeholders) return false
+ if (maxLines != other.textLayoutInput.maxLines) return false
+ if (softWrap != other.textLayoutInput.softWrap) return false
+ if (overflow != other.textLayoutInput.overflow) return false
+ if (density != other.textLayoutInput.density) return false
+ if (layoutDirection != other.textLayoutInput.layoutDirection) return false
+ if (fontFamilyResolver !== other.textLayoutInput.fontFamilyResolver) return false
+ if (constraints.maxWidth != other.textLayoutInput.constraints.maxWidth) return false
+ if (constraints.maxHeight != other.textLayoutInput.constraints.maxHeight) return false
+ }
+
+ return true
+ }
+}
\ No newline at end of file
diff --git a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/TextPainter.kt b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/TextPainter.kt
index a97f8cb..2c683d3 100644
--- a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/TextPainter.kt
+++ b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/TextPainter.kt
@@ -19,10 +19,24 @@
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Canvas
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.Shadow
+import androidx.compose.ui.graphics.drawscope.DrawScope
+import androidx.compose.ui.graphics.drawscope.translate
+import androidx.compose.ui.graphics.isUnspecified
+import androidx.compose.ui.graphics.takeOrElse
+import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.text.style.modulate
+import androidx.compose.ui.unit.IntSize
+import kotlin.math.ceil
+import kotlin.math.roundToInt
object TextPainter {
+
+ // TODO(b/236964276): Deprecate when TextMeasurer and drawText are no longer Experimental
/**
* Paints the text onto the given canvas.
*
@@ -64,4 +78,253 @@
}
}
}
-}
\ No newline at end of file
+}
+
+/**
+ * Draw styled text using a TextMeasurer.
+ *
+ * This draw function supports multi-styling and async font loading.
+ *
+ * TextMeasurer carries an internal cache to optimize text layout measurement for repeated calls
+ * in draw phase. If layout affecting attributes like font size, font weight, overflow, softWrap,
+ * etc. are changed in consecutive calls to this method, TextMeasurer and its internal cache that
+ * holds layout results may not offer any benefits. Check out [TextMeasurer] and drawText
+ * overloads that take [TextLayoutResult] to learn more about text layout and draw phase
+ * optimizations.
+ *
+ * @param textMeasurer Measures and lays out the text
+ * @param text Text to be drawn
+ * @param topLeft Offsets the text from top left point of the current coordinate system.
+ * @param style the [TextStyle] to be applied to the text
+ * @param overflow How visual overflow should be handled.
+ * @param softWrap Whether the text should break at soft line breaks. If false, the glyphs in
+ * the text will be positioned as if there was unlimited horizontal space. If [softWrap] is
+ * false, [overflow] and TextAlign may have unexpected effects.
+ * @param maxLines An optional maximum number of lines for the text to span, wrapping if
+ * necessary. If the text exceeds the given number of lines, it will be truncated according to
+ * [overflow] and [softWrap]. If it is not null, then it must be greater than zero.
+ * @param placeholders a list of [Placeholder]s that specify ranges of text which will be
+ * skipped during layout and replaced with [Placeholder]. It's required that the range of each
+ * [Placeholder] doesn't cross paragraph boundary, otherwise [IllegalArgumentException] is
+ * thrown.
+ * @param size how wide and tall the text is allowed to be. [IntSize.width] will define the width
+ * of the text. [IntSize.height] helps defining the number of lines that fit if [softWrap] is
+ * enabled and [overflow] is [TextOverflow.Ellipsis]. Otherwise, [IntSize.height] either defines
+ * where the text is clipped ([TextOverflow.Clip]) or becomes no-op.
+ *
+ * @see TextMeasurer
+ */
+@ExperimentalTextApi
+fun DrawScope.drawText(
+ textMeasurer: TextMeasurer,
+ text: AnnotatedString,
+ topLeft: Offset = Offset.Zero,
+ style: TextStyle = TextStyle.Default,
+ overflow: TextOverflow = TextOverflow.Clip,
+ softWrap: Boolean = true,
+ maxLines: Int = Int.MAX_VALUE,
+ placeholders: List<AnnotatedString.Range<Placeholder>> = emptyList(),
+ size: IntSize = IntSize(
+ width = ceil(this.size.width).roundToInt(),
+ height = ceil(this.size.height).roundToInt()
+ )
+) {
+ val textLayoutResult = textMeasurer.measure(
+ text = text,
+ style = style,
+ overflow = overflow,
+ softWrap = softWrap,
+ maxLines = maxLines,
+ placeholders = placeholders,
+ size = size,
+ layoutDirection = layoutDirection,
+ density = this
+ )
+
+ drawText(textLayoutResult, topLeft = topLeft)
+}
+
+/**
+ * Draw text using a TextMeasurer.
+ *
+ * This draw function supports only one text style, and async font loading.
+ *
+ * TextMeasurer carries an internal cache to optimize text layout measurement for repeated calls
+ * in draw phase. If layout affecting attributes like font size, font weight, overflow, softWrap,
+ * etc. are changed in consecutive calls to this method, TextMeasurer and its internal cache that
+ * holds layout results may not offer any benefits. Check out [TextMeasurer] and drawText overloads that take [TextLayoutResult] to learn
+ * more about text layout and draw phase optimizations.
+ *
+ * @param textMeasurer Measures and lays out the text
+ * @param text Text to be drawn
+ * @param topLeft Offsets the text from top left point of the current coordinate system.
+ * @param style the [TextStyle] to be applied to the text
+ * @param overflow How visual overflow should be handled.
+ * @param softWrap Whether the text should break at soft line breaks. If false, the glyphs in
+ * the text will be positioned as if there was unlimited horizontal space. If [softWrap] is
+ * false, [overflow] and TextAlign may have unexpected effects.
+ * @param maxLines An optional maximum number of lines for the text to span, wrapping if
+ * necessary. If the text exceeds the given number of lines, it will be truncated according to
+ * [overflow] and [softWrap]. If it is not null, then it must be greater than zero.
+ * @param size how wide and tall the text is allowed to be. [IntSize.width] will define the width
+ * of the text. [IntSize.height] helps defining the number of lines that fit if [softWrap] is
+ * enabled and [overflow] is [TextOverflow.Ellipsis]. Otherwise, [IntSize.height] either defines
+ * where the text is clipped ([TextOverflow.Clip]) or becomes no-op.
+ *
+ * @see TextMeasurer
+ */
+@ExperimentalTextApi
+fun DrawScope.drawText(
+ textMeasurer: TextMeasurer,
+ text: String,
+ topLeft: Offset = Offset.Zero,
+ style: TextStyle = TextStyle.Default,
+ overflow: TextOverflow = TextOverflow.Clip,
+ softWrap: Boolean = true,
+ maxLines: Int = Int.MAX_VALUE,
+ size: IntSize = IntSize(
+ width = ceil(this.size.width).roundToInt(),
+ height = ceil(this.size.height).roundToInt()
+ )
+) {
+ val textLayoutResult = textMeasurer.measure(
+ text = AnnotatedString(text),
+ style = style,
+ overflow = overflow,
+ softWrap = softWrap,
+ maxLines = maxLines,
+ size = size,
+ layoutDirection = layoutDirection,
+ density = this
+ )
+
+ translate(topLeft.x, topLeft.y) {
+ TextPainter.paint(
+ canvas = this.drawContext.canvas,
+ textLayoutResult = textLayoutResult
+ )
+ }
+}
+
+/**
+ * Draw an existing text layout as produced by [TextMeasurer].
+ *
+ * This draw function cannot relayout when async font loading resolves. If using async fonts or
+ * other dynamic text layout, you are responsible for invalidating layout on changes.
+ *
+ * @param textLayoutResult Text Layout to be drawn
+ * @param color Text color to use
+ * @param topLeft Offsets the text from top left point of the current coordinate system.
+ * @param alpha opacity to be applied to the [color] from 0.0f to 1.0f representing fully
+ * transparent to fully opaque respectively
+ * @param shadow The shadow effect applied on the text.
+ * @param textDecoration The decorations to paint on the text (e.g., an underline).
+ *
+ * @sample androidx.compose.ui.text.samples.DrawTextLayoutResultSample
+ */
+@ExperimentalTextApi
+fun DrawScope.drawText(
+ textLayoutResult: TextLayoutResult,
+ color: Color = Color.Unspecified,
+ topLeft: Offset = Offset.Zero,
+ alpha: Float = Float.NaN,
+ shadow: Shadow? = null,
+ textDecoration: TextDecoration? = null
+) {
+ val newShadow = shadow ?: textLayoutResult.layoutInput.style.shadow
+ val newTextDecoration = textDecoration ?: textLayoutResult.layoutInput.style.textDecoration
+
+ // if text layout was created using brush, and [color] is unspecified, we should treat this
+ // like drawText(brush) call
+ val style = if (textLayoutResult.layoutInput.style.brush != null && color.isUnspecified) {
+ textLayoutResult.layoutInput.style.copy(
+ brush = textLayoutResult.layoutInput.style.brush,
+ alpha = if (!alpha.isNaN()) alpha else textLayoutResult.layoutInput.style.alpha,
+ shadow = newShadow,
+ textDecoration = newTextDecoration
+ )
+ } else {
+ val newColor = color.takeOrElse { textLayoutResult.layoutInput.style.color }.modulate(alpha)
+ textLayoutResult.layoutInput.style.copy(
+ color = newColor,
+ shadow = newShadow,
+ textDecoration = newTextDecoration
+ )
+ }
+
+ translate(topLeft.x, topLeft.y) {
+ TextPainter.paint(
+ canvas = this.drawContext.canvas,
+ textLayoutResult = textLayoutResult.copy(
+ TextLayoutInput(
+ text = textLayoutResult.layoutInput.text,
+ style = style,
+ placeholders = textLayoutResult.layoutInput.placeholders,
+ maxLines = textLayoutResult.layoutInput.maxLines,
+ softWrap = textLayoutResult.layoutInput.softWrap,
+ overflow = textLayoutResult.layoutInput.overflow,
+ density = textLayoutResult.layoutInput.density,
+ layoutDirection = textLayoutResult.layoutInput.layoutDirection,
+ fontFamilyResolver = textLayoutResult.layoutInput.fontFamilyResolver,
+ constraints = textLayoutResult.layoutInput.constraints,
+ )
+ )
+ )
+ }
+}
+
+/**
+ * Draw an existing text layout as produced by [TextMeasurer].
+ *
+ * This draw function cannot relayout when async font loading resolves. If using async fonts or
+ * other dynamic text layout, you are responsible for invalidating layout on changes.
+ *
+ * @param textLayoutResult Text Layout to be drawn
+ * @param brush The brush to use when drawing the text.
+ * @param topLeft Offsets the text from top left point of the current coordinate system.
+ * @param alpha Opacity to be applied to [brush] from 0.0f to 1.0f representing fully
+ * transparent to fully opaque respectively.
+ * @param shadow The shadow effect applied on the text.
+ * @param textDecoration The decorations to paint on the text (e.g., an underline).
+ *
+ * @sample androidx.compose.ui.text.samples.DrawTextLayoutResultSample
+ */
+@ExperimentalTextApi
+fun DrawScope.drawText(
+ textLayoutResult: TextLayoutResult,
+ brush: Brush,
+ topLeft: Offset = Offset.Zero,
+ alpha: Float = Float.NaN,
+ shadow: Shadow? = null,
+ textDecoration: TextDecoration? = null
+) {
+ val newShadow = shadow ?: textLayoutResult.layoutInput.style.shadow
+ val newTextDecoration = textDecoration ?: textLayoutResult.layoutInput.style.textDecoration
+
+ val style = textLayoutResult.layoutInput.style.copy(
+ brush = brush,
+ alpha = if (!alpha.isNaN()) alpha else textLayoutResult.layoutInput.style.alpha,
+ shadow = newShadow,
+ textDecoration = newTextDecoration
+ )
+
+ translate(topLeft.x, topLeft.y) {
+ TextPainter.paint(
+ canvas = this.drawContext.canvas,
+ textLayoutResult = textLayoutResult.copy(
+ TextLayoutInput(
+ text = textLayoutResult.layoutInput.text,
+ style = style,
+ placeholders = textLayoutResult.layoutInput.placeholders,
+ maxLines = textLayoutResult.layoutInput.maxLines,
+ softWrap = textLayoutResult.layoutInput.softWrap,
+ overflow = textLayoutResult.layoutInput.overflow,
+ density = textLayoutResult.layoutInput.density,
+ layoutDirection = textLayoutResult.layoutInput.layoutDirection,
+ fontFamilyResolver = textLayoutResult.layoutInput.fontFamilyResolver,
+ constraints = textLayoutResult.layoutInput.constraints,
+ )
+ )
+ )
+ }
+}
diff --git a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/TextStyle.kt b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/TextStyle.kt
index 123266e..11d0b0f 100644
--- a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/TextStyle.kt
+++ b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/TextStyle.kt
@@ -729,6 +729,14 @@
}
@OptIn(ExperimentalTextApi::class)
+ internal fun hashCodeLayoutAffectingAttributes(): Int {
+ var result = spanStyle.hashCodeLayoutAffectingAttributes()
+ result = 31 * result + paragraphStyle.hashCode()
+ result = 31 * result + (platformStyle?.hashCode() ?: 0)
+ return result
+ }
+
+ @OptIn(ExperimentalTextApi::class)
override fun toString(): String {
return "TextStyle(" +
"color=$color, " +
diff --git a/compose/ui/ui/api/current.txt b/compose/ui/ui/api/current.txt
index d230019..b4e751a 100644
--- a/compose/ui/ui/api/current.txt
+++ b/compose/ui/ui/api/current.txt
@@ -2978,6 +2978,13 @@
}
+package androidx.compose.ui.text {
+
+ public final class TextMeasurerHelperKt {
+ }
+
+}
+
package androidx.compose.ui.text.input {
public final class CursorAnchorInfoBuilderKt {
diff --git a/compose/ui/ui/api/public_plus_experimental_current.txt b/compose/ui/ui/api/public_plus_experimental_current.txt
index 2eeed57..450d6e1 100644
--- a/compose/ui/ui/api/public_plus_experimental_current.txt
+++ b/compose/ui/ui/api/public_plus_experimental_current.txt
@@ -3196,6 +3196,14 @@
}
+package androidx.compose.ui.text {
+
+ public final class TextMeasurerHelperKt {
+ method @androidx.compose.runtime.Composable @androidx.compose.ui.text.ExperimentalTextApi public static androidx.compose.ui.text.TextMeasurer rememberTextMeasurer(optional androidx.compose.ui.text.font.FontFamily.Resolver fontFamilyResolver, optional androidx.compose.ui.unit.Density density, optional androidx.compose.ui.unit.LayoutDirection layoutDirection, optional int cacheSize);
+ }
+
+}
+
package androidx.compose.ui.text.input {
public final class CursorAnchorInfoBuilderKt {
diff --git a/compose/ui/ui/api/restricted_current.txt b/compose/ui/ui/api/restricted_current.txt
index 3896d4a..89a3ce4 100644
--- a/compose/ui/ui/api/restricted_current.txt
+++ b/compose/ui/ui/api/restricted_current.txt
@@ -3014,6 +3014,13 @@
}
+package androidx.compose.ui.text {
+
+ public final class TextMeasurerHelperKt {
+ }
+
+}
+
package androidx.compose.ui.text.input {
public final class CursorAnchorInfoBuilderKt {
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusTraversalTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusTraversalTest.kt
index 68e5d6a..e519805 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusTraversalTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusTraversalTest.kt
@@ -30,6 +30,7 @@
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.test.junit4.ComposeContentTestRule
import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.test.filters.FlakyTest
import androidx.test.filters.MediumTest
import com.google.common.truth.Truth.assertThat
import org.junit.Rule
@@ -61,6 +62,7 @@
fun initParameters() = listOf(Param(Left), Param(Right), Param(Up), Param(Down))
}
+ @FlakyTest(bugId = 233373546)
@OptIn(ExperimentalComposeUiApi::class)
@Test
fun movesFocusAmongSiblingsDeepInTheFocusHierarchy() {
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/text/TextMeasurerHelperTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/text/TextMeasurerHelperTest.kt
new file mode 100644
index 0000000..ff12cf1
--- /dev/null
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/text/TextMeasurerHelperTest.kt
@@ -0,0 +1,128 @@
+/*
+ * 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.
+ */
+
+@file:OptIn(ExperimentalTextApi::class)
+
+package androidx.compose.ui.text
+
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.LocalFontFamilyResolver
+import androidx.compose.ui.platform.LocalLayoutDirection
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.text.font.createFontFamilyResolver
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.LayoutDirection
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class TextMeasurerHelperTest {
+
+ @get:Rule
+ val rule = createComposeRule()
+
+ val context = InstrumentationRegistry.getInstrumentation().targetContext
+
+ @Test
+ fun whenFontFamilyResolverChanges_TextMeasurerShouldChange() {
+ val fontFamilyResolver = mutableStateOf(createFontFamilyResolver(context))
+ val measurers = mutableSetOf<TextMeasurer>()
+
+ rule.setContent {
+ CompositionLocalProvider(
+ LocalFontFamilyResolver provides fontFamilyResolver.value
+ ) {
+ val textMeasurer = rememberTextMeasurer()
+ measurers.add(textMeasurer)
+ }
+ }
+
+ rule.waitForIdle()
+ // FontFamily.Resolver implementation has only instance check for equality
+ // new instance should always be unequal to any other instance
+ fontFamilyResolver.value = createFontFamilyResolver(context)
+ rule.waitForIdle()
+
+ assertThat(measurers.size).isEqualTo(2)
+ }
+
+ @Test
+ fun whenDensityChanges_TextMeasurerShouldChange() {
+ val density = mutableStateOf(Density(1f))
+ val measurers = mutableSetOf<TextMeasurer>()
+
+ rule.setContent {
+ CompositionLocalProvider(
+ LocalDensity provides density.value
+ ) {
+ val textMeasurer = rememberTextMeasurer()
+ measurers.add(textMeasurer)
+ }
+ }
+
+ rule.waitForIdle()
+ density.value = Density(2f)
+ rule.waitForIdle()
+
+ assertThat(measurers.size).isEqualTo(2)
+ }
+
+ @Test
+ fun whenLayoutDirectionChanges_TextMeasurerShouldChange() {
+ val layoutDirection = mutableStateOf(LayoutDirection.Ltr)
+ val measurers = mutableSetOf<TextMeasurer>()
+
+ rule.setContent {
+ CompositionLocalProvider(
+ LocalLayoutDirection provides layoutDirection.value
+ ) {
+ val textMeasurer = rememberTextMeasurer()
+ measurers.add(textMeasurer)
+ }
+ }
+
+ rule.waitForIdle()
+ layoutDirection.value = LayoutDirection.Rtl
+ rule.waitForIdle()
+
+ assertThat(measurers.size).isEqualTo(2)
+ }
+
+ @Test
+ fun whenCacheSizeChanges_TextMeasurerShouldChange() {
+ val cacheSize = mutableStateOf(4)
+ val measurers = mutableSetOf<TextMeasurer>()
+
+ rule.setContent {
+ val textMeasurer = rememberTextMeasurer(cacheSize = cacheSize.value)
+ measurers.add(textMeasurer)
+ }
+
+ rule.waitForIdle()
+ cacheSize.value = 8
+ rule.waitForIdle()
+
+ assertThat(measurers.size).isEqualTo(2)
+ }
+}
\ No newline at end of file
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/text/TextMeasurerHelper.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/text/TextMeasurerHelper.kt
new file mode 100644
index 0000000..b745d89
--- /dev/null
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/text/TextMeasurerHelper.kt
@@ -0,0 +1,59 @@
+/*
+ * 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.compose.ui.text
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.LocalFontFamilyResolver
+import androidx.compose.ui.platform.LocalLayoutDirection
+import androidx.compose.ui.text.font.FontFamily
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.LayoutDirection
+
+/**
+ * This value should reflect the default cache size for TextMeasurer.
+ */
+private val DefaultCacheSize: Int = 8
+
+/**
+ * Creates and remembers a [TextMeasurer] that reads default values for optional parameters from
+ * CompositionLocals. Returned TextMeasurer also carries an internal [TextLayoutCache] at a given
+ * capacity. Provide 0 as capacity to opt-out from internal caching behavior.
+ *
+ * All given parameters can be overridden during a [TextMeasurer.measure] call except the maximum
+ * size of the TextLayoutCache. Instead the cache can be disabled at will during measure by passing
+ * in skipCache as true.
+ *
+ * @param fontFamilyResolver default [FontFamily.Resolver] to be used to load the font given
+ * in [SpanStyle]s.
+ * @param density default density.
+ * @param layoutDirection default layout direction.
+ * @param cacheSize Capacity of internal cache inside TextMeasurer.
+ */
+@ExperimentalTextApi
+@Composable
+fun rememberTextMeasurer(
+ fontFamilyResolver: FontFamily.Resolver = LocalFontFamilyResolver.current,
+ density: Density = LocalDensity.current,
+ layoutDirection: LayoutDirection = LocalLayoutDirection.current,
+ cacheSize: Int = DefaultCacheSize
+): TextMeasurer {
+ return remember(fontFamilyResolver, density, layoutDirection, cacheSize) {
+ TextMeasurer(fontFamilyResolver, density, layoutDirection, cacheSize)
+ }
+}
\ No newline at end of file
diff --git a/core/core/src/androidTest/AndroidManifest.xml b/core/core/src/androidTest/AndroidManifest.xml
index acd1fb7..d87622f 100644
--- a/core/core/src/androidTest/AndroidManifest.xml
+++ b/core/core/src/androidTest/AndroidManifest.xml
@@ -106,6 +106,10 @@
android:name="androidx.core.widget.EdgeEffectCompatTest$EdgeEffectCompatTestActivity"
android:exported="true" />
<activity
+ android:name="androidx.core.app.ComponentActivity"
+ android:exported="true" />
+
+ <activity
android:name="androidx.core.view.WindowCompatActivity"
android:exported="true"
android:theme="@android:style/Theme.Light.NoTitleBar" />
diff --git a/core/core/src/androidTest/java/androidx/core/widget/NestedScrollViewStretchFlingTest.kt b/core/core/src/androidTest/java/androidx/core/widget/NestedScrollViewStretchFlingTest.kt
new file mode 100644
index 0000000..90dd90e
--- /dev/null
+++ b/core/core/src/androidTest/java/androidx/core/widget/NestedScrollViewStretchFlingTest.kt
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 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.core.widget
+
+import android.animation.ValueAnimator
+import android.graphics.Color
+import android.os.Build
+import android.view.MotionEvent
+import android.view.ViewGroup
+import android.widget.FrameLayout
+import android.widget.LinearLayout
+import androidx.core.app.ComponentActivity
+import androidx.test.ext.junit.rules.ActivityScenarioRule
+import androidx.test.filters.MediumTest
+import androidx.test.filters.SdkSuppress
+import androidx.testutils.PollingCheck
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+
+/**
+ * On S and higher, a large fling back should remove the stretch and start flinging the content.
+ */
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
+@MediumTest
+class NestedScrollViewStretchFlingTest {
+ lateinit var nestedScrollView: NestedScrollView
+
+ @Rule
+ @JvmField
+ @Suppress("DEPRECATION")
+ val mRule = ActivityScenarioRule(ComponentActivity::class.java)
+
+ @Before
+ fun setup() {
+ val drawLatch = CountDownLatch(1)
+ mRule.scenario.onActivity { activity ->
+ nestedScrollView = NestedScrollView(activity)
+ val layoutParams = ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT
+ )
+ activity.setContentView(nestedScrollView, layoutParams)
+
+ val linearLayout = LinearLayout(activity)
+ linearLayout.orientation = LinearLayout.VERTICAL
+ val linearLayoutParams = ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT
+ )
+ nestedScrollView.addView(linearLayout, linearLayoutParams)
+
+ repeat(1000) {
+ val child = FrameLayout(activity)
+ child.setBackgroundColor(Color.HSVToColor(floatArrayOf(it * 10f, 1f, 1f)))
+ val childLayoutParams = ViewGroup.LayoutParams(50, 50)
+ linearLayout.addView(child, childLayoutParams)
+ }
+ nestedScrollView.viewTreeObserver.addOnPreDrawListener {
+ drawLatch.countDown()
+ true
+ }
+
+ // Disabled animations will cause EdgeEffects to not do anything.
+ // This will enable animations for our Activity.
+ val setDurationScale =
+ ValueAnimator::class.java.getDeclaredMethod("setDurationScale", Float::class.java)
+
+ setDurationScale.invoke(null, 1f)
+ }
+ assertTrue(drawLatch.await(1, TimeUnit.SECONDS))
+ }
+
+ @Test
+ fun flingContentAfterStretchOnTop() {
+ stretchThenFling(stretchMotionUp = false) {
+ assertTrue(nestedScrollView.mEdgeGlowTop.distance > 0f)
+ assertEquals(0, nestedScrollView.scrollY)
+ }
+
+ // Wait for the stretch to release
+ PollingCheck.waitFor(1000L) {
+ nestedScrollView.mEdgeGlowTop.isFinished
+ }
+
+ var lastScroll = 0
+ PollingCheck.waitFor(1000L) {
+ var nextScroll = 0
+ mRule.scenario.onActivity {
+ nextScroll = nestedScrollView.scrollY
+ }
+ val changed = nextScroll == lastScroll
+ lastScroll = nextScroll
+ !changed
+ }
+
+ assertTrue(lastScroll > 0)
+ }
+
+ @Test
+ fun flingContentAfterStretchOnBottom() {
+ mRule.scenario.onActivity {
+ nestedScrollView.scrollTo(0, nestedScrollView.scrollRange)
+ assertEquals(nestedScrollView.scrollRange, nestedScrollView.scrollY)
+ }
+ stretchThenFling(stretchMotionUp = true) {
+ assertTrue(nestedScrollView.mEdgeGlowBottom.distance > 0f)
+ assertEquals(nestedScrollView.scrollRange, nestedScrollView.scrollY)
+ }
+
+ // Wait for the stretch to release
+ PollingCheck.waitFor(1000L) {
+ nestedScrollView.mEdgeGlowBottom.isFinished
+ }
+
+ var lastScroll = 0
+ PollingCheck.waitFor(1000L) {
+ var nextScroll = 0
+ mRule.scenario.onActivity {
+ nextScroll = nestedScrollView.scrollY
+ }
+ val changed = nextScroll == lastScroll
+ lastScroll = nextScroll
+ !changed
+ }
+
+ assertTrue(lastScroll < nestedScrollView.scrollRange)
+ }
+
+ private fun stretchThenFling(stretchMotionUp: Boolean, onFlingStart: () -> Unit) {
+ val x = nestedScrollView.width / 2f
+ val yStart = nestedScrollView.height / 2f
+
+ val yStretch = if (stretchMotionUp) 0f else nestedScrollView.height.toFloat()
+
+ val stretchTime = 20L
+ val endStretchTime = 500L
+
+ val events = mutableListOf<MotionEvent>()
+ // down
+ events += MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, x, yStart, 0)
+ // stretch
+ events += MotionEvent.obtain(0, stretchTime, MotionEvent.ACTION_MOVE, x, yStretch, 0)
+ // hold
+ events += MotionEvent.obtain(0, endStretchTime, MotionEvent.ACTION_MOVE, x, yStretch, 0)
+
+ val yFling = (yStretch + yStart) / 2f
+ val yFlingHalf = (yStretch + yFling) / 2f
+ val flingHalfTime = endStretchTime + 10L
+ val flingTime = flingHalfTime + 10L
+
+ // fling
+ events += MotionEvent.obtain(0, flingHalfTime, MotionEvent.ACTION_MOVE, x, yFlingHalf, 0)
+ events += MotionEvent.obtain(0, flingTime, MotionEvent.ACTION_MOVE, x, yFling, 0)
+ events += MotionEvent.obtain(0, flingTime, MotionEvent.ACTION_UP, x, yFling, 0)
+
+ events.forEachIndexed { index, event ->
+ mRule.scenario.onActivity {
+ nestedScrollView.dispatchTouchEvent(event)
+ if (index == events.lastIndex) {
+ onFlingStart()
+ }
+ }
+ }
+ }
+}
diff --git a/core/core/src/main/java/androidx/core/widget/NestedScrollView.java b/core/core/src/main/java/androidx/core/widget/NestedScrollView.java
index 4fa9e27..ff5861e 100644
--- a/core/core/src/main/java/androidx/core/widget/NestedScrollView.java
+++ b/core/core/src/main/java/androidx/core/widget/NestedScrollView.java
@@ -24,6 +24,7 @@
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Rect;
+import android.hardware.SensorManager;
import android.os.Build;
import android.os.Bundle;
import android.os.Parcel;
@@ -82,6 +83,22 @@
private static final int DEFAULT_SMOOTH_SCROLL_DURATION = 250;
/**
+ * The following are copied from OverScroller to determine how far a fling will go.
+ */
+ private static final float SCROLL_FRICTION = 0.015f;
+ private static final float INFLEXION = 0.35f; // Tension lines cross at (INFLEXION, 1)
+ private static final float DECELERATION_RATE = (float) (Math.log(0.78) / Math.log(0.9));
+ private final float mPhysicalCoeff;
+
+ /**
+ * When flinging the stretch towards scrolling content, it should destretch quicker than the
+ * fling would normally do. The visual effect of flinging the stretch looks strange as little
+ * appears to happen at first and then when the stretch disappears, the content starts
+ * scrolling quickly.
+ */
+ private static final float FLING_DESTRETCH_FACTOR = 4f;
+
+ /**
* Interface definition for a callback to be invoked when the scroll
* X or Y positions of a view change.
*
@@ -215,6 +232,12 @@
mEdgeGlowTop = EdgeEffectCompat.create(context, attrs);
mEdgeGlowBottom = EdgeEffectCompat.create(context, attrs);
+ final float ppi = context.getResources().getDisplayMetrics().density * 160.0f;
+ mPhysicalCoeff = SensorManager.GRAVITY_EARTH // g (m/s^2)
+ * 39.37f // inch/meter
+ * ppi
+ * 0.84f; // look and feel tuning
+
initScrollView();
final TypedArray a = context.obtainStyledAttributes(
@@ -1014,12 +1037,86 @@
return true;
}
+ /**
+ * Returns true if edgeEffect should call onAbsorb() with veclocity or false if it should
+ * animate with a fling. It will animate with a fling if the velocity will remove the
+ * EdgeEffect through its normal operation.
+ *
+ * @param edgeEffect The EdgeEffect that might absorb the velocity.
+ * @param velocity The velocity of the fling motion
+ * @return true if the velocity should be absorbed or false if it should be flung.
+ */
+ private boolean shouldAbsorb(@NonNull EdgeEffect edgeEffect, int velocity) {
+ if (velocity > 0) {
+ return true;
+ }
+ float distance = EdgeEffectCompat.getDistance(edgeEffect) * getHeight();
+
+ // This is flinging without the spring, so let's see if it will fling past the overscroll
+ float flingDistance = getSplineFlingDistance(-velocity);
+
+ return flingDistance < distance;
+ }
+
+ /**
+ * If mTopGlow or mBottomGlow is currently active and the motion will remove some of the
+ * stretch, this will consume any of unconsumedY that the glow can. If the motion would
+ * increase the stretch, or the EdgeEffect isn't a stretch, then nothing will be consumed.
+ *
+ * @param unconsumedY The vertical delta that might be consumed by the vertical EdgeEffects
+ * @return The remaining unconsumed delta after the edge effects have consumed.
+ */
+ int consumeFlingInVerticalStretch(int unconsumedY) {
+ int height = getHeight();
+ if (unconsumedY > 0 && EdgeEffectCompat.getDistance(mEdgeGlowTop) != 0f) {
+ float deltaDistance = -unconsumedY * FLING_DESTRETCH_FACTOR / height;
+ int consumed = Math.round(-height / FLING_DESTRETCH_FACTOR
+ * EdgeEffectCompat.onPullDistance(mEdgeGlowTop, deltaDistance, 0.5f));
+ if (consumed != unconsumedY) {
+ mEdgeGlowTop.finish();
+ }
+ return unconsumedY - consumed;
+ }
+ if (unconsumedY < 0 && EdgeEffectCompat.getDistance(mEdgeGlowBottom) != 0f) {
+ float deltaDistance = unconsumedY * FLING_DESTRETCH_FACTOR / height;
+ int consumed = Math.round(height / FLING_DESTRETCH_FACTOR
+ * EdgeEffectCompat.onPullDistance(mEdgeGlowBottom, deltaDistance, 0.5f));
+ if (consumed != unconsumedY) {
+ mEdgeGlowBottom.finish();
+ }
+ return unconsumedY - consumed;
+ }
+ return unconsumedY;
+ }
+
+ /**
+ * Copied from OverScroller, this returns the distance that a fling with the given velocity
+ * will go.
+ * @param velocity The velocity of the fling
+ * @return The distance that will be traveled by a fling of the given velocity.
+ */
+ private float getSplineFlingDistance(int velocity) {
+ final double l =
+ Math.log(INFLEXION * Math.abs(velocity) / (SCROLL_FRICTION * mPhysicalCoeff));
+ final double decelMinusOne = DECELERATION_RATE - 1.0;
+ return (float) (SCROLL_FRICTION * mPhysicalCoeff
+ * Math.exp(DECELERATION_RATE / decelMinusOne * l));
+ }
+
private boolean edgeEffectFling(int velocityY) {
boolean consumed = true;
if (EdgeEffectCompat.getDistance(mEdgeGlowTop) != 0) {
- mEdgeGlowTop.onAbsorb(velocityY);
+ if (shouldAbsorb(mEdgeGlowTop, velocityY)) {
+ mEdgeGlowTop.onAbsorb(velocityY);
+ } else {
+ fling(-velocityY);
+ }
} else if (EdgeEffectCompat.getDistance(mEdgeGlowBottom) != 0) {
- mEdgeGlowBottom.onAbsorb(-velocityY);
+ if (shouldAbsorb(mEdgeGlowBottom, -velocityY)) {
+ mEdgeGlowBottom.onAbsorb(-velocityY);
+ } else {
+ fling(-velocityY);
+ }
} else {
consumed = false;
}
@@ -1704,7 +1801,7 @@
mScroller.computeScrollOffset();
final int y = mScroller.getCurrY();
- int unconsumed = y - mLastScrollerY;
+ int unconsumed = consumeFlingInVerticalStretch(y - mLastScrollerY);
mLastScrollerY = y;
// Nested Scrolling Pre Pass
diff --git a/datastore/datastore-multiprocess/src/main/AndroidManifest.xml b/datastore/datastore-multiprocess/src/main/AndroidManifest.xml
deleted file mode 100644
index 2aeafa4..0000000
--- a/datastore/datastore-multiprocess/src/main/AndroidManifest.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?><!--
- 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.
- -->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="androidx.datastore.multiprocess">
-
-</manifest>
diff --git a/development/build_log_simplifier/messages.ignore b/development/build_log_simplifier/messages.ignore
index 070a903..dd30501 100644
--- a/development/build_log_simplifier/messages.ignore
+++ b/development/build_log_simplifier/messages.ignore
@@ -375,11 +375,6 @@
\^
# Gradle will log if you are not authenticated to upload scans
A build scan was not published as you have not authenticated with server 'ge\.androidx\.dev'\.
-# > Task :core:core:processDebugAndroidTestManifest
-package=".*" found in source AndroidManifest\.xml: .*/AndroidManifest\.xml\.
-Setting the namespace via a source AndroidManifest\.xml's package attribute is deprecated\.
-Please instead set the namespace \(or testNamespace\) in the module's build\.gradle file, as described here: https://developer\.android\.com/studio/build/configure\-app\-module\#set\-namespace
-This migration can be done automatically using the AGP Upgrade Assistant, please refer to https://developer\.android\.com/studio/build/agp\-upgrade\-assistant for more information\.
# Room unresolved type error messages
Found an unresolved type in androidx\.room\.RoomDatabase\.Builder\$createFromAsset\(kotlin\.String\) \(RoomDatabase\.kt:[0-9]+\)
Found an unresolved type in androidx\.room\.RoomDatabase\.Builder\$createFromAsset\(kotlin\.String\,\ androidx\.room\.RoomDatabase\.PrepackagedDatabaseCallback\) \(RoomDatabase\.kt:[0-9]+\)
diff --git a/development/gradleRemoteCache/.gitignore b/development/gradleRemoteCache/.gitignore
deleted file mode 100644
index f23b948..0000000
--- a/development/gradleRemoteCache/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-*.jar
\ No newline at end of file
diff --git a/development/gradleRemoteCache/OWNERS b/development/gradleRemoteCache/OWNERS
deleted file mode 100644
index 3235a23..0000000
--- a/development/gradleRemoteCache/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
[email protected]
\ No newline at end of file
diff --git a/development/gradleRemoteCache/README.md b/development/gradleRemoteCache/README.md
deleted file mode 100644
index 328b00f..0000000
--- a/development/gradleRemoteCache/README.md
+++ /dev/null
@@ -1,61 +0,0 @@
-# Setting up the Gradle Build Cache Node on Google Cloud Platform.
-
-To setup the [Gradle Remote Cache](https://docs.gradle.com/build-cache-node) you need to do the following:
-
-## Create a new Instance
-
-* Open the Cloud Platform [console](https://console.cloud.google.com/home/dashboard?project=fetch-licenses).
-
-* In the search box type in and select `VM Instances`.
-
-* Click on an existing node to see details page, then use `Create Similar` to create a new node.
- *Note*: This node has to be tagged with a network tag called `gradle-remote-cache-node`
- for it to be picked up by the load balancer. Make sure you create the node in the zone `us-east-1-b`.
-
-* Click `Allow HTTP Traffic` and `Allow HTTPs Traffic`. By doing do, you are allowing UberProxy access
- to the remote cache. The load balancer is only available when you are on a corp network.
-
-* Connect to the newly created node using an SSH session. You can use the `gcloud` CLI for this.
- *Note*: Use the `external` IP of the newly created node to SSH.
-
-```bash
-# Note: To switch projects use `gcloud config set project fetch-licenses`
-# Will show the newly created instance
-gcloud compute instances list
-# Will setup ssh configurations
-gcloud compute config-ssh
-ssh 123.123.123.123
-```
-
-## Starting the Gradle Remote Cache Node
-
-```bash
-# Install some prerequisite packages
-sudo apt update
-sudo apt upgrade
-sudo apt install openjdk-11-jdk tmux wget
-# Create a folder `Workspace` in the home directory.
-mkdir Workspace
-cd Workspace
-mkdir -p data/conf
-# using the template in this checkout create config.yaml
-vi data/conf/config.yaml
-# using the template in this checkout create run_node, replace YOURUSERNAME with your username
-vi run_node
-chmod +x run_node
-mkdir gradle-node
-wget https://docs.gradle.com/build-cache-node/jar/build-cache-node-11.1.jar -P gradle-node
-# Create a `tmux` session
-tmux new -s gradle
-sudo ./run_node &
-# Detach from the tmux session ctrl+b then d
-exit
-```
-
-## Update the `gradle-remote-cache-group` instance group.
-
-* Open `Instance groups` in gcloud console
-* Click on `gradle-remote-cache-group` and select `Edit Group`.
-* Select the new node(s), from the drop-down list.
-* Remove old nodes from the list
-* Click `Save`.
diff --git a/development/gradleRemoteCache/config.yaml b/development/gradleRemoteCache/config.yaml
deleted file mode 100644
index 5c5a832..0000000
--- a/development/gradleRemoteCache/config.yaml
+++ /dev/null
@@ -1,9 +0,0 @@
----
-version: 3
-uiAccess:
- type: "open"
-cache:
- accessControl:
- anonymousLevel: "readwrite"
- targetSize: 150000
- maxArtifactSize: 2500
diff --git a/development/gradleRemoteCache/data/.empty b/development/gradleRemoteCache/data/.empty
deleted file mode 100644
index e69de29..0000000
--- a/development/gradleRemoteCache/data/.empty
+++ /dev/null
diff --git a/development/gradleRemoteCache/gradle-node/.empty b/development/gradleRemoteCache/gradle-node/.empty
deleted file mode 100644
index e69de29..0000000
--- a/development/gradleRemoteCache/gradle-node/.empty
+++ /dev/null
diff --git a/development/gradleRemoteCache/run_node b/development/gradleRemoteCache/run_node
deleted file mode 100644
index 0b50c0f..0000000
--- a/development/gradleRemoteCache/run_node
+++ /dev/null
@@ -1 +0,0 @@
-java -jar /home/YOURUSERNAME/Workspace/gradle-node/build-cache-node-11.1.jar start --data-dir /home/YOURUSERNAME/Workspace/data --port 80 --no-warn-anon-cache-write --no-warn-anon-ui-access
\ No newline at end of file
diff --git a/development/importMaven/importMaven.sh b/development/importMaven/importMaven.sh
index a6d6e44..91a8fc8 100755
--- a/development/importMaven/importMaven.sh
+++ b/development/importMaven/importMaven.sh
@@ -1,9 +1,9 @@
#! /bin/bash
# helper script to build importMaven and execute with the given arguments.
-set -ex
+set -e
WORKING_DIR=`pwd`
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
# build importMaven
-(cd $SCRIPT_DIR && ./gradlew installDist)
+(cd $SCRIPT_DIR && ./gradlew installDist -q)
# execute the output binary
(SUPPORT_REPO=$SCRIPT_DIR/../.. $SCRIPT_DIR/build/install/importMaven/bin/importMaven $@)
\ No newline at end of file
diff --git a/development/importMaven/src/main/kotlin/androidx/build/importMaven/KmpConfig.kt b/development/importMaven/src/main/kotlin/androidx/build/importMaven/KmpConfig.kt
index 77f8011..693c6f0 100644
--- a/development/importMaven/src/main/kotlin/androidx/build/importMaven/KmpConfig.kt
+++ b/development/importMaven/src/main/kotlin/androidx/build/importMaven/KmpConfig.kt
@@ -25,5 +25,8 @@
KonanTarget.LINUX_X64,
KonanTarget.MINGW_X64,
KonanTarget.MINGW_X86,
+ KonanTarget.IOS_ARM64,
+ KonanTarget.IOS_SIMULATOR_ARM64,
+ KonanTarget.IOS_X64,
)
}
\ No newline at end of file
diff --git a/development/update_studio.sh b/development/update_studio.sh
index f17e998..473cc42 100755
--- a/development/update_studio.sh
+++ b/development/update_studio.sh
@@ -1,7 +1,7 @@
#!/bin/bash
# Get versions
-AGP_VERSION=${1:-7.4.0-alpha05}
-STUDIO_VERSION_STRING=${2:-"Android Studio Electric Eel (2022.1.1) Canary 5"}
+AGP_VERSION=${1:-7.4.0-alpha07}
+STUDIO_VERSION_STRING=${2:-"Android Studio Electric Eel (2022.1.1) Canary 7"}
STUDIO_IFRAME_LINK=`curl "https://developer.android.com/studio/archive.html" | grep iframe | sed "s/.*src=\"\([a-zA-Z0-9\/\._]*\)\".*/https:\/\/android-dot-devsite-v2-prod.appspot.com\1/g"`
STUDIO_LINK=`curl -s $STUDIO_IFRAME_LINK | grep -C30 "$STUDIO_VERSION_STRING" | grep Linux | tail -n 1 | sed 's/.*a href="\(.*\).*"/\1/g'`
STUDIO_VERSION=`echo $STUDIO_LINK | sed "s/.*ide-zips\/\(.*\)\/android-studio-.*/\1/g"`
@@ -43,4 +43,4 @@
ARTIFACTS_TO_DOWNLOAD+="com.google.testing.platform:core:$ATP_VERSION"
# Download all the artifacts
-./development/importMaven/import_maven_artifacts.py -n "$ARTIFACTS_TO_DOWNLOAD"
\ No newline at end of file
+./development/importMaven/importMaven.sh import-artifact --artifacts "$ARTIFACTS_TO_DOWNLOAD"
\ No newline at end of file
diff --git a/docs-public/build.gradle b/docs-public/build.gradle
index d0f6958..f43fd20 100644
--- a/docs-public/build.gradle
+++ b/docs-public/build.gradle
@@ -3,6 +3,10 @@
id("AndroidXDocsPlugin")
}
+android {
+ namespace "androidx.docs.publicdocs"
+}
+
dependencies {
docs("androidx.activity:activity:1.5.0")
docs("androidx.activity:activity-compose:1.5.0")
diff --git a/docs-public/src/main/AndroidManifest.xml b/docs-public/src/main/AndroidManifest.xml
deleted file mode 100644
index c0c456b..0000000
--- a/docs-public/src/main/AndroidManifest.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?><!--
- ~ Copyright 2020 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.
- -->
-<manifest package="androidx.docs.publicdocs"/>
diff --git a/docs-tip-of-tree/build.gradle b/docs-tip-of-tree/build.gradle
index c3f41cf..581ad2dc 100644
--- a/docs-tip-of-tree/build.gradle
+++ b/docs-tip-of-tree/build.gradle
@@ -3,6 +3,10 @@
id("AndroidXDocsPlugin")
}
+android {
+ namespace "androidx.docs.tipoftree"
+}
+
dependencies {
docs(project(":activity:activity"))
docs(project(":activity:activity-compose"))
diff --git a/docs-tip-of-tree/src/main/AndroidManifest.xml b/docs-tip-of-tree/src/main/AndroidManifest.xml
deleted file mode 100644
index bb41812..0000000
--- a/docs-tip-of-tree/src/main/AndroidManifest.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?><!--
- ~ Copyright 2020 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.
- -->
-<manifest package="androidx.docs.tipoftree"/>
diff --git a/emoji2/emoji2-views-helper/AndroidManifest.xml b/emoji2/emoji2-views-helper/AndroidManifest.xml
deleted file mode 100644
index 95c4426..0000000
--- a/emoji2/emoji2-views-helper/AndroidManifest.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-<manifest />
diff --git a/emoji2/emoji2-views-helper/build.gradle b/emoji2/emoji2-views-helper/build.gradle
index 667f154..c177372 100644
--- a/emoji2/emoji2-views-helper/build.gradle
+++ b/emoji2/emoji2-views-helper/build.gradle
@@ -23,12 +23,6 @@
}
android {
- sourceSets {
- main {
- // We use a non-standard manifest path.
- manifest.srcFile 'AndroidManifest.xml'
- }
- }
namespace "androidx.emoji2.viewsintegration"
}
diff --git a/emoji2/emoji2-views-helper/src/androidTest/AndroidManifest.xml b/emoji2/emoji2-views-helper/src/androidTest/AndroidManifest.xml
deleted file mode 100644
index 63f527c..0000000
--- a/emoji2/emoji2-views-helper/src/androidTest/AndroidManifest.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-<manifest>
-
- <application>
- </application>
-
-</manifest>
\ No newline at end of file
diff --git a/emoji2/emoji2-views/src/main/AndroidManifest.xml b/emoji2/emoji2-views/src/main/AndroidManifest.xml
deleted file mode 100644
index 95c4426..0000000
--- a/emoji2/emoji2-views/src/main/AndroidManifest.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-<manifest />
diff --git a/glance/glance-appwidget/integration-tests/template-demos/src/main/AndroidManifest.xml b/glance/glance-appwidget/integration-tests/template-demos/src/main/AndroidManifest.xml
index a94dd60..4f61b07 100644
--- a/glance/glance-appwidget/integration-tests/template-demos/src/main/AndroidManifest.xml
+++ b/glance/glance-appwidget/integration-tests/template-demos/src/main/AndroidManifest.xml
@@ -13,8 +13,7 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="androidx.glance.appwidget.template.demos">
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:allowBackup="false"
diff --git a/glance/glance-wear-tiles/integration-tests/template-demos/src/main/AndroidManifest.xml b/glance/glance-wear-tiles/integration-tests/template-demos/src/main/AndroidManifest.xml
index b551d1f..7ff81be 100644
--- a/glance/glance-wear-tiles/integration-tests/template-demos/src/main/AndroidManifest.xml
+++ b/glance/glance-wear-tiles/integration-tests/template-demos/src/main/AndroidManifest.xml
@@ -13,8 +13,7 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="androidx.glance.wear.tiles.template.demos">
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-feature android:name="android.hardware.type.watch" />
<meta-data android:name="com.google.android.wearable.standalone" android:value="true" />
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 1f6319c..1095b04 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -2,13 +2,13 @@
# -----------------------------------------------------------------------------
# All of the following should be updated in sync.
# -----------------------------------------------------------------------------
-androidGradlePlugin = "7.4.0-alpha05"
+androidGradlePlugin = "7.4.0-alpha07"
# NOTE: When updating the lint version we also need to update the `api` version
# supported by `IssueRegistry`'s.' For e.g. r.android.com/1331903
-androidLint = "30.4.0-alpha05"
+androidLint = "30.4.0-alpha07"
# Once you have a chosen version of AGP to upgrade to, go to
# https://developer.android.com/studio/archive and find the matching version of Studio.
-androidStudio = "2022.1.1.5"
+androidStudio = "2022.1.1.7"
# -----------------------------------------------------------------------------
androidGradlePluginMin = "7.0.4"
diff --git a/health/health-connect-client/src/main/java/androidx/health/connect/client/units/Percentage.kt b/health/health-connect-client/src/main/java/androidx/health/connect/client/units/Percentage.kt
index a3a0aae..c7a13d1 100644
--- a/health/health-connect-client/src/main/java/androidx/health/connect/client/units/Percentage.kt
+++ b/health/health-connect-client/src/main/java/androidx/health/connect/client/units/Percentage.kt
@@ -16,7 +16,7 @@
package androidx.health.connect.client.units
-/** Represents a percentage value. */
+/** Represents a value as a percentage, not a fraction - for example 100%, 89.62%, etc. */
class Percentage(val value: Double) : Comparable<Percentage> {
override fun compareTo(other: Percentage): Int = value.compareTo(other.value)
@@ -43,22 +43,22 @@
override fun toString(): String = "$value%"
}
-/** Creates [Percentage] with the specified value. */
+/** Creates [Percentage] with the specified percentage value, not a fraction. */
@get:JvmSynthetic
val Double.percent: Percentage
get() = Percentage(value = this)
-/** Creates [Percentage] with the specified value. */
+/** Creates [Percentage] with the specified percentage value, not a fraction. */
@get:JvmSynthetic
val Long.percent: Percentage
get() = toDouble().percent
-/** Creates [Percentage] with the specified value. */
+/** Creates [Percentage] with the specified percentage value, not a fraction. */
@get:JvmSynthetic
val Float.percent: Percentage
get() = toDouble().percent
-/** Creates [Percentage] with the specified value. */
+/** Creates [Percentage] with the specified percentage value, not a fraction. */
@get:JvmSynthetic
val Int.percent: Percentage
get() = toDouble().percent
diff --git a/leanback/leanback/api/current.txt b/leanback/leanback/api/current.txt
index d38fb99..6c2710a 100644
--- a/leanback/leanback/api/current.txt
+++ b/leanback/leanback/api/current.txt
@@ -2167,19 +2167,19 @@
public static class ListRowPresenter.SelectItemViewHolderTask extends androidx.leanback.widget.Presenter.ViewHolderTask {
ctor public ListRowPresenter.SelectItemViewHolderTask(int);
method public int getItemPosition();
- method public androidx.leanback.widget.Presenter.ViewHolderTask! getItemTask();
+ method public androidx.leanback.widget.Presenter.ViewHolderTask? getItemTask();
method public boolean isSmoothScroll();
method public void setItemPosition(int);
- method public void setItemTask(androidx.leanback.widget.Presenter.ViewHolderTask!);
+ method public void setItemTask(androidx.leanback.widget.Presenter.ViewHolderTask?);
method public void setSmoothScroll(boolean);
}
public static class ListRowPresenter.ViewHolder extends androidx.leanback.widget.RowPresenter.ViewHolder {
- ctor public ListRowPresenter.ViewHolder(android.view.View!, androidx.leanback.widget.HorizontalGridView!, androidx.leanback.widget.ListRowPresenter!);
- method public final androidx.leanback.widget.ItemBridgeAdapter! getBridgeAdapter();
- method public final androidx.leanback.widget.HorizontalGridView! getGridView();
- method public androidx.leanback.widget.Presenter.ViewHolder! getItemViewHolder(int);
- method public final androidx.leanback.widget.ListRowPresenter! getListRowPresenter();
+ ctor public ListRowPresenter.ViewHolder(android.view.View, androidx.leanback.widget.HorizontalGridView, androidx.leanback.widget.ListRowPresenter);
+ method public final androidx.leanback.widget.ItemBridgeAdapter getBridgeAdapter();
+ method public final androidx.leanback.widget.HorizontalGridView getGridView();
+ method public androidx.leanback.widget.Presenter.ViewHolder? getItemViewHolder(int);
+ method public final androidx.leanback.widget.ListRowPresenter getListRowPresenter();
method public int getSelectedPosition();
}
@@ -2702,8 +2702,8 @@
method public final androidx.leanback.widget.Row! getRow();
method public final Object! getRowObject();
method public final float getSelectLevel();
- method public Object! getSelectedItem();
- method public androidx.leanback.widget.Presenter.ViewHolder! getSelectedItemViewHolder();
+ method public Object? getSelectedItem();
+ method public androidx.leanback.widget.Presenter.ViewHolder? getSelectedItemViewHolder();
method public final boolean isExpanded();
method public final boolean isSelected();
method public final void setActivated(boolean);
diff --git a/leanback/leanback/api/public_plus_experimental_current.txt b/leanback/leanback/api/public_plus_experimental_current.txt
index d38fb99..6c2710a 100644
--- a/leanback/leanback/api/public_plus_experimental_current.txt
+++ b/leanback/leanback/api/public_plus_experimental_current.txt
@@ -2167,19 +2167,19 @@
public static class ListRowPresenter.SelectItemViewHolderTask extends androidx.leanback.widget.Presenter.ViewHolderTask {
ctor public ListRowPresenter.SelectItemViewHolderTask(int);
method public int getItemPosition();
- method public androidx.leanback.widget.Presenter.ViewHolderTask! getItemTask();
+ method public androidx.leanback.widget.Presenter.ViewHolderTask? getItemTask();
method public boolean isSmoothScroll();
method public void setItemPosition(int);
- method public void setItemTask(androidx.leanback.widget.Presenter.ViewHolderTask!);
+ method public void setItemTask(androidx.leanback.widget.Presenter.ViewHolderTask?);
method public void setSmoothScroll(boolean);
}
public static class ListRowPresenter.ViewHolder extends androidx.leanback.widget.RowPresenter.ViewHolder {
- ctor public ListRowPresenter.ViewHolder(android.view.View!, androidx.leanback.widget.HorizontalGridView!, androidx.leanback.widget.ListRowPresenter!);
- method public final androidx.leanback.widget.ItemBridgeAdapter! getBridgeAdapter();
- method public final androidx.leanback.widget.HorizontalGridView! getGridView();
- method public androidx.leanback.widget.Presenter.ViewHolder! getItemViewHolder(int);
- method public final androidx.leanback.widget.ListRowPresenter! getListRowPresenter();
+ ctor public ListRowPresenter.ViewHolder(android.view.View, androidx.leanback.widget.HorizontalGridView, androidx.leanback.widget.ListRowPresenter);
+ method public final androidx.leanback.widget.ItemBridgeAdapter getBridgeAdapter();
+ method public final androidx.leanback.widget.HorizontalGridView getGridView();
+ method public androidx.leanback.widget.Presenter.ViewHolder? getItemViewHolder(int);
+ method public final androidx.leanback.widget.ListRowPresenter getListRowPresenter();
method public int getSelectedPosition();
}
@@ -2702,8 +2702,8 @@
method public final androidx.leanback.widget.Row! getRow();
method public final Object! getRowObject();
method public final float getSelectLevel();
- method public Object! getSelectedItem();
- method public androidx.leanback.widget.Presenter.ViewHolder! getSelectedItemViewHolder();
+ method public Object? getSelectedItem();
+ method public androidx.leanback.widget.Presenter.ViewHolder? getSelectedItemViewHolder();
method public final boolean isExpanded();
method public final boolean isSelected();
method public final void setActivated(boolean);
diff --git a/leanback/leanback/api/restricted_current.txt b/leanback/leanback/api/restricted_current.txt
index 6aec3b7..af1803f 100644
--- a/leanback/leanback/api/restricted_current.txt
+++ b/leanback/leanback/api/restricted_current.txt
@@ -2380,19 +2380,19 @@
public static class ListRowPresenter.SelectItemViewHolderTask extends androidx.leanback.widget.Presenter.ViewHolderTask {
ctor public ListRowPresenter.SelectItemViewHolderTask(int);
method public int getItemPosition();
- method public androidx.leanback.widget.Presenter.ViewHolderTask! getItemTask();
+ method public androidx.leanback.widget.Presenter.ViewHolderTask? getItemTask();
method public boolean isSmoothScroll();
method public void setItemPosition(int);
- method public void setItemTask(androidx.leanback.widget.Presenter.ViewHolderTask!);
+ method public void setItemTask(androidx.leanback.widget.Presenter.ViewHolderTask?);
method public void setSmoothScroll(boolean);
}
public static class ListRowPresenter.ViewHolder extends androidx.leanback.widget.RowPresenter.ViewHolder {
- ctor public ListRowPresenter.ViewHolder(android.view.View!, androidx.leanback.widget.HorizontalGridView!, androidx.leanback.widget.ListRowPresenter!);
- method public final androidx.leanback.widget.ItemBridgeAdapter! getBridgeAdapter();
- method public final androidx.leanback.widget.HorizontalGridView! getGridView();
- method public androidx.leanback.widget.Presenter.ViewHolder! getItemViewHolder(int);
- method public final androidx.leanback.widget.ListRowPresenter! getListRowPresenter();
+ ctor public ListRowPresenter.ViewHolder(android.view.View, androidx.leanback.widget.HorizontalGridView, androidx.leanback.widget.ListRowPresenter);
+ method public final androidx.leanback.widget.ItemBridgeAdapter getBridgeAdapter();
+ method public final androidx.leanback.widget.HorizontalGridView getGridView();
+ method public androidx.leanback.widget.Presenter.ViewHolder? getItemViewHolder(int);
+ method public final androidx.leanback.widget.ListRowPresenter getListRowPresenter();
method public int getSelectedPosition();
}
@@ -2962,8 +2962,8 @@
method public final androidx.leanback.widget.Row! getRow();
method public final Object! getRowObject();
method public final float getSelectLevel();
- method public Object! getSelectedItem();
- method public androidx.leanback.widget.Presenter.ViewHolder! getSelectedItemViewHolder();
+ method public Object? getSelectedItem();
+ method public androidx.leanback.widget.Presenter.ViewHolder? getSelectedItemViewHolder();
method public final boolean isExpanded();
method public final boolean isSelected();
method public final void setActivated(boolean);
diff --git a/leanback/leanback/src/main/java/androidx/leanback/widget/ListRowPresenter.java b/leanback/leanback/src/main/java/androidx/leanback/widget/ListRowPresenter.java
index bd5b6f5..4e7fd49 100644
--- a/leanback/leanback/src/main/java/androidx/leanback/widget/ListRowPresenter.java
+++ b/leanback/leanback/src/main/java/androidx/leanback/widget/ListRowPresenter.java
@@ -20,6 +20,8 @@
import android.view.View;
import android.view.ViewGroup;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.leanback.R;
import androidx.leanback.system.Settings;
import androidx.leanback.transition.TransitionHelper;
@@ -67,7 +69,11 @@
final int mPaddingLeft;
final int mPaddingRight;
- public ViewHolder(View rootView, HorizontalGridView gridView, ListRowPresenter p) {
+ public ViewHolder(
+ @NonNull View rootView,
+ @NonNull HorizontalGridView gridView,
+ @NonNull ListRowPresenter p
+ ) {
super(rootView);
mGridView = gridView;
mListRowPresenter = p;
@@ -81,6 +87,7 @@
* Gets ListRowPresenter that creates this ViewHolder.
* @return ListRowPresenter that creates this ViewHolder.
*/
+ @NonNull
public final ListRowPresenter getListRowPresenter() {
return mListRowPresenter;
}
@@ -89,6 +96,7 @@
* Gets HorizontalGridView that shows a list of items.
* @return HorizontalGridView that shows a list of items.
*/
+ @NonNull
public final HorizontalGridView getGridView() {
return mGridView;
}
@@ -97,6 +105,7 @@
* Gets ItemBridgeAdapter that creates the list of items.
* @return ItemBridgeAdapter that creates the list of items.
*/
+ @NonNull
public final ItemBridgeAdapter getBridgeAdapter() {
return mItemBridgeAdapter;
}
@@ -115,6 +124,7 @@
* @param position Position of the item in adapter.
* @return ViewHolder bounds to the item.
*/
+ @Nullable
public Presenter.ViewHolder getItemViewHolder(int position) {
ItemBridgeAdapter.ViewHolder ibvh = (ItemBridgeAdapter.ViewHolder) mGridView
.findViewHolderForAdapterPosition(position);
@@ -124,11 +134,13 @@
return ibvh.getViewHolder();
}
+ @Nullable
@Override
public Presenter.ViewHolder getSelectedItemViewHolder() {
return getItemViewHolder(getSelectedPosition());
}
+ @Nullable
@Override
public Object getSelectedItem() {
ItemBridgeAdapter.ViewHolder ibvh = (ItemBridgeAdapter.ViewHolder) mGridView
@@ -191,6 +203,7 @@
* Returns optional task to run when the item is selected, null for no task.
* @return Optional task to run when the item is selected, null for no task.
*/
+ @Nullable
public Presenter.ViewHolderTask getItemTask() {
return mItemTask;
}
@@ -199,12 +212,12 @@
* Sets task to run when the item is selected, null for no task.
* @param itemTask Optional task to run when the item is selected, null for no task.
*/
- public void setItemTask(Presenter.ViewHolderTask itemTask) {
+ public void setItemTask(@Nullable ViewHolderTask itemTask) {
mItemTask = itemTask;
}
@Override
- public void run(Presenter.ViewHolder holder) {
+ public void run(@Nullable Presenter.ViewHolder holder) {
if (holder instanceof ListRowPresenter.ViewHolder) {
HorizontalGridView gridView = ((ListRowPresenter.ViewHolder) holder).getGridView();
androidx.leanback.widget.ViewHolderTask task = null;
@@ -212,7 +225,7 @@
task = new androidx.leanback.widget.ViewHolderTask() {
final Presenter.ViewHolderTask itemTask = mItemTask;
@Override
- public void run(RecyclerView.ViewHolder rvh) {
+ public void run(@NonNull RecyclerView.ViewHolder rvh) {
ItemBridgeAdapter.ViewHolder ibvh = (ItemBridgeAdapter.ViewHolder) rvh;
itemTask.run(ibvh.getViewHolder());
}
diff --git a/leanback/leanback/src/main/java/androidx/leanback/widget/RowPresenter.java b/leanback/leanback/src/main/java/androidx/leanback/widget/RowPresenter.java
index 164bb28..cce3c17 100644
--- a/leanback/leanback/src/main/java/androidx/leanback/widget/RowPresenter.java
+++ b/leanback/leanback/src/main/java/androidx/leanback/widget/RowPresenter.java
@@ -17,6 +17,7 @@
import android.view.ViewGroup;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.leanback.app.HeadersFragment;
import androidx.leanback.graphics.ColorOverlayDimmer;
@@ -296,6 +297,7 @@
* Return {@link ViewHolder} of currently selected item inside a row ViewHolder.
* @return The selected item's ViewHolder.
*/
+ @Nullable
public Presenter.ViewHolder getSelectedItemViewHolder() {
return null;
}
@@ -304,6 +306,7 @@
* Return currently selected item inside a row ViewHolder.
* @return The selected item.
*/
+ @Nullable
public Object getSelectedItem() {
return null;
}
diff --git a/lifecycle/integration-tests/testapp/build.gradle b/lifecycle/integration-tests/testapp/build.gradle
index e20cd13..c1e3d0a 100644
--- a/lifecycle/integration-tests/testapp/build.gradle
+++ b/lifecycle/integration-tests/testapp/build.gradle
@@ -37,6 +37,7 @@
androidTestImplementation(libs.testRunner)
androidTestImplementation(libs.testRules)
androidTestImplementation(libs.espressoCore)
+ androidTestImplementation(libs.truth)
testImplementation(libs.junit)
testImplementation(libs.mockitoCore)
diff --git a/lifecycle/integration-tests/testapp/src/androidTest/java/androidx/lifecycle/PartiallyCoveredActivityTest.java b/lifecycle/integration-tests/testapp/src/androidTest/java/androidx/lifecycle/PartiallyCoveredActivityTest.java
index 9c1aa32..31c9597 100644
--- a/lifecycle/integration-tests/testapp/src/androidTest/java/androidx/lifecycle/PartiallyCoveredActivityTest.java
+++ b/lifecycle/integration-tests/testapp/src/androidTest/java/androidx/lifecycle/PartiallyCoveredActivityTest.java
@@ -29,8 +29,8 @@
import static androidx.lifecycle.testapp.TestEvent.LIFECYCLE_EVENT;
import static androidx.lifecycle.testapp.TestEvent.OWNER_CALLBACK;
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.MatcherAssert.assertThat;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
import static java.util.Arrays.asList;
import static java.util.Collections.singletonList;
@@ -46,7 +46,6 @@
import androidx.lifecycle.testapp.NavigationDialogActivity;
import androidx.lifecycle.testapp.TestEvent;
import androidx.test.core.app.ApplicationProvider;
-import androidx.test.filters.FlakyTest;
import androidx.test.filters.LargeTest;
import androidx.test.filters.SdkSuppress;
import androidx.test.platform.app.InstrumentationRegistry;
@@ -58,7 +57,6 @@
import org.junit.runners.Parameterized;
import java.util.List;
-import java.util.concurrent.ExecutionException;
import kotlin.Pair;
@@ -127,7 +125,6 @@
runTest(activity);
}
- @FlakyTest(bugId = 206645367)
@Test
public void coveredWithDialog_fragment() throws Throwable {
CollectingSupportFragment fragment = new CollectingSupportFragment();
@@ -151,12 +148,12 @@
FragmentActivity dialog = launchDialog();
assertStateSaving();
waitForIdle();
- assertThat(owner.copyCollectedEvents(), is(EXPECTED[0]));
+ assertThat(owner.copyCollectedEvents()).isEqualTo(EXPECTED[0]);
List<Pair<TestEvent, Lifecycle.Event>> expected;
if (mDismissDialog) {
dialog.finish();
TestUtils.waitTillResumed(activityRule.getActivity(), activityRule);
- assertThat(owner.copyCollectedEvents(), is(flatMap(EXPECTED[0], EXPECTED[1])));
+ assertThat(owner.copyCollectedEvents()).isEqualTo(flatMap(EXPECTED[0], EXPECTED[1]));
expected = flatMap(EXPECTED[0], EXPECTED[1], EXPECTED[2]);
} else {
expected = flatMap(CREATE, START, RESUME, PAUSE, STOP, DESTROY);
@@ -164,22 +161,22 @@
CollectingSupportActivity activity = activityRule.getActivity();
activityRule.finishActivity();
TestUtils.waitTillDestroyed(activity, activityRule);
- assertThat(owner.copyCollectedEvents(), is(expected));
+ assertThat(owner.copyCollectedEvents()).isEqualTo(expected);
}
// test sanity
- private void assertStateSaving() throws ExecutionException, InterruptedException {
+ private void assertStateSaving() throws InterruptedException {
final CollectingSupportActivity activity = activityRule.getActivity();
if (sShouldSave) {
// state should be saved. wait for it to be saved
- assertThat("test sanity",
- activity.waitForStateSave(20), is(true));
- assertThat("test sanity", activity.getSupportFragmentManager()
- .isStateSaved(), is(true));
+ assertWithMessage("activity failed to call saveInstanceState")
+ .that(activity.waitForStateSave(20)).isTrue();
+ assertWithMessage("the state should have been saved")
+ .that(activity.getSupportFragmentManager().isStateSaved()).isTrue();
} else {
// should should not be saved
- assertThat("test sanity", activity.getSupportFragmentManager()
- .isStateSaved(), is(false));
+ assertWithMessage("the state should not be saved")
+ .that(activity.getSupportFragmentManager().isStateSaved()).isFalse();
}
}
diff --git a/lifecycle/lifecycle-runtime-compose/integration-tests/lifecycle-runtime-compose-demos/src/main/AndroidManifest.xml b/lifecycle/lifecycle-runtime-compose/integration-tests/lifecycle-runtime-compose-demos/src/main/AndroidManifest.xml
deleted file mode 100644
index fce8bf8..0000000
--- a/lifecycle/lifecycle-runtime-compose/integration-tests/lifecycle-runtime-compose-demos/src/main/AndroidManifest.xml
+++ /dev/null
@@ -1,17 +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.
- -->
-
-<manifest package="androidx.lifecycle.runtime.compose.demos" />
diff --git a/lifecycle/lifecycle-runtime-compose/samples/src/main/AndroidManifest.xml b/lifecycle/lifecycle-runtime-compose/samples/src/main/AndroidManifest.xml
deleted file mode 100644
index edbc6cf..0000000
--- a/lifecycle/lifecycle-runtime-compose/samples/src/main/AndroidManifest.xml
+++ /dev/null
@@ -1,17 +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.
- -->
-
-<manifest package="androidx.lifecycle.runtime.compose.samples" />
diff --git a/lifecycle/lifecycle-runtime-compose/src/androidTest/AndroidManifest.xml b/lifecycle/lifecycle-runtime-compose/src/androidTest/AndroidManifest.xml
deleted file mode 100644
index f77804a..0000000
--- a/lifecycle/lifecycle-runtime-compose/src/androidTest/AndroidManifest.xml
+++ /dev/null
@@ -1,17 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- 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.
- -->
-<manifest package="androidx.lifecycle.runtime.compose" />
diff --git a/lifecycle/lifecycle-runtime-compose/src/main/AndroidManifest.xml b/lifecycle/lifecycle-runtime-compose/src/main/AndroidManifest.xml
deleted file mode 100644
index f77804a..0000000
--- a/lifecycle/lifecycle-runtime-compose/src/main/AndroidManifest.xml
+++ /dev/null
@@ -1,17 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- 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.
- -->
-<manifest package="androidx.lifecycle.runtime.compose" />
diff --git a/lifecycle/lifecycle-runtime/.gitignore b/lifecycle/lifecycle-runtime/.gitignore
deleted file mode 100644
index 796b96d..0000000
--- a/lifecycle/lifecycle-runtime/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-/build
diff --git a/lifecycle/lifecycle-runtime/src/main/AndroidManifest.xml b/lifecycle/lifecycle-runtime/src/main/AndroidManifest.xml
deleted file mode 100644
index 9752998..0000000
--- a/lifecycle/lifecycle-runtime/src/main/AndroidManifest.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2016 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.
- -->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android">
-</manifest>
diff --git a/media/version-compat-tests/current/client/build.gradle b/media/version-compat-tests/current/client/build.gradle
index fec712a..6950588 100644
--- a/media/version-compat-tests/current/client/build.gradle
+++ b/media/version-compat-tests/current/client/build.gradle
@@ -19,6 +19,10 @@
id("com.android.library")
}
+android {
+ namespace "android.support.mediacompat.client"
+}
+
dependencies {
androidTestImplementation(project(":media:media"))
androidTestImplementation(project(":media:version-compat-tests:lib"))
diff --git a/media/version-compat-tests/current/client/src/androidTest/AndroidManifest.xml b/media/version-compat-tests/current/client/src/androidTest/AndroidManifest.xml
index 1f737c9..aaf0585 100644
--- a/media/version-compat-tests/current/client/src/androidTest/AndroidManifest.xml
+++ b/media/version-compat-tests/current/client/src/androidTest/AndroidManifest.xml
@@ -14,8 +14,7 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="android.support.mediacompat.client.test">
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<queries>
<package android:name="android.support.mediacompat.service.test" />
</queries>
diff --git a/media/version-compat-tests/current/client/src/main/AndroidManifest.xml b/media/version-compat-tests/current/client/src/main/AndroidManifest.xml
deleted file mode 100644
index 9724d2b..0000000
--- a/media/version-compat-tests/current/client/src/main/AndroidManifest.xml
+++ /dev/null
@@ -1,17 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright 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.
--->
-<manifest package="android.support.mediacompat.client"/>
diff --git a/media/version-compat-tests/current/service/build.gradle b/media/version-compat-tests/current/service/build.gradle
index fec712a..6374b29 100644
--- a/media/version-compat-tests/current/service/build.gradle
+++ b/media/version-compat-tests/current/service/build.gradle
@@ -19,6 +19,10 @@
id("com.android.library")
}
+android {
+ namespace "android.support.mediacompat.service"
+}
+
dependencies {
androidTestImplementation(project(":media:media"))
androidTestImplementation(project(":media:version-compat-tests:lib"))
diff --git a/media/version-compat-tests/current/service/src/androidTest/AndroidManifest.xml b/media/version-compat-tests/current/service/src/androidTest/AndroidManifest.xml
index 344b26a..dea570c 100644
--- a/media/version-compat-tests/current/service/src/androidTest/AndroidManifest.xml
+++ b/media/version-compat-tests/current/service/src/androidTest/AndroidManifest.xml
@@ -14,8 +14,7 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="android.support.mediacompat.service.test">
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application>
<receiver
diff --git a/media/version-compat-tests/current/service/src/main/AndroidManifest.xml b/media/version-compat-tests/current/service/src/main/AndroidManifest.xml
deleted file mode 100644
index 5e25a83..0000000
--- a/media/version-compat-tests/current/service/src/main/AndroidManifest.xml
+++ /dev/null
@@ -1,17 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright 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.
--->
-<manifest package="android.support.mediacompat.service"/>
diff --git a/media/version-compat-tests/lib/src/main/AndroidManifest.xml b/media/version-compat-tests/lib/src/main/AndroidManifest.xml
deleted file mode 100644
index 4cea376..0000000
--- a/media/version-compat-tests/lib/src/main/AndroidManifest.xml
+++ /dev/null
@@ -1,17 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright 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.
--->
-<manifest />
diff --git a/media/version-compat-tests/previous/client/build.gradle b/media/version-compat-tests/previous/client/build.gradle
index a7fe5f5..c199f15 100644
--- a/media/version-compat-tests/previous/client/build.gradle
+++ b/media/version-compat-tests/previous/client/build.gradle
@@ -19,6 +19,10 @@
id("com.android.library")
}
+android {
+ namespace "android.support.mediacompat.client"
+}
+
dependencies {
androidTestImplementation("androidx.media:media:1.4.1")
androidTestImplementation(project(":media:version-compat-tests:lib"))
diff --git a/media/version-compat-tests/previous/client/src/androidTest/AndroidManifest.xml b/media/version-compat-tests/previous/client/src/androidTest/AndroidManifest.xml
index 1f737c9..aaf0585 100644
--- a/media/version-compat-tests/previous/client/src/androidTest/AndroidManifest.xml
+++ b/media/version-compat-tests/previous/client/src/androidTest/AndroidManifest.xml
@@ -14,8 +14,7 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="android.support.mediacompat.client.test">
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<queries>
<package android:name="android.support.mediacompat.service.test" />
</queries>
diff --git a/media/version-compat-tests/previous/client/src/main/AndroidManifest.xml b/media/version-compat-tests/previous/client/src/main/AndroidManifest.xml
deleted file mode 100644
index 9724d2b..0000000
--- a/media/version-compat-tests/previous/client/src/main/AndroidManifest.xml
+++ /dev/null
@@ -1,17 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright 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.
--->
-<manifest package="android.support.mediacompat.client"/>
diff --git a/media/version-compat-tests/previous/service/build.gradle b/media/version-compat-tests/previous/service/build.gradle
index a7fe5f5..4695e46 100644
--- a/media/version-compat-tests/previous/service/build.gradle
+++ b/media/version-compat-tests/previous/service/build.gradle
@@ -19,6 +19,10 @@
id("com.android.library")
}
+android {
+ namespace "android.support.mediacompat.service"
+}
+
dependencies {
androidTestImplementation("androidx.media:media:1.4.1")
androidTestImplementation(project(":media:version-compat-tests:lib"))
diff --git a/media/version-compat-tests/previous/service/src/androidTest/AndroidManifest.xml b/media/version-compat-tests/previous/service/src/androidTest/AndroidManifest.xml
index 344b26a..dea570c 100644
--- a/media/version-compat-tests/previous/service/src/androidTest/AndroidManifest.xml
+++ b/media/version-compat-tests/previous/service/src/androidTest/AndroidManifest.xml
@@ -14,8 +14,7 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="android.support.mediacompat.service.test">
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application>
<receiver
diff --git a/media/version-compat-tests/previous/service/src/main/AndroidManifest.xml b/media/version-compat-tests/previous/service/src/main/AndroidManifest.xml
deleted file mode 100644
index 5e25a83..0000000
--- a/media/version-compat-tests/previous/service/src/main/AndroidManifest.xml
+++ /dev/null
@@ -1,17 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright 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.
--->
-<manifest package="android.support.mediacompat.service"/>
diff --git a/media2/media2-session/version-compat-tests/common/src/main/AndroidManifest.xml b/media2/media2-session/version-compat-tests/common/src/main/AndroidManifest.xml
deleted file mode 100644
index 4cf4915..0000000
--- a/media2/media2-session/version-compat-tests/common/src/main/AndroidManifest.xml
+++ /dev/null
@@ -1,17 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright 2018 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<manifest />
diff --git a/media2/media2-session/version-compat-tests/current/client/build.gradle b/media2/media2-session/version-compat-tests/current/client/build.gradle
index 7933e6d..147a6a7 100644
--- a/media2/media2-session/version-compat-tests/current/client/build.gradle
+++ b/media2/media2-session/version-compat-tests/current/client/build.gradle
@@ -33,6 +33,7 @@
defaultConfig {
minSdkVersion 16
}
+ namespace "androidx.media2.test.client"
}
androidx {
diff --git a/media2/media2-session/version-compat-tests/current/client/src/androidTest/AndroidManifest.xml b/media2/media2-session/version-compat-tests/current/client/src/androidTest/AndroidManifest.xml
index ce9acde..1cb0b1d3 100644
--- a/media2/media2-session/version-compat-tests/current/client/src/androidTest/AndroidManifest.xml
+++ b/media2/media2-session/version-compat-tests/current/client/src/androidTest/AndroidManifest.xml
@@ -14,8 +14,7 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="androidx.media2.test.client.test">
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application android:supportsRtl="true">
<activity
diff --git a/media2/media2-session/version-compat-tests/current/client/src/main/AndroidManifest.xml b/media2/media2-session/version-compat-tests/current/client/src/main/AndroidManifest.xml
deleted file mode 100644
index 185bf1e..0000000
--- a/media2/media2-session/version-compat-tests/current/client/src/main/AndroidManifest.xml
+++ /dev/null
@@ -1,17 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright 2018 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<manifest package="androidx.media2.test.client"/>
diff --git a/media2/media2-session/version-compat-tests/current/service/build.gradle b/media2/media2-session/version-compat-tests/current/service/build.gradle
index 2a87a6a..61b94fc 100644
--- a/media2/media2-session/version-compat-tests/current/service/build.gradle
+++ b/media2/media2-session/version-compat-tests/current/service/build.gradle
@@ -32,6 +32,7 @@
defaultConfig {
minSdkVersion 16
}
+ namespace "androidx.media2.test.service"
}
androidx {
diff --git a/media2/media2-session/version-compat-tests/current/service/src/androidTest/AndroidManifest.xml b/media2/media2-session/version-compat-tests/current/service/src/androidTest/AndroidManifest.xml
index 3a1f4f4..d06e43a 100644
--- a/media2/media2-session/version-compat-tests/current/service/src/androidTest/AndroidManifest.xml
+++ b/media2/media2-session/version-compat-tests/current/service/src/androidTest/AndroidManifest.xml
@@ -14,8 +14,7 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="androidx.media2.test.service.test">
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application>
<receiver
diff --git a/media2/media2-session/version-compat-tests/current/service/src/main/AndroidManifest.xml b/media2/media2-session/version-compat-tests/current/service/src/main/AndroidManifest.xml
deleted file mode 100644
index 15fbd02..0000000
--- a/media2/media2-session/version-compat-tests/current/service/src/main/AndroidManifest.xml
+++ /dev/null
@@ -1,17 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright 2018 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<manifest package="androidx.media2.test.service"/>
diff --git a/media2/media2-session/version-compat-tests/previous/client/build.gradle b/media2/media2-session/version-compat-tests/previous/client/build.gradle
index 65dec59..634a725 100644
--- a/media2/media2-session/version-compat-tests/previous/client/build.gradle
+++ b/media2/media2-session/version-compat-tests/previous/client/build.gradle
@@ -33,6 +33,7 @@
defaultConfig {
minSdkVersion 16
}
+ namespace "androidx.media2.test.client"
}
androidx {
diff --git a/media2/media2-session/version-compat-tests/previous/client/src/androidTest/AndroidManifest.xml b/media2/media2-session/version-compat-tests/previous/client/src/androidTest/AndroidManifest.xml
index ce9acde..1cb0b1d3 100644
--- a/media2/media2-session/version-compat-tests/previous/client/src/androidTest/AndroidManifest.xml
+++ b/media2/media2-session/version-compat-tests/previous/client/src/androidTest/AndroidManifest.xml
@@ -14,8 +14,7 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="androidx.media2.test.client.test">
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application android:supportsRtl="true">
<activity
diff --git a/media2/media2-session/version-compat-tests/previous/client/src/main/AndroidManifest.xml b/media2/media2-session/version-compat-tests/previous/client/src/main/AndroidManifest.xml
deleted file mode 100644
index 185bf1e..0000000
--- a/media2/media2-session/version-compat-tests/previous/client/src/main/AndroidManifest.xml
+++ /dev/null
@@ -1,17 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright 2018 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<manifest package="androidx.media2.test.client"/>
diff --git a/media2/media2-session/version-compat-tests/previous/service/build.gradle b/media2/media2-session/version-compat-tests/previous/service/build.gradle
index db6d7a6..48791a6 100644
--- a/media2/media2-session/version-compat-tests/previous/service/build.gradle
+++ b/media2/media2-session/version-compat-tests/previous/service/build.gradle
@@ -32,6 +32,7 @@
defaultConfig {
minSdkVersion 16
}
+ namespace "androidx.media2.test.service"
}
androidx {
diff --git a/media2/media2-session/version-compat-tests/previous/service/src/androidTest/AndroidManifest.xml b/media2/media2-session/version-compat-tests/previous/service/src/androidTest/AndroidManifest.xml
index 3a1f4f4..d06e43a 100644
--- a/media2/media2-session/version-compat-tests/previous/service/src/androidTest/AndroidManifest.xml
+++ b/media2/media2-session/version-compat-tests/previous/service/src/androidTest/AndroidManifest.xml
@@ -14,8 +14,7 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="androidx.media2.test.service.test">
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application>
<receiver
diff --git a/media2/media2-session/version-compat-tests/previous/service/src/main/AndroidManifest.xml b/media2/media2-session/version-compat-tests/previous/service/src/main/AndroidManifest.xml
deleted file mode 100644
index 15fbd02..0000000
--- a/media2/media2-session/version-compat-tests/previous/service/src/main/AndroidManifest.xml
+++ /dev/null
@@ -1,17 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright 2018 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<manifest package="androidx.media2.test.service"/>
diff --git a/navigation/navigation-common/src/main/java/androidx/navigation/NavType.kt b/navigation/navigation-common/src/main/java/androidx/navigation/NavType.kt
index 5d15eed..dfce454 100644
--- a/navigation/navigation-common/src/main/java/androidx/navigation/NavType.kt
+++ b/navigation/navigation-common/src/main/java/androidx/navigation/NavType.kt
@@ -68,7 +68,10 @@
/**
* Parse a value of this type from a String and then combine that
- * parsed value with the given previousValue of the same type.
+ * parsed value with the given previousValue of the same type to
+ * provide a new value that contains both the new and previous value.
+ *
+ * By default, the given value will replace the previousValue.
*
* @param value string representation of a value of this type
* @param previousValue previously parsed value of this type
diff --git a/navigation/navigation-dynamic-features-fragment/src/androidTest/java/androidx/navigation/dynamicfeatures/fragment/ui/DefaultProgressFragmentTest.kt b/navigation/navigation-dynamic-features-fragment/src/androidTest/java/androidx/navigation/dynamicfeatures/fragment/ui/DefaultProgressFragmentTest.kt
index 39dcd18..57d781b 100644
--- a/navigation/navigation-dynamic-features-fragment/src/androidTest/java/androidx/navigation/dynamicfeatures/fragment/ui/DefaultProgressFragmentTest.kt
+++ b/navigation/navigation-dynamic-features-fragment/src/androidTest/java/androidx/navigation/dynamicfeatures/fragment/ui/DefaultProgressFragmentTest.kt
@@ -19,6 +19,7 @@
import androidx.navigation.dynamicfeatures.fragment.R as mainR
import androidx.navigation.dynamicfeatures.fragment.test.R as testR
import android.widget.TextView
+import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.navigation.dynamicfeatures.fragment.DynamicNavHostFragment
import androidx.navigation.dynamicfeatures.fragment.NavigationActivity
@@ -27,6 +28,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
import androidx.testutils.withActivity
+import com.google.android.play.core.splitinstall.SplitInstallSessionState
import com.google.android.play.core.splitinstall.model.SplitInstallSessionStatus
import com.google.common.truth.Truth.assertThat
import java.util.concurrent.CountDownLatch
@@ -68,11 +70,16 @@
// the test to wait for the failure signal from the splitInstall session. To do that
// we observe the livedata of the DefaultProgressFragment's viewModel, and wait for
// it to fail before we check for test failure.
- viewModel.installMonitor!!.status.observe(defaultProgressFragment) {
- if (it.status() == SplitInstallSessionStatus.FAILED) {
- failureCountdownLatch.countDown()
+ val liveData = viewModel.installMonitor!!.status
+ val observer = object : Observer<SplitInstallSessionState> {
+ override fun onChanged(state: SplitInstallSessionState) {
+ if (state.status() == SplitInstallSessionStatus.FAILED) {
+ liveData.removeObserver(this)
+ failureCountdownLatch.countDown()
+ }
}
}
+ liveData.observe(defaultProgressFragment, observer)
}
assertThat(failureCountdownLatch.await(1000, TimeUnit.MILLISECONDS)).isTrue()
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/InternalXAnnotationValue.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/InternalXAnnotationValue.kt
index 92e456b8..4315bee 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/InternalXAnnotationValue.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/InternalXAnnotationValue.kt
@@ -26,7 +26,7 @@
* distinguish between `String` and `String[]` you can check `valueType.isArray()`.
*/
private enum class Kind {
- BOOLEAN, INT, SHORT, LONG, FLOAT, DOUBLE, BYTE, STRING, ENUM, ANNOTATION, TYPE;
+ BOOLEAN, INT, SHORT, LONG, FLOAT, DOUBLE, BYTE, CHAR, STRING, ENUM, ANNOTATION, TYPE;
companion object {
fun of(type: XType): Kind {
if (type.isArray()) {
@@ -40,6 +40,7 @@
type.typeName == TypeName.FLOAT -> FLOAT
type.typeName == TypeName.DOUBLE -> DOUBLE
type.typeName == TypeName.BYTE -> BYTE
+ type.typeName == TypeName.CHAR -> CHAR
type.typeName == InternalXAnnotationValue.STRING -> STRING
type.typeName.rawTypeName() == CLASS -> TYPE
type.typeName.rawTypeName() == KCLASS -> TYPE
@@ -120,6 +121,12 @@
/** Returns true if the value is a list of [Double] */
final override fun hasDoubleListValue() = kind == Kind.DOUBLE && hasListValue()
+ /** Returns true if the value is an [Char] */
+ final override fun hasCharValue() = kind == Kind.CHAR && !hasListValue()
+
+ /** Returns true if the value is a list of [Char] */
+ final override fun hasCharListValue() = kind == Kind.CHAR && hasListValue()
+
/** Returns true if the value is an [Byte] */
final override fun hasByteValue() = kind == Kind.BYTE && !hasListValue()
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XAnnotationValue.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XAnnotationValue.kt
index db883c9..b85213b 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XAnnotationValue.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XAnnotationValue.kt
@@ -170,6 +170,18 @@
/** Returns the value as a list of [Byte]. */
fun asByteList(): List<Byte> = asAnnotationValueList().map { it.asByte() }
+ /** Returns true if the value is an [Char] */
+ fun hasCharValue(): Boolean
+
+ /** Returns the value as a [Char]. */
+ fun asChar(): Char = value as Char
+
+ /** Returns true if the value is a list of [Char] */
+ fun hasCharListValue(): Boolean
+
+ /** Returns the value as a list of [Char]. */
+ fun asCharList(): List<Char> = asAnnotationValueList().map { it.asChar() }
+
/** Returns true if the value is a list. */
fun hasListValue(): Boolean
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XAnnotationValueTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XAnnotationValueTest.kt
index ad0c9ee..78f29d7 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XAnnotationValueTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XAnnotationValueTest.kt
@@ -476,6 +476,64 @@
}
@Test
+ fun testCharValue() {
+ runTest(
+ javaSource = Source.java(
+ "MyClass",
+ """
+ @interface MyAnnotation {
+ char charParam();
+ char[] charArrayParam();
+ char[] charVarArgsParam(); // There's no varargs in java so use array
+ }
+ @MyAnnotation(
+ charParam = '1',
+ charArrayParam = {'2', '3', '4'},
+ charVarArgsParam = {'5', '6', '7'}
+ )
+ class MyClass {}
+ """.trimIndent()
+ ) as Source.JavaSource,
+ kotlinSource = Source.kotlin(
+ "MyClass.kt",
+ """
+ annotation class MyAnnotation(
+ val charParam: Char,
+ val charArrayParam: CharArray,
+ vararg val charVarArgsParam: Char,
+ )
+ @MyAnnotation(
+ charParam = '1',
+ charArrayParam = ['2', '3', '4'],
+ charVarArgsParam = ['5', '6', '7'],
+ )
+ class MyClass
+ """.trimIndent()
+ ) as Source.KotlinSource
+ ) { invocation ->
+ val annotation = invocation.processingEnv.requireTypeElement("MyClass")
+ .getAllAnnotations()
+ .single { it.name == "MyAnnotation" }
+
+ val charParam = annotation.getAnnotationValue("charParam")
+ assertThat(charParam.hasCharValue()).isTrue()
+ assertThat(charParam.asChar()).isEqualTo('1')
+
+ val charArrayParam = annotation.getAnnotationValue("charArrayParam")
+ assertThat(charArrayParam.hasCharListValue()).isTrue()
+ assertThat(charArrayParam.asCharList())
+ .containsExactly('2', '3', '4')
+ .inOrder()
+
+ val charVarArgsParam = annotation.getAnnotationValue("charVarArgsParam")
+ assertThat(charVarArgsParam.hasCharListValue()).isTrue()
+ assertThat(charVarArgsParam.asCharList())
+ .containsExactly('5', '6', '7')
+ .inOrder()
+ }
+ }
+
+ @Test
fun testStringValue() {
runTest(
javaSource = Source.java(
diff --git a/wear/compose/compose-material/src/androidAndroidTest/AndroidManifest.xml b/wear/compose/compose-material/src/androidAndroidTest/AndroidManifest.xml
deleted file mode 100644
index f405734..0000000
--- a/wear/compose/compose-material/src/androidAndroidTest/AndroidManifest.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright 2021 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.
- -->
-
-<manifest package="androidx.wear.compose.material.test" />
diff --git a/wear/tiles/tiles-material/src/androidTest/AndroidManifest.xml b/wear/tiles/tiles-material/src/androidTest/AndroidManifest.xml
index 55cb77e..2ebf2db 100644
--- a/wear/tiles/tiles-material/src/androidTest/AndroidManifest.xml
+++ b/wear/tiles/tiles-material/src/androidTest/AndroidManifest.xml
@@ -14,8 +14,7 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="androidx.wear.tiles.material">
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:label="Golden Tests"
android:supportsRtl="true"
diff --git a/webkit/webkit/src/main/java/androidx/webkit/internal/WebMessageAdapter.java b/webkit/webkit/src/main/java/androidx/webkit/internal/WebMessageAdapter.java
index aad2697..a65f15a 100644
--- a/webkit/webkit/src/main/java/androidx/webkit/internal/WebMessageAdapter.java
+++ b/webkit/webkit/src/main/java/androidx/webkit/internal/WebMessageAdapter.java
@@ -79,6 +79,10 @@
* {@link WebMessageCompat} objects - a class apps recognize.
*/
@NonNull
+ // Suppress deprecation warning for usage of WebMessageBoundaryInterface's getData() method,
+ // TODO([email protected]): remove this once changes corresponding to https://crrev.com/c/3607795
+ // are done in webkit.
+ @SuppressWarnings("deprecation")
public static WebMessageCompat webMessageCompatFromBoundaryInterface(
@NonNull WebMessageBoundaryInterface boundaryInterface) {
return new WebMessageCompat(boundaryInterface.getData(),
diff --git a/work/work-multiprocess/src/androidTest/java/androidx/work/multiprocess/RemoteCoroutineWorkerTest.kt b/work/work-multiprocess/src/androidTest/java/androidx/work/multiprocess/RemoteCoroutineWorkerTest.kt
index 99806fa..330da1c 100644
--- a/work/work-multiprocess/src/androidTest/java/androidx/work/multiprocess/RemoteCoroutineWorkerTest.kt
+++ b/work/work-multiprocess/src/androidTest/java/androidx/work/multiprocess/RemoteCoroutineWorkerTest.kt
@@ -163,7 +163,8 @@
mTaskExecutor,
mForegroundProcessor,
mDatabase,
- mDatabase.workSpecDao().getWorkSpec(request.stringId)!!
+ mDatabase.workSpecDao().getWorkSpec(request.stringId)!!,
+ emptyList()
).build()
}
}
diff --git a/work/work-multiprocess/src/androidTest/java/androidx/work/multiprocess/RemoteListenableWorkerTest.kt b/work/work-multiprocess/src/androidTest/java/androidx/work/multiprocess/RemoteListenableWorkerTest.kt
index 337878a..7a62ee7 100644
--- a/work/work-multiprocess/src/androidTest/java/androidx/work/multiprocess/RemoteListenableWorkerTest.kt
+++ b/work/work-multiprocess/src/androidTest/java/androidx/work/multiprocess/RemoteListenableWorkerTest.kt
@@ -188,7 +188,8 @@
mTaskExecutor,
mForegroundProcessor,
mDatabase,
- mDatabase.workSpecDao().getWorkSpec(request.stringId)!!
+ mDatabase.workSpecDao().getWorkSpec(request.stringId)!!,
+ emptyList()
).build()
}
diff --git a/work/work-runtime/src/androidTest/java/androidx/work/WorkUpdateTest.kt b/work/work-runtime/src/androidTest/java/androidx/work/WorkUpdateTest.kt
index 21cd4ba..a1ff449 100644
--- a/work/work-runtime/src/androidTest/java/androidx/work/WorkUpdateTest.kt
+++ b/work/work-runtime/src/androidTest/java/androidx/work/WorkUpdateTest.kt
@@ -75,9 +75,10 @@
val workManager = WorkManagerImpl(
context, configuration, taskExecutor, db, schedulers, processor, trackers
)
+ val greedyScheduler = GreedyScheduler(context, configuration, trackers, workManager)
init {
- schedulers.add(GreedyScheduler(context, configuration, trackers, workManager))
+ schedulers.add(greedyScheduler)
WorkManagerImpl.setDelegate(workManager)
}
@@ -168,6 +169,34 @@
@Test
@MediumTest
+ fun updateTagsWhileRunning() {
+ val request = OneTimeWorkRequest.Builder(TestWorker::class.java)
+ .setConstraints(Constraints(requiresCharging = true))
+ .addTag("original").build()
+ workManager.enqueue(request).result.get()
+ val serialExecutorBlocker = CountDownLatch(1)
+ // stop any execution on serialTaskExecutor
+ taskExecutor.serialTaskExecutor.execute {
+ serialExecutorBlocker.await()
+ }
+ // will add startWork task to the serialTaskExecutor queue
+ greedyScheduler.onAllConstraintsMet(listOf(request.workSpec))
+ val updatedRequest = OneTimeWorkRequest.Builder(TestWorker::class.java)
+ .setConstraints(Constraints(requiresCharging = true))
+ .setId(request.id)
+ .addTag("updated")
+ .build()
+ // will add update task to the serialTaskExecutor queue
+ val updateResult = workManager.updateWork(updatedRequest)
+ serialExecutorBlocker.countDown()
+ val worker = workerFactory.awaitWorker(request.id)
+ assertThat(worker.tags).contains("original")
+ assertThat(worker.tags).doesNotContain("updated")
+ assertThat(updateResult.get()).isEqualTo(APPLIED_FOR_NEXT_RUN)
+ }
+
+ @Test
+ @MediumTest
fun updateWorkerClass() {
// requiresCharging constraint is faked, so it will never be satisfied
val oneTimeWorkRequest = OneTimeWorkRequest.Builder(TestWorker::class.java)
diff --git a/work/work-runtime/src/androidTest/java/androidx/work/impl/ControlledWorkerWrapperTest.kt b/work/work-runtime/src/androidTest/java/androidx/work/impl/ControlledWorkerWrapperTest.kt
index a9141a5..6e80617 100644
--- a/work/work-runtime/src/androidTest/java/androidx/work/impl/ControlledWorkerWrapperTest.kt
+++ b/work/work-runtime/src/androidTest/java/androidx/work/impl/ControlledWorkerWrapperTest.kt
@@ -136,7 +136,8 @@
taskExecutor,
NoOpForegroundProcessor,
workDatabase,
- workDatabase.workSpecDao().getWorkSpec(id)!!
+ workDatabase.workSpecDao().getWorkSpec(id)!!,
+ emptyList()
).build()
}
}
diff --git a/work/work-runtime/src/androidTest/java/androidx/work/impl/WorkContinuationImplTest.java b/work/work-runtime/src/androidTest/java/androidx/work/impl/WorkContinuationImplTest.java
index be2df12..aef0621 100644
--- a/work/work-runtime/src/androidTest/java/androidx/work/impl/WorkContinuationImplTest.java
+++ b/work/work-runtime/src/androidTest/java/androidx/work/impl/WorkContinuationImplTest.java
@@ -315,7 +315,8 @@
new InstantWorkTaskExecutor(),
foregroundProcessor,
mDatabase,
- workSpecDao.getWorkSpec(joinId))
+ workSpecDao.getWorkSpec(joinId),
+ new ArrayList<>())
.build()
.run();
diff --git a/work/work-runtime/src/androidTest/java/androidx/work/impl/WorkerWrapperTest.java b/work/work-runtime/src/androidTest/java/androidx/work/impl/WorkerWrapperTest.java
index b138562..a652619 100644
--- a/work/work-runtime/src/androidTest/java/androidx/work/impl/WorkerWrapperTest.java
+++ b/work/work-runtime/src/androidTest/java/androidx/work/impl/WorkerWrapperTest.java
@@ -1228,7 +1228,8 @@
mWorkTaskExecutor,
mMockForegroundProcessor,
mDatabase,
- mWorkSpecDao.getWorkSpec(work.getStringId())
+ mWorkSpecDao.getWorkSpec(work.getStringId()),
+ mDatabase.workTagDao().getTagsForWorkSpecId(work.getStringId())
).build();
FutureListener listener = createAndAddFutureListener(workerWrapper);
@@ -1278,7 +1279,9 @@
mWorkTaskExecutor,
mMockForegroundProcessor,
mDatabase,
- mWorkSpecDao.getWorkSpec(work.getStringId())).build();
+ mWorkSpecDao.getWorkSpec(work.getStringId()),
+ mDatabase.workTagDao().getWorkSpecIdsWithTag(work.getStringId())
+ ).build();
workerWrapper.interrupt();
workerWrapper.run();
@@ -1305,7 +1308,9 @@
mWorkTaskExecutor,
mMockForegroundProcessor,
mDatabase,
- mWorkSpecDao.getWorkSpec(workSpecId));
+ mWorkSpecDao.getWorkSpec(workSpecId),
+ mDatabase.workTagDao().getWorkSpecIdsWithTag(workSpecId)
+ );
}
private FutureListener createAndAddFutureListener(WorkerWrapper workerWrapper) {
diff --git a/work/work-runtime/src/androidTest/java/androidx/work/impl/foreground/WorkerWrapperForegroundTest.kt b/work/work-runtime/src/androidTest/java/androidx/work/impl/foreground/WorkerWrapperForegroundTest.kt
index 01ce6aa..6b0ae8d 100644
--- a/work/work-runtime/src/androidTest/java/androidx/work/impl/foreground/WorkerWrapperForegroundTest.kt
+++ b/work/work-runtime/src/androidTest/java/androidx/work/impl/foreground/WorkerWrapperForegroundTest.kt
@@ -122,7 +122,8 @@
taskExecutor,
foregroundProcessor,
workDatabase,
- workDatabase.workSpecDao().getWorkSpec(request.stringId)!!
+ workDatabase.workSpecDao().getWorkSpec(request.stringId)!!,
+ emptyList()
).build()
wrapper.run()
@@ -144,7 +145,8 @@
taskExecutor,
foregroundProcessor,
workDatabase,
- workDatabase.workSpecDao().getWorkSpec(request.stringId)!!
+ workDatabase.workSpecDao().getWorkSpec(request.stringId)!!,
+ emptyList()
).build()
wrapper.run()
diff --git a/work/work-runtime/src/androidTest/java/androidx/work/impl/workers/ConstraintTrackingWorkerTest.java b/work/work-runtime/src/androidTest/java/androidx/work/impl/workers/ConstraintTrackingWorkerTest.java
index 6d9facb..d5380aa 100644
--- a/work/work-runtime/src/androidTest/java/androidx/work/impl/workers/ConstraintTrackingWorkerTest.java
+++ b/work/work-runtime/src/androidTest/java/androidx/work/impl/workers/ConstraintTrackingWorkerTest.java
@@ -81,6 +81,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
@@ -364,7 +365,8 @@
mWorkTaskExecutor,
mForegroundProcessor,
mDatabase,
- mDatabase.workSpecDao().getWorkSpec(mWork.getStringId())
+ mDatabase.workSpecDao().getWorkSpec(mWork.getStringId()),
+ new ArrayList<>()
);
}
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/Processor.java b/work/work-runtime/src/main/java/androidx/work/impl/Processor.java
index 5efb6a1..72e52a3 100644
--- a/work/work-runtime/src/main/java/androidx/work/impl/Processor.java
+++ b/work/work-runtime/src/main/java/androidx/work/impl/Processor.java
@@ -116,8 +116,13 @@
@NonNull StartStopToken startStopToken,
@Nullable WorkerParameters.RuntimeExtras runtimeExtras) {
WorkGenerationalId id = startStopToken.getId();
+ String workSpecId = id.getWorkSpecId();
+ ArrayList<String> tags = new ArrayList<>();
WorkSpec workSpec = mWorkDatabase.runInTransaction(
- () -> mWorkDatabase.workSpecDao().getWorkSpec(id.getWorkSpecId())
+ () -> {
+ tags.addAll(mWorkDatabase.workTagDao().getTagsForWorkSpecId(workSpecId));
+ return mWorkDatabase.workSpecDao().getWorkSpec(workSpecId);
+ }
);
if (workSpec == null) {
Logger.get().warning(TAG, "Didn't find WorkSpec for id " + id);
@@ -128,7 +133,6 @@
synchronized (mLock) {
// Work may get triggered multiple times if they have passing constraints
// and new work with those constraints are added.
- String workSpecId = id.getWorkSpecId();
if (isEnqueued(workSpecId)) {
// there must be another run if it is enqueued.
Set<StartStopToken> tokens = mWorkRuns.get(workSpecId);
@@ -164,7 +168,8 @@
mWorkTaskExecutor,
this,
mWorkDatabase,
- workSpec)
+ workSpec,
+ tags)
.withSchedulers(mSchedulers)
.withRuntimeExtras(runtimeExtras)
.build();
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/WorkerWrapper.java b/work/work-runtime/src/main/java/androidx/work/impl/WorkerWrapper.java
index d5ad905..b2a0d12 100644
--- a/work/work-runtime/src/main/java/androidx/work/impl/WorkerWrapper.java
+++ b/work/work-runtime/src/main/java/androidx/work/impl/WorkerWrapper.java
@@ -48,7 +48,6 @@
import androidx.work.impl.model.WorkGenerationalId;
import androidx.work.impl.model.WorkSpec;
import androidx.work.impl.model.WorkSpecDao;
-import androidx.work.impl.model.WorkTagDao;
import androidx.work.impl.utils.PackageManagerHelper;
import androidx.work.impl.utils.SynchronousExecutor;
import androidx.work.impl.utils.WorkForegroundRunnable;
@@ -97,7 +96,6 @@
private WorkDatabase mWorkDatabase;
private WorkSpecDao mWorkSpecDao;
private DependencyDao mDependencyDao;
- private WorkTagDao mWorkTagDao;
private List<String> mTags;
private String mWorkDescription;
@@ -128,7 +126,7 @@
mWorkDatabase = builder.mWorkDatabase;
mWorkSpecDao = mWorkDatabase.workSpecDao();
mDependencyDao = mWorkDatabase.dependencyDao();
- mWorkTagDao = mWorkDatabase.workTagDao();
+ mTags = builder.mTags;
}
@NonNull
@@ -143,7 +141,6 @@
@WorkerThread
@Override
public void run() {
- mTags = mWorkTagDao.getTagsForWorkSpecId(mWorkSpecId);
mWorkDescription = createWorkDescription(mTags);
runWorker();
}
@@ -644,6 +641,7 @@
@NonNull WorkDatabase mWorkDatabase;
@NonNull WorkSpec mWorkSpec;
List<Scheduler> mSchedulers;
+ private final List<String> mTags;
@NonNull
WorkerParameters.RuntimeExtras mRuntimeExtras = new WorkerParameters.RuntimeExtras();
@@ -652,13 +650,16 @@
@NonNull TaskExecutor workTaskExecutor,
@NonNull ForegroundProcessor foregroundProcessor,
@NonNull WorkDatabase database,
- @NonNull WorkSpec workSpec) {
+ @NonNull WorkSpec workSpec,
+ @NonNull List<String> tags
+ ) {
mAppContext = context.getApplicationContext();
mWorkTaskExecutor = workTaskExecutor;
mForegroundProcessor = foregroundProcessor;
mConfiguration = configuration;
mWorkDatabase = database;
mWorkSpec = workSpec;
+ mTags = tags;
}
/**