Merge "Move transformToScreen to android-only extension" into androidx-main
diff --git a/appcompat/appcompat/build.gradle b/appcompat/appcompat/build.gradle
index aa6cbc1..afea44c 100644
--- a/appcompat/appcompat/build.gradle
+++ b/appcompat/appcompat/build.gradle
@@ -63,6 +63,7 @@
exclude group: "androidx.appcompat", module: "appcompat"
exclude group: "androidx.core", module: "core"
})
+ androidTestImplementation(project(":internal-testutils-fonts"))
testImplementation(libs.kotlinStdlib)
testImplementation(libs.testCore)
diff --git a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatTextViewTest.java b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatTextViewTest.java
index c329690..65b0e5b 100644
--- a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatTextViewTest.java
+++ b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatTextViewTest.java
@@ -18,11 +18,14 @@
import static androidx.appcompat.testutils.TestUtilsActions.setEnabled;
import static androidx.appcompat.testutils.TestUtilsActions.setTextAppearance;
import static androidx.appcompat.testutils.TestUtilsMatchers.isBackground;
+import static androidx.core.view.ViewKt.drawToBitmap;
import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.action.ViewActions.scrollTo;
import static androidx.test.espresso.assertion.ViewAssertions.matches;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
+import static junit.framework.Assert.assertFalse;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
@@ -30,8 +33,10 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import android.app.Instrumentation;
import android.content.pm.PackageManager;
import android.content.res.ColorStateList;
+import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PorterDuff;
@@ -67,6 +72,7 @@
import androidx.test.filters.LargeTest;
import androidx.test.filters.MediumTest;
import androidx.test.filters.SdkSuppress;
+import androidx.test.platform.app.InstrumentationRegistry;
import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat;
import org.junit.Ignore;
@@ -476,7 +482,15 @@
public void testFontVariation_setInXml() {
final AppCompatTextView textView = mActivity.findViewById(
R.id.textview_fontVariation_textView);
- assertEquals("'wdth' 30", textView.getFontVariationSettings());
+ assertEquals("'wght' 200", textView.getFontVariationSettings());
+
+ Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+ runCommandAndAssertBitmapChangedOrSame(textView, /* expectSame*/ true, () ->
+ instrumentation.runOnMainSync(() -> {
+ textView.setFontVariationSettings("'wght' 100"); /* reset */
+ textView.setFontVariationSettings("'wght' 200");
+ })
+ );
}
@SdkSuppress(minSdkVersion = 26)
@@ -485,6 +499,14 @@
final AppCompatTextView textView = mActivity.findViewById(
R.id.textview_fontVariation_textAppearance);
assertEquals("'wght' 300", textView.getFontVariationSettings());
+
+ Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+ runCommandAndAssertBitmapChangedOrSame(textView, /* expectSame*/ true, () ->
+ instrumentation.runOnMainSync(() -> {
+ textView.setFontVariationSettings("'wght' 100"); /* reset */
+ textView.setFontVariationSettings("'wght' 300");
+ })
+ );
}
@SdkSuppress(minSdkVersion = 26)
@@ -494,7 +516,7 @@
R.id.textview_fontVariation_textView_and_textAppearance);
//FontVariation is set in both AppCompatTextView and textAppearance,
//we should use the one in AppCompatTextView.
- assertEquals("'wdth' 30", textView.getFontVariationSettings());
+ assertEquals("'wght' 700", textView.getFontVariationSettings());
}
@SdkSuppress(minSdkVersion = 26)
@@ -502,12 +524,60 @@
@UiThreadTest
public void testFontVariation_setTextAppearance() throws Throwable {
final AppCompatTextView textView = mActivity.findViewById(
- R.id.textview_simple
+ R.id.textview_fontVariation_textView
);
textView.setTextAppearance(textView.getContext(), R.style.TextView_FontVariation);
assertEquals("'wght' 300", textView.getFontVariationSettings());
}
+ @SdkSuppress(minSdkVersion = 26)
+ @Test
+ public void testFontVariation_setTextAppearance_changesPixels() throws Throwable {
+ final AppCompatTextView textView = mActivity.findViewById(
+ R.id.textview_fontVariation_textView
+ );
+
+ Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+ instrumentation.runOnMainSync(() ->
+ textView.setFontVariationSettings("'wght' 900")
+ );
+ runCommandAndAssertBitmapChangedOrSame(textView, /* expectSame*/ false, () ->
+ instrumentation.runOnMainSync(() ->
+ textView.setTextAppearance(textView.getContext(),
+ R.style.TextView_FontVariation)
+ )
+ );
+ }
+
+ /**
+ * Run a command and confirm that the result of the command was a visible change to at least
+ * one pixel or exact similarity
+ *
+ * @param subject view to change
+ * @param expectSame when true, expect pixels to be identical, otherwise expect differences
+ * @param command comamnd to be called. it is not dispatched
+ */
+ public void runCommandAndAssertBitmapChangedOrSame(TextView subject, Boolean expectSame,
+ Runnable command) {
+ Bitmap before = drawToBitmap(subject, Bitmap.Config.ARGB_8888);
+ try {
+ command.run();
+ Bitmap after = drawToBitmap(subject, Bitmap.Config.ARGB_8888);
+ try {
+ if (expectSame) {
+ assertTrue(before.sameAs(after));
+ } else {
+ assertFalse(before.sameAs(after));
+ }
+ } finally {
+ after.recycle();
+ }
+ } finally {
+ before.recycle();
+ }
+ }
+
+
@Test
@UiThreadTest
public void testBaselineAttributes() {
diff --git a/appcompat/appcompat/src/androidTest/res/layout/appcompat_textview_activity.xml b/appcompat/appcompat/src/androidTest/res/layout/appcompat_textview_activity.xml
index 151113a..6b88361 100644
--- a/appcompat/appcompat/src/androidTest/res/layout/appcompat_textview_activity.xml
+++ b/appcompat/appcompat/src/androidTest/res/layout/appcompat_textview_activity.xml
@@ -380,21 +380,25 @@
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/textview_fontVariation_textView"
+ android:text="A"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- app:fontVariationSettings="'wdth' 30"/>
+ android:fontFamily="@font/variable_font"
+ app:fontVariationSettings="'wght' 200"/>
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/textview_fontVariation_textAppearance"
+ android:text="A"
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:fontFamily="@font/variable_font"
android:textAppearance="@style/TextView_FontVariation"/>
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/textview_fontVariation_textView_and_textAppearance"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- app:fontVariationSettings="'wdth' 30"
+ app:fontVariationSettings="'wght' 700"
android:textAppearance="@style/TextView_FontVariation"/>
<androidx.appcompat.widget.AppCompatTextView
diff --git a/appcompat/appcompat/src/main/java/androidx/appcompat/widget/AppCompatTextHelper.java b/appcompat/appcompat/src/main/java/androidx/appcompat/widget/AppCompatTextHelper.java
index 158f467..ef3029a8 100644
--- a/appcompat/appcompat/src/main/java/androidx/appcompat/widget/AppCompatTextHelper.java
+++ b/appcompat/appcompat/src/main/java/androidx/appcompat/widget/AppCompatTextHelper.java
@@ -76,6 +76,8 @@
private int mStyle = Typeface.NORMAL;
private int mFontWeight = TEXT_FONT_WEIGHT_UNSPECIFIED;
private Typeface mFontTypeface;
+ @Nullable
+ private String mFontVariationSettings = null;
private boolean mAsyncFontPending;
AppCompatTextHelper(@NonNull TextView view) {
@@ -134,7 +136,6 @@
ColorStateList textColor = null;
ColorStateList textColorHint = null;
ColorStateList textColorLink = null;
- String fontVariation = null;
String localeListString = null;
// First check TextAppearance's textAllCaps value
@@ -164,10 +165,6 @@
if (a.hasValue(R.styleable.TextAppearance_textLocale)) {
localeListString = a.getString(R.styleable.TextAppearance_textLocale);
}
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
- && a.hasValue(R.styleable.TextAppearance_fontVariationSettings)) {
- fontVariation = a.getString(R.styleable.TextAppearance_fontVariationSettings);
- }
a.recycle();
}
@@ -197,10 +194,6 @@
localeListString = a.getString(R.styleable.TextAppearance_textLocale);
}
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
- && a.hasValue(R.styleable.TextAppearance_fontVariationSettings)) {
- fontVariation = a.getString(R.styleable.TextAppearance_fontVariationSettings);
- }
// In P, when the text size attribute is 0, this would not be set. Fix this here.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P
&& a.hasValue(R.styleable.TextAppearance_android_textSize)) {
@@ -224,16 +217,9 @@
if (!hasPwdTm && allCapsSet) {
setAllCaps(allCaps);
}
- if (mFontTypeface != null) {
- if (mFontWeight == TEXT_FONT_WEIGHT_UNSPECIFIED) {
- mView.setTypeface(mFontTypeface, mStyle);
- } else {
- mView.setTypeface(mFontTypeface);
- }
- }
- if (fontVariation != null) {
- Api26Impl.setFontVariationSettings(mView, fontVariation);
- }
+
+ applyFontAndVariationSettings(/* forceNullSet */ false);
+
if (localeListString != null) {
if (Build.VERSION.SDK_INT >= 24) {
Api24Impl.setTextLocales(mView, Api24Impl.forLanguageTags(localeListString));
@@ -355,7 +341,42 @@
}
}
- private void updateTypefaceAndStyle(Context context, TintTypedArray a) {
+ /**
+ * Apply mFontTypeface previously loaded from XML, and apply mFontVariationSettings to it.
+ *
+ * This should only be called from xml initialization, or setTextAppearance.
+ *
+ * @param forceNullSet Explicit null values should clobber existing Typefaces
+ */
+ private void applyFontAndVariationSettings(boolean forceNullSet) {
+ if (mFontTypeface != null) {
+ if (mFontWeight == TEXT_FONT_WEIGHT_UNSPECIFIED) {
+ mView.setTypeface(mFontTypeface, mStyle);
+ } else {
+ mView.setTypeface(mFontTypeface);
+ }
+ } else if (forceNullSet) {
+ mView.setTypeface(null);
+ }
+
+ if (mFontVariationSettings != null && Build.VERSION.SDK_INT >= 26) {
+ Api26Impl.setFontVariationSettings(mView, mFontVariationSettings);
+ }
+
+ mFontVariationSettings = null;
+ mFontTypeface = null;
+ }
+
+ /**
+ * Load mFontTypeface from an xml, may be called multiple times with e.g. style, textAppearance,
+ * and attribute items.
+ *
+ * Note: Setting multiple fonts at different levels currently triggers multiple font-loads.
+ *
+ * @param context to check if restrictions avoid loading downloadable fonts
+ * @param a attributes to read from, e.g. a textappearance
+ */
+ private boolean updateTypefaceAndStyle(Context context, TintTypedArray a) {
mStyle = a.getInt(R.styleable.TextAppearance_android_textStyle, mStyle);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
@@ -366,6 +387,11 @@
}
}
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
+ && a.hasValue(R.styleable.TextAppearance_fontVariationSettings)) {
+ mFontVariationSettings = a.getString(R.styleable.TextAppearance_fontVariationSettings);
+ }
+
if (a.hasValue(R.styleable.TextAppearance_android_fontFamily)
|| a.hasValue(R.styleable.TextAppearance_fontFamily)) {
mFontTypeface = null;
@@ -375,27 +401,12 @@
final int fontWeight = mFontWeight;
final int style = mStyle;
if (!context.isRestricted()) {
- final WeakReference<TextView> textViewWeak = new WeakReference<>(mView);
- ResourcesCompat.FontCallback replyCallback = new ResourcesCompat.FontCallback() {
- @Override
- public void onFontRetrieved(@NonNull Typeface typeface) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
- if (fontWeight != TEXT_FONT_WEIGHT_UNSPECIFIED) {
- typeface = Api28Impl.create(typeface, fontWeight,
- (style & Typeface.ITALIC) != 0);
- }
- }
- onAsyncTypefaceReceived(textViewWeak, typeface);
- }
-
- @Override
- public void onFontRetrievalFailed(int reason) {
- // Do nothing.
- }
- };
+ ResourcesCompat.FontCallback replyCallback = makeFontCallback(fontWeight,
+ style);
try {
// Note the callback will be triggered on the UI thread.
final Typeface typeface = a.getFont(fontFamilyId, mStyle, replyCallback);
+ // assume Typeface does have fontVariationSettings in this path
if (typeface != null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P
&& mFontWeight != TEXT_FONT_WEIGHT_UNSPECIFIED) {
@@ -426,7 +437,7 @@
}
}
}
- return;
+ return true;
}
if (a.hasValue(R.styleable.TextAppearance_android_typeface)) {
@@ -446,13 +457,39 @@
mFontTypeface = Typeface.MONOSPACE;
break;
}
+ return true;
}
+ return false;
+ }
+
+ @NonNull
+ private ResourcesCompat.FontCallback makeFontCallback(int fontWeight, int style) {
+ final WeakReference<TextView> textViewWeak = new WeakReference<>(mView);
+ return new ResourcesCompat.FontCallback() {
+ @Override
+ public void onFontRetrieved(@NonNull Typeface typeface) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
+ if (fontWeight != TEXT_FONT_WEIGHT_UNSPECIFIED) {
+ typeface = Api28Impl.create(typeface, fontWeight,
+ (style & Typeface.ITALIC) != 0);
+ }
+ }
+ onAsyncTypefaceReceived(textViewWeak, typeface);
+ }
+
+ @Override
+ public void onFontRetrievalFailed(int reason) {
+ // Do nothing.
+ }
+ };
}
@SuppressWarnings("WeakerAccess") /* synthetic access */
void onAsyncTypefaceReceived(WeakReference<TextView> textViewWeak, final Typeface typeface) {
if (mAsyncFontPending) {
+ // we assume that typeface has the correct variation settings from androidx.core
mFontTypeface = typeface;
+ // TODO(b/266112457) unset mFontVariationSettings here so we don't double apply it
final TextView textView = textViewWeak.get();
if (textView != null) {
if (textView.isAttachedToWindow()) {
@@ -512,20 +549,9 @@
}
}
- updateTypefaceAndStyle(context, a);
-
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
- && a.hasValue(R.styleable.TextAppearance_fontVariationSettings)) {
- final String fontVariation = a.getString(
- R.styleable.TextAppearance_fontVariationSettings);
- if (fontVariation != null) {
- Api26Impl.setFontVariationSettings(mView, fontVariation);
- }
- }
+ boolean containsTypeface = updateTypefaceAndStyle(context, a);
a.recycle();
- if (mFontTypeface != null) {
- mView.setTypeface(mFontTypeface, mStyle);
- }
+ applyFontAndVariationSettings(containsTypeface);
}
void setAllCaps(boolean allCaps) {
diff --git a/appcompat/appcompat/src/main/java/androidx/appcompat/widget/AppCompatTextView.java b/appcompat/appcompat/src/main/java/androidx/appcompat/widget/AppCompatTextView.java
index 0b7c10b..7d78ebe 100644
--- a/appcompat/appcompat/src/main/java/androidx/appcompat/widget/AppCompatTextView.java
+++ b/appcompat/appcompat/src/main/java/androidx/appcompat/widget/AppCompatTextView.java
@@ -211,7 +211,13 @@
@Override
public void setTextAppearance(Context context, int resId) {
+ Typeface previousTypeface = getTypeface();
super.setTextAppearance(context, resId);
+ // don't allow setTextAppearance to clobber Typeface, we will correctly read it in
+ // onSetTextAppearance if non-null
+ if (getTypeface() == null) {
+ super.setTypeface(previousTypeface);
+ }
if (mTextHelper != null) {
mTextHelper.onSetTextAppearance(context, resId);
}
diff --git a/appcompat/integration-tests/receive-content-testapp/build.gradle b/appcompat/integration-tests/receive-content-testapp/build.gradle
index 2e4ddbe..e414862 100644
--- a/appcompat/integration-tests/receive-content-testapp/build.gradle
+++ b/appcompat/integration-tests/receive-content-testapp/build.gradle
@@ -32,7 +32,7 @@
implementation(libs.material)
// Align dependencies in debugRuntimeClasspath and debugAndroidTestRuntimeClasspath.
- androidTestImplementation("androidx.annotation:annotation-experimental:1.4.0")
+ androidTestImplementation("androidx.annotation:annotation-experimental:1.4.1")
androidTestImplementation("androidx.lifecycle:lifecycle-common:2.6.1")
androidTestImplementation(libs.testCore)
diff --git a/benchmark/benchmark-common/build.gradle b/benchmark/benchmark-common/build.gradle
index a9743ac..024ae02 100644
--- a/benchmark/benchmark-common/build.gradle
+++ b/benchmark/benchmark-common/build.gradle
@@ -75,7 +75,7 @@
dependencies {
implementation(libs.kotlinStdlib)
api("androidx.annotation:annotation:1.7.0")
- api("androidx.annotation:annotation-experimental:1.4.0")
+ api("androidx.annotation:annotation-experimental:1.4.1")
implementation("androidx.tracing:tracing-ktx:1.0.0")
implementation("androidx.tracing:tracing-perfetto-handshake:1.0.0")
implementation("androidx.test:monitor:1.6.1")
diff --git a/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/BenchmarkStateConfigTest.kt b/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/BenchmarkStateConfigTest.kt
index c5837c8..bc0ea41 100644
--- a/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/BenchmarkStateConfigTest.kt
+++ b/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/BenchmarkStateConfigTest.kt
@@ -18,6 +18,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
+import androidx.test.filters.SdkSuppress
import androidx.test.filters.SmallTest
import kotlin.test.assertEquals
import kotlin.test.assertNotEquals
@@ -140,6 +141,7 @@
expectedIterations = null, // iterations are dynamic
)
+ @SdkSuppress(minSdkVersion = 22) // See b/300658578
@Test
fun profilerMethodTracing() =
validateConfig(
@@ -160,6 +162,7 @@
expectedProfilerIterations = 1,
)
+ @SdkSuppress(minSdkVersion = 22) // See b/300658578
@Test
fun profilerMethodTracing_perfCompareMode() =
validateConfig(
diff --git a/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/ProfilerTest.kt b/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/ProfilerTest.kt
index 2366c48..0aecc6e 100644
--- a/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/ProfilerTest.kt
+++ b/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/ProfilerTest.kt
@@ -55,14 +55,20 @@
profiler == MethodTracing && Build.VERSION.SDK_INT == Build.VERSION_CODES.LOLLIPOP,
)
- val outputRelPath = profiler.start("test")!!.outputRelativePath
+ val result = profiler.start("test")!!
+ val outputRelPath = result.outputRelativePath
profiler.stop()
val file = File(Outputs.outputDirectory, outputRelPath)
-
assertTrue(
actual = regex.matches(outputRelPath),
message = "expected profiler output path $outputRelPath to match $regex"
)
+
+ if (result.convertBeforeSync != null) {
+ // profiler doesn't create file until conversion occurs
+ assertFalse(file.exists(), "Profiler should not yet create: ${file.absolutePath}")
+ result.convertBeforeSync?.invoke()
+ }
assertTrue(file.exists(), "Profiler should create: ${file.absolutePath}")
// we don't delete the file to enable inspecting the file
diff --git a/benchmark/benchmark-common/src/main/java/androidx/benchmark/BenchmarkState.kt b/benchmark/benchmark-common/src/main/java/androidx/benchmark/BenchmarkState.kt
index 64752cd..d51f969 100644
--- a/benchmark/benchmark-common/src/main/java/androidx/benchmark/BenchmarkState.kt
+++ b/benchmark/benchmark-common/src/main/java/androidx/benchmark/BenchmarkState.kt
@@ -585,6 +585,7 @@
return // nothing to report, BenchmarkState wasn't used
}
+ profilerResult?.convertBeforeSync?.invoke()
if (perfettoTracePath != null) {
profilerResult?.embedInPerfettoTrace(perfettoTracePath)
}
diff --git a/benchmark/benchmark-common/src/main/java/androidx/benchmark/Profiler.kt b/benchmark/benchmark-common/src/main/java/androidx/benchmark/Profiler.kt
index 100cdc0..35b8a6a 100644
--- a/benchmark/benchmark-common/src/main/java/androidx/benchmark/Profiler.kt
+++ b/benchmark/benchmark-common/src/main/java/androidx/benchmark/Profiler.kt
@@ -53,6 +53,7 @@
val type: ProfilerOutput.Type,
val outputRelativePath: String,
val source: Profiler?,
+ val convertBeforeSync: (() -> Unit)? = null
) {
fun embedInPerfettoTrace(perfettoTracePath: String) {
@@ -86,13 +87,15 @@
label: String,
type: ProfilerOutput.Type,
outputRelativePath: String,
- source: Profiler
+ source: Profiler,
+ convertBeforeSync: (() -> Unit)? = null
) =
ResultFile(
label = label,
outputRelativePath = outputRelativePath,
type = type,
- source = source
+ source = source,
+ convertBeforeSync = convertBeforeSync
)
}
}
@@ -329,19 +332,23 @@
label = "Stack Sampling Trace",
outputRelativePath = outputRelativePath!!,
type = ProfilerOutput.Type.StackSamplingTrace,
- source = this
+ source = this,
+ convertBeforeSync = this::convertBeforeSync
)
}
@RequiresApi(29)
override fun stop() {
session!!.stopRecording()
+ securityPerfHarden.resetIfOverridden()
+ }
+
+ @RequiresApi(29)
+ fun convertBeforeSync() {
Outputs.writeFile(fileName = outputRelativePath!!) {
session!!.convertSimpleperfOutputToProto("simpleperf.data", it.absolutePath)
+ session = null
}
-
- session = null
- securityPerfHarden.resetIfOverridden()
}
override fun config(packageNames: List<String>) =
diff --git a/browser/browser/build.gradle b/browser/browser/build.gradle
index cdae3ea..aa9834f 100644
--- a/browser/browser/build.gradle
+++ b/browser/browser/build.gradle
@@ -29,7 +29,7 @@
dependencies {
api("androidx.core:core:1.1.0")
api("androidx.annotation:annotation:1.2.0")
- api("androidx.annotation:annotation-experimental:1.4.0")
+ api("androidx.annotation:annotation-experimental:1.4.1")
api(libs.guavaListenableFuture)
implementation("androidx.collection:collection:1.1.0")
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt
index e453f50..464914e 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt
@@ -293,7 +293,7 @@
task: AbstractTestTask,
anchorTask: Task,
) {
- anchorTask.dependsOn(task)
+ if (task.name !in listOf("linuxx64StubsTest", "jvmStubsTest")) anchorTask.dependsOn(task)
val ignoreFailuresProperty =
project.providers.gradleProperty(TEST_FAILURES_DO_NOT_FAIL_TEST_TASK)
val ignoreFailures = ignoreFailuresProperty.isPresent
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXMultiplatformExtension.kt b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXMultiplatformExtension.kt
index f96abcd..eec2dc0 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXMultiplatformExtension.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXMultiplatformExtension.kt
@@ -618,7 +618,13 @@
fun linuxX64Stubs(block: Action<KotlinNativeTarget>? = null): KotlinNativeTarget? {
supportedPlatforms.add(PlatformIdentifier.LINUX_X_64_STUBS)
return if (project.enableLinux()) {
- kotlinExtension.linuxX64("linuxx64Stubs").also { block?.execute(it) }
+ kotlinExtension.linuxX64("linuxx64Stubs") {
+ block?.execute(this)
+ project.tasks.named("linuxx64StubsTest").configure {
+ // don't try running common tests for stubs target
+ it.enabled = false
+ }
+ }
} else {
null
}
@@ -756,7 +762,7 @@
* does not appear in this list will use its [KotlinPlatformType] name.
*/
private val allowedTargetNameSuffixes =
- setOf("android", "desktop", "jvm", "jvmStubs", "linuxx64Stubs")
+ setOf("android", "desktop", "jvm", "commonStubs", "jvmStubs", "linuxx64Stubs")
/** The preferred source file suffix for the target's platform type. */
private val KotlinTarget.preferredSourceFileSuffix: String
@@ -772,4 +778,4 @@
* Set of targets are there to serve as stubs, but are not expected to be consumed by library
* consumers.
*/
-internal val setOfStubTargets = setOf("jvmStubs", "linuxx64Stubs")
+internal val setOfStubTargets = setOf("commonStubs", "jvmStubs", "linuxx64Stubs")
diff --git a/camera/camera-camera2-pipe-integration/build.gradle b/camera/camera-camera2-pipe-integration/build.gradle
index 44442f2..b71053d 100644
--- a/camera/camera-camera2-pipe-integration/build.gradle
+++ b/camera/camera-camera2-pipe-integration/build.gradle
@@ -72,7 +72,7 @@
androidTestImplementation(libs.kotlinCoroutinesAndroid)
androidTestImplementation(libs.kotlinStdlib)
androidTestImplementation(libs.truth)
- androidTestImplementation("androidx.annotation:annotation-experimental:1.4.0")
+ androidTestImplementation("androidx.annotation:annotation-experimental:1.4.1")
androidTestImplementation(project(":camera:camera-lifecycle"))
androidTestImplementation(project(":camera:camera-testing")) {
// Ensure camera-testing does not pull in androidx.test dependencies
diff --git a/camera/camera-camera2-pipe-testing/src/main/java/androidx/camera/camera2/pipe/testing/CameraControllerSimulator.kt b/camera/camera-camera2-pipe-testing/src/main/java/androidx/camera/camera2/pipe/testing/CameraControllerSimulator.kt
index ef0c3d9..c23f556 100644
--- a/camera/camera-camera2-pipe-testing/src/main/java/androidx/camera/camera2/pipe/testing/CameraControllerSimulator.kt
+++ b/camera/camera-camera2-pipe-testing/src/main/java/androidx/camera/camera2/pipe/testing/CameraControllerSimulator.kt
@@ -40,7 +40,7 @@
* take time to configure and become ready.
*/
class CameraControllerSimulator(
- private val cameraContext: CameraContext,
+ cameraContext: CameraContext,
private val graphConfig: CameraGraph.Config,
private val graphListener: GraphListener,
private val streamGraph: StreamGraph
diff --git a/camera/camera-camera2-pipe-testing/src/main/java/androidx/camera/camera2/pipe/testing/CameraGraphSimulator.kt b/camera/camera-camera2-pipe-testing/src/main/java/androidx/camera/camera2/pipe/testing/CameraGraphSimulator.kt
index df26736..7e4fea2 100644
--- a/camera/camera-camera2-pipe-testing/src/main/java/androidx/camera/camera2/pipe/testing/CameraGraphSimulator.kt
+++ b/camera/camera-camera2-pipe-testing/src/main/java/androidx/camera/camera2/pipe/testing/CameraGraphSimulator.kt
@@ -33,7 +33,6 @@
import androidx.camera.camera2.pipe.RequestFailure
import androidx.camera.camera2.pipe.StreamId
import kotlinx.atomicfu.atomic
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.withTimeout
@@ -70,7 +69,6 @@
* test completes and allows the test to provide more fine grained control over the
* interactions.
*/
- @OptIn(ExperimentalCoroutinesApi::class)
fun create(
scope: TestScope,
context: Context,
@@ -150,6 +148,10 @@
cameraController.simulateCameraError(graphStateError)
}
+ /**
+ * Configure all streams in the CameraGraph with fake surfaces that match the size of the first
+ * output stream.
+ */
fun simulateFakeSurfaceConfiguration() {
check(!closed.value) {
"Cannot call simulateFakeSurfaceConfiguration on $this after close."
diff --git a/camera/camera-camera2-pipe-testing/src/main/java/androidx/camera/camera2/pipe/testing/FakeMetadata.kt b/camera/camera-camera2-pipe-testing/src/main/java/androidx/camera/camera2/pipe/testing/FakeCameraMetadata.kt
similarity index 100%
rename from camera/camera-camera2-pipe-testing/src/main/java/androidx/camera/camera2/pipe/testing/FakeMetadata.kt
rename to camera/camera-camera2-pipe-testing/src/main/java/androidx/camera/camera2/pipe/testing/FakeCameraMetadata.kt
diff --git a/camera/camera-camera2-pipe-testing/src/main/java/androidx/camera/camera2/pipe/testing/FakeImageReader.kt b/camera/camera-camera2-pipe-testing/src/main/java/androidx/camera/camera2/pipe/testing/FakeImageReader.kt
index 0f7d4b0..bbd23b0 100644
--- a/camera/camera-camera2-pipe-testing/src/main/java/androidx/camera/camera2/pipe/testing/FakeImageReader.kt
+++ b/camera/camera-camera2-pipe-testing/src/main/java/androidx/camera/camera2/pipe/testing/FakeImageReader.kt
@@ -41,20 +41,32 @@
val isClosed: Boolean
get() = closed.value
- fun simulateImage(timestamp: Long) {
+ /**
+ * Simulate an image at a specific [timestamp]. The timebase for an imageReader is undefined.
+ */
+ fun simulateImage(timestamp: Long): FakeImage {
val outputId = outputs.keys.single()
- simulateImage(outputId, timestamp)
+ return simulateImage(outputId, timestamp)
}
- fun simulateImage(outputId: OutputId, timestamp: Long) {
+ /**
+ * Simulate an image using a specific [outputId] and [timestamp]. The timebase for an
+ * imageReader is undefined.
+ */
+ fun simulateImage(outputId: OutputId, timestamp: Long): FakeImage {
val size =
checkNotNull(outputs[outputId]) {
"Unexpected $outputId! Available outputs are $outputs"
}
val image = FakeImage(size.width, size.height, format.value, timestamp)
simulateImage(outputId, image)
+ return image
}
+ /**
+ * Simulate an image using a specific [ImageWrapper] for the given outputId. The size must
+ * match.
+ */
fun simulateImage(outputId: OutputId, image: ImageWrapper) {
val size =
checkNotNull(outputs[outputId]) {
@@ -111,3 +123,17 @@
}
}
}
+
+class FakeOnImageListener : ImageReaderWrapper.OnImageListener {
+ val onImageEvents = mutableListOf<OnImageEvent>()
+
+ override fun onImage(streamId: StreamId, outputId: OutputId, image: ImageWrapper) {
+ onImageEvents.add(OnImageEvent(streamId, outputId, image))
+ }
+
+ data class OnImageEvent(
+ val streamId: StreamId,
+ val outputId: OutputId,
+ val image: ImageWrapper
+ )
+}
diff --git a/camera/camera-camera2-pipe-testing/src/main/java/androidx/camera/camera2/pipe/testing/FakeThreads.kt b/camera/camera-camera2-pipe-testing/src/main/java/androidx/camera/camera2/pipe/testing/FakeThreads.kt
index f447c70..fcf69f6 100644
--- a/camera/camera-camera2-pipe-testing/src/main/java/androidx/camera/camera2/pipe/testing/FakeThreads.kt
+++ b/camera/camera-camera2-pipe-testing/src/main/java/androidx/camera/camera2/pipe/testing/FakeThreads.kt
@@ -21,20 +21,18 @@
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.asExecutor
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
object FakeThreads {
fun fromDispatcher(dispatcher: CoroutineDispatcher): Threads {
- val scope = CoroutineScope(dispatcher.plus(CoroutineName("CXCP-TestScope")))
+ val scope = CoroutineScope(dispatcher + CoroutineName("CXCP-TestScope"))
return create(scope, dispatcher)
}
- @OptIn(ExperimentalCoroutinesApi::class)
fun fromTestScope(scope: TestScope): Threads {
- val dispatcher = StandardTestDispatcher(scope.testScheduler)
+ val dispatcher = StandardTestDispatcher(scope.testScheduler, "CXCP-TestScope")
return create(scope, dispatcher)
}
diff --git a/camera/camera-camera2-pipe-testing/src/test/java/androidx/camera/camera2/pipe/testing/CameraGraphSimulatorTest.kt b/camera/camera-camera2-pipe-testing/src/test/java/androidx/camera/camera2/pipe/testing/CameraGraphSimulatorTest.kt
index af4b275..a30a1e04 100644
--- a/camera/camera-camera2-pipe-testing/src/test/java/androidx/camera/camera2/pipe/testing/CameraGraphSimulatorTest.kt
+++ b/camera/camera-camera2-pipe-testing/src/test/java/androidx/camera/camera2/pipe/testing/CameraGraphSimulatorTest.kt
@@ -41,6 +41,7 @@
import kotlinx.coroutines.flow.take
import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.withContext
@@ -54,6 +55,7 @@
@RunWith(RobolectricCameraPipeTestRunner::class)
@Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
class CameraGraphSimulatorTest {
+ private val testScope = TestScope()
private val metadata =
FakeCameraMetadata(
mapOf(CameraCharacteristics.LENS_FACING to CameraCharacteristics.LENS_FACING_FRONT)
@@ -65,10 +67,11 @@
CameraGraph.Config(camera = metadata.camera, streams = listOf(streamConfig))
private val context = ApplicationProvider.getApplicationContext() as Context
+ private val simulator = CameraGraphSimulator.create(testScope, context, metadata, graphConfig)
@Test
- fun simulatorCanSimulateRepeatingFrames() = runTest {
- CameraGraphSimulator.create(this, context, metadata, graphConfig).use { simulator ->
+ fun simulatorCanSimulateRepeatingFrames() =
+ testScope.runTest {
val stream = simulator.cameraGraph.streams[streamConfig]!!
val listener = FakeRequestListener()
val request = Request(streams = listOf(stream.id), listeners = listOf(listener))
@@ -153,11 +156,10 @@
simulateCallbacks.join()
}
- }
@Test
- fun simulatorAbortsRequests() = runTest {
- CameraGraphSimulator.create(this, context, metadata, graphConfig).use { simulator ->
+ fun simulatorAbortsRequests() =
+ testScope.runTest {
val stream = simulator.cameraGraph.streams[streamConfig]!!
val listener = FakeRequestListener()
val request = Request(streams = listOf(stream.id), listeners = listOf(listener))
@@ -168,11 +170,10 @@
val abortedEvent = listener.onAbortedFlow.first()
assertThat(abortedEvent.request).isSameInstanceAs(request)
}
- }
@Test
- fun simulatorCanIssueBufferLoss() = runTest {
- CameraGraphSimulator.create(this, context, metadata, graphConfig).use { simulator ->
+ fun simulatorCanIssueBufferLoss() =
+ testScope.runTest {
val stream = simulator.cameraGraph.streams[streamConfig]!!
val listener = FakeRequestListener()
val request = Request(streams = listOf(stream.id), listeners = listOf(listener))
@@ -193,11 +194,10 @@
assertThat(lossEvent.requestMetadata.request).isSameInstanceAs(request)
assertThat(lossEvent.streamId).isEqualTo(stream.id)
}
- }
@Test
- fun simulatorCanIssueMultipleFrames() = runTest {
- CameraGraphSimulator.create(this, context, metadata, graphConfig).use { simulator ->
+ fun simulatorCanIssueMultipleFrames() =
+ testScope.runTest {
val stream = simulator.cameraGraph.streams[streamConfig]!!
val listener = FakeRequestListener()
val request = Request(streams = listOf(stream.id), listeners = listOf(listener))
@@ -279,11 +279,10 @@
simulateCallbacks.join()
}
- }
@Test
- fun simulatorCanSimulateGraphState() = runTest {
- CameraGraphSimulator.create(this, context, metadata, graphConfig).use { simulator ->
+ fun simulatorCanSimulateGraphState() =
+ testScope.runTest {
assertThat(simulator.cameraGraph.graphState.value).isEqualTo(GraphStateStopped)
simulator.cameraGraph.start()
@@ -298,11 +297,10 @@
simulator.simulateCameraStopped()
assertThat(simulator.cameraGraph.graphState.value).isEqualTo(GraphStateStopped)
}
- }
@Test
- fun simulatorCanSimulateGraphError() = runTest {
- CameraGraphSimulator.create(this, context, metadata, graphConfig).use { simulator ->
+ fun simulatorCanSimulateGraphError() =
+ testScope.runTest {
val error = GraphStateError(CameraError.ERROR_CAMERA_DEVICE, willAttemptRetry = true)
simulator.simulateCameraError(error)
@@ -325,5 +323,4 @@
simulator.simulateCameraError(error)
assertThat(simulator.cameraGraph.graphState.value).isEqualTo(GraphStateStopped)
}
- }
}
diff --git a/camera/camera-camera2-pipe-testing/src/test/java/androidx/camera/camera2/pipe/testing/FakeCameraDevicesTest.kt b/camera/camera-camera2-pipe-testing/src/test/java/androidx/camera/camera2/pipe/testing/FakeCameraDevicesTest.kt
index 1ed5597..d91dd8e 100644
--- a/camera/camera-camera2-pipe-testing/src/test/java/androidx/camera/camera2/pipe/testing/FakeCameraDevicesTest.kt
+++ b/camera/camera-camera2-pipe-testing/src/test/java/androidx/camera/camera2/pipe/testing/FakeCameraDevicesTest.kt
@@ -21,16 +21,14 @@
import androidx.camera.camera2.pipe.CameraBackendId
import androidx.camera.camera2.pipe.testing.FakeCameraBackend.Companion.FAKE_CAMERA_BACKEND_ID
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.annotation.Config
-@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(RobolectricCameraPipeTestRunner::class)
@Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
-public class FakeCameraDevicesTest {
+class FakeCameraDevicesTest {
private val EXTERNAL_BACKEND_ID =
CameraBackendId("androidx.camera.camera2.pipe.testing.FakeCameraDevicesTest")
private val metadata1 =
diff --git a/camera/camera-camera2-pipe-testing/src/test/java/androidx/camera/camera2/pipe/testing/FakeImageReaderTest.kt b/camera/camera-camera2-pipe-testing/src/test/java/androidx/camera/camera2/pipe/testing/FakeImageReaderTest.kt
new file mode 100644
index 0000000..af283e0d
--- /dev/null
+++ b/camera/camera-camera2-pipe-testing/src/test/java/androidx/camera/camera2/pipe/testing/FakeImageReaderTest.kt
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.camera2.pipe.testing
+
+import android.os.Build
+import android.util.Size
+import androidx.camera.camera2.pipe.OutputId
+import androidx.camera.camera2.pipe.StreamFormat
+import androidx.camera.camera2.pipe.StreamId
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.annotation.Config
+
+@RunWith(RobolectricCameraPipeTestRunner::class)
+@Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
+class FakeImageReaderTest {
+ private val imageReader =
+ FakeImageReader.create(StreamFormat.PRIVATE, StreamId(32), OutputId(42), Size(640, 480), 10)
+
+ @After
+ fun cleanup() {
+ imageReader.close()
+ }
+
+ @Test
+ fun imageReaderCanBeClosed() {
+ assertThat(imageReader.isClosed).isFalse()
+
+ imageReader.close()
+
+ assertThat(imageReader.isClosed).isTrue()
+ }
+
+ @Test
+ fun imageReaderCanSimulateImages() {
+ val fakeImage = imageReader.simulateImage(100)
+
+ assertThat(fakeImage.width).isEqualTo(640)
+ assertThat(fakeImage.height).isEqualTo(480)
+ assertThat(fakeImage.format).isEqualTo(StreamFormat.PRIVATE.value)
+ assertThat(fakeImage.timestamp).isEqualTo(100)
+ assertThat(fakeImage.isClosed).isFalse()
+ }
+
+ @Test
+ fun closingAnImageReaderDoesNotCloseImages() {
+ val fakeImage = imageReader.simulateImage(100)
+ imageReader.close()
+
+ assertThat(fakeImage.isClosed).isFalse()
+ }
+
+ @Test
+ fun imageReaderSimulatesANewImageEachTime() {
+ val fakeImage1 = imageReader.simulateImage(100)
+ val fakeImage2 = imageReader.simulateImage(100)
+
+ assertThat(fakeImage1).isNotSameInstanceAs(fakeImage2)
+ fakeImage2.close()
+
+ assertThat(fakeImage1.isClosed).isFalse()
+ assertThat(fakeImage2.isClosed).isTrue()
+ }
+
+ @Test
+ fun simulatingImagesArePassedToListener() {
+ val fakeListener = FakeOnImageListener()
+
+ imageReader.simulateImage(100)
+ imageReader.setOnImageListener(fakeListener)
+ val image2 = imageReader.simulateImage(200)
+ val image3 = imageReader.simulateImage(300)
+
+ assertThat(fakeListener.onImageEvents.size).isEqualTo(2)
+
+ assertThat(fakeListener.onImageEvents[0].image).isNotNull()
+ assertThat(fakeListener.onImageEvents[0].image).isSameInstanceAs(image2)
+ assertThat(fakeListener.onImageEvents[0].streamId).isEqualTo(StreamId(32))
+ assertThat(fakeListener.onImageEvents[0].outputId).isEqualTo(OutputId(42))
+
+ assertThat(fakeListener.onImageEvents[1].image).isNotNull()
+ assertThat(fakeListener.onImageEvents[1].image).isSameInstanceAs(image3)
+ assertThat(fakeListener.onImageEvents[1].streamId).isEqualTo(StreamId(32))
+ assertThat(fakeListener.onImageEvents[1].outputId).isEqualTo(OutputId(42))
+ }
+}
diff --git a/camera/camera-camera2-pipe-testing/src/test/java/androidx/camera/camera2/pipe/testing/FakeMetadataTest.kt b/camera/camera-camera2-pipe-testing/src/test/java/androidx/camera/camera2/pipe/testing/FakeMetadataTest.kt
index 973f28a..e276d6c 100644
--- a/camera/camera-camera2-pipe-testing/src/test/java/androidx/camera/camera2/pipe/testing/FakeMetadataTest.kt
+++ b/camera/camera-camera2-pipe-testing/src/test/java/androidx/camera/camera2/pipe/testing/FakeMetadataTest.kt
@@ -25,7 +25,6 @@
import androidx.camera.camera2.pipe.FrameNumber
import androidx.camera.camera2.pipe.MetadataTransform
import androidx.camera.camera2.pipe.Request
-import androidx.test.filters.SdkSuppress
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
@@ -33,10 +32,9 @@
import org.robolectric.annotation.Config
@RunWith(JUnit4::class)
-@SdkSuppress(minSdkVersion = 21)
-public class MetadataTest {
+class MetadataTest {
@Test
- public fun testMetadataCanRetrieveValues() {
+ fun testMetadataCanRetrieveValues() {
val metadata = FakeMetadata(mapOf(FakeMetadata.TEST_KEY to 42))
assertThat(metadata[FakeMetadata.TEST_KEY]).isNotNull()
@@ -49,7 +47,7 @@
@RunWith(RobolectricCameraPipeTestRunner::class)
@Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
-public class CameraMetadataTest {
+class CameraMetadataTest {
@Test
fun cameraMetadataIsNotEqual() {
val metadata1 =
@@ -68,7 +66,7 @@
}
@Test
- public fun canRetrieveCameraCharacteristicsOrCameraMetadataViaInterface() {
+ fun canRetrieveCameraCharacteristicsOrCameraMetadataViaInterface() {
val metadata =
FakeCameraMetadata(
mapOf(CameraCharacteristics.LENS_FACING to CameraCharacteristics.LENS_FACING_FRONT),
@@ -85,10 +83,10 @@
@RunWith(RobolectricCameraPipeTestRunner::class)
@Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
-public class RequestMetadataTest {
+class RequestMetadataTest {
@Test
- public fun canRetrieveCaptureRequestOrCameraMetadataViaInterface() {
+ fun canRetrieveCaptureRequestOrCameraMetadataViaInterface() {
val requestMetadata =
FakeRequestMetadata(
requestParameters = mapOf(CaptureRequest.JPEG_QUALITY to 95),
@@ -114,9 +112,9 @@
@RunWith(RobolectricCameraPipeTestRunner::class)
@Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
-public class FrameMetadataTest {
+class FrameMetadataTest {
@Test
- public fun canRetrieveCaptureRequestOrCameraMetadataViaInterface() {
+ fun canRetrieveCaptureRequestOrCameraMetadataViaInterface() {
val metadata =
FakeFrameMetadata(
resultMetadata = mapOf(CaptureResult.JPEG_QUALITY to 95),
@@ -133,7 +131,7 @@
@RunWith(RobolectricCameraPipeTestRunner::class)
@Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
-public class MetadataTransformTest {
+class MetadataTransformTest {
private val metadata =
FakeCameraMetadata(
mapOf(CameraCharacteristics.LENS_FACING to CameraCharacteristics.LENS_FACING_FRONT),
@@ -154,7 +152,7 @@
private val fakeFrameInfo = FakeFrameInfo(requestMetadata = requestMetadata)
@Test
- public fun defaultMetadataTransformIsNoOp() {
+ fun defaultMetadataTransformIsNoOp() {
val transform = MetadataTransform()
val overrides =
transform.transformFn.computeOverridesFor(fakeFrameInfo, CameraId("Fake"), listOf())
@@ -163,7 +161,7 @@
}
@Test
- public fun canCreateAndInvokeMetadataTransform() {
+ fun canCreateAndInvokeMetadataTransform() {
val transform =
MetadataTransform(
transformFn =
@@ -189,7 +187,7 @@
}
@Test
- public fun canUseCameraMetadataForTransforms() {
+ fun canUseCameraMetadataForTransforms() {
val transform =
MetadataTransform(
transformFn =
diff --git a/camera/camera-camera2-pipe-testing/src/test/java/androidx/camera/camera2/pipe/testing/FakeThreadsTest.kt b/camera/camera-camera2-pipe-testing/src/test/java/androidx/camera/camera2/pipe/testing/FakeThreadsTest.kt
new file mode 100644
index 0000000..db747e0
--- /dev/null
+++ b/camera/camera-camera2-pipe-testing/src/test/java/androidx/camera/camera2/pipe/testing/FakeThreadsTest.kt
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.camera2.pipe.testing
+
+import android.os.Build
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.advanceUntilIdle
+import kotlinx.coroutines.test.runTest
+import org.junit.Assert.assertThrows
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.annotation.Config
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(RobolectricCameraPipeTestRunner::class)
+@Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
+class FakeThreadsTest {
+ private val testScope = TestScope()
+ private val fakeThreads = FakeThreads.fromTestScope(testScope)
+
+ @Test
+ fun fakeThreadsUseDelaySkipping() =
+ testScope.runTest {
+ launch(fakeThreads.backgroundDispatcher) { delay(1000000) }.join()
+ launch(fakeThreads.blockingDispatcher) { delay(1000000) }.join()
+ launch(fakeThreads.lightweightDispatcher) { delay(1000000) }.join()
+ fakeThreads.globalScope.launch { delay(1000000) }.join()
+
+ var backgroundTaskExecuted = false
+ var blockingTaskExecuted = false
+ var lightweightTaskExecuted = false
+ fakeThreads.backgroundExecutor.execute { backgroundTaskExecuted = true }
+ fakeThreads.blockingExecutor.execute { blockingTaskExecuted = true }
+ fakeThreads.lightweightExecutor.execute { lightweightTaskExecuted = true }
+ advanceUntilIdle()
+
+ assertThat(backgroundTaskExecuted).isTrue()
+ assertThat(blockingTaskExecuted).isTrue()
+ assertThat(lightweightTaskExecuted).isTrue()
+ }
+
+ @Test
+ fun exceptionsInDispatcherPropagateToTestScopeFailure() {
+
+ // Exceptions in GlobalScope is propagated out of the test.
+ assertThrows(RuntimeException::class.java) {
+ val scope = TestScope()
+ val localFakeThreads = FakeThreads.fromTestScope(scope)
+ scope.runTest {
+ localFakeThreads.globalScope.launch { throw RuntimeException("globalScope") }
+ }
+ }
+
+ // Exceptions in Dispatchers are propagated out of the test.
+ assertThrows(RuntimeException::class.java) {
+ val scope = TestScope()
+ val localFakeThreads = FakeThreads.fromTestScope(scope)
+ scope.runTest {
+ launch(localFakeThreads.backgroundDispatcher) {
+ throw RuntimeException("backgroundDispatcher")
+ }
+ }
+ }
+
+ // Exceptions in Executors are propagated out of the test.
+ assertThrows(RuntimeException::class.java) {
+ val scope = TestScope()
+ val localFakeThreads = FakeThreads.fromTestScope(scope)
+ scope.runTest {
+ localFakeThreads.backgroundExecutor.execute {
+ throw RuntimeException("backgroundExecutor")
+ }
+ }
+ }
+ }
+}
diff --git a/camera/camera-camera2-pipe-testing/src/test/java/androidx/camera/camera2/pipe/testing/RobolectricCameraPipeTestRunner.kt b/camera/camera-camera2-pipe-testing/src/test/java/androidx/camera/camera2/pipe/testing/RobolectricCameraPipeTestRunner.kt
index 86b2ba2..b12508e 100644
--- a/camera/camera-camera2-pipe-testing/src/test/java/androidx/camera/camera2/pipe/testing/RobolectricCameraPipeTestRunner.kt
+++ b/camera/camera-camera2-pipe-testing/src/test/java/androidx/camera/camera2/pipe/testing/RobolectricCameraPipeTestRunner.kt
@@ -36,8 +36,7 @@
* objects, constructors with default values for parameters, and data classes with inline classes.
* We don't need shadowing of our classes because we want to use the actual objects in our tests.
*/
-public class RobolectricCameraPipeTestRunner(testClass: Class<*>) :
- RobolectricTestRunner(testClass) {
+class RobolectricCameraPipeTestRunner(testClass: Class<*>) : RobolectricTestRunner(testClass) {
override fun createClassLoaderConfig(method: FrameworkMethod?): InstrumentationConfiguration {
val builder = InstrumentationConfiguration.Builder(super.createClassLoaderConfig(method))
builder.doNotInstrumentPackage("androidx.camera.camera2.pipe")
@@ -46,19 +45,19 @@
}
}
-@JvmInline public value class TestValue(public val value: String)
+@JvmInline value class TestValue(public val value: String)
-public data class TestData(val value1: TestValue, val value2: String)
+data class TestData(val value1: TestValue, val value2: String)
@RunWith(JUnit4::class)
-public class DataWithInlineClassJUnitTest {
+class DataWithInlineClassJUnitTest {
@Test
- public fun inlineClassesAreEqualInJUnit() {
+ fun inlineClassesAreEqualInJUnit() {
assertThat(TestValue("42")).isEqualTo(TestValue("42"))
}
@Test
- public fun dataWithInlineClassesAreEqualInJUnit() {
+ fun dataWithInlineClassesAreEqualInJUnit() {
assertThat(TestData(value1 = TestValue("Test value #1"), value2 = "Test value #2"))
.isEqualTo(TestData(value1 = TestValue("Test value #1"), value2 = "Test value #2"))
}
@@ -66,14 +65,14 @@
@RunWith(RobolectricCameraPipeTestRunner::class)
@Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
-public class DataWithInlineClassRobolectricTest {
+class DataWithInlineClassRobolectricTest {
@Test
- public fun inlineClassesAreEqualInRobolectric() {
+ fun inlineClassesAreEqualInRobolectric() {
assertThat(TestValue("42")).isEqualTo(TestValue("42"))
}
@Test
- public fun dataWithInlineClassesAreEqualInRobolectric() {
+ fun dataWithInlineClassesAreEqualInRobolectric() {
assertThat(TestData(value1 = TestValue("Test value #1"), value2 = "Test value #2"))
.isEqualTo(TestData(value1 = TestValue("Test value #1"), value2 = "Test value #2"))
}
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/CameraGraphImpl.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/CameraGraphImpl.kt
index 5db75d8..3b93611 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/CameraGraphImpl.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/CameraGraphImpl.kt
@@ -42,6 +42,7 @@
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.Deferred
+import kotlinx.coroutines.Job
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.ensureActive
@@ -172,29 +173,37 @@
override fun <T> useSessionIn(
scope: CoroutineScope,
action: suspend CoroutineScope.(CameraGraph.Session) -> T
- ): Deferred<T> =
- scope.async(start = CoroutineStart.UNDISPATCHED) {
- ensureActive() // Exit early if the parent scope has been canceled.
+ ): Deferred<T> {
+ // https://github.com/Kotlin/kotlinx.coroutines/issues/1578
+ // To handle `runBlocking` we need to use `job.complete()` in `result.invokeOnCompletion`.
+ // However, if we do this directly on the scope that is provided it will cause
+ // SupervisorScopes to block and never complete. To work around this, we create a childJob,
+ // propagate the existing context, and use that as the context for scope.async.
+ val childJob = Job(scope.coroutineContext[Job])
+ val context = scope.coroutineContext + childJob
+ val result =
+ scope.async(context = context, start = CoroutineStart.UNDISPATCHED) {
+ ensureActive() // Exit early if the parent scope has been canceled.
- // It is very important to acquire *and* suspend here. Invoking a coroutine using
- // UNDISPATCHED will execute on the current thread until the suspension point, and this
- // will
- // force the execution to switch to the provided scope after ensuring the lock is
- // acquired
- // or in the queue. This guarantees exclusion, ordering, and execution within the
- // correct
- // scope.
- val token = sessionMutex.acquireTokenAndSuspend()
+ // It is very important to acquire *and* suspend here. Invoking a coroutine using
+ // UNDISPATCHED will execute on the current thread until the suspension point, and
+ // this will force the execution to switch to the provided scope after ensuring the
+ // lock is acquired or in the queue. This guarantees exclusion, ordering, and
+ // execution within the correct scope.
+ val token = sessionMutex.acquireTokenAndSuspend()
- // Create and use the session.
- createSessionFromToken(token).use {
- // Wrap the block in a coroutineScope to ensure all operations are completed before
- // exiting and releasing the lock. The lock can be released early if the calling
- // action
- // decided to call session.close() early.
- coroutineScope { action(it) }
+ // Create and use the session
+ createSessionFromToken(token).use {
+ // Wrap the block in a coroutineScope to ensure all operations are completed
+ // before exiting and releasing the lock. The lock can be released early if the
+ // calling action decides to call session.close() early.
+ coroutineScope { action(it) }
+ }
}
- }
+
+ result.invokeOnCompletion { childJob.complete() }
+ return result
+ }
private fun createSessionFromToken(token: Token) =
CameraGraphSessionImpl(token, graphProcessor, controller3A, frameCaptureQueue)
diff --git a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/graph/CameraGraphImplTest.kt b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/graph/CameraGraphImplTest.kt
index 8df2b23..78ebf5d 100644
--- a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/graph/CameraGraphImplTest.kt
+++ b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/graph/CameraGraphImplTest.kt
@@ -42,11 +42,21 @@
import androidx.camera.camera2.pipe.testing.FakeThreads
import androidx.camera.camera2.pipe.testing.RobolectricCameraPipeTestRunner
import androidx.test.core.app.ApplicationProvider
+import androidx.testutils.assertThrows
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CancellationException
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.async
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.isActive
+import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.runTest
+import kotlinx.coroutines.yield
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.eq
@@ -61,6 +71,8 @@
@DoNotInstrument
@Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
internal class CameraGraphImplTest {
+ private val testScope = TestScope()
+
private val context = ApplicationProvider.getApplicationContext() as Context
private val metadata =
FakeCameraMetadata(
@@ -77,184 +89,374 @@
private val stream2Config =
CameraStream.Config.create(Size(1920, 1080), StreamFormat.YUV_420_888)
- private lateinit var cameraController: CameraControllerSimulator
- private lateinit var stream1: CameraStream
- private lateinit var stream2: CameraStream
+ private val graphConfig =
+ CameraGraph.Config(
+ camera = metadata.camera,
+ streams = listOf(stream1Config, stream2Config),
+ )
+ private val threads = FakeThreads.fromTestScope(testScope)
+ private val backend = FakeCameraBackend(fakeCameras = mapOf(metadata.camera to metadata))
+ private val backends =
+ CameraBackendsImpl(
+ defaultBackendId = backend.id,
+ cameraBackends = mapOf(backend.id to CameraBackendFactory { backend }),
+ context,
+ threads
+ )
+ private val cameraContext = CameraBackendsImpl.CameraBackendContext(context, threads, backends)
+ private val graphLifecycleManager = GraphLifecycleManager(threads)
+ private val streamGraph = StreamGraphImpl(metadata, graphConfig)
+ private val imageSourceMap = ImageSourceMap(graphConfig, streamGraph, threads)
+ private val frameCaptureQueue = FrameCaptureQueue()
+ private val frameDistributor =
+ FrameDistributor(imageSourceMap.imageSources, frameCaptureQueue) {}
+ private val cameraController =
+ CameraControllerSimulator(cameraContext, graphConfig, fakeGraphProcessor, streamGraph)
- private fun initializeCameraGraphImpl(scope: TestScope): CameraGraphImpl {
- val graphConfig =
- CameraGraph.Config(
- camera = metadata.camera,
- streams = listOf(stream1Config, stream2Config),
- )
- val threads = FakeThreads.fromTestScope(scope)
- val backend = FakeCameraBackend(fakeCameras = mapOf(metadata.camera to metadata))
- val backends =
- CameraBackendsImpl(
- defaultBackendId = backend.id,
- cameraBackends = mapOf(backend.id to CameraBackendFactory { backend }),
- context,
- threads
- )
- val cameraContext = CameraBackendsImpl.CameraBackendContext(context, threads, backends)
- val graphLifecycleManager = GraphLifecycleManager(threads)
- val streamGraph = StreamGraphImpl(metadata, graphConfig)
- val imageSourceMap = ImageSourceMap(graphConfig, streamGraph, threads)
- val frameCaptureQueue = FrameCaptureQueue()
- val frameDistributor = FrameDistributor(imageSourceMap.imageSources, frameCaptureQueue) {}
- cameraController =
- CameraControllerSimulator(cameraContext, graphConfig, fakeGraphProcessor, streamGraph)
+ private val surfaceGraph =
+ SurfaceGraph(streamGraph, cameraController, cameraSurfaceManager, emptyMap())
+ private val audioRestriction = FakeAudioRestrictionController()
+ private val cameraGraphId = CameraGraphId.nextId()
+ private val cameraGraph =
+ CameraGraphImpl(
+ graphConfig,
+ metadata,
+ cameraGraphId,
+ graphLifecycleManager,
+ fakeGraphProcessor,
+ fakeGraphProcessor,
+ streamGraph,
+ surfaceGraph,
+ backend,
+ cameraController,
+ GraphState3A(),
+ Listener3A(),
+ frameDistributor,
+ frameCaptureQueue,
+ audioRestriction
+ )
+ private val stream1: CameraStream =
+ checkNotNull(cameraGraph.streams[stream1Config]) {
+ "Failed to find stream for $stream1Config!"
+ }
+
+ private val stream2 =
+ checkNotNull(cameraGraph.streams[stream2Config]) {
+ "Failed to find stream for $stream2Config!"
+ }
+
+ init {
cameraSurfaceManager.addListener(fakeSurfaceListener)
- val surfaceGraph =
- SurfaceGraph(streamGraph, cameraController, cameraSurfaceManager, emptyMap())
- val audioRestriction = FakeAudioRestrictionController()
- val cameraGraphId = CameraGraphId.nextId()
- val graph =
- CameraGraphImpl(
- graphConfig,
- metadata,
- cameraGraphId,
- graphLifecycleManager,
- fakeGraphProcessor,
- fakeGraphProcessor,
- streamGraph,
- surfaceGraph,
- backend,
- cameraController,
- GraphState3A(),
- Listener3A(),
- frameDistributor,
- frameCaptureQueue,
- audioRestriction
- )
- stream1 =
- checkNotNull(graph.streams[stream1Config]) {
- "Failed to find stream for $stream1Config!"
- }
+ }
- stream2 =
- checkNotNull(graph.streams[stream2Config]) {
- "Failed to find stream for $stream2Config!"
- }
- return graph
+ @Test fun createCameraGraphImpl() = testScope.runTest { assertThat(cameraGraph).isNotNull() }
+
+ @Test
+ fun testAcquireSession() =
+ testScope.runTest {
+ val session = cameraGraph.acquireSession()
+ assertThat(session).isNotNull()
+ }
+
+ @Test
+ fun testAcquireSessionOrNull() =
+ testScope.runTest {
+ val session = cameraGraph.acquireSessionOrNull()
+ assertThat(session).isNotNull()
+ }
+
+ @Test
+ fun testAcquireSessionOrNullAfterAcquireSession() =
+ testScope.runTest {
+ val session = cameraGraph.acquireSession()
+ assertThat(session).isNotNull()
+
+ // Since a session is already active, an attempt to acquire another session will fail.
+ val session1 = cameraGraph.acquireSessionOrNull()
+ assertThat(session1).isNull()
+
+ // Closing an active session should allow a new session instance to be created.
+ session.close()
+
+ val session2 = cameraGraph.acquireSessionOrNull()
+ assertThat(session2).isNotNull()
+ }
+
+ @Test
+ fun sessionSubmitsRequestsToGraphProcessor() =
+ testScope.runTest {
+ val session = checkNotNull(cameraGraph.acquireSessionOrNull())
+ val request = Request(listOf())
+ session.submit(request)
+ advanceUntilIdle()
+
+ assertThat(fakeGraphProcessor.requestQueue).contains(listOf(request))
+ }
+
+ @Test
+ fun sessionSetsRepeatingRequestOnGraphProcessor() =
+ testScope.runTest {
+ val session = checkNotNull(cameraGraph.acquireSessionOrNull())
+ val request = Request(listOf())
+ session.startRepeating(request)
+ advanceUntilIdle()
+
+ assertThat(fakeGraphProcessor.repeatingRequest).isSameInstanceAs(request)
+ }
+
+ @Test
+ fun sessionAbortsRequestOnGraphProcessor() =
+ testScope.runTest {
+ val session = checkNotNull(cameraGraph.acquireSessionOrNull())
+ val request = Request(listOf())
+ session.submit(request)
+ session.abort()
+ advanceUntilIdle()
+
+ assertThat(fakeGraphProcessor.requestQueue).isEmpty()
+ }
+
+ @Test
+ fun closingSessionDoesNotCloseGraphProcessor() =
+ testScope.runTest {
+ val session = cameraGraph.acquireSessionOrNull()
+ checkNotNull(session).close()
+ advanceUntilIdle()
+
+ assertThat(fakeGraphProcessor.closed).isFalse()
+ }
+
+ @Test
+ fun closingCameraGraphClosesGraphProcessor() =
+ testScope.runTest {
+ cameraGraph.close()
+ assertThat(fakeGraphProcessor.closed).isTrue()
+ }
+
+ @Test
+ fun stoppingCameraGraphStopsGraphProcessor() =
+ testScope.runTest {
+ assertThat(cameraController.started).isFalse()
+ assertThat(fakeGraphProcessor.closed).isFalse()
+ cameraGraph.start()
+ assertThat(cameraController.started).isTrue()
+ cameraGraph.stop()
+ assertThat(cameraController.started).isFalse()
+ assertThat(fakeGraphProcessor.closed).isFalse()
+ cameraGraph.start()
+ assertThat(cameraController.started).isTrue()
+ cameraGraph.close()
+ assertThat(cameraController.started).isFalse()
+ assertThat(fakeGraphProcessor.closed).isTrue()
+ }
+
+ @Test
+ fun closingCameraGraphClosesAssociatedSurfaces() =
+ testScope.runTest {
+ cameraGraph.setSurface(stream1.id, imageReader1.surface)
+ cameraGraph.setSurface(stream2.id, imageReader2.surface)
+ cameraGraph.close()
+
+ verify(fakeSurfaceListener, times(1)).onSurfaceActive(eq(imageReader1.surface))
+ verify(fakeSurfaceListener, times(1)).onSurfaceActive(eq(imageReader2.surface))
+ verify(fakeSurfaceListener, times(1)).onSurfaceInactive(eq(imageReader1.surface))
+ verify(fakeSurfaceListener, times(1)).onSurfaceInactive(eq(imageReader1.surface))
+ }
+
+ @Test
+ fun useSessionInOperatesInOrder() =
+ testScope.runTest {
+ val events = mutableListOf<Int>()
+ val job1 =
+ cameraGraph.useSessionIn(testScope) {
+ yield()
+ events += 2
+ }
+ val job2 =
+ cameraGraph.useSessionIn(testScope) {
+ delay(100)
+ events += 3
+ }
+ val job3 =
+ cameraGraph.useSessionIn(testScope) {
+ yield()
+ events += 4
+ }
+
+ events += 1
+ job1.join()
+ job2.join()
+ job3.join()
+
+ assertThat(events).containsExactly(1, 2, 3, 4).inOrder()
+ }
+
+ @Test
+ fun useSessionWithEarlyCloseAllowsInterleavedExecution() =
+ testScope.runTest {
+ val events = mutableListOf<Int>()
+ val job1 =
+ cameraGraph.useSessionIn(testScope) { session ->
+ yield()
+ events += 2
+ session.close()
+ delay(1000)
+ events += 5
+ }
+ val job2 =
+ cameraGraph.useSessionIn(testScope) {
+ delay(100)
+ events += 3
+ }
+ val job3 =
+ cameraGraph.useSessionIn(testScope) {
+ yield()
+ events += 4
+ }
+
+ events += 1
+ job1.join()
+ job2.join()
+ job3.join()
+
+ assertThat(events).containsExactly(1, 2, 3, 4, 5).inOrder()
+ }
+
+ @Test
+ fun useSessionInWithRunBlockingDoesNotStall() = runBlocking {
+ val deferred = cameraGraph.useSessionIn(this) { delay(1) }
+ deferred.await() // Make sure this does not block.
}
@Test
- fun createCameraGraphImpl() = runTest {
- val cameraGraphImpl = initializeCameraGraphImpl(this)
- assertThat(cameraGraphImpl).isNotNull()
- }
+ fun coroutineScope_isCanceledWithException() =
+ testScope.runTest {
+ val scope = CoroutineScope(Job())
+
+ val deferred = scope.async { throw RuntimeException() }
+ deferred.join()
+
+ // Ensure the deferred is completed with an exception, and that the scope is NOT active.
+ assertThat(deferred.isCompleted).isTrue()
+ assertThat(deferred.getCompletionExceptionOrNull())
+ .isInstanceOf(RuntimeException::class.java)
+ assertThrows<RuntimeException> { deferred.await() }
+ assertThat(scope.isActive).isFalse()
+ }
@Test
- fun testAcquireSession() = runTest {
- val cameraGraphImpl = initializeCameraGraphImpl(this)
- val session = cameraGraphImpl.acquireSession()
- assertThat(session).isNotNull()
- }
+ fun coroutineSupervisorScope_isNotCanceledWithException() =
+ testScope.runTest {
+ val scope = CoroutineScope(SupervisorJob())
+
+ val deferred = scope.async { throw RuntimeException() }
+ deferred.join()
+
+ // Ensure the deferred is completed with an exception, and that the scope remains
+ // active.
+ assertThat(deferred.isCompleted).isTrue()
+ assertThat(deferred.getCompletionExceptionOrNull())
+ .isInstanceOf(RuntimeException::class.java)
+ assertThrows<RuntimeException> { deferred.await() }
+ assertThat(scope.isActive).isTrue()
+ }
@Test
- fun testAcquireSessionOrNull() = runTest {
- val cameraGraphImpl = initializeCameraGraphImpl(this)
- val session = cameraGraphImpl.acquireSessionOrNull()
- assertThat(session).isNotNull()
- }
+ fun useSessionIn_scopeIsCanceledWithException() =
+ testScope.runTest {
+ val scope = CoroutineScope(Job())
+
+ val deferred = cameraGraph.useSessionIn(scope) { throw RuntimeException() }
+ deferred.join()
+
+ assertThat(deferred.isCompleted).isTrue()
+ assertThat(deferred.getCompletionExceptionOrNull())
+ .isInstanceOf(RuntimeException::class.java)
+ assertThrows<RuntimeException> { deferred.await() }
+ assertThat(scope.isActive).isFalse() // Regular scopes are canceled
+ }
@Test
- fun testAcquireSessionOrNullAfterAcquireSession() = runTest {
- val cameraGraphImpl = initializeCameraGraphImpl(this)
- val session = cameraGraphImpl.acquireSession()
- assertThat(session).isNotNull()
+ fun useSessionIn_supervisorScopeIsNotCanceledWithException() =
+ testScope.runTest {
+ val scope = CoroutineScope(SupervisorJob())
+ val deferred = cameraGraph.useSessionIn(scope) { throw RuntimeException() }
+ deferred.join()
- // Since a session is already active, an attempt to acquire another session will fail.
- val session1 = cameraGraphImpl.acquireSessionOrNull()
- assertThat(session1).isNull()
-
- // Closing an active session should allow a new session instance to be created.
- session.close()
-
- val session2 = cameraGraphImpl.acquireSessionOrNull()
- assertThat(session2).isNotNull()
- }
+ assertThat(deferred.isCompleted).isTrue()
+ assertThat(deferred.getCompletionExceptionOrNull())
+ .isInstanceOf(RuntimeException::class.java)
+ assertThrows<RuntimeException> { deferred.await() }
+ assertThat(scope.isActive).isTrue() // Supervisor scopes are not canceled
+ }
@Test
- fun sessionSubmitsRequestsToGraphProcessor() = runTest {
- val cameraGraphImpl = initializeCameraGraphImpl(this)
- val session = checkNotNull(cameraGraphImpl.acquireSessionOrNull())
- val request = Request(listOf())
- session.submit(request)
- advanceUntilIdle()
+ fun coroutineSupervisorTestScope_isNotCanceledWithException() =
+ testScope.runTest {
+ // This illustrates the correct way to create a scope that uses the testScope
+ // dispatcher, does delay skipping, but also does not fail the test if an exception
+ // occurs when doing scope.async. This is useful if, for example, in a real environment
+ // scope represents a supervisor job that will not crash if a coroutine fails and if
+ // some other system is handling the result of the deferred.
+ val scope = CoroutineScope(testScope.coroutineContext + Job())
- assertThat(fakeGraphProcessor.requestQueue).contains(listOf(request))
- }
+ val deferred =
+ scope.async {
+ delay(100000) // Delay skipping
+ throw RuntimeException()
+ }
+ deferred.join()
+
+ assertThat(deferred.isCompleted).isTrue()
+ assertThat(deferred.getCompletionExceptionOrNull())
+ .isInstanceOf(RuntimeException::class.java)
+ assertThrows<RuntimeException> { deferred.await() }
+ assertThat(scope.isActive).isFalse()
+ assertThat(testScope.isActive).isTrue()
+ }
@Test
- fun sessionSetsRepeatingRequestOnGraphProcessor() = runTest {
- val cameraGraphImpl = initializeCameraGraphImpl(this)
- val session = checkNotNull(cameraGraphImpl.acquireSessionOrNull())
- val request = Request(listOf())
- session.startRepeating(request)
- advanceUntilIdle()
+ fun useSessionIn_withSupervisorTestScopeDoesNotCancelTestScope() =
+ testScope.runTest {
+ // Create a scope that uses the testScope dispatcher and delaySkipping, but does not
+ // fail
+ // the test if an exception occurs in useSessionIn.
+ val scope = CoroutineScope(testScope.coroutineContext + SupervisorJob())
- assertThat(fakeGraphProcessor.repeatingRequest).isSameInstanceAs(request)
- }
+ // If you pass in a testScope to useSessionIn, any exception will cause the test to
+ // fail. If, instead, you want to test that the deferred handles the exception, you must
+ // pass in an independent CoroutineScope.
+ val deferred = cameraGraph.useSessionIn(scope) { throw RuntimeException() }
+ deferred.join()
+
+ assertThat(deferred.isCompleted).isTrue()
+ assertThat(deferred.getCompletionExceptionOrNull())
+ .isInstanceOf(RuntimeException::class.java)
+ assertThat(scope.isActive).isTrue() // Supervisor scopes are not canceled
+ assertThat(testScope.isActive).isTrue()
+ }
@Test
- fun sessionAbortsRequestOnGraphProcessor() = runTest {
- val cameraGraphImpl = initializeCameraGraphImpl(this)
- val session = checkNotNull(cameraGraphImpl.acquireSessionOrNull())
- val request = Request(listOf())
- session.submit(request)
- session.abort()
- advanceUntilIdle()
+ fun useSessionIn_withCancellationDoesNotFailTest() =
+ testScope.runTest {
+ val deferred =
+ cameraGraph.useSessionIn(testScope) {
+ throw CancellationException() // Throwing cancellation does not cause the test
+ // to fail.
+ }
+ deferred.join()
- assertThat(fakeGraphProcessor.requestQueue).isEmpty()
- }
+ assertThat(deferred.isActive).isFalse()
+ assertThat(deferred.isCompleted).isTrue()
+ assertThat(deferred.isCancelled).isTrue()
+ assertThat(deferred.getCompletionExceptionOrNull())
+ .isInstanceOf(CancellationException::class.java)
+ assertThat(testScope.isActive).isTrue()
+ }
@Test
- fun closingSessionDoesNotCloseGraphProcessor() = runTest {
- val cameraGraphImpl = initializeCameraGraphImpl(this)
- val session = cameraGraphImpl.acquireSessionOrNull()
- checkNotNull(session).close()
- advanceUntilIdle()
-
- assertThat(fakeGraphProcessor.closed).isFalse()
- }
-
- @Test
- fun closingCameraGraphClosesGraphProcessor() = runTest {
- val cameraGraphImpl = initializeCameraGraphImpl(this)
- cameraGraphImpl.close()
- assertThat(fakeGraphProcessor.closed).isTrue()
- }
-
- @Test
- fun stoppingCameraGraphStopsGraphProcessor() = runTest {
- val cameraGraph = initializeCameraGraphImpl(this)
-
- assertThat(cameraController.started).isFalse()
- assertThat(fakeGraphProcessor.closed).isFalse()
- cameraGraph.start()
- assertThat(cameraController.started).isTrue()
- cameraGraph.stop()
- assertThat(cameraController.started).isFalse()
- assertThat(fakeGraphProcessor.closed).isFalse()
- cameraGraph.start()
- assertThat(cameraController.started).isTrue()
- cameraGraph.close()
- assertThat(cameraController.started).isFalse()
- assertThat(fakeGraphProcessor.closed).isTrue()
- }
-
- @Test
- fun closingCameraGraphClosesAssociatedSurfaces() = runTest {
- val cameraGraph = initializeCameraGraphImpl(this)
- cameraGraph.setSurface(stream1.id, imageReader1.surface)
- cameraGraph.setSurface(stream2.id, imageReader2.surface)
- cameraGraph.close()
-
- verify(fakeSurfaceListener, times(1)).onSurfaceActive(eq(imageReader1.surface))
- verify(fakeSurfaceListener, times(1)).onSurfaceActive(eq(imageReader2.surface))
- verify(fakeSurfaceListener, times(1)).onSurfaceInactive(eq(imageReader1.surface))
- verify(fakeSurfaceListener, times(1)).onSurfaceInactive(eq(imageReader1.surface))
- }
+ fun useSession_throwsExceptions() =
+ testScope.runTest {
+ assertThrows<RuntimeException> { cameraGraph.useSession { throw RuntimeException() } }
+ }
}
diff --git a/camera/camera-camera2/build.gradle b/camera/camera-camera2/build.gradle
index 5fdd589..9c33c0e 100644
--- a/camera/camera-camera2/build.gradle
+++ b/camera/camera-camera2/build.gradle
@@ -47,7 +47,7 @@
testImplementation(libs.robolectric)
testImplementation(libs.mockitoCore4)
testImplementation(libs.kotlinCoroutinesTest)
- testImplementation("androidx.annotation:annotation-experimental:1.4.0")
+ testImplementation("androidx.annotation:annotation-experimental:1.4.1")
testImplementation("androidx.concurrent:concurrent-futures-ktx:1.1.0")
testImplementation("androidx.lifecycle:lifecycle-runtime-testing:2.3.1")
testImplementation(project(":camera:camera-video"))
@@ -72,7 +72,7 @@
}
androidTestImplementation(libs.kotlinStdlib)
androidTestImplementation(libs.kotlinCoroutinesAndroid)
- androidTestImplementation("androidx.annotation:annotation-experimental:1.4.0")
+ androidTestImplementation("androidx.annotation:annotation-experimental:1.4.1")
androidTestImplementation(project(":internal-testutils-truth"))
androidTestImplementation("org.jetbrains.kotlinx:atomicfu:0.13.1")
androidTestImplementation("androidx.exifinterface:exifinterface:1.0.0")
diff --git a/camera/camera-core/build.gradle b/camera/camera-core/build.gradle
index 8349b71..7277d2d 100644
--- a/camera/camera-core/build.gradle
+++ b/camera/camera-core/build.gradle
@@ -33,7 +33,7 @@
api("androidx.annotation:annotation:1.2.0")
api("androidx.lifecycle:lifecycle-livedata:2.1.0")
api(libs.guavaListenableFuture)
- api("androidx.annotation:annotation-experimental:1.4.0")
+ api("androidx.annotation:annotation-experimental:1.4.1")
api(libs.kotlinStdlib) // Added for annotation-experimental
api("androidx.core:core:1.1.0")
implementation(libs.kotlinCoroutinesAndroid)
diff --git a/camera/camera-lifecycle/build.gradle b/camera/camera-lifecycle/build.gradle
index 84855f8..6de8011 100644
--- a/camera/camera-lifecycle/build.gradle
+++ b/camera/camera-lifecycle/build.gradle
@@ -53,7 +53,7 @@
}
androidTestImplementation(libs.kotlinStdlib)
androidTestImplementation(libs.kotlinCoroutinesAndroid)
- androidTestImplementation("androidx.annotation:annotation-experimental:1.4.0")
+ androidTestImplementation("androidx.annotation:annotation-experimental:1.4.1")
androidTestImplementation("androidx.concurrent:concurrent-futures-ktx:1.1.0")
androidTestImplementation(project(":internal-testutils-truth"))
androidTestImplementation("org.jetbrains.kotlinx:atomicfu:0.13.1")
diff --git a/camera/camera-view/build.gradle b/camera/camera-view/build.gradle
index 34164df..77d81b0 100644
--- a/camera/camera-view/build.gradle
+++ b/camera/camera-view/build.gradle
@@ -36,7 +36,7 @@
api(project(":camera:camera-video"))
implementation(project(":camera:camera-lifecycle"))
- implementation("androidx.annotation:annotation-experimental:1.4.0")
+ implementation("androidx.annotation:annotation-experimental:1.4.1")
implementation(libs.guavaListenableFuture)
implementation("androidx.core:core:1.3.2")
implementation("androidx.concurrent:concurrent-futures:1.0.0")
diff --git a/camera/camera-viewfinder-core/build.gradle b/camera/camera-viewfinder-core/build.gradle
index 9666c33..664b6ab 100644
--- a/camera/camera-viewfinder-core/build.gradle
+++ b/camera/camera-viewfinder-core/build.gradle
@@ -31,7 +31,7 @@
dependencies {
api("androidx.annotation:annotation:1.2.0")
- implementation("androidx.annotation:annotation-experimental:1.4.0")
+ implementation("androidx.annotation:annotation-experimental:1.4.1")
implementation(libs.guavaListenableFuture)
implementation("androidx.core:core:1.7.0")
implementation("androidx.concurrent:concurrent-futures:1.1.0")
diff --git a/camera/camera-viewfinder/build.gradle b/camera/camera-viewfinder/build.gradle
index dc43d15..11a7321 100644
--- a/camera/camera-viewfinder/build.gradle
+++ b/camera/camera-viewfinder/build.gradle
@@ -32,7 +32,7 @@
dependencies {
api("androidx.annotation:annotation:1.2.0")
api(project(':camera:camera-viewfinder-core'))
- implementation("androidx.annotation:annotation-experimental:1.4.0")
+ implementation("androidx.annotation:annotation-experimental:1.4.1")
implementation(libs.guavaListenableFuture)
implementation("androidx.core:core:1.7.0")
implementation("androidx.concurrent:concurrent-futures:1.1.0")
diff --git a/camera/integration-tests/avsynctestapp/build.gradle b/camera/integration-tests/avsynctestapp/build.gradle
index 58084b0..c64700e 100644
--- a/camera/integration-tests/avsynctestapp/build.gradle
+++ b/camera/integration-tests/avsynctestapp/build.gradle
@@ -60,7 +60,7 @@
}
// Align dependencies in debugRuntimeClasspath and debugAndroidTestRuntimeClasspath.
- androidTestImplementation("androidx.annotation:annotation-experimental:1.4.0")
+ androidTestImplementation("androidx.annotation:annotation-experimental:1.4.1")
androidTestImplementation(project(":annotation:annotation"))
androidTestImplementation("androidx.lifecycle:lifecycle-common:2.8.3")
diff --git a/camera/integration-tests/coretestapp/build.gradle b/camera/integration-tests/coretestapp/build.gradle
index 2c76e8d..1d35fd6 100644
--- a/camera/integration-tests/coretestapp/build.gradle
+++ b/camera/integration-tests/coretestapp/build.gradle
@@ -95,7 +95,7 @@
debugImplementation(libs.testRunner)
// Align dependencies in debugRuntimeClasspath and debugAndroidTestRuntimeClasspath.
- androidTestImplementation("androidx.annotation:annotation-experimental:1.4.0")
+ androidTestImplementation("androidx.annotation:annotation-experimental:1.4.1")
// Testing framework
androidTestImplementation(libs.testCore)
diff --git a/camera/integration-tests/diagnosetestapp/build.gradle b/camera/integration-tests/diagnosetestapp/build.gradle
index ecf5b7f..cceb4a2 100644
--- a/camera/integration-tests/diagnosetestapp/build.gradle
+++ b/camera/integration-tests/diagnosetestapp/build.gradle
@@ -61,7 +61,7 @@
testImplementation("junit:junit:4.12")
// Align dependencies in debugRuntimeClasspath and debugAndroidTestRuntimeClasspath.
- androidTestImplementation("androidx.annotation:annotation-experimental:1.4.0")
+ androidTestImplementation("androidx.annotation:annotation-experimental:1.4.1")
// Testing framework
androidTestImplementation(libs.testExtJunit)
diff --git a/camera/integration-tests/extensionstestapp/build.gradle b/camera/integration-tests/extensionstestapp/build.gradle
index 850c2a0..fa75554 100644
--- a/camera/integration-tests/extensionstestapp/build.gradle
+++ b/camera/integration-tests/extensionstestapp/build.gradle
@@ -71,7 +71,7 @@
implementation("androidx.viewpager2:viewpager2:1.0.0")
// Align dependencies in debugRuntimeClasspath and debugAndroidTestRuntimeClasspath.
- androidTestImplementation("androidx.annotation:annotation-experimental:1.4.0")
+ androidTestImplementation("androidx.annotation:annotation-experimental:1.4.1")
androidTestImplementation(project(":concurrent:concurrent-futures"))
androidTestImplementation(project(":concurrent:concurrent-futures-ktx"))
diff --git a/camera/integration-tests/timingtestapp/build.gradle b/camera/integration-tests/timingtestapp/build.gradle
index 454a1e0..8d51ef2 100644
--- a/camera/integration-tests/timingtestapp/build.gradle
+++ b/camera/integration-tests/timingtestapp/build.gradle
@@ -66,7 +66,7 @@
implementation(libs.kotlinCoroutinesAndroid)
// Align dependencies in debugRuntimeClasspath and debugAndroidTestRuntimeClasspath.
- androidTestImplementation("androidx.annotation:annotation-experimental:1.4.0")
+ androidTestImplementation("androidx.annotation:annotation-experimental:1.4.1")
// Testing framework
androidTestImplementation(project(":concurrent:concurrent-futures"))
diff --git a/camera/integration-tests/uiwidgetstestapp/build.gradle b/camera/integration-tests/uiwidgetstestapp/build.gradle
index b791c61..453f43f 100644
--- a/camera/integration-tests/uiwidgetstestapp/build.gradle
+++ b/camera/integration-tests/uiwidgetstestapp/build.gradle
@@ -96,7 +96,7 @@
implementation("androidx.compose.material:material-icons-extended:1.4.0")
// Align dependencies in debugRuntimeClasspath and debugAndroidTestRuntimeClasspath.
- androidTestImplementation("androidx.annotation:annotation-experimental:1.4.0")
+ androidTestImplementation("androidx.annotation:annotation-experimental:1.4.1")
androidTestImplementation(project(":concurrent:concurrent-futures"))
androidTestImplementation(project(":concurrent:concurrent-futures-ktx"))
diff --git a/camera/integration-tests/viewfindertestapp/build.gradle b/camera/integration-tests/viewfindertestapp/build.gradle
index d6be6e3..84dcc2a 100644
--- a/camera/integration-tests/viewfindertestapp/build.gradle
+++ b/camera/integration-tests/viewfindertestapp/build.gradle
@@ -55,7 +55,7 @@
implementation(libs.constraintLayout)
// Align dependencies in debugRuntimeClasspath and debugAndroidTestRuntimeClasspath.
- androidTestImplementation("androidx.annotation:annotation-experimental:1.4.0")
+ androidTestImplementation("androidx.annotation:annotation-experimental:1.4.1")
// Testing framework
androidTestImplementation(libs.testExtJunit)
diff --git a/camera/integration-tests/viewtestapp/build.gradle b/camera/integration-tests/viewtestapp/build.gradle
index f3896a1..4cff2d8 100644
--- a/camera/integration-tests/viewtestapp/build.gradle
+++ b/camera/integration-tests/viewtestapp/build.gradle
@@ -80,7 +80,7 @@
implementation("androidx.compose.foundation:foundation:1.4.0")
// Align dependencies in debugRuntimeClasspath and debugAndroidTestRuntimeClasspath.
- androidTestImplementation("androidx.annotation:annotation-experimental:1.4.0")
+ androidTestImplementation("androidx.annotation:annotation-experimental:1.4.1")
// Testing framework
androidTestImplementation(libs.testExtJunit)
diff --git a/car/app/app-automotive/build.gradle b/car/app/app-automotive/build.gradle
index 0c5bb12..dd4116b 100644
--- a/car/app/app-automotive/build.gradle
+++ b/car/app/app-automotive/build.gradle
@@ -31,7 +31,7 @@
dependencies {
api(project(":car:app:app"))
api(libs.guavaListenableFuture)
- api("androidx.annotation:annotation-experimental:1.4.0")
+ api("androidx.annotation:annotation-experimental:1.4.1")
implementation(libs.guavaAndroid)
implementation("androidx.concurrent:concurrent-futures:1.1.0")
implementation("androidx.fragment:fragment:1.3.0")
diff --git a/car/app/app-testing/build.gradle b/car/app/app-testing/build.gradle
index cb47a3a..9220169 100644
--- a/car/app/app-testing/build.gradle
+++ b/car/app/app-testing/build.gradle
@@ -34,7 +34,7 @@
implementation "androidx.lifecycle:lifecycle-common-java8:2.2.0"
implementation 'androidx.annotation:annotation:1.1.0'
implementation(libs.robolectric)
- implementation("androidx.annotation:annotation-experimental:1.4.0")
+ implementation("androidx.annotation:annotation-experimental:1.4.1")
testImplementation(project(":car:app:app-projected"))
testImplementation(libs.junit)
diff --git a/car/app/app/build.gradle b/car/app/app/build.gradle
index 972e81b..1c26d21 100644
--- a/car/app/app/build.gradle
+++ b/car/app/app/build.gradle
@@ -62,7 +62,7 @@
implementation ("androidx.media:media:1.6.0")
// Session and Screen both implement LifeCycleOwner so this needs to be exposed.
api("androidx.lifecycle:lifecycle-common-java8:2.2.0")
- api("androidx.annotation:annotation-experimental:1.4.0")
+ api("androidx.annotation:annotation-experimental:1.4.1")
api(libs.kotlinStdlib)
implementation(libs.kotlinStdlibCommon)
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/ScatterMap.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/ScatterMap.kt
index 023e31a..b431f18 100644
--- a/collection/collection/src/commonMain/kotlin/androidx/collection/ScatterMap.kt
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/ScatterMap.kt
@@ -1096,7 +1096,7 @@
*/
private fun adjustStorage() {
if (_capacity > GroupWidth && _size.toULong() * 32UL <= _capacity.toULong() * 25UL) {
- resizeStorage(_capacity)
+ removeDeletedMarkers()
} else {
resizeStorage(nextCapacity(_capacity))
}
diff --git a/compose/animation/animation-core/api/current.txt b/compose/animation/animation-core/api/current.txt
index 985b8c6..350a33b 100644
--- a/compose/animation/animation-core/api/current.txt
+++ b/compose/animation/animation-core/api/current.txt
@@ -125,8 +125,8 @@
method @Deprecated @androidx.compose.runtime.Stable public static <T> androidx.compose.animation.core.InfiniteRepeatableSpec<T> infiniteRepeatable(androidx.compose.animation.core.DurationBasedAnimationSpec<T> animation, optional androidx.compose.animation.core.RepeatMode repeatMode);
method @androidx.compose.runtime.Stable public static <T> androidx.compose.animation.core.InfiniteRepeatableSpec<T> infiniteRepeatable(androidx.compose.animation.core.DurationBasedAnimationSpec<T> animation, optional androidx.compose.animation.core.RepeatMode repeatMode, optional long initialStartOffset);
method @androidx.compose.runtime.Stable public static <T> androidx.compose.animation.core.KeyframesSpec<T> keyframes(kotlin.jvm.functions.Function1<? super androidx.compose.animation.core.KeyframesSpec.KeyframesSpecConfig<T>,kotlin.Unit> init);
- method @SuppressCompatibility @androidx.compose.animation.core.ExperimentalAnimationSpecApi public static <T> androidx.compose.animation.core.KeyframesWithSplineSpec<T> keyframesWithSpline(@FloatRange(from=0.0, to=1.0) float periodicBias, kotlin.jvm.functions.Function1<? super androidx.compose.animation.core.KeyframesWithSplineSpec.KeyframesWithSplineSpecConfig<T>,kotlin.Unit> init);
- method @SuppressCompatibility @androidx.compose.animation.core.ExperimentalAnimationSpecApi public static <T> androidx.compose.animation.core.KeyframesWithSplineSpec<T> keyframesWithSpline(kotlin.jvm.functions.Function1<? super androidx.compose.animation.core.KeyframesWithSplineSpec.KeyframesWithSplineSpecConfig<T>,kotlin.Unit> init);
+ method public static <T> androidx.compose.animation.core.KeyframesWithSplineSpec<T> keyframesWithSpline(@FloatRange(from=0.0, to=1.0) float periodicBias, kotlin.jvm.functions.Function1<? super androidx.compose.animation.core.KeyframesWithSplineSpec.KeyframesWithSplineSpecConfig<T>,kotlin.Unit> init);
+ method public static <T> androidx.compose.animation.core.KeyframesWithSplineSpec<T> keyframesWithSpline(kotlin.jvm.functions.Function1<? super androidx.compose.animation.core.KeyframesWithSplineSpec.KeyframesWithSplineSpecConfig<T>,kotlin.Unit> init);
method @Deprecated @androidx.compose.runtime.Stable public static <T> androidx.compose.animation.core.RepeatableSpec<T> repeatable(int iterations, androidx.compose.animation.core.DurationBasedAnimationSpec<T> animation, optional androidx.compose.animation.core.RepeatMode repeatMode);
method @androidx.compose.runtime.Stable public static <T> androidx.compose.animation.core.RepeatableSpec<T> repeatable(int iterations, androidx.compose.animation.core.DurationBasedAnimationSpec<T> animation, optional androidx.compose.animation.core.RepeatMode repeatMode, optional long initialStartOffset);
method @androidx.compose.runtime.Stable public static <T> androidx.compose.animation.core.SnapSpec<T> snap(optional int delayMillis);
@@ -220,7 +220,7 @@
property public final int mode;
}
- @SuppressCompatibility @androidx.compose.animation.core.ExperimentalAnimationSpecApi @kotlin.jvm.JvmInline public final value class ArcMode {
+ @kotlin.jvm.JvmInline public final value class ArcMode {
field public static final androidx.compose.animation.core.ArcMode.Companion Companion;
}
@@ -496,7 +496,7 @@
ctor public KeyframesSpec.KeyframesSpecConfig();
method public infix androidx.compose.animation.core.KeyframesSpec.KeyframeEntity<T> at(T, @IntRange(from=0L) int timeStamp);
method public infix androidx.compose.animation.core.KeyframesSpec.KeyframeEntity<T> atFraction(T, @FloatRange(from=0.0, to=1.0) float fraction);
- method @SuppressCompatibility @androidx.compose.animation.core.ExperimentalAnimationSpecApi public infix androidx.compose.animation.core.KeyframesSpec.KeyframeEntity<T> using(androidx.compose.animation.core.KeyframesSpec.KeyframeEntity<T>, int arcMode);
+ method public infix androidx.compose.animation.core.KeyframesSpec.KeyframeEntity<T> using(androidx.compose.animation.core.KeyframesSpec.KeyframeEntity<T>, int arcMode);
method @Deprecated public infix void with(androidx.compose.animation.core.KeyframesSpec.KeyframeEntity<T>, androidx.compose.animation.core.Easing easing);
}
@@ -512,7 +512,7 @@
property @IntRange(from=0L) public final int durationMillis;
}
- @SuppressCompatibility @androidx.compose.animation.core.ExperimentalAnimationSpecApi @androidx.compose.runtime.Immutable public final class KeyframesWithSplineSpec<T> implements androidx.compose.animation.core.DurationBasedAnimationSpec<T> {
+ @androidx.compose.runtime.Immutable public final class KeyframesWithSplineSpec<T> implements androidx.compose.animation.core.DurationBasedAnimationSpec<T> {
ctor public KeyframesWithSplineSpec(androidx.compose.animation.core.KeyframesWithSplineSpec.KeyframesWithSplineSpecConfig<T> config);
ctor public KeyframesWithSplineSpec(androidx.compose.animation.core.KeyframesWithSplineSpec.KeyframesWithSplineSpecConfig<T> config, @FloatRange(from=0.0, to=1.0) float periodicBias);
method public androidx.compose.animation.core.KeyframesWithSplineSpec.KeyframesWithSplineSpecConfig<T> getConfig();
@@ -520,7 +520,7 @@
property public final androidx.compose.animation.core.KeyframesWithSplineSpec.KeyframesWithSplineSpecConfig<T> config;
}
- @SuppressCompatibility @androidx.compose.animation.core.ExperimentalAnimationSpecApi public static final class KeyframesWithSplineSpec.KeyframesWithSplineSpecConfig<T> extends androidx.compose.animation.core.KeyframesSpecBaseConfig<T,androidx.compose.animation.core.KeyframesSpec.KeyframeEntity<T>> {
+ public static final class KeyframesWithSplineSpec.KeyframesWithSplineSpecConfig<T> extends androidx.compose.animation.core.KeyframesSpecBaseConfig<T,androidx.compose.animation.core.KeyframesSpec.KeyframeEntity<T>> {
ctor public KeyframesWithSplineSpec.KeyframesWithSplineSpecConfig();
}
diff --git a/compose/animation/animation-core/api/restricted_current.txt b/compose/animation/animation-core/api/restricted_current.txt
index 49922b8..0bf428d 100644
--- a/compose/animation/animation-core/api/restricted_current.txt
+++ b/compose/animation/animation-core/api/restricted_current.txt
@@ -125,8 +125,8 @@
method @Deprecated @androidx.compose.runtime.Stable public static <T> androidx.compose.animation.core.InfiniteRepeatableSpec<T> infiniteRepeatable(androidx.compose.animation.core.DurationBasedAnimationSpec<T> animation, optional androidx.compose.animation.core.RepeatMode repeatMode);
method @androidx.compose.runtime.Stable public static <T> androidx.compose.animation.core.InfiniteRepeatableSpec<T> infiniteRepeatable(androidx.compose.animation.core.DurationBasedAnimationSpec<T> animation, optional androidx.compose.animation.core.RepeatMode repeatMode, optional long initialStartOffset);
method @androidx.compose.runtime.Stable public static <T> androidx.compose.animation.core.KeyframesSpec<T> keyframes(kotlin.jvm.functions.Function1<? super androidx.compose.animation.core.KeyframesSpec.KeyframesSpecConfig<T>,kotlin.Unit> init);
- method @SuppressCompatibility @androidx.compose.animation.core.ExperimentalAnimationSpecApi public static <T> androidx.compose.animation.core.KeyframesWithSplineSpec<T> keyframesWithSpline(@FloatRange(from=0.0, to=1.0) float periodicBias, kotlin.jvm.functions.Function1<? super androidx.compose.animation.core.KeyframesWithSplineSpec.KeyframesWithSplineSpecConfig<T>,kotlin.Unit> init);
- method @SuppressCompatibility @androidx.compose.animation.core.ExperimentalAnimationSpecApi public static <T> androidx.compose.animation.core.KeyframesWithSplineSpec<T> keyframesWithSpline(kotlin.jvm.functions.Function1<? super androidx.compose.animation.core.KeyframesWithSplineSpec.KeyframesWithSplineSpecConfig<T>,kotlin.Unit> init);
+ method public static <T> androidx.compose.animation.core.KeyframesWithSplineSpec<T> keyframesWithSpline(@FloatRange(from=0.0, to=1.0) float periodicBias, kotlin.jvm.functions.Function1<? super androidx.compose.animation.core.KeyframesWithSplineSpec.KeyframesWithSplineSpecConfig<T>,kotlin.Unit> init);
+ method public static <T> androidx.compose.animation.core.KeyframesWithSplineSpec<T> keyframesWithSpline(kotlin.jvm.functions.Function1<? super androidx.compose.animation.core.KeyframesWithSplineSpec.KeyframesWithSplineSpecConfig<T>,kotlin.Unit> init);
method @Deprecated @androidx.compose.runtime.Stable public static <T> androidx.compose.animation.core.RepeatableSpec<T> repeatable(int iterations, androidx.compose.animation.core.DurationBasedAnimationSpec<T> animation, optional androidx.compose.animation.core.RepeatMode repeatMode);
method @androidx.compose.runtime.Stable public static <T> androidx.compose.animation.core.RepeatableSpec<T> repeatable(int iterations, androidx.compose.animation.core.DurationBasedAnimationSpec<T> animation, optional androidx.compose.animation.core.RepeatMode repeatMode, optional long initialStartOffset);
method @androidx.compose.runtime.Stable public static <T> androidx.compose.animation.core.SnapSpec<T> snap(optional int delayMillis);
@@ -220,7 +220,7 @@
property public final int mode;
}
- @SuppressCompatibility @androidx.compose.animation.core.ExperimentalAnimationSpecApi @kotlin.jvm.JvmInline public final value class ArcMode {
+ @kotlin.jvm.JvmInline public final value class ArcMode {
field public static final androidx.compose.animation.core.ArcMode.Companion Companion;
}
@@ -496,7 +496,7 @@
ctor public KeyframesSpec.KeyframesSpecConfig();
method public infix androidx.compose.animation.core.KeyframesSpec.KeyframeEntity<T> at(T, @IntRange(from=0L) int timeStamp);
method public infix androidx.compose.animation.core.KeyframesSpec.KeyframeEntity<T> atFraction(T, @FloatRange(from=0.0, to=1.0) float fraction);
- method @SuppressCompatibility @androidx.compose.animation.core.ExperimentalAnimationSpecApi public infix androidx.compose.animation.core.KeyframesSpec.KeyframeEntity<T> using(androidx.compose.animation.core.KeyframesSpec.KeyframeEntity<T>, int arcMode);
+ method public infix androidx.compose.animation.core.KeyframesSpec.KeyframeEntity<T> using(androidx.compose.animation.core.KeyframesSpec.KeyframeEntity<T>, int arcMode);
method @Deprecated public infix void with(androidx.compose.animation.core.KeyframesSpec.KeyframeEntity<T>, androidx.compose.animation.core.Easing easing);
}
@@ -512,7 +512,7 @@
property @IntRange(from=0L) public final int durationMillis;
}
- @SuppressCompatibility @androidx.compose.animation.core.ExperimentalAnimationSpecApi @androidx.compose.runtime.Immutable public final class KeyframesWithSplineSpec<T> implements androidx.compose.animation.core.DurationBasedAnimationSpec<T> {
+ @androidx.compose.runtime.Immutable public final class KeyframesWithSplineSpec<T> implements androidx.compose.animation.core.DurationBasedAnimationSpec<T> {
ctor public KeyframesWithSplineSpec(androidx.compose.animation.core.KeyframesWithSplineSpec.KeyframesWithSplineSpecConfig<T> config);
ctor public KeyframesWithSplineSpec(androidx.compose.animation.core.KeyframesWithSplineSpec.KeyframesWithSplineSpecConfig<T> config, @FloatRange(from=0.0, to=1.0) float periodicBias);
method public androidx.compose.animation.core.KeyframesWithSplineSpec.KeyframesWithSplineSpecConfig<T> getConfig();
@@ -520,7 +520,7 @@
property public final androidx.compose.animation.core.KeyframesWithSplineSpec.KeyframesWithSplineSpecConfig<T> config;
}
- @SuppressCompatibility @androidx.compose.animation.core.ExperimentalAnimationSpecApi public static final class KeyframesWithSplineSpec.KeyframesWithSplineSpecConfig<T> extends androidx.compose.animation.core.KeyframesSpecBaseConfig<T,androidx.compose.animation.core.KeyframesSpec.KeyframeEntity<T>> {
+ public static final class KeyframesWithSplineSpec.KeyframesWithSplineSpecConfig<T> extends androidx.compose.animation.core.KeyframesSpecBaseConfig<T,androidx.compose.animation.core.KeyframesSpec.KeyframeEntity<T>> {
ctor public KeyframesWithSplineSpec.KeyframesWithSplineSpecConfig();
}
diff --git a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/AnimationSpec.kt b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/AnimationSpec.kt
index 4a4b87e..61e8977 100644
--- a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/AnimationSpec.kt
+++ b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/AnimationSpec.kt
@@ -620,14 +620,12 @@
* E.g.: [RectToVector] assigns its values as `[left, top, right, bottom]` so the pairs of
* dimensions animated as arcs are: `[left, top]` and `[right, bottom]`.
*/
- @ExperimentalAnimationSpecApi
infix fun KeyframeEntity<T>.using(arcMode: ArcMode): KeyframeEntity<T> {
this.arcMode = arcMode
return this
}
}
- @OptIn(ExperimentalAnimationSpecApi::class)
override fun <V : AnimationVector> vectorize(
converter: TwoWayConverter<T, V>
): VectorizedKeyframesSpec<V> {
@@ -664,7 +662,6 @@
}
/** Holder class for building a keyframes animation. */
- @OptIn(ExperimentalAnimationSpecApi::class)
class KeyframeEntity<T>
internal constructor(
value: T,
@@ -712,7 +709,6 @@
* @sample androidx.compose.animation.core.samples.KeyframesBuilderForDpOffsetWithSplines
* @see keyframesWithSpline
*/
-@ExperimentalAnimationSpecApi
@Immutable
class KeyframesWithSplineSpec<T>(
val config: KeyframesWithSplineSpecConfig<T>,
@@ -735,7 +731,6 @@
this.periodicBias = periodicBias
}
- @ExperimentalAnimationSpecApi
class KeyframesWithSplineSpecConfig<T> :
KeyframesSpecBaseConfig<T, KeyframesSpec.KeyframeEntity<T>>() {
@@ -822,7 +817,13 @@
}
/**
- * Creates a [KeyframesWithSplineSpec] animation, initialized with [init]. For example:
+ * Creates a [KeyframesWithSplineSpec] animation, initialized with [init].
+ *
+ * For more details on implementation, see [KeyframesWithSplineSpec].
+ *
+ * Use overload that takes a [Float] parameter to use periodic splines.
+ *
+ * Example:
*
* @sample androidx.compose.animation.core.samples.KeyframesBuilderForOffsetWithSplines
* @param init Initialization function for the [KeyframesWithSplineSpec] animation
@@ -830,7 +831,6 @@
* @sample androidx.compose.animation.core.samples.KeyframesBuilderForDpOffsetWithSplines
* @see KeyframesWithSplineSpec.KeyframesWithSplineSpecConfig
*/
-@ExperimentalAnimationSpecApi
fun <T> keyframesWithSpline(
init: KeyframesWithSplineSpec.KeyframesWithSplineSpecConfig<T>.() -> Unit
): KeyframesWithSplineSpec<T> =
@@ -859,7 +859,6 @@
* @param init Initialization function for the [KeyframesWithSplineSpec] animation
* @see KeyframesWithSplineSpec.KeyframesWithSplineSpecConfig
*/
-@ExperimentalAnimationSpecApi
fun <T> keyframesWithSpline(
@FloatRange(0.0, 1.0) periodicBias: Float,
init: KeyframesWithSplineSpec.KeyframesWithSplineSpecConfig<T>.() -> Unit
diff --git a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/ArcSpline.kt b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/ArcSpline.kt
index bc934c3..a816ef1 100644
--- a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/ArcSpline.kt
+++ b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/ArcSpline.kt
@@ -29,7 +29,6 @@
* @param y Array of values (of size n), where each value is spread on a [FloatArray] for each of
* its dimensions, expected to be of even size since two values are needed to interpolate arcs.
*/
-@ExperimentalAnimationSpecApi
internal class ArcSpline(arcModes: IntArray, timePoints: FloatArray, y: Array<FloatArray>) {
private val arcs: Array<Array<Arc>>
private val isExtrapolate = true
diff --git a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/MonoSpline.kt b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/MonoSpline.kt
index 00d0577..508d41d 100644
--- a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/MonoSpline.kt
+++ b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/MonoSpline.kt
@@ -22,7 +22,6 @@
* This performs a spline interpolation in multiple dimensions time is an array of all positions and
* y is a list of arrays each with the values at each point
*/
-@ExperimentalAnimationSpecApi
internal class MonoSpline(time: FloatArray, y: Array<FloatArray>, periodicBias: Float) {
private val timePoints: FloatArray
private val values: Array<FloatArray>
diff --git a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/VectorizedAnimationSpec.kt b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/VectorizedAnimationSpec.kt
index f937784..266e8dd 100644
--- a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/VectorizedAnimationSpec.kt
+++ b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/VectorizedAnimationSpec.kt
@@ -210,7 +210,6 @@
*
* @see [KeyframesSpec]
*/
-@OptIn(ExperimentalAnimationSpecApi::class)
class VectorizedKeyframesSpec<V : AnimationVector>
internal constructor(
// List of all timestamps. Must include start (time = 0), end (time = durationMillis) and all
@@ -519,7 +518,6 @@
* @see ArcLinear
* @see ArcAnimationSpec
*/
-@ExperimentalAnimationSpecApi
@JvmInline
value class ArcMode internal constructor(internal val value: Int) {
diff --git a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/VectorizedMonoSplineKeyframesSpec.kt b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/VectorizedMonoSplineKeyframesSpec.kt
index 89d9ab6..ddc9938 100644
--- a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/VectorizedMonoSplineKeyframesSpec.kt
+++ b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/VectorizedMonoSplineKeyframesSpec.kt
@@ -20,7 +20,6 @@
import androidx.collection.IntObjectMap
/** Implementation of [VectorizedMonoSplineKeyframesSpec] using [MonoSpline]. */
-@ExperimentalAnimationSpecApi
internal class VectorizedMonoSplineKeyframesSpec<V : AnimationVector>(
private val timestamps: IntList,
private val keyframes: IntObjectMap<Pair<V, Easing>>,
diff --git a/compose/animation/animation-graphics/build.gradle b/compose/animation/animation-graphics/build.gradle
index f3a0352c..c33dea6 100644
--- a/compose/animation/animation-graphics/build.gradle
+++ b/compose/animation/animation-graphics/build.gradle
@@ -53,7 +53,7 @@
}
androidMain.dependencies {
api("androidx.annotation:annotation:1.1.0")
- api("androidx.annotation:annotation-experimental:1.4.0")
+ api("androidx.annotation:annotation-experimental:1.4.1")
implementation("androidx.core:core-ktx:1.5.0")
}
diff --git a/compose/animation/animation/build.gradle b/compose/animation/animation/build.gradle
index 6dd8e8b5..5294d9d 100644
--- a/compose/animation/animation/build.gradle
+++ b/compose/animation/animation/build.gradle
@@ -71,7 +71,7 @@
dependsOn(jvmMain)
dependencies {
api("androidx.annotation:annotation:1.1.0")
- api("androidx.annotation:annotation-experimental:1.4.0")
+ api("androidx.annotation:annotation-experimental:1.4.1")
}
}
diff --git a/compose/foundation/foundation-layout/build.gradle b/compose/foundation/foundation-layout/build.gradle
index 5019aed..33c700e 100644
--- a/compose/foundation/foundation-layout/build.gradle
+++ b/compose/foundation/foundation-layout/build.gradle
@@ -63,7 +63,7 @@
dependsOn(jvmMain)
dependencies {
api("androidx.annotation:annotation:1.1.0")
- api("androidx.annotation:annotation-experimental:1.4.0")
+ api("androidx.annotation:annotation-experimental:1.4.1")
implementation("androidx.core:core:1.7.0")
implementation("androidx.compose.animation:animation-core:1.2.1")
}
diff --git a/compose/foundation/foundation/api/current.txt b/compose/foundation/foundation/api/current.txt
index 425b837..1bd3eb3 100644
--- a/compose/foundation/foundation/api/current.txt
+++ b/compose/foundation/foundation/api/current.txt
@@ -825,6 +825,10 @@
method public androidx.compose.ui.Modifier fillParentMaxWidth(androidx.compose.ui.Modifier, optional @FloatRange(from=0.0, to=1.0) float fraction);
}
+ public final class LazyListAnimateScrollScopeKt {
+ method public static androidx.compose.foundation.lazy.layout.LazyLayoutAnimateScrollScope LazyLayoutAnimateScrollScope(androidx.compose.foundation.lazy.LazyListState state);
+ }
+
public interface LazyListItemInfo {
method public default Object? getContentType();
method public int getIndex();
@@ -951,6 +955,10 @@
property public final int currentLineSpan;
}
+ public final class LazyGridAnimateScrollScopeKt {
+ method public static androidx.compose.foundation.lazy.layout.LazyLayoutAnimateScrollScope LazyLayoutAnimateScrollScope(androidx.compose.foundation.lazy.grid.LazyGridState state);
+ }
+
public final class LazyGridDslKt {
method @androidx.compose.runtime.Composable public static void LazyHorizontalGrid(androidx.compose.foundation.lazy.grid.GridCells rows, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.lazy.grid.LazyGridState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional boolean reverseLayout, optional androidx.compose.foundation.layout.Arrangement.Horizontal horizontalArrangement, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.foundation.gestures.FlingBehavior flingBehavior, optional boolean userScrollEnabled, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.lazy.grid.LazyGridScope,kotlin.Unit> content);
method @androidx.compose.runtime.Composable public static void LazyVerticalGrid(androidx.compose.foundation.lazy.grid.GridCells columns, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.lazy.grid.LazyGridState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional boolean reverseLayout, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.foundation.layout.Arrangement.Horizontal horizontalArrangement, optional androidx.compose.foundation.gestures.FlingBehavior flingBehavior, optional boolean userScrollEnabled, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.lazy.grid.LazyGridScope,kotlin.Unit> content);
@@ -1106,6 +1114,19 @@
property public final T value;
}
+ public interface LazyLayoutAnimateScrollScope {
+ method public int calculateDistanceTo(int targetIndex, optional int targetOffset);
+ method public int getFirstVisibleItemIndex();
+ method public int getFirstVisibleItemScrollOffset();
+ method public int getItemCount();
+ method public int getLastVisibleItemIndex();
+ method public void snapToItem(androidx.compose.foundation.gestures.ScrollScope, int index, optional int offset);
+ property public abstract int firstVisibleItemIndex;
+ property public abstract int firstVisibleItemScrollOffset;
+ property public abstract int itemCount;
+ property public abstract int lastVisibleItemIndex;
+ }
+
public abstract class LazyLayoutIntervalContent<Interval extends androidx.compose.foundation.lazy.layout.LazyLayoutIntervalContent.Interval> {
ctor public LazyLayoutIntervalContent();
method public final Object? getContentType(int index);
@@ -1209,6 +1230,10 @@
package androidx.compose.foundation.lazy.staggeredgrid {
+ public final class LazyStaggeredGridAnimateScrollScopeKt {
+ method public static androidx.compose.foundation.lazy.layout.LazyLayoutAnimateScrollScope LazyLayoutAnimateScrollScope(androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridState state);
+ }
+
public final class LazyStaggeredGridDslKt {
method @androidx.compose.runtime.Composable public static void LazyHorizontalStaggeredGrid(androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells rows, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional boolean reverseLayout, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional float horizontalItemSpacing, optional androidx.compose.foundation.gestures.FlingBehavior flingBehavior, optional boolean userScrollEnabled, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridScope,kotlin.Unit> content);
method @androidx.compose.runtime.Composable public static void LazyVerticalStaggeredGrid(androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells columns, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional boolean reverseLayout, optional float verticalItemSpacing, optional androidx.compose.foundation.layout.Arrangement.Horizontal horizontalArrangement, optional androidx.compose.foundation.gestures.FlingBehavior flingBehavior, optional boolean userScrollEnabled, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridScope,kotlin.Unit> content);
@@ -1394,6 +1419,10 @@
property public abstract java.util.List<androidx.compose.foundation.pager.PageInfo> visiblePagesInfo;
}
+ public final class PagerLazyAnimateScrollScopeKt {
+ method public static androidx.compose.foundation.lazy.layout.LazyLayoutAnimateScrollScope LazyLayoutAnimateScrollScope(androidx.compose.foundation.pager.PagerState state);
+ }
+
public sealed interface PagerScope {
}
diff --git a/compose/foundation/foundation/api/restricted_current.txt b/compose/foundation/foundation/api/restricted_current.txt
index 82f15b1..52eacab 100644
--- a/compose/foundation/foundation/api/restricted_current.txt
+++ b/compose/foundation/foundation/api/restricted_current.txt
@@ -827,6 +827,10 @@
method public androidx.compose.ui.Modifier fillParentMaxWidth(androidx.compose.ui.Modifier, optional @FloatRange(from=0.0, to=1.0) float fraction);
}
+ public final class LazyListAnimateScrollScopeKt {
+ method public static androidx.compose.foundation.lazy.layout.LazyLayoutAnimateScrollScope LazyLayoutAnimateScrollScope(androidx.compose.foundation.lazy.LazyListState state);
+ }
+
public interface LazyListItemInfo {
method public default Object? getContentType();
method public int getIndex();
@@ -953,6 +957,10 @@
property public final int currentLineSpan;
}
+ public final class LazyGridAnimateScrollScopeKt {
+ method public static androidx.compose.foundation.lazy.layout.LazyLayoutAnimateScrollScope LazyLayoutAnimateScrollScope(androidx.compose.foundation.lazy.grid.LazyGridState state);
+ }
+
public final class LazyGridDslKt {
method @androidx.compose.runtime.Composable public static void LazyHorizontalGrid(androidx.compose.foundation.lazy.grid.GridCells rows, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.lazy.grid.LazyGridState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional boolean reverseLayout, optional androidx.compose.foundation.layout.Arrangement.Horizontal horizontalArrangement, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.foundation.gestures.FlingBehavior flingBehavior, optional boolean userScrollEnabled, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.lazy.grid.LazyGridScope,kotlin.Unit> content);
method @androidx.compose.runtime.Composable public static void LazyVerticalGrid(androidx.compose.foundation.lazy.grid.GridCells columns, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.lazy.grid.LazyGridState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional boolean reverseLayout, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.foundation.layout.Arrangement.Horizontal horizontalArrangement, optional androidx.compose.foundation.gestures.FlingBehavior flingBehavior, optional boolean userScrollEnabled, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.lazy.grid.LazyGridScope,kotlin.Unit> content);
@@ -1108,6 +1116,19 @@
property public final T value;
}
+ public interface LazyLayoutAnimateScrollScope {
+ method public int calculateDistanceTo(int targetIndex, optional int targetOffset);
+ method public int getFirstVisibleItemIndex();
+ method public int getFirstVisibleItemScrollOffset();
+ method public int getItemCount();
+ method public int getLastVisibleItemIndex();
+ method public void snapToItem(androidx.compose.foundation.gestures.ScrollScope, int index, optional int offset);
+ property public abstract int firstVisibleItemIndex;
+ property public abstract int firstVisibleItemScrollOffset;
+ property public abstract int itemCount;
+ property public abstract int lastVisibleItemIndex;
+ }
+
public abstract class LazyLayoutIntervalContent<Interval extends androidx.compose.foundation.lazy.layout.LazyLayoutIntervalContent.Interval> {
ctor public LazyLayoutIntervalContent();
method public final Object? getContentType(int index);
@@ -1211,6 +1232,10 @@
package androidx.compose.foundation.lazy.staggeredgrid {
+ public final class LazyStaggeredGridAnimateScrollScopeKt {
+ method public static androidx.compose.foundation.lazy.layout.LazyLayoutAnimateScrollScope LazyLayoutAnimateScrollScope(androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridState state);
+ }
+
public final class LazyStaggeredGridDslKt {
method @androidx.compose.runtime.Composable public static void LazyHorizontalStaggeredGrid(androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells rows, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional boolean reverseLayout, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional float horizontalItemSpacing, optional androidx.compose.foundation.gestures.FlingBehavior flingBehavior, optional boolean userScrollEnabled, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridScope,kotlin.Unit> content);
method @androidx.compose.runtime.Composable public static void LazyVerticalStaggeredGrid(androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells columns, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional boolean reverseLayout, optional float verticalItemSpacing, optional androidx.compose.foundation.layout.Arrangement.Horizontal horizontalArrangement, optional androidx.compose.foundation.gestures.FlingBehavior flingBehavior, optional boolean userScrollEnabled, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridScope,kotlin.Unit> content);
@@ -1396,6 +1421,10 @@
property public abstract java.util.List<androidx.compose.foundation.pager.PageInfo> visiblePagesInfo;
}
+ public final class PagerLazyAnimateScrollScopeKt {
+ method public static androidx.compose.foundation.lazy.layout.LazyLayoutAnimateScrollScope LazyLayoutAnimateScrollScope(androidx.compose.foundation.pager.PagerState state);
+ }
+
public sealed interface PagerScope {
}
diff --git a/compose/foundation/foundation/build.gradle b/compose/foundation/foundation/build.gradle
index 2afbfde..95118fd 100644
--- a/compose/foundation/foundation/build.gradle
+++ b/compose/foundation/foundation/build.gradle
@@ -68,7 +68,7 @@
dependsOn(jvmMain)
dependencies {
api("androidx.annotation:annotation:1.1.0")
- api("androidx.annotation:annotation-experimental:1.4.0")
+ api("androidx.annotation:annotation-experimental:1.4.1")
implementation("androidx.emoji2:emoji2:1.3.0")
implementation("androidx.core:core:1.13.1")
}
diff --git a/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/LazyDslSamples.kt b/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/LazyDslSamples.kt
index 3db1162..5dc0573 100644
--- a/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/LazyDslSamples.kt
+++ b/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/LazyDslSamples.kt
@@ -17,18 +17,24 @@
package androidx.compose.foundation.samples
import androidx.annotation.Sampled
+import androidx.compose.animation.core.tween
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.LazyLayoutAnimateScrollScope
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
@@ -37,11 +43,13 @@
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
+import kotlinx.coroutines.launch
@Sampled
@Composable
@@ -142,3 +150,44 @@
}
@Composable private fun ScrollToTopButton(@Suppress("UNUSED_PARAMETER") listState: LazyListState) {}
+
+@Sampled
+@Composable
+fun CustomLazyListAnimateToItemScrollSample() {
+ val itemsList = (0..100).toList()
+ val state = rememberLazyListState()
+ val scope = rememberCoroutineScope()
+ val animatedScrollScope = remember(state) { LazyLayoutAnimateScrollScope(state) }
+
+ Column(Modifier.verticalScroll(rememberScrollState())) {
+ Button(
+ onClick = {
+ scope.launch {
+ state.scroll {
+ with(animatedScrollScope) {
+ snapToItem(40, 0) // teleport to item 40
+ val distance = calculateDistanceTo(50).toFloat()
+ var previousValue = 0f
+ androidx.compose.animation.core.animate(
+ 0f,
+ distance,
+ animationSpec = tween(5_000)
+ ) { currentValue, _ ->
+ previousValue += scrollBy(currentValue - previousValue)
+ }
+ }
+ }
+ }
+ }
+ ) {
+ Text("Scroll To Item 50")
+ }
+ LazyRow(state = state) {
+ items(itemsList) {
+ Box(Modifier.padding(2.dp).background(Color.Red).size(45.dp)) {
+ Text(it.toString())
+ }
+ }
+ }
+ }
+}
diff --git a/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/LazyGridSamples.kt b/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/LazyGridSamples.kt
index 2025fc1..d7cc99e 100644
--- a/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/LazyGridSamples.kt
+++ b/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/LazyGridSamples.kt
@@ -17,20 +17,29 @@
package androidx.compose.foundation.samples
import androidx.annotation.Sampled
+import androidx.compose.animation.core.tween
+import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.GridItemSpan
import androidx.compose.foundation.lazy.grid.LazyGridState
import androidx.compose.foundation.lazy.grid.LazyHorizontalGrid
+import androidx.compose.foundation.lazy.grid.LazyLayoutAnimateScrollScope
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items
import androidx.compose.foundation.lazy.grid.itemsIndexed
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
@@ -39,11 +48,13 @@
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
+import kotlinx.coroutines.launch
@Sampled
@Composable
@@ -191,3 +202,48 @@
}
@Composable private fun ScrollToTopButton(@Suppress("UNUSED_PARAMETER") gridState: LazyGridState) {}
+
+@Sampled
+@Composable
+fun CustomLazyGridAnimateToItemScrollSample() {
+ val itemsList = (0..100).toList()
+ val state = rememberLazyGridState()
+ val scope = rememberCoroutineScope()
+ val animatedScrollScope = remember(state) { LazyLayoutAnimateScrollScope(state) }
+
+ Column(Modifier.verticalScroll(rememberScrollState())) {
+ Button(
+ onClick = {
+ scope.launch {
+ state.scroll {
+ with(animatedScrollScope) {
+ snapToItem(40, 0) // teleport to item 40
+ val distance = calculateDistanceTo(50).toFloat()
+ var previousValue = 0f
+ androidx.compose.animation.core.animate(
+ 0f,
+ distance,
+ animationSpec = tween(5_000)
+ ) { currentValue, _ ->
+ previousValue += scrollBy(currentValue - previousValue)
+ }
+ }
+ }
+ }
+ }
+ ) {
+ Text("Scroll To Item 50")
+ }
+ LazyHorizontalGrid(
+ state = state,
+ rows = GridCells.Fixed(3),
+ modifier = Modifier.height(600.dp).fillMaxWidth()
+ ) {
+ items(itemsList) {
+ Box(Modifier.padding(2.dp).background(Color.Red).size(45.dp)) {
+ Text(it.toString())
+ }
+ }
+ }
+ }
+}
diff --git a/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/LazyStaggeredGridSamples.kt b/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/LazyStaggeredGridSamples.kt
index 5110d5c..3063491 100644
--- a/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/LazyStaggeredGridSamples.kt
+++ b/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/LazyStaggeredGridSamples.kt
@@ -17,30 +17,44 @@
package androidx.compose.foundation.samples
import androidx.annotation.Sampled
+import androidx.compose.animation.core.tween
+import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentSize
+import androidx.compose.foundation.lazy.LazyLayoutAnimateScrollScope
+import androidx.compose.foundation.lazy.grid.items
+import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.staggeredgrid.LazyHorizontalStaggeredGrid
+import androidx.compose.foundation.lazy.staggeredgrid.LazyLayoutAnimateScrollScope
import androidx.compose.foundation.lazy.staggeredgrid.LazyVerticalStaggeredGrid
import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells
import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridItemSpan
import androidx.compose.foundation.lazy.staggeredgrid.items
import androidx.compose.foundation.lazy.staggeredgrid.itemsIndexed
+import androidx.compose.foundation.lazy.staggeredgrid.rememberLazyStaggeredGridState
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
+import kotlinx.coroutines.launch
@Sampled
@Preview
@@ -146,3 +160,48 @@
}
}
}
+
+@Sampled
+@Composable
+fun CustomLazyStaggeredGridAnimateToItemScrollSample() {
+ val itemsList = (0..100).toList()
+ val state = rememberLazyStaggeredGridState()
+ val scope = rememberCoroutineScope()
+ val animatedScrollScope = remember(state) { LazyLayoutAnimateScrollScope(state) }
+
+ Column(Modifier.verticalScroll(rememberScrollState())) {
+ Button(
+ onClick = {
+ scope.launch {
+ state.scroll {
+ with(animatedScrollScope) {
+ snapToItem(40, 0) // teleport to item 40
+ val distance = calculateDistanceTo(50).toFloat()
+ var previousValue = 0f
+ androidx.compose.animation.core.animate(
+ 0f,
+ distance,
+ animationSpec = tween(5_000)
+ ) { currentValue, _ ->
+ previousValue += scrollBy(currentValue - previousValue)
+ }
+ }
+ }
+ }
+ }
+ ) {
+ Text("Scroll To Item 50")
+ }
+ LazyHorizontalStaggeredGrid(
+ state = state,
+ rows = StaggeredGridCells.Fixed(3),
+ modifier = Modifier.height(600.dp).fillMaxWidth()
+ ) {
+ items(itemsList) {
+ Box(Modifier.padding(2.dp).background(Color.Red).size(45.dp)) {
+ Text(it.toString())
+ }
+ }
+ }
+ }
+}
diff --git a/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/PagerSamples.kt b/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/PagerSamples.kt
index a5deb22..ec737e2 100644
--- a/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/PagerSamples.kt
+++ b/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/PagerSamples.kt
@@ -18,6 +18,7 @@
import androidx.annotation.Sampled
import androidx.compose.animation.core.animate
+import androidx.compose.animation.core.tween
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
@@ -28,7 +29,10 @@
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.lazy.LazyLayoutAnimateScrollScope
import androidx.compose.foundation.pager.HorizontalPager
+import androidx.compose.foundation.pager.LazyLayoutAnimateScrollScope
import androidx.compose.foundation.pager.PageSize
import androidx.compose.foundation.pager.PagerState
import androidx.compose.foundation.pager.VerticalPager
@@ -52,6 +56,7 @@
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
@@ -340,3 +345,44 @@
}
}
}
+
+@Preview
+@Sampled
+@Composable
+fun CustomPagerAnimateToPageScrollSample() {
+ val itemsList = (0..100).toList()
+ val state = rememberPagerState { itemsList.size }
+ val scope = rememberCoroutineScope()
+ val animatedScrollScope = remember(state) { LazyLayoutAnimateScrollScope(state) }
+
+ Column(Modifier.verticalScroll(rememberScrollState())) {
+ Button(
+ onClick = {
+ scope.launch {
+ state.scroll {
+ with(animatedScrollScope) {
+ snapToItem(40, 0) // teleport to item 40
+ val distance = calculateDistanceTo(50).toFloat()
+ var previousValue = 0f
+ androidx.compose.animation.core.animate(
+ 0f,
+ distance,
+ animationSpec = tween(5_000)
+ ) { currentValue, _ ->
+ previousValue += scrollBy(currentValue - previousValue)
+ }
+ }
+ }
+ }
+ }
+ ) {
+ Text("Scroll To Item 50")
+ }
+
+ HorizontalPager(state) {
+ Box(Modifier.padding(2.dp).background(Color.Red).height(600.dp).fillMaxWidth()) {
+ Text(itemsList[it].toString())
+ }
+ }
+ }
+}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/BasicTextIntrinsicWidthWrappingRegressionTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/BasicTextIntrinsicWidthWrappingRegressionTest.kt
new file mode 100644
index 0000000..b22e34f
--- /dev/null
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/BasicTextIntrinsicWidthWrappingRegressionTest.kt
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.text
+
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.text.SpanStyle
+import androidx.compose.ui.text.TextLayoutResult
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.sp
+import androidx.test.filters.MediumTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+/**
+ * Regression test for [b/346918500](https://issuetracker.google.com/346918500).
+ *
+ * This specific reproduction case would cause a visual overflow or line wrapping for start indices
+ * 5..7. We expect instead that there is no visual overflow nor line wrapping for any of these
+ * cases.
+ */
+@MediumTest
+@RunWith(Parameterized::class)
+class BasicTextIntrinsicWidthWrappingRegressionTest(spanStartIndex: Int) {
+ companion object {
+ private const val TEXT = "\u8AAD\u307F\u8FBC\u307F\u4E2D..."
+
+ @JvmStatic
+ @Parameterized.Parameters(name = "spanStartIndex={0}")
+ fun data(): Array<Array<Any?>> {
+ return Array(TEXT.length + 1) { arrayOf(it) }
+ }
+ }
+
+ @get:Rule val rule = createComposeRule()
+
+ // These values are exact for the reproduction case (along with TEXT above).
+ private val densityScale = 2.625f
+ private val fontScale = 1f
+ private val fontSize = 16.sp
+ private val lineHeight = 20.sp
+
+ // This value is not exact for the reproduction case.
+ private val textColor = Color.Black
+
+ // These values are all derived from other values.
+ private val density = Density(densityScale, fontScale)
+ private val spanTextColor = textColor.copy(alpha = 0.3f)
+ private val spanStyle = SpanStyle(color = spanTextColor)
+ private val spanStyles = listOf(AnnotatedString.Range(spanStyle, spanStartIndex, TEXT.length))
+ private val annotatedString = AnnotatedString(TEXT, spanStyles)
+ private val style = TextStyle(color = textColor, fontSize = fontSize, lineHeight = lineHeight)
+
+ private fun runTest(softWrap: Boolean = true, maxLines: Int = Int.MAX_VALUE) {
+ lateinit var textLayout: TextLayoutResult
+ rule.setContent {
+ CompositionLocalProvider(LocalDensity provides density) {
+ BasicText(
+ text = annotatedString,
+ style = style,
+ softWrap = softWrap,
+ maxLines = maxLines,
+ onTextLayout = { textLayout = it }
+ )
+ }
+ }
+ assertThat(textLayout.hasVisualOverflow).isFalse()
+ assertThat(textLayout.lineCount).isEqualTo(1)
+ }
+
+ @Test fun whenSoftWrapFalse_lineCountIsOne() = runTest(softWrap = false)
+
+ @Test fun whenMaxLinesOne_lineCountIsOne() = runTest(maxLines = 1)
+}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListAnimateScrollScope.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListAnimateScrollScope.kt
index 803ecd5..9b02c77 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListAnimateScrollScope.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListAnimateScrollScope.kt
@@ -17,11 +17,23 @@
package androidx.compose.foundation.lazy
import androidx.compose.foundation.gestures.ScrollScope
+import androidx.compose.foundation.lazy.grid.LazyLayoutAnimateScrollScope
import androidx.compose.foundation.lazy.layout.LazyLayoutAnimateScrollScope
+import androidx.compose.foundation.pager.LazyLayoutAnimateScrollScope
import androidx.compose.ui.util.fastFirstOrNull
import androidx.compose.ui.util.fastSumBy
-internal fun LazyLayoutAnimateScrollScope(state: LazyListState): LazyLayoutAnimateScrollScope {
+/**
+ * An implementation of [LazyLayoutAnimateScrollScope] that can be used with LazyLists. Please refer
+ * to the sample to learn how to use this API.
+ *
+ * @param state The [LazyListState] associated with the layout where this animated scroll should be
+ * performed.
+ * @return An implementation of [LazyLayoutAnimateScrollScope] that works with [LazyRow] and
+ * [LazyColumn].
+ * @sample androidx.compose.foundation.samples.CustomLazyListAnimateToItemScrollSample
+ */
+fun LazyLayoutAnimateScrollScope(state: LazyListState): LazyLayoutAnimateScrollScope {
return object : LazyLayoutAnimateScrollScope {
override val firstVisibleItemIndex: Int
@@ -36,8 +48,8 @@
override val itemCount: Int
get() = state.layoutInfo.totalItemsCount
- override fun ScrollScope.snapToItem(index: Int, scrollOffset: Int) {
- state.snapToItemIndexInternal(index, scrollOffset, forceRemeasure = true)
+ override fun ScrollScope.snapToItem(index: Int, offset: Int) {
+ state.snapToItemIndexInternal(index, offset, forceRemeasure = true)
}
override fun calculateDistanceTo(targetIndex: Int, targetOffset: Int): Int {
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridAnimateScrollScope.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridAnimateScrollScope.kt
index 73f286a..46353b5 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridAnimateScrollScope.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridAnimateScrollScope.kt
@@ -19,10 +19,20 @@
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.ScrollScope
import androidx.compose.foundation.lazy.layout.LazyLayoutAnimateScrollScope
+import androidx.compose.foundation.pager.LazyLayoutAnimateScrollScope
import androidx.compose.ui.util.fastFirstOrNull
import kotlin.math.max
-internal fun LazyLayoutAnimateScrollScope(state: LazyGridState): LazyLayoutAnimateScrollScope {
+/**
+ * An implementation of [LazyLayoutAnimateScrollScope] that can be used with LazyGrids.
+ *
+ * @param state The [LazyGridState] associated with the layout where this animated scroll should be
+ * performed.
+ * @return An implementation of [LazyLayoutAnimateScrollScope] that works with [LazyHorizontalGrid]
+ * and [LazyVerticalGrid].
+ * @sample androidx.compose.foundation.samples.CustomLazyGridAnimateToItemScrollSample
+ */
+fun LazyLayoutAnimateScrollScope(state: LazyGridState): LazyLayoutAnimateScrollScope {
return object : LazyLayoutAnimateScrollScope {
override val firstVisibleItemIndex: Int
get() = state.firstVisibleItemIndex
@@ -36,8 +46,8 @@
override val itemCount: Int
get() = state.layoutInfo.totalItemsCount
- override fun ScrollScope.snapToItem(index: Int, scrollOffset: Int) {
- state.snapToItemIndexInternal(index, scrollOffset, forceRemeasure = true)
+ override fun ScrollScope.snapToItem(index: Int, offset: Int) {
+ state.snapToItemIndexInternal(index, offset, forceRemeasure = true)
}
override fun calculateDistanceTo(targetIndex: Int, targetOffset: Int): Int {
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyAnimateScroll.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutAnimateScroll.kt
similarity index 89%
rename from compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyAnimateScroll.kt
rename to compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutAnimateScroll.kt
index c66d785..03125f9 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyAnimateScroll.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutAnimateScroll.kt
@@ -46,8 +46,15 @@
/**
* A scope to allow customization of animated scroll in LazyLayouts. This scope contains all needed
* information to perform an animatedScroll in a scrollable LazyLayout.
+ *
+ * For implementations for the most common layouts see:
+ *
+ * @see androidx.compose.foundation.lazy.grid.LazyLayoutAnimateScrollScope
+ * @see androidx.compose.foundation.lazy.staggeredgrid.LazyLayoutAnimateScrollScope
+ * @see androidx.compose.foundation.lazy.LazyLayoutAnimateScrollScope
+ * @see androidx.compose.foundation.pager.LazyLayoutAnimateScrollScope
*/
-internal interface LazyLayoutAnimateScrollScope {
+interface LazyLayoutAnimateScrollScope {
/** The index of the first visible item in the lazy layout. */
val firstVisibleItemIndex: Int
@@ -64,14 +71,24 @@
/** The total item count. */
val itemCount: Int
- /** Immediately scroll to [index] and settle in [scrollOffset]. */
- fun ScrollScope.snapToItem(index: Int, scrollOffset: Int)
+ /**
+ * Immediately scroll to [index] and settle in [offset].
+ *
+ * @param index The position index where we should immediately snap to.
+ * @param offset The offset where we should immediately snap to.
+ */
+ fun ScrollScope.snapToItem(index: Int, offset: Int = 0)
/**
* The "expected" distance to [targetIndex]. This means the "expected" offset of [targetIndex]
* in the layout. In a LazyLayout, non-visible items don't have an actual offset, so this method
* should return an approximation of the scroll offset to [targetIndex]. If [targetIndex] is
* visible, then an "exact" offset should be provided.
+ *
+ * @param targetIndex The index position with respect to which this calculation should be done.
+ * @param targetOffset The offset with respect to which this calculation should be done.
+ * @return The expected distance to scroll so [targetIndex] is the firstVisibleItemIndex with
+ * [targetOffset] as the firstVisibleItemScrollOffset.
*/
fun calculateDistanceTo(targetIndex: Int, targetOffset: Int = 0): Int
}
@@ -196,10 +213,7 @@
) {
// Teleport
debugLog { "Teleport forward" }
- snapToItem(
- index = index - numOfItemsForTeleport,
- scrollOffset = 0
- )
+ snapToItem(index = index - numOfItemsForTeleport, offset = 0)
}
} else {
if (
@@ -208,10 +222,7 @@
) {
// Teleport
debugLog { "Teleport backward" }
- snapToItem(
- index = index + numOfItemsForTeleport,
- scrollOffset = 0
- )
+ snapToItem(index = index + numOfItemsForTeleport, offset = 0)
}
}
}
@@ -225,7 +236,7 @@
"item $firstVisibleItemIndex at $firstVisibleItemScrollOffset," +
" target is $scrollOffset"
}
- snapToItem(index = index, scrollOffset = scrollOffset)
+ snapToItem(index = index, offset = scrollOffset)
loop = false
cancelAnimation()
return@animateTo
@@ -277,7 +288,7 @@
// rounding error (otherwise we tend to end up with the previous item scrolled the
// tiniest bit onscreen)
// TODO: prevent temporarily scrolling *past* the item
- snapToItem(index = index, scrollOffset = scrollOffset)
+ snapToItem(index = index, offset = scrollOffset)
}
}
}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridAnimateScrollScope.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridAnimateScrollScope.kt
index 036dce0..88978cc 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridAnimateScrollScope.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridAnimateScrollScope.kt
@@ -18,13 +18,22 @@
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.ScrollScope
+import androidx.compose.foundation.lazy.grid.LazyLayoutAnimateScrollScope
import androidx.compose.foundation.lazy.layout.LazyLayoutAnimateScrollScope
+import androidx.compose.foundation.pager.LazyLayoutAnimateScrollScope
import androidx.compose.ui.util.fastFirstOrNull
import androidx.compose.ui.util.fastSumBy
-internal fun LazyLayoutAnimateScrollScope(
- state: LazyStaggeredGridState
-): LazyLayoutAnimateScrollScope {
+/**
+ * An implementation of [LazyLayoutAnimateScrollScope] that can be used with LazyStaggeredGrids.
+ *
+ * @param state The [LazyStaggeredGridState] associated with the layout where this animated scroll
+ * should be performed.
+ * @return An implementation of [LazyLayoutAnimateScrollScope] that works with
+ * [LazyHorizontalStaggeredGrid] and [LazyVerticalStaggeredGrid].
+ * @sample androidx.compose.foundation.samples.CustomLazyStaggeredGridAnimateToItemScrollSample
+ */
+fun LazyLayoutAnimateScrollScope(state: LazyStaggeredGridState): LazyLayoutAnimateScrollScope {
return object : LazyLayoutAnimateScrollScope {
override val firstVisibleItemIndex: Int
@@ -39,8 +48,8 @@
override val itemCount: Int
get() = state.layoutInfo.totalItemsCount
- override fun ScrollScope.snapToItem(index: Int, scrollOffset: Int) {
- with(state) { snapToItemInternal(index, scrollOffset, forceRemeasure = true) }
+ override fun ScrollScope.snapToItem(index: Int, offset: Int) {
+ with(state) { snapToItemInternal(index, offset, forceRemeasure = true) }
}
override fun calculateDistanceTo(targetIndex: Int, targetOffset: Int): Int {
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerLazyAnimateScrollScope.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerLazyAnimateScrollScope.kt
index 2177800..695d831 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerLazyAnimateScrollScope.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerLazyAnimateScrollScope.kt
@@ -24,8 +24,14 @@
* A [LazyLayoutAnimateScrollScope] that allows customization of animated scroll in [Pager]. The
* scope contains information about the layout where animated scroll can be performed as well as the
* necessary tools to do that respecting the scroll mutation priority.
+ *
+ * @param state The [PagerState] associated with the layout where this animated scroll should be
+ * performed.
+ * @return An implementation of [LazyLayoutAnimateScrollScope] that works with [HorizontalPager] and
+ * [VerticalPager].
+ * @sample androidx.compose.foundation.samples.CustomPagerAnimateToPageScrollSample
*/
-internal fun LazyLayoutAnimateScrollScope(state: PagerState): LazyLayoutAnimateScrollScope {
+fun LazyLayoutAnimateScrollScope(state: PagerState): LazyLayoutAnimateScrollScope {
return object : LazyLayoutAnimateScrollScope {
override val firstVisibleItemIndex: Int
@@ -40,8 +46,8 @@
override val itemCount: Int
get() = state.pageCount
- override fun ScrollScope.snapToItem(index: Int, scrollOffset: Int) {
- val offsetFraction = scrollOffset / state.pageSizeWithSpacing.toFloat()
+ override fun ScrollScope.snapToItem(index: Int, offset: Int) {
+ val offsetFraction = offset / state.pageSizeWithSpacing.toFloat()
state.snapToItem(index, offsetFraction, forceRemeasure = true)
}
diff --git a/compose/material/material/build.gradle b/compose/material/material/build.gradle
index 3b446ea..a550d824 100644
--- a/compose/material/material/build.gradle
+++ b/compose/material/material/build.gradle
@@ -70,7 +70,7 @@
dependsOn(jvmMain)
dependencies {
api("androidx.annotation:annotation:1.1.0")
- api("androidx.annotation:annotation-experimental:1.4.0")
+ api("androidx.annotation:annotation-experimental:1.4.1")
// TODO: remove next 3 dependencies when b/202810604 is fixed
implementation("androidx.savedstate:savedstate:1.2.1")
diff --git a/compose/material3/adaptive/adaptive-layout/build.gradle b/compose/material3/adaptive/adaptive-layout/build.gradle
index 987896ee..bc2a2d1 100644
--- a/compose/material3/adaptive/adaptive-layout/build.gradle
+++ b/compose/material3/adaptive/adaptive-layout/build.gradle
@@ -68,7 +68,7 @@
dependsOn(jvmMain)
dependencies {
api("androidx.annotation:annotation:1.1.0")
- api("androidx.annotation:annotation-experimental:1.4.0")
+ api("androidx.annotation:annotation-experimental:1.4.1")
}
}
diff --git a/compose/material3/adaptive/adaptive-navigation/build.gradle b/compose/material3/adaptive/adaptive-navigation/build.gradle
index e6f2983..9c75325 100644
--- a/compose/material3/adaptive/adaptive-navigation/build.gradle
+++ b/compose/material3/adaptive/adaptive-navigation/build.gradle
@@ -62,7 +62,7 @@
dependsOn(jvmMain)
dependencies {
api("androidx.annotation:annotation:1.1.0")
- api("androidx.annotation:annotation-experimental:1.4.0")
+ api("androidx.annotation:annotation-experimental:1.4.1")
implementation("androidx.activity:activity-compose:1.8.2")
}
}
diff --git a/compose/material3/adaptive/adaptive/build.gradle b/compose/material3/adaptive/adaptive/build.gradle
index 2e28d64..2289202 100644
--- a/compose/material3/adaptive/adaptive/build.gradle
+++ b/compose/material3/adaptive/adaptive/build.gradle
@@ -62,7 +62,7 @@
dependsOn(jvmMain)
dependencies {
api("androidx.annotation:annotation:1.1.0")
- api("androidx.annotation:annotation-experimental:1.4.0")
+ api("androidx.annotation:annotation-experimental:1.4.1")
api("androidx.window:window:1.3.0-rc01")
}
}
diff --git a/compose/material3/material3-adaptive-navigation-suite/build.gradle b/compose/material3/material3-adaptive-navigation-suite/build.gradle
index 35ca4949..83a6e86 100644
--- a/compose/material3/material3-adaptive-navigation-suite/build.gradle
+++ b/compose/material3/material3-adaptive-navigation-suite/build.gradle
@@ -63,7 +63,7 @@
dependsOn(jvmMain)
dependencies {
api("androidx.annotation:annotation:1.1.0")
- api("androidx.annotation:annotation-experimental:1.4.0")
+ api("androidx.annotation:annotation-experimental:1.4.1")
}
}
diff --git a/compose/material3/material3-window-size-class/build.gradle b/compose/material3/material3-window-size-class/build.gradle
index 1f443e0..e415e38 100644
--- a/compose/material3/material3-window-size-class/build.gradle
+++ b/compose/material3/material3-window-size-class/build.gradle
@@ -61,7 +61,7 @@
androidMain {
dependsOn(jvmMain)
dependencies {
- api("androidx.annotation:annotation-experimental:1.4.0")
+ api("androidx.annotation:annotation-experimental:1.4.1")
implementation("androidx.window:window:1.0.0")
}
}
diff --git a/compose/material3/material3/api/current.txt b/compose/material3/material3/api/current.txt
index ba89d47..deda2bc 100644
--- a/compose/material3/material3/api/current.txt
+++ b/compose/material3/material3/api/current.txt
@@ -1645,14 +1645,20 @@
}
public final class ShapeDefaults {
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public androidx.compose.foundation.shape.CornerBasedShape getExtraExtraLarge();
method public androidx.compose.foundation.shape.CornerBasedShape getExtraLarge();
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public androidx.compose.foundation.shape.CornerBasedShape getExtraLargeIncreased();
method public androidx.compose.foundation.shape.CornerBasedShape getExtraSmall();
method public androidx.compose.foundation.shape.CornerBasedShape getLarge();
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public androidx.compose.foundation.shape.CornerBasedShape getLargeIncreased();
method public androidx.compose.foundation.shape.CornerBasedShape getMedium();
method public androidx.compose.foundation.shape.CornerBasedShape getSmall();
+ property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final androidx.compose.foundation.shape.CornerBasedShape ExtraExtraLarge;
property public final androidx.compose.foundation.shape.CornerBasedShape ExtraLarge;
+ property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final androidx.compose.foundation.shape.CornerBasedShape ExtraLargeIncreased;
property public final androidx.compose.foundation.shape.CornerBasedShape ExtraSmall;
property public final androidx.compose.foundation.shape.CornerBasedShape Large;
+ property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final androidx.compose.foundation.shape.CornerBasedShape LargeIncreased;
property public final androidx.compose.foundation.shape.CornerBasedShape Medium;
property public final androidx.compose.foundation.shape.CornerBasedShape Small;
field public static final androidx.compose.material3.ShapeDefaults INSTANCE;
@@ -1660,15 +1666,23 @@
@androidx.compose.runtime.Immutable public final class Shapes {
ctor public Shapes(optional androidx.compose.foundation.shape.CornerBasedShape extraSmall, optional androidx.compose.foundation.shape.CornerBasedShape small, optional androidx.compose.foundation.shape.CornerBasedShape medium, optional androidx.compose.foundation.shape.CornerBasedShape large, optional androidx.compose.foundation.shape.CornerBasedShape extraLarge);
+ ctor @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public Shapes(optional androidx.compose.foundation.shape.CornerBasedShape extraSmall, optional androidx.compose.foundation.shape.CornerBasedShape small, optional androidx.compose.foundation.shape.CornerBasedShape medium, optional androidx.compose.foundation.shape.CornerBasedShape large, optional androidx.compose.foundation.shape.CornerBasedShape extraLarge, optional androidx.compose.foundation.shape.CornerBasedShape largeIncreased, optional androidx.compose.foundation.shape.CornerBasedShape extraLargeIncreased, optional androidx.compose.foundation.shape.CornerBasedShape extraExtraLarge);
method public androidx.compose.material3.Shapes copy(optional androidx.compose.foundation.shape.CornerBasedShape extraSmall, optional androidx.compose.foundation.shape.CornerBasedShape small, optional androidx.compose.foundation.shape.CornerBasedShape medium, optional androidx.compose.foundation.shape.CornerBasedShape large, optional androidx.compose.foundation.shape.CornerBasedShape extraLarge);
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public androidx.compose.material3.Shapes copy(optional androidx.compose.foundation.shape.CornerBasedShape extraSmall, optional androidx.compose.foundation.shape.CornerBasedShape small, optional androidx.compose.foundation.shape.CornerBasedShape medium, optional androidx.compose.foundation.shape.CornerBasedShape large, optional androidx.compose.foundation.shape.CornerBasedShape extraLarge, optional androidx.compose.foundation.shape.CornerBasedShape largeIncreased, optional androidx.compose.foundation.shape.CornerBasedShape extraLargeIncreased, optional androidx.compose.foundation.shape.CornerBasedShape extraExtraLarge);
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public androidx.compose.foundation.shape.CornerBasedShape getExtraExtraLarge();
method public androidx.compose.foundation.shape.CornerBasedShape getExtraLarge();
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public androidx.compose.foundation.shape.CornerBasedShape getExtraLargeIncreased();
method public androidx.compose.foundation.shape.CornerBasedShape getExtraSmall();
method public androidx.compose.foundation.shape.CornerBasedShape getLarge();
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public androidx.compose.foundation.shape.CornerBasedShape getLargeIncreased();
method public androidx.compose.foundation.shape.CornerBasedShape getMedium();
method public androidx.compose.foundation.shape.CornerBasedShape getSmall();
+ property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final androidx.compose.foundation.shape.CornerBasedShape extraExtraLarge;
property public final androidx.compose.foundation.shape.CornerBasedShape extraLarge;
+ property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final androidx.compose.foundation.shape.CornerBasedShape extraLargeIncreased;
property public final androidx.compose.foundation.shape.CornerBasedShape extraSmall;
property public final androidx.compose.foundation.shape.CornerBasedShape large;
+ property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final androidx.compose.foundation.shape.CornerBasedShape largeIncreased;
property public final androidx.compose.foundation.shape.CornerBasedShape medium;
property public final androidx.compose.foundation.shape.CornerBasedShape small;
}
diff --git a/compose/material3/material3/api/restricted_current.txt b/compose/material3/material3/api/restricted_current.txt
index ba89d47..deda2bc 100644
--- a/compose/material3/material3/api/restricted_current.txt
+++ b/compose/material3/material3/api/restricted_current.txt
@@ -1645,14 +1645,20 @@
}
public final class ShapeDefaults {
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public androidx.compose.foundation.shape.CornerBasedShape getExtraExtraLarge();
method public androidx.compose.foundation.shape.CornerBasedShape getExtraLarge();
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public androidx.compose.foundation.shape.CornerBasedShape getExtraLargeIncreased();
method public androidx.compose.foundation.shape.CornerBasedShape getExtraSmall();
method public androidx.compose.foundation.shape.CornerBasedShape getLarge();
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public androidx.compose.foundation.shape.CornerBasedShape getLargeIncreased();
method public androidx.compose.foundation.shape.CornerBasedShape getMedium();
method public androidx.compose.foundation.shape.CornerBasedShape getSmall();
+ property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final androidx.compose.foundation.shape.CornerBasedShape ExtraExtraLarge;
property public final androidx.compose.foundation.shape.CornerBasedShape ExtraLarge;
+ property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final androidx.compose.foundation.shape.CornerBasedShape ExtraLargeIncreased;
property public final androidx.compose.foundation.shape.CornerBasedShape ExtraSmall;
property public final androidx.compose.foundation.shape.CornerBasedShape Large;
+ property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final androidx.compose.foundation.shape.CornerBasedShape LargeIncreased;
property public final androidx.compose.foundation.shape.CornerBasedShape Medium;
property public final androidx.compose.foundation.shape.CornerBasedShape Small;
field public static final androidx.compose.material3.ShapeDefaults INSTANCE;
@@ -1660,15 +1666,23 @@
@androidx.compose.runtime.Immutable public final class Shapes {
ctor public Shapes(optional androidx.compose.foundation.shape.CornerBasedShape extraSmall, optional androidx.compose.foundation.shape.CornerBasedShape small, optional androidx.compose.foundation.shape.CornerBasedShape medium, optional androidx.compose.foundation.shape.CornerBasedShape large, optional androidx.compose.foundation.shape.CornerBasedShape extraLarge);
+ ctor @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public Shapes(optional androidx.compose.foundation.shape.CornerBasedShape extraSmall, optional androidx.compose.foundation.shape.CornerBasedShape small, optional androidx.compose.foundation.shape.CornerBasedShape medium, optional androidx.compose.foundation.shape.CornerBasedShape large, optional androidx.compose.foundation.shape.CornerBasedShape extraLarge, optional androidx.compose.foundation.shape.CornerBasedShape largeIncreased, optional androidx.compose.foundation.shape.CornerBasedShape extraLargeIncreased, optional androidx.compose.foundation.shape.CornerBasedShape extraExtraLarge);
method public androidx.compose.material3.Shapes copy(optional androidx.compose.foundation.shape.CornerBasedShape extraSmall, optional androidx.compose.foundation.shape.CornerBasedShape small, optional androidx.compose.foundation.shape.CornerBasedShape medium, optional androidx.compose.foundation.shape.CornerBasedShape large, optional androidx.compose.foundation.shape.CornerBasedShape extraLarge);
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public androidx.compose.material3.Shapes copy(optional androidx.compose.foundation.shape.CornerBasedShape extraSmall, optional androidx.compose.foundation.shape.CornerBasedShape small, optional androidx.compose.foundation.shape.CornerBasedShape medium, optional androidx.compose.foundation.shape.CornerBasedShape large, optional androidx.compose.foundation.shape.CornerBasedShape extraLarge, optional androidx.compose.foundation.shape.CornerBasedShape largeIncreased, optional androidx.compose.foundation.shape.CornerBasedShape extraLargeIncreased, optional androidx.compose.foundation.shape.CornerBasedShape extraExtraLarge);
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public androidx.compose.foundation.shape.CornerBasedShape getExtraExtraLarge();
method public androidx.compose.foundation.shape.CornerBasedShape getExtraLarge();
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public androidx.compose.foundation.shape.CornerBasedShape getExtraLargeIncreased();
method public androidx.compose.foundation.shape.CornerBasedShape getExtraSmall();
method public androidx.compose.foundation.shape.CornerBasedShape getLarge();
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public androidx.compose.foundation.shape.CornerBasedShape getLargeIncreased();
method public androidx.compose.foundation.shape.CornerBasedShape getMedium();
method public androidx.compose.foundation.shape.CornerBasedShape getSmall();
+ property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final androidx.compose.foundation.shape.CornerBasedShape extraExtraLarge;
property public final androidx.compose.foundation.shape.CornerBasedShape extraLarge;
+ property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final androidx.compose.foundation.shape.CornerBasedShape extraLargeIncreased;
property public final androidx.compose.foundation.shape.CornerBasedShape extraSmall;
property public final androidx.compose.foundation.shape.CornerBasedShape large;
+ property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final androidx.compose.foundation.shape.CornerBasedShape largeIncreased;
property public final androidx.compose.foundation.shape.CornerBasedShape medium;
property public final androidx.compose.foundation.shape.CornerBasedShape small;
}
diff --git a/compose/material3/material3/build.gradle b/compose/material3/material3/build.gradle
index 07129e1..4b4ba3e 100644
--- a/compose/material3/material3/build.gradle
+++ b/compose/material3/material3/build.gradle
@@ -70,7 +70,7 @@
dependsOn(jvmMain)
dependencies {
api("androidx.annotation:annotation:1.1.0")
- api("androidx.annotation:annotation-experimental:1.4.0")
+ api("androidx.annotation:annotation-experimental:1.4.1")
implementation("androidx.activity:activity-compose:1.8.2")
implementation("androidx.lifecycle:lifecycle-common-java8:2.6.1")
@@ -132,4 +132,4 @@
sourceSets.androidTest.assets.srcDirs +=
project.rootDir.absolutePath + "/../../golden/compose/material3/material3"
namespace "androidx.compose.material3"
-}
\ No newline at end of file
+}
diff --git a/compose/material3/material3/integration-tests/material3-demos/src/main/java/androidx/compose/material3/demos/ShapeDemos.kt b/compose/material3/material3/integration-tests/material3-demos/src/main/java/androidx/compose/material3/demos/ShapeDemos.kt
index b34e08d..59a1689 100644
--- a/compose/material3/material3/integration-tests/material3-demos/src/main/java/androidx/compose/material3/demos/ShapeDemos.kt
+++ b/compose/material3/material3/integration-tests/material3-demos/src/main/java/androidx/compose/material3/demos/ShapeDemos.kt
@@ -23,6 +23,7 @@
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Button
+import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
@@ -32,6 +33,7 @@
import androidx.compose.ui.unit.dp
@Composable
+@OptIn(ExperimentalMaterial3ExpressiveApi::class)
fun ShapeDemo() {
val shapes = MaterialTheme.shapes
Column(
@@ -48,8 +50,14 @@
Spacer(modifier = Modifier.height(16.dp))
Button(onClick = {}, shape = shapes.large) { Text("Large") }
Spacer(modifier = Modifier.height(16.dp))
+ Button(onClick = {}, shape = shapes.largeIncreased) { Text("Large Increased") }
+ Spacer(modifier = Modifier.height(16.dp))
Button(onClick = {}, shape = shapes.extraLarge) { Text("Extra Large") }
Spacer(modifier = Modifier.height(16.dp))
+ Button(onClick = {}, shape = shapes.extraLargeIncreased) { Text("Extra Large Increased") }
+ Spacer(modifier = Modifier.height(16.dp))
+ Button(onClick = {}, shape = shapes.extraExtraLarge) { Text("Extra Extra Large") }
+ Spacer(modifier = Modifier.height(16.dp))
Button(onClick = {}, shape = CircleShape) { Text("Full") }
}
}
diff --git a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ShapesScreenshotTest.kt b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ShapesScreenshotTest.kt
index 5000da1..d8a1f0f 100644
--- a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ShapesScreenshotTest.kt
+++ b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ShapesScreenshotTest.kt
@@ -44,6 +44,7 @@
@MediumTest
@RunWith(AndroidJUnit4::class)
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+@OptIn(ExperimentalMaterial3ExpressiveApi::class)
class ShapesScreenshotTest {
@get:Rule val rule = createComposeRule()
@@ -69,8 +70,18 @@
Spacer(modifier = Modifier.height(16.dp))
Button(onClick = {}, shape = shapes.large) { Text("Large") }
Spacer(modifier = Modifier.height(16.dp))
+ Button(onClick = {}, shape = shapes.largeIncreased) { Text("Large Increased") }
+ Spacer(modifier = Modifier.height(16.dp))
Button(onClick = {}, shape = shapes.extraLarge) { Text("Extra Large") }
Spacer(modifier = Modifier.height(16.dp))
+ Button(onClick = {}, shape = shapes.extraLargeIncreased) {
+ Text("Extra Large Increased")
+ }
+ Spacer(modifier = Modifier.height(16.dp))
+ Button(onClick = {}, shape = shapes.extraExtraLarge) {
+ Text("Extra Extra Large")
+ }
+ Spacer(modifier = Modifier.height(16.dp))
Button(onClick = {}, shape = CircleShape) { Text("Full") }
}
}
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Shapes.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Shapes.kt
index cd9c254..ed14759 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Shapes.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Shapes.kt
@@ -19,6 +19,7 @@
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.CornerBasedShape
import androidx.compose.foundation.shape.CornerSize
+import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.tokens.ShapeKeyTokens
import androidx.compose.material3.tokens.ShapeTokens
import androidx.compose.runtime.Composable
@@ -40,8 +41,9 @@
* - Extra Small
* - Small
* - Medium
- * - Large
- * - Extra Large
+ * - Large, Large Increased
+ * - Extra Large, Extra Large Increased
+ * - Extra Extra Large
*
* You can customize the shape system for all components in the [MaterialTheme] or you can do it on
* a per component basis.
@@ -66,16 +68,138 @@
* use this shape.
* @param extraLarge A shape style with 4 same-sized corners whose size are bigger than
* [Shapes.large] and smaller than [CircleShape]. By default large FABs use this shape.
+ * @param largeIncreased A shape style with 4 same-sized corners whose size are bigger than
+ * [Shapes.medium] and smaller than [Shapes.extraLarge]. Slightly larger variant to
+ * [Shapes.large].
+ * @param extraLargeIncreased A shape style with 4 same-sized corners whose size are bigger than
+ * [Shapes.large] and smaller than [Shapes.extraExtraLarge]. Slightly larger variant to
+ * [Shapes.extraLarge].
+ * @param extraExtraLarge A shape style with 4 same-sized corners whose size are bigger than
+ * [Shapes.extraLarge] and smaller than [CircleShape].
*/
+// TODO: Update new shape descriptions to list what components leverage them by default.
@Immutable
-class Shapes(
+class Shapes
+@ExperimentalMaterial3ExpressiveApi
+constructor(
// Shapes None and Full are omitted as None is a RectangleShape and Full is a CircleShape.
val extraSmall: CornerBasedShape = ShapeDefaults.ExtraSmall,
val small: CornerBasedShape = ShapeDefaults.Small,
val medium: CornerBasedShape = ShapeDefaults.Medium,
val large: CornerBasedShape = ShapeDefaults.Large,
val extraLarge: CornerBasedShape = ShapeDefaults.ExtraLarge,
+ largeIncreased: CornerBasedShape = ShapeDefaults.LargeIncreased,
+ extraLargeIncreased: CornerBasedShape = ShapeDefaults.ExtraLargeIncreased,
+ extraExtraLarge: CornerBasedShape = ShapeDefaults.ExtraExtraLarge,
) {
+ @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
+ @get:ExperimentalMaterial3ExpressiveApi
+ @ExperimentalMaterial3ExpressiveApi
+ /**
+ * A shape style with 4 same-sized corners whose size are bigger than [Shapes.medium] and
+ * smaller than [Shapes.extraLarge]. Slightly larger variant to [Shapes.large].
+ */
+ val largeIncreased = largeIncreased
+
+ @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
+ @get:ExperimentalMaterial3ExpressiveApi
+ @ExperimentalMaterial3ExpressiveApi
+ /**
+ * A shape style with 4 same-sized corners whose size are bigger than [Shapes.large] and smaller
+ * than [Shapes.extraExtraLarge]. Slightly larger variant to [Shapes.extraLarge].
+ */
+ val extraLargeIncreased = extraLargeIncreased
+
+ @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
+ @get:ExperimentalMaterial3ExpressiveApi
+ @ExperimentalMaterial3ExpressiveApi
+ /**
+ * A shape style with 4 same-sized corners whose size are bigger than [Shapes.extraLarge] and
+ * smaller than [CircleShape].
+ */
+ val extraExtraLarge = extraExtraLarge
+
+ /**
+ * Material surfaces can be displayed in different shapes. Shapes direct attention, identify
+ * components, communicate state, and express brand.
+ *
+ * The shape scale defines the style of container corners, offering a range of roundedness from
+ * square to fully circular.
+ *
+ * There are different sizes of shapes:
+ * - Extra Small
+ * - Small
+ * - Medium
+ * - Large, Large Increased
+ * - Extra Large, Extra Large Increased
+ * - Extra Extra Large
+ *
+ * You can customize the shape system for all components in the [MaterialTheme] or you can do it
+ * on a per component basis.
+ *
+ * You can change the shape that a component has by overriding the shape parameter for that
+ * component. For example, by default, buttons use the shape style “full.” If your product
+ * requires a smaller amount of roundedness, you can override the shape parameter with a
+ * different shape value like [MaterialTheme.shapes.small].
+ *
+ * To learn more about shapes, see
+ * [Material Design shapes](https://m3.material.io/styles/shape/overview).
+ *
+ * @param extraSmall A shape style with 4 same-sized corners whose size are bigger than
+ * [RectangleShape] and smaller than [Shapes.small]. By default autocomplete menu, select
+ * menu, snackbars, standard menu, and text fields use this shape.
+ * @param small A shape style with 4 same-sized corners whose size are bigger than
+ * [Shapes.extraSmall] and smaller than [Shapes.medium]. By default chips use this shape.
+ * @param medium A shape style with 4 same-sized corners whose size are bigger than
+ * [Shapes.small] and smaller than [Shapes.large]. By default cards and small FABs use this
+ * shape.
+ * @param large A shape style with 4 same-sized corners whose size are bigger than
+ * [Shapes.medium] and smaller than [Shapes.extraLarge]. By default extended FABs, FABs, and
+ * navigation drawers use this shape.
+ * @param extraLarge A shape style with 4 same-sized corners whose size are bigger than
+ * [Shapes.large] and smaller than [CircleShape]. By default large FABs use this shape.
+ */
+ @OptIn(ExperimentalMaterial3ExpressiveApi::class)
+ constructor(
+ extraSmall: CornerBasedShape = ShapeDefaults.ExtraSmall,
+ small: CornerBasedShape = ShapeDefaults.Small,
+ medium: CornerBasedShape = ShapeDefaults.Medium,
+ large: CornerBasedShape = ShapeDefaults.Large,
+ extraLarge: CornerBasedShape = ShapeDefaults.ExtraLarge,
+ ) : this(
+ extraSmall = extraSmall,
+ small = small,
+ medium = medium,
+ large = large,
+ extraLarge = extraLarge,
+ largeIncreased = ShapeDefaults.LargeIncreased,
+ extraLargeIncreased = ShapeDefaults.ExtraLargeIncreased,
+ extraExtraLarge = ShapeDefaults.ExtraExtraLarge,
+ )
+
+ /** Returns a copy of this Shapes, optionally overriding some of the values. */
+ @ExperimentalMaterial3ExpressiveApi
+ fun copy(
+ extraSmall: CornerBasedShape = this.extraSmall,
+ small: CornerBasedShape = this.small,
+ medium: CornerBasedShape = this.medium,
+ large: CornerBasedShape = this.large,
+ extraLarge: CornerBasedShape = this.extraLarge,
+ largeIncreased: CornerBasedShape = this.largeIncreased,
+ extraLargeIncreased: CornerBasedShape = this.extraLargeIncreased,
+ extraExtraLarge: CornerBasedShape = this.extraExtraLarge,
+ ): Shapes =
+ Shapes(
+ extraSmall = extraSmall,
+ small = small,
+ medium = medium,
+ large = large,
+ extraLarge = extraLarge,
+ largeIncreased = largeIncreased,
+ extraLargeIncreased = extraLargeIncreased,
+ extraExtraLarge = extraExtraLarge,
+ )
+
/** Returns a copy of this Shapes, optionally overriding some of the values. */
fun copy(
extraSmall: CornerBasedShape = this.extraSmall,
@@ -92,6 +216,7 @@
extraLarge = extraLarge,
)
+ @OptIn(ExperimentalMaterial3ExpressiveApi::class)
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is Shapes) return false
@@ -100,25 +225,36 @@
if (medium != other.medium) return false
if (large != other.large) return false
if (extraLarge != other.extraLarge) return false
+ if (largeIncreased != other.largeIncreased) return false
+ if (extraLargeIncreased != other.extraLargeIncreased) return false
+ if (extraExtraLarge != other.extraExtraLarge) return false
return true
}
+ @OptIn(ExperimentalMaterial3ExpressiveApi::class)
override fun hashCode(): Int {
var result = extraSmall.hashCode()
result = 31 * result + small.hashCode()
result = 31 * result + medium.hashCode()
result = 31 * result + large.hashCode()
result = 31 * result + extraLarge.hashCode()
+ result = 31 * result + largeIncreased.hashCode()
+ result = 31 * result + extraLargeIncreased.hashCode()
+ result = 31 * result + extraExtraLarge.hashCode()
return result
}
+ @OptIn(ExperimentalMaterial3ExpressiveApi::class)
override fun toString(): String {
return "Shapes(" +
"extraSmall=$extraSmall, " +
"small=$small, " +
"medium=$medium, " +
"large=$large, " +
- "extraLarge=$extraLarge)"
+ "largeIncreased=$largeIncreased, " +
+ "extraLarge=$extraLarge, " +
+ "extralargeIncreased=$extraLargeIncreased, " +
+ "extraExtraLarge=$extraExtraLarge)"
}
}
@@ -136,38 +272,96 @@
/** Large sized corner shape */
val Large: CornerBasedShape = ShapeTokens.CornerLarge
+ @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
+ @get:ExperimentalMaterial3ExpressiveApi
+ @ExperimentalMaterial3ExpressiveApi
+ /** Large sized corner shape, slightly larger than [Large] */
+ val LargeIncreased: CornerBasedShape = RoundedCornerShape(16.dp)
+
/** Extra large sized corner shape */
val ExtraLarge: CornerBasedShape = ShapeTokens.CornerExtraLarge
+
+ @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
+ @get:ExperimentalMaterial3ExpressiveApi
+ @ExperimentalMaterial3ExpressiveApi
+ /** Extra large sized corner shape, slightly larger than [ExtraLarge] */
+ val ExtraLargeIncreased: CornerBasedShape = RoundedCornerShape(32.dp)
+
+ @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
+ @get:ExperimentalMaterial3ExpressiveApi
+ @ExperimentalMaterial3ExpressiveApi
+ /** An extra extra large (XXL) sized corner shape */
+ val ExtraExtraLarge: CornerBasedShape = RoundedCornerShape(48.dp)
+
+ /** A non-rounded corner size */
+ internal val CornerNone: CornerSize = CornerSize(0.dp)
+
+ /** An extra small rounded corner size */
+ internal val CornerExtraSmall: CornerSize = CornerSize(4.dp)
+
+ /** A small rounded corner size */
+ internal val CornerSmall: CornerSize = CornerSize(8.dp)
+
+ /** A medium rounded corner size */
+ internal val CornerMedium: CornerSize = CornerSize(12.dp)
+
+ /** A large rounded corner size */
+ internal val CornerLarge: CornerSize = CornerSize(16.dp)
+
+ /** A large rounded corner size, slightly larger than [CornerLarge] */
+ internal val CornerLargeIncreased: CornerSize = CornerSize(20.dp)
+
+ /** An extra large rounded corner size */
+ internal val CornerExtraLarge: CornerSize = CornerSize(28.dp)
+
+ /** An extra large rounded corner size, slightly larger than [CornerExtraLarge] */
+ internal val CornerExtraLargeIncreased: CornerSize = CornerSize(32.dp)
+
+ /** An extra extra large (XXL) rounded corner size */
+ internal val CornerExtraExtraLarge: CornerSize = CornerSize(48.dp)
+
+ /** A fully rounded corner size */
+ internal val CornerFull: CornerSize = CornerSize(100)
}
/** Helper function for component shape tokens. Used to grab the top values of a shape parameter. */
-internal fun CornerBasedShape.top(): CornerBasedShape {
- return copy(bottomStart = CornerSize(0.0.dp), bottomEnd = CornerSize(0.0.dp))
+internal fun CornerBasedShape.top(
+ bottomSize: CornerSize = ShapeDefaults.CornerNone
+): CornerBasedShape {
+ return copy(bottomStart = bottomSize, bottomEnd = bottomSize)
}
/**
* Helper function for component shape tokens. Used to grab the bottom values of a shape parameter.
*/
-internal fun CornerBasedShape.bottom(): CornerBasedShape {
- return copy(topStart = CornerSize(0.0.dp), topEnd = CornerSize(0.0.dp))
+internal fun CornerBasedShape.bottom(
+ topSize: CornerSize = ShapeDefaults.CornerNone
+): CornerBasedShape {
+ return copy(topStart = topSize, topEnd = topSize)
}
/**
* Helper function for component shape tokens. Used to grab the start values of a shape parameter.
*/
-internal fun CornerBasedShape.start(): CornerBasedShape {
- return copy(topEnd = CornerSize(0.0.dp), bottomEnd = CornerSize(0.0.dp))
+internal fun CornerBasedShape.start(
+ endSize: CornerSize = ShapeDefaults.CornerNone
+): CornerBasedShape {
+ return copy(topEnd = endSize, bottomEnd = endSize)
}
/** Helper function for component shape tokens. Used to grab the end values of a shape parameter. */
-internal fun CornerBasedShape.end(): CornerBasedShape {
- return copy(topStart = CornerSize(0.0.dp), bottomStart = CornerSize(0.0.dp))
+internal fun CornerBasedShape.end(
+ startSize: CornerSize = ShapeDefaults.CornerNone
+): CornerBasedShape {
+ return copy(topStart = startSize, bottomStart = startSize)
}
/**
* Helper function for component shape tokens. Here is an example on how to use component color
* tokens: ``MaterialTheme.shapes.fromToken(FabPrimarySmallTokens.ContainerShape)``
*/
+// TODO: When tokens are available, add mappings for largeIncreased, extraLargeIncreased,
+// extraExtraLarge
internal fun Shapes.fromToken(value: ShapeKeyTokens): Shape {
return when (value) {
ShapeKeyTokens.CornerExtraLarge -> extraLarge
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SplitButton.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SplitButton.kt
index feb749e..1eb3f62 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SplitButton.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SplitButton.kt
@@ -34,6 +34,7 @@
import androidx.compose.material3.SplitButtonDefaults.InnerCornerRadiusPercentage
import androidx.compose.material3.SplitButtonDefaults.LeadingButtonShape
import androidx.compose.material3.internal.ProvideContentColorTextStyle
+import androidx.compose.material3.tokens.SplitButtonSmallTokens
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.getValue
@@ -477,7 +478,7 @@
@ExperimentalMaterial3ExpressiveApi
object SplitButtonDefaults {
/** Default spacing between the `leading` and `trailing` button */
- val Spacing = 2.dp
+ val Spacing = SplitButtonSmallTokens.BetweenSpace
/**
* Default corner radius percentage for the inner corners, a.k.a. leading button `end` corners
@@ -505,7 +506,7 @@
* Default minimum height of the split button. This applies to both [LeadingButton] and
* [TrailingButton]. Applies to all 4 variants of the split button
*/
- private val MinHeight = ButtonDefaults.MinHeight
+ private val MinHeight = SplitButtonSmallTokens.ContainerHeight
/** Default minimum width of the [TrailingButton]. */
private val TrailingButtonMinWidth = LeadingButtonMinWidth
diff --git a/compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/SplitButtonSmallTokens.kt
similarity index 63%
copy from compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt
copy to compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/SplitButtonSmallTokens.kt
index 9df4b05..749a2aa 100644
--- a/compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/SplitButtonSmallTokens.kt
@@ -14,10 +14,16 @@
* limitations under the License.
*/
-package androidx.compose.ui.graphics
+// VERSION: v0_5_0
+// GENERATED CODE - DO NOT MODIFY BY HAND
-actual class NativePaint
+package androidx.compose.material3.tokens
-actual fun Paint(): Paint = implementedInJetBrainsFork()
+import androidx.compose.ui.unit.dp
-actual fun BlendMode.isSupported(): Boolean = implementedInJetBrainsFork()
+internal object SplitButtonSmallTokens {
+ val BetweenSpace = 2.0.dp
+ val ContainerHeight = 40.0.dp
+ val ContainerShape = ShapeKeyTokens.CornerFull
+ val InnerCornerShape = ShapeTokens.CornerExtraSmall
+}
diff --git a/compose/runtime/runtime/build.gradle b/compose/runtime/runtime/build.gradle
index aee37a5..9618c50 100644
--- a/compose/runtime/runtime/build.gradle
+++ b/compose/runtime/runtime/build.gradle
@@ -88,7 +88,7 @@
dependsOn(jvmMain)
dependencies {
api(libs.kotlinCoroutinesAndroid)
- api("androidx.annotation:annotation-experimental:1.4.0")
+ api("androidx.annotation:annotation-experimental:1.4.1")
}
}
diff --git a/compose/ui/ui-geometry/build.gradle b/compose/ui/ui-geometry/build.gradle
index e988544..49a573a 100644
--- a/compose/ui/ui-geometry/build.gradle
+++ b/compose/ui/ui-geometry/build.gradle
@@ -34,6 +34,7 @@
androidXMultiplatform {
android()
jvmStubs()
+ linuxX64Stubs()
defaultPlatform(PlatformIdentifier.ANDROID)
@@ -65,10 +66,17 @@
}
}
+ commonStubsMain {
+ dependsOn(commonMain)
+ }
+
jvmStubsMain {
dependsOn(jvmMain)
- dependencies {
- }
+ dependsOn(commonStubsMain)
+ }
+
+ linuxx64StubsMain {
+ dependsOn(commonStubsMain)
}
androidInstrumentedTest {
diff --git a/compose/ui/ui-graphics/build.gradle b/compose/ui/ui-graphics/build.gradle
index a8bf89b..d761f6d 100644
--- a/compose/ui/ui-graphics/build.gradle
+++ b/compose/ui/ui-graphics/build.gradle
@@ -34,6 +34,7 @@
androidXMultiplatform {
android()
jvmStubs()
+ linuxX64Stubs()
defaultPlatform(PlatformIdentifier.ANDROID)
@@ -56,28 +57,26 @@
}
}
- jvmMain {
- dependsOn(commonMain)
- dependencies {
- }
- }
-
androidMain {
- dependsOn(jvmMain)
+ dependsOn(commonMain)
dependencies {
// This has stub APIs for access to legacy Android APIs, so we don't want
// any dependency on this module.
compileOnly(project(":compose:ui:ui-android-stubs"))
implementation("androidx.graphics:graphics-path:1.0.1")
implementation libs.androidx.core
- api("androidx.annotation:annotation-experimental:1.4.0")
+ api("androidx.annotation:annotation-experimental:1.4.1")
}
}
+ commonStubsMain {
+ dependsOn(commonMain)
+ }
jvmStubsMain {
- dependsOn(jvmMain)
- dependencies {
- }
+ dependsOn(commonStubsMain)
+ }
+ linuxx64StubsMain {
+ dependsOn(commonStubsMain)
}
androidInstrumentedTest {
diff --git a/compose/ui/ui-graphics/src/jvmMain/kotlin/androidx/compose/ui/graphics/internal/JvmDefaultWithCompatibility.jvm.kt b/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/internal/JvmDefaultWithCompatibility.android.kt
similarity index 100%
rename from compose/ui/ui-graphics/src/jvmMain/kotlin/androidx/compose/ui/graphics/internal/JvmDefaultWithCompatibility.jvm.kt
rename to compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/internal/JvmDefaultWithCompatibility.android.kt
diff --git a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Bezier.kt b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Bezier.kt
index 0904ef1..dd5fd27 100644
--- a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Bezier.kt
+++ b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Bezier.kt
@@ -23,6 +23,7 @@
import androidx.compose.ui.util.fastMaxOf
import androidx.compose.ui.util.fastMinOf
import androidx.compose.ui.util.lerp
+import kotlin.math.PI
import kotlin.math.abs
import kotlin.math.acos
import kotlin.math.cos
@@ -31,7 +32,7 @@
import kotlin.math.sign
import kotlin.math.sqrt
-private const val Tau = Math.PI * 2.0
+private const val Tau = PI * 2.0
private const val Epsilon = 1e-7
// We use a fairly high epsilon here because it's post double->float conversion
// and because we use a fast approximation of cbrt(). The epsilon we use here is
diff --git a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Float16.kt b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Float16.kt
index 1c87aef..24e2318 100644
--- a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Float16.kt
+++ b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Float16.kt
@@ -16,6 +16,7 @@
package androidx.compose.ui.graphics
import androidx.compose.ui.util.floatFromBits
+import kotlin.jvm.JvmInline
/**
* The `Float16` class is a wrapper and a utility class to manipulate half-precision 16-bit
diff --git a/compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt b/compose/ui/ui-graphics/src/commonStubsMain/kotlin/androidx/compose/ui/graphics/BlendMode.commonStubs.kt
similarity index 89%
copy from compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt
copy to compose/ui/ui-graphics/src/commonStubsMain/kotlin/androidx/compose/ui/graphics/BlendMode.commonStubs.kt
index 9df4b05..2cba823 100644
--- a/compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt
+++ b/compose/ui/ui-graphics/src/commonStubsMain/kotlin/androidx/compose/ui/graphics/BlendMode.commonStubs.kt
@@ -16,8 +16,4 @@
package androidx.compose.ui.graphics
-actual class NativePaint
-
-actual fun Paint(): Paint = implementedInJetBrainsFork()
-
actual fun BlendMode.isSupported(): Boolean = implementedInJetBrainsFork()
diff --git a/compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Canvas.jvmStubs.kt b/compose/ui/ui-graphics/src/commonStubsMain/kotlin/androidx/compose/ui/graphics/Canvas.commonStubs.kt
similarity index 100%
rename from compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Canvas.jvmStubs.kt
rename to compose/ui/ui-graphics/src/commonStubsMain/kotlin/androidx/compose/ui/graphics/Canvas.commonStubs.kt
diff --git a/compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/ColorFilter.jvmStubs.kt b/compose/ui/ui-graphics/src/commonStubsMain/kotlin/androidx/compose/ui/graphics/ColorFilter.commonStubs.kt
similarity index 100%
rename from compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/ColorFilter.jvmStubs.kt
rename to compose/ui/ui-graphics/src/commonStubsMain/kotlin/androidx/compose/ui/graphics/ColorFilter.commonStubs.kt
diff --git a/compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/ImageBitmap.jvmStubs.kt b/compose/ui/ui-graphics/src/commonStubsMain/kotlin/androidx/compose/ui/graphics/ImageBitmap.commonStubs.kt
similarity index 100%
rename from compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/ImageBitmap.jvmStubs.kt
rename to compose/ui/ui-graphics/src/commonStubsMain/kotlin/androidx/compose/ui/graphics/ImageBitmap.commonStubs.kt
diff --git a/compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/NotImplemented.jvmStubs.kt b/compose/ui/ui-graphics/src/commonStubsMain/kotlin/androidx/compose/ui/graphics/NotImplemented.commonStubs.kt
similarity index 100%
rename from compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/NotImplemented.jvmStubs.kt
rename to compose/ui/ui-graphics/src/commonStubsMain/kotlin/androidx/compose/ui/graphics/NotImplemented.commonStubs.kt
diff --git a/compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt b/compose/ui/ui-graphics/src/commonStubsMain/kotlin/androidx/compose/ui/graphics/Paint.commonStubs.kt
similarity index 90%
rename from compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt
rename to compose/ui/ui-graphics/src/commonStubsMain/kotlin/androidx/compose/ui/graphics/Paint.commonStubs.kt
index 9df4b05..92ef826 100644
--- a/compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt
+++ b/compose/ui/ui-graphics/src/commonStubsMain/kotlin/androidx/compose/ui/graphics/Paint.commonStubs.kt
@@ -19,5 +19,3 @@
actual class NativePaint
actual fun Paint(): Paint = implementedInJetBrainsFork()
-
-actual fun BlendMode.isSupported(): Boolean = implementedInJetBrainsFork()
diff --git a/compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Path.jvmStubs.kt b/compose/ui/ui-graphics/src/commonStubsMain/kotlin/androidx/compose/ui/graphics/Path.commonStubs.kt
similarity index 100%
rename from compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Path.jvmStubs.kt
rename to compose/ui/ui-graphics/src/commonStubsMain/kotlin/androidx/compose/ui/graphics/Path.commonStubs.kt
diff --git a/compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/PathEffect.jvmStubs.kt b/compose/ui/ui-graphics/src/commonStubsMain/kotlin/androidx/compose/ui/graphics/PathEffect.commonStubs.kt
similarity index 100%
rename from compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/PathEffect.jvmStubs.kt
rename to compose/ui/ui-graphics/src/commonStubsMain/kotlin/androidx/compose/ui/graphics/PathEffect.commonStubs.kt
diff --git a/compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/PathIterator.jvmStubs.kt b/compose/ui/ui-graphics/src/commonStubsMain/kotlin/androidx/compose/ui/graphics/PathIterator.commonStubs.kt
similarity index 100%
rename from compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/PathIterator.jvmStubs.kt
rename to compose/ui/ui-graphics/src/commonStubsMain/kotlin/androidx/compose/ui/graphics/PathIterator.commonStubs.kt
diff --git a/compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/PathMeasure.jvmStubs.kt b/compose/ui/ui-graphics/src/commonStubsMain/kotlin/androidx/compose/ui/graphics/PathMeasure.commonStubs.kt
similarity index 100%
rename from compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/PathMeasure.jvmStubs.kt
rename to compose/ui/ui-graphics/src/commonStubsMain/kotlin/androidx/compose/ui/graphics/PathMeasure.commonStubs.kt
diff --git a/compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/RenderEffect.jvmStubs.kt b/compose/ui/ui-graphics/src/commonStubsMain/kotlin/androidx/compose/ui/graphics/RenderEffect.commonStubs.kt
similarity index 100%
rename from compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/RenderEffect.jvmStubs.kt
rename to compose/ui/ui-graphics/src/commonStubsMain/kotlin/androidx/compose/ui/graphics/RenderEffect.commonStubs.kt
diff --git a/compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Shader.jvmStubs.kt b/compose/ui/ui-graphics/src/commonStubsMain/kotlin/androidx/compose/ui/graphics/Shader.commonStubs.kt
similarity index 100%
rename from compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Shader.jvmStubs.kt
rename to compose/ui/ui-graphics/src/commonStubsMain/kotlin/androidx/compose/ui/graphics/Shader.commonStubs.kt
diff --git a/compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/TileMode.jvmStubs.kt b/compose/ui/ui-graphics/src/commonStubsMain/kotlin/androidx/compose/ui/graphics/TileMode.commonStubs.kt
similarity index 100%
rename from compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/TileMode.jvmStubs.kt
rename to compose/ui/ui-graphics/src/commonStubsMain/kotlin/androidx/compose/ui/graphics/TileMode.commonStubs.kt
diff --git a/compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt b/compose/ui/ui-graphics/src/commonStubsMain/kotlin/androidx/compose/ui/graphics/internal/JvmDefaultWithCompatibility.commonStubs.kt
similarity index 69%
copy from compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt
copy to compose/ui/ui-graphics/src/commonStubsMain/kotlin/androidx/compose/ui/graphics/internal/JvmDefaultWithCompatibility.commonStubs.kt
index 9df4b05..4ea4274 100644
--- a/compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt
+++ b/compose/ui/ui-graphics/src/commonStubsMain/kotlin/androidx/compose/ui/graphics/internal/JvmDefaultWithCompatibility.commonStubs.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2024 The Android Open Source Project
+ * 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.
@@ -14,10 +14,6 @@
* limitations under the License.
*/
-package androidx.compose.ui.graphics
+package androidx.compose.ui.graphics.internal
-actual class NativePaint
-
-actual fun Paint(): Paint = implementedInJetBrainsFork()
-
-actual fun BlendMode.isSupported(): Boolean = implementedInJetBrainsFork()
+internal actual annotation class JvmDefaultWithCompatibility
diff --git a/compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/layer/GraphicsLayer.jvmStubs.kt b/compose/ui/ui-graphics/src/commonStubsMain/kotlin/androidx/compose/ui/graphics/layer/GraphicsLayer.commonStubs.kt
similarity index 100%
rename from compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/layer/GraphicsLayer.jvmStubs.kt
rename to compose/ui/ui-graphics/src/commonStubsMain/kotlin/androidx/compose/ui/graphics/layer/GraphicsLayer.commonStubs.kt
diff --git a/compose/ui/ui-graphics/src/commonTest/kotlin/androidx/compose/ui/graphics/vector/FastFloatParserTest.kt b/compose/ui/ui-graphics/src/commonTest/kotlin/androidx/compose/ui/graphics/vector/FastFloatParserTest.kt
index f5a0efe..11d18a0 100644
--- a/compose/ui/ui-graphics/src/commonTest/kotlin/androidx/compose/ui/graphics/vector/FastFloatParserTest.kt
+++ b/compose/ui/ui-graphics/src/commonTest/kotlin/androidx/compose/ui/graphics/vector/FastFloatParserTest.kt
@@ -18,6 +18,7 @@
import kotlin.math.abs
import kotlin.test.Test
+import kotlin.test.assertTrue
class FastFloatParserTest {
@Test
@@ -30,15 +31,16 @@
val nodes = parser.parsePathString("H$number").toNodes()
- assert(nodes.isNotEmpty()) { line }
+ assertTrue(nodes.isNotEmpty(), line)
val x = (nodes[0] as PathNode.HorizontalTo).x
- assert(abs(x.toBits() - bits) < 2) {
+ assertTrue(
+ abs(x.toBits() - bits) < 2,
"Expected: 0x$bits\n" +
"Actual: 0x${x.toBits()}\n" +
" in $line (toFloat() = ${number.toFloat()})"
- }
+ )
}
}
}
diff --git a/compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/ComposeInFragmentTest.kt b/compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/ComposeInFragmentTest.kt
index e05db87..17ea237 100644
--- a/compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/ComposeInFragmentTest.kt
+++ b/compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/ComposeInFragmentTest.kt
@@ -21,13 +21,20 @@
import android.view.View
import android.view.ViewGroup
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.padding
import androidx.compose.material.Text
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.test.ExperimentalTestApi
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.runEmptyComposeUiTest
+import androidx.compose.ui.unit.dp
import androidx.fragment.app.Fragment
+import androidx.fragment.app.commit
import androidx.fragment.app.testing.launchFragmentInContainer
+import androidx.fragment.app.testing.withFragment
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
import org.junit.Test
@@ -37,13 +44,37 @@
@RunWith(AndroidJUnit4::class)
@OptIn(ExperimentalTestApi::class)
class ComposeInFragmentTest {
- @Test
- fun test() = runEmptyComposeUiTest {
- launchFragmentInContainer<CustomFragment>()
- onNodeWithText("Hello Compose").assertExists()
+ companion object {
+ const val fragment1Text = "Compose in fragment 1"
+ const val fragment2Text = "Compose in fragment 2"
}
- class CustomFragment : Fragment() {
+ @Test
+ fun test() {
+ runEmptyComposeUiTest {
+ val fragment1 = Fragment1()
+ val fragment2 = Fragment2()
+
+ // Launch fragment 1
+ val fragmentScenario = launchFragmentInContainer<Fragment1> { fragment1 }
+ onNodeWithText(fragment1Text).assertExists()
+ onNodeWithText(fragment2Text).assertDoesNotExist()
+
+ // Add fragment 2
+ fragmentScenario.withFragment {
+ parentFragmentManager.commit { add(android.R.id.content, fragment2) }
+ }
+ onNodeWithText(fragment1Text).assertExists()
+ onNodeWithText(fragment2Text).assertExists()
+
+ // Remove fragment 1
+ fragmentScenario.withFragment { parentFragmentManager.commit { remove(fragment1) } }
+ onNodeWithText(fragment1Text).assertDoesNotExist()
+ onNodeWithText(fragment2Text).assertExists()
+ }
+ }
+
+ class Fragment1 : Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
@@ -52,12 +83,28 @@
return container?.let {
ComposeView(container.context).apply {
layoutParams = ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)
+ setContent {
+ Text(fragment1Text, Modifier.background(Color.White).padding(10.dp))
+ }
}
}
}
+ }
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- (view as ComposeView).setContent { Text("Hello Compose") }
+ class Fragment2 : Fragment() {
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ return container?.let {
+ ComposeView(container.context).apply {
+ layoutParams = ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)
+ setContent {
+ Text(fragment2Text, Modifier.background(Color.White).padding(10.dp))
+ }
+ }
+ }
}
}
}
diff --git a/compose/ui/ui-text/build.gradle b/compose/ui/ui-text/build.gradle
index b843707..5c4c82a 100644
--- a/compose/ui/ui-text/build.gradle
+++ b/compose/ui/ui-text/build.gradle
@@ -34,6 +34,7 @@
androidXMultiplatform {
android()
jvmStubs()
+ linuxX64Stubs()
defaultPlatform(PlatformIdentifier.ANDROID)
@@ -48,7 +49,7 @@
// when updating the runtime version please also update the runtime-saveable version
implementation(project(":compose:runtime:runtime"))
- implementation("androidx.compose.runtime:runtime-saveable:1.6.0")
+ implementation(project(":compose:runtime:runtime-saveable"))
implementation(project(":compose:ui:ui-util"))
}
@@ -72,7 +73,7 @@
dependsOn(jvmMain)
dependencies {
api("androidx.annotation:annotation:1.1.0")
- api("androidx.annotation:annotation-experimental:1.4.0")
+ api("androidx.annotation:annotation-experimental:1.4.1")
implementation("androidx.core:core:1.7.0")
implementation("androidx.emoji2:emoji2:1.4.0")
implementation("androidx.collection:collection:1.4.0")
@@ -83,6 +84,10 @@
dependsOn(jvmMain)
}
+ linuxx64StubsMain {
+ dependsOn(commonMain)
+ }
+
androidInstrumentedTest {
dependsOn(commonTest)
dependencies {
diff --git a/compose/ui/ui-text/src/commonTest/kotlin/androidx/compose/ui/text/SaversTest.kt b/compose/ui/ui-text/src/androidUnitTest/kotlin/androidx/compose/ui/text/SaversTest.kt
similarity index 100%
rename from compose/ui/ui-text/src/commonTest/kotlin/androidx/compose/ui/text/SaversTest.kt
rename to compose/ui/ui-text/src/androidUnitTest/kotlin/androidx/compose/ui/text/SaversTest.kt
diff --git a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/AnnotatedString.kt b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/AnnotatedString.kt
index 66e075a..380f9d4 100644
--- a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/AnnotatedString.kt
+++ b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/AnnotatedString.kt
@@ -30,6 +30,7 @@
import androidx.compose.ui.util.fastMap
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.contract
+import kotlin.jvm.JvmName
/**
* The basic data structure of text with multiple styles. To construct an [AnnotatedString] you can
@@ -464,7 +465,7 @@
* copied and any other information held in the sequence, such as Android `Span`s, will be
* dropped.
*/
- @Suppress("BuilderSetStyle")
+ @Suppress("BuilderSetStyle", "PARAMETER_NAME_CHANGED_ON_OVERRIDE")
override fun append(text: CharSequence?): Builder {
if (text is AnnotatedString) {
append(text)
@@ -487,7 +488,7 @@
* @param start The index of the first character in [text] to copy over (inclusive).
* @param end The index after the last character in [text] to copy over (exclusive).
*/
- @Suppress("BuilderSetStyle")
+ @Suppress("BuilderSetStyle", "PARAMETER_NAME_CHANGED_ON_OVERRIDE")
override fun append(text: CharSequence?, start: Int, end: Int): Builder {
if (text is AnnotatedString) {
append(text, start, end)
@@ -498,6 +499,7 @@
}
// Kdoc comes from interface method.
+ @Suppress("PARAMETER_NAME_CHANGED_ON_OVERRIDE")
override fun append(char: Char): Builder {
this.text.append(char)
return this
diff --git a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/ParagraphStyle.kt b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/ParagraphStyle.kt
index b8e0abc..afeeab0 100644
--- a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/ParagraphStyle.kt
+++ b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/ParagraphStyle.kt
@@ -30,6 +30,7 @@
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.isSpecified
import androidx.compose.ui.unit.isUnspecified
+import kotlin.jvm.JvmName
private val DefaultLineHeight = TextUnit.Unspecified
diff --git a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/StringAnnotation.kt b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/StringAnnotation.kt
index 4c293b1..0ba312c 100644
--- a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/StringAnnotation.kt
+++ b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/StringAnnotation.kt
@@ -16,6 +16,8 @@
package androidx.compose.ui.text
+import kotlin.jvm.JvmInline
+
/**
* An [AnnotatedString.Annotation] class which holds a String [value].
*
diff --git a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/TextGranularity.kt b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/TextGranularity.kt
index 169a02e..9d0cc84 100644
--- a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/TextGranularity.kt
+++ b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/TextGranularity.kt
@@ -16,6 +16,8 @@
package androidx.compose.ui.text
+import kotlin.jvm.JvmInline
+
/**
* Used by [Paragraph.getRangeForRect]. It specifies the minimal unit of the text ranges that is
* considered by the [Paragraph.getRangeForRect].
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 527f81a..6461877 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
@@ -40,6 +40,7 @@
import androidx.compose.ui.text.style.TextMotion
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.TextUnit
+import kotlin.jvm.JvmName
/**
* Styling configuration for a `Text`.
diff --git a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/input/GapBuffer.kt b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/input/GapBuffer.kt
index 6b968f6..1275d48 100644
--- a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/input/GapBuffer.kt
+++ b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/input/GapBuffer.kt
@@ -186,8 +186,8 @@
* @param builder The output string builder
*/
fun append(builder: StringBuilder) {
- builder.append(buffer, 0, gapStart)
- builder.append(buffer, gapEnd, capacity - gapEnd)
+ builder.appendRange(buffer, 0, gapStart)
+ builder.appendRange(buffer, gapEnd, capacity)
}
/**
diff --git a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/style/Hyphens.kt b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/style/Hyphens.kt
index f189eea..7ef0236 100644
--- a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/style/Hyphens.kt
+++ b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/style/Hyphens.kt
@@ -16,6 +16,8 @@
package androidx.compose.ui.text.style
+import kotlin.jvm.JvmInline
+
/**
* Automatic hyphenation configuration.
*
diff --git a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/style/LineHeightStyle.kt b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/style/LineHeightStyle.kt
index 5ad11e9..4e60d4b 100644
--- a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/style/LineHeightStyle.kt
+++ b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/style/LineHeightStyle.kt
@@ -17,6 +17,7 @@
package androidx.compose.ui.text.style
import androidx.compose.ui.text.PlatformParagraphStyle
+import kotlin.jvm.JvmInline
/**
* The configuration for line height such as alignment of the line in the provided line height,
diff --git a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/style/TextForegroundStyle.kt b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/style/TextForegroundStyle.kt
index 47b3aa7..9cd6708 100644
--- a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/style/TextForegroundStyle.kt
+++ b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/style/TextForegroundStyle.kt
@@ -25,6 +25,7 @@
import androidx.compose.ui.graphics.lerp as lerpColor
import androidx.compose.ui.text.lerpDiscrete
import androidx.compose.ui.util.lerp
+import kotlin.jvm.JvmName
/**
* An internal interface to represent possible ways to draw Text e.g. color, brush. This interface
diff --git a/compose/ui/ui-text/src/jvmStubsMain/kotlin/androidx/compose/ui/text/style/LineBreak.jvmStubs.kt b/compose/ui/ui-text/src/jvmStubsMain/kotlin/androidx/compose/ui/text/style/LineBreak.jvmStubs.kt
index 719a93f..9fac85a 100644
--- a/compose/ui/ui-text/src/jvmStubsMain/kotlin/androidx/compose/ui/text/style/LineBreak.jvmStubs.kt
+++ b/compose/ui/ui-text/src/jvmStubsMain/kotlin/androidx/compose/ui/text/style/LineBreak.jvmStubs.kt
@@ -18,17 +18,18 @@
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.Stable
+import androidx.compose.ui.text.implementedInJetBrainsFork
@Immutable
@JvmInline
actual value class LineBreak private constructor(internal val mask: Int) {
actual companion object {
- @Stable actual val Simple: LineBreak = LineBreak(1)
+ @Stable actual val Simple: LineBreak = implementedInJetBrainsFork()
- @Stable actual val Heading: LineBreak = LineBreak(2)
+ @Stable actual val Heading: LineBreak = implementedInJetBrainsFork()
- @Stable actual val Paragraph: LineBreak = LineBreak(3)
+ @Stable actual val Paragraph: LineBreak = implementedInJetBrainsFork()
- @Stable actual val Unspecified: LineBreak = LineBreak(Int.MIN_VALUE)
+ @Stable actual val Unspecified: LineBreak = implementedInJetBrainsFork()
}
}
diff --git a/compose/ui/ui-text/src/jvmStubsMain/kotlin/androidx/compose/ui/text/style/TextMotion.jvmStubs.kt b/compose/ui/ui-text/src/jvmStubsMain/kotlin/androidx/compose/ui/text/style/TextMotion.jvmStubs.kt
index eb79c74..2ef3ee0 100644
--- a/compose/ui/ui-text/src/jvmStubsMain/kotlin/androidx/compose/ui/text/style/TextMotion.jvmStubs.kt
+++ b/compose/ui/ui-text/src/jvmStubsMain/kotlin/androidx/compose/ui/text/style/TextMotion.jvmStubs.kt
@@ -17,12 +17,13 @@
package androidx.compose.ui.text.style
import androidx.compose.runtime.Immutable
+import androidx.compose.ui.text.implementedInJetBrainsFork
@Immutable
actual class TextMotion private constructor() {
actual companion object {
- actual val Static: TextMotion = TextMotion()
+ actual val Static: TextMotion = implementedInJetBrainsFork()
- actual val Animated: TextMotion = TextMotion()
+ actual val Animated: TextMotion = implementedInJetBrainsFork()
}
}
diff --git a/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/ActualAtomicReferenceJvm.linuxx64Stubs.kt b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/ActualAtomicReferenceJvm.linuxx64Stubs.kt
new file mode 100644
index 0000000..be2547b
--- /dev/null
+++ b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/ActualAtomicReferenceJvm.linuxx64Stubs.kt
@@ -0,0 +1,31 @@
+/*
+ * 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.
+ */
+
+package androidx.compose.ui.text
+
+internal actual class AtomicReference<V> actual constructor(value: V) {
+ init {
+ implementedInJetBrainsFork()
+ }
+
+ actual fun get(): V = implementedInJetBrainsFork()
+
+ actual fun set(value: V): Unit = implementedInJetBrainsFork()
+
+ actual fun getAndSet(value: V): V = implementedInJetBrainsFork()
+
+ actual fun compareAndSet(expect: V, newValue: V): Boolean = implementedInJetBrainsFork()
+}
diff --git a/compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/AnnotatedString.linuxx64Stubs.kt
similarity index 69%
copy from compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt
copy to compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/AnnotatedString.linuxx64Stubs.kt
index 9df4b05..4bd2666 100644
--- a/compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt
+++ b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/AnnotatedString.linuxx64Stubs.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2024 The Android Open Source Project
+ * 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.
@@ -14,10 +14,8 @@
* limitations under the License.
*/
-package androidx.compose.ui.graphics
+package androidx.compose.ui.text
-actual class NativePaint
-
-actual fun Paint(): Paint = implementedInJetBrainsFork()
-
-actual fun BlendMode.isSupported(): Boolean = implementedInJetBrainsFork()
+internal actual fun AnnotatedString.transform(
+ transform: (String, Int, Int) -> String
+): AnnotatedString = implementedInJetBrainsFork()
diff --git a/compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/JvmCharHelpers.linuxx64Stubs.kt
similarity index 67%
copy from compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt
copy to compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/JvmCharHelpers.linuxx64Stubs.kt
index 9df4b05..97fc3e3 100644
--- a/compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt
+++ b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/JvmCharHelpers.linuxx64Stubs.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2024 The Android Open Source Project
+ * 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.
@@ -14,10 +14,8 @@
* limitations under the License.
*/
-package androidx.compose.ui.graphics
+package androidx.compose.ui.text
-actual class NativePaint
+internal actual fun String.findPrecedingBreak(index: Int): Int = implementedInJetBrainsFork()
-actual fun Paint(): Paint = implementedInJetBrainsFork()
-
-actual fun BlendMode.isSupported(): Boolean = implementedInJetBrainsFork()
+internal actual fun String.findFollowingBreak(index: Int): Int = implementedInJetBrainsFork()
diff --git a/compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/NotImplemented.linuxx64Stubs.kt
similarity index 64%
copy from compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt
copy to compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/NotImplemented.linuxx64Stubs.kt
index 9df4b05..bf9fe98 100644
--- a/compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt
+++ b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/NotImplemented.linuxx64Stubs.kt
@@ -14,10 +14,14 @@
* limitations under the License.
*/
-package androidx.compose.ui.graphics
+package androidx.compose.ui.text
-actual class NativePaint
-
-actual fun Paint(): Paint = implementedInJetBrainsFork()
-
-actual fun BlendMode.isSupported(): Boolean = implementedInJetBrainsFork()
+@Suppress("NOTHING_TO_INLINE")
+internal inline fun implementedInJetBrainsFork(): Nothing =
+ throw NotImplementedError(
+ """
+ Implemented only in JetBrains fork.
+ Please use `org.jetbrains.compose.ui:ui-text` package instead.
+ """
+ .trimIndent()
+ )
diff --git a/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/Paragraph.linuxx64Stubs.kt b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/Paragraph.linuxx64Stubs.kt
new file mode 100644
index 0000000..9781139
--- /dev/null
+++ b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/Paragraph.linuxx64Stubs.kt
@@ -0,0 +1,117 @@
+/*
+ * 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.annotation.IntRange
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.graphics.BlendMode
+import androidx.compose.ui.graphics.Brush
+import androidx.compose.ui.graphics.Canvas
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.Path
+import androidx.compose.ui.graphics.Shadow
+import androidx.compose.ui.graphics.drawscope.DrawStyle
+import androidx.compose.ui.text.internal.JvmDefaultWithCompatibility
+import androidx.compose.ui.text.style.ResolvedTextDirection
+import androidx.compose.ui.text.style.TextDecoration
+
+@JvmDefaultWithCompatibility
+actual sealed interface Paragraph {
+ actual val width: Float
+ actual val height: Float
+ actual val minIntrinsicWidth: Float
+ actual val maxIntrinsicWidth: Float
+ actual val firstBaseline: Float
+ actual val lastBaseline: Float
+ actual val didExceedMaxLines: Boolean
+ actual val lineCount: Int
+ actual val placeholderRects: List<Rect?>
+
+ actual fun getPathForRange(start: Int, end: Int): Path
+
+ actual fun getCursorRect(offset: Int): Rect
+
+ actual fun getLineLeft(lineIndex: Int): Float
+
+ actual fun getLineRight(lineIndex: Int): Float
+
+ actual fun getLineTop(lineIndex: Int): Float
+
+ actual fun getLineBaseline(lineIndex: Int): Float
+
+ actual fun getLineBottom(lineIndex: Int): Float
+
+ actual fun getLineHeight(lineIndex: Int): Float
+
+ actual fun getLineWidth(lineIndex: Int): Float
+
+ actual fun getLineStart(lineIndex: Int): Int
+
+ actual fun getLineEnd(lineIndex: Int, visibleEnd: Boolean): Int
+
+ actual fun isLineEllipsized(lineIndex: Int): Boolean
+
+ actual fun getLineForOffset(offset: Int): Int
+
+ actual fun getHorizontalPosition(offset: Int, usePrimaryDirection: Boolean): Float
+
+ actual fun getParagraphDirection(offset: Int): ResolvedTextDirection
+
+ actual fun getBidiRunDirection(offset: Int): ResolvedTextDirection
+
+ actual fun getLineForVerticalPosition(vertical: Float): Int
+
+ actual fun getOffsetForPosition(position: Offset): Int
+
+ actual fun getRangeForRect(
+ rect: Rect,
+ granularity: TextGranularity,
+ inclusionStrategy: TextInclusionStrategy
+ ): TextRange
+
+ actual fun getBoundingBox(offset: Int): Rect
+
+ actual fun fillBoundingBoxes(
+ range: TextRange,
+ array: FloatArray,
+ @IntRange(from = 0) arrayStart: Int
+ )
+
+ actual fun getWordBoundary(offset: Int): TextRange
+
+ actual fun paint(canvas: Canvas, color: Color, shadow: Shadow?, textDecoration: TextDecoration?)
+
+ actual fun paint(
+ canvas: Canvas,
+ color: Color,
+ shadow: Shadow?,
+ textDecoration: TextDecoration?,
+ drawStyle: DrawStyle?,
+ blendMode: BlendMode
+ )
+
+ actual fun paint(
+ canvas: Canvas,
+ brush: Brush,
+ alpha: Float,
+ shadow: Shadow?,
+ textDecoration: TextDecoration?,
+ drawStyle: DrawStyle?,
+ blendMode: BlendMode
+ )
+}
diff --git a/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/Savers.linuxx64Stubs.kt b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/Savers.linuxx64Stubs.kt
new file mode 100644
index 0000000..edec094
--- /dev/null
+++ b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/Savers.linuxx64Stubs.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.ui.text
+
+import androidx.compose.runtime.saveable.Saver
+import androidx.compose.ui.text.style.LineBreak
+import androidx.compose.ui.text.style.TextMotion
+
+internal actual val PlatformParagraphStyle.Companion.Saver: Saver<PlatformParagraphStyle, Any>
+ get() = PlatformParagraphStyleSaver
+
+private val PlatformParagraphStyleSaver =
+ Saver<PlatformParagraphStyle, Any>(save = {}, restore = { PlatformParagraphStyle() })
+
+internal actual val LineBreak.Companion.Saver: Saver<LineBreak, Any>
+ get() = LineBreakSaver
+
+private val LineBreakSaver =
+ Saver<LineBreak, Any>(
+ save = { it.mask },
+ restore = {
+ val mask = it as Int
+ when (mask) {
+ 1 -> LineBreak.Simple
+ 2 -> LineBreak.Heading
+ 3 -> LineBreak.Paragraph
+ else -> {
+ LineBreak.Unspecified
+ }
+ }
+ }
+ )
+
+internal actual val TextMotion.Companion.Saver: Saver<TextMotion, Any>
+ get() = TextMotionSaver
+
+private val TextMotionSaver =
+ Saver<TextMotion, Any>(
+ save = { if (it == TextMotion.Static) 0 else 1 },
+ restore = {
+ if (it == 0) {
+ TextMotion.Static
+ } else {
+ TextMotion.Animated
+ }
+ }
+ )
diff --git a/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/TextStyle.linuxx64Stubs.kt b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/TextStyle.linuxx64Stubs.kt
new file mode 100644
index 0000000..55558913
--- /dev/null
+++ b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/TextStyle.linuxx64Stubs.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
+
+internal actual fun createPlatformTextStyle(
+ spanStyle: PlatformSpanStyle?,
+ paragraphStyle: PlatformParagraphStyle?
+): PlatformTextStyle = implementedInJetBrainsFork()
+
+actual class PlatformTextStyle {
+ actual val spanStyle: PlatformSpanStyle?
+ get() = implementedInJetBrainsFork()
+
+ actual val paragraphStyle: PlatformParagraphStyle?
+ get() = implementedInJetBrainsFork()
+}
+
+actual class PlatformParagraphStyle {
+ actual companion object {
+ actual val Default: PlatformParagraphStyle = implementedInJetBrainsFork()
+ }
+
+ actual fun merge(other: PlatformParagraphStyle?): PlatformParagraphStyle =
+ implementedInJetBrainsFork()
+}
+
+actual class PlatformSpanStyle {
+ actual companion object {
+ actual val Default: PlatformSpanStyle = implementedInJetBrainsFork()
+ }
+
+ actual fun merge(other: PlatformSpanStyle?): PlatformSpanStyle = implementedInJetBrainsFork()
+}
+
+actual fun lerp(
+ start: PlatformParagraphStyle,
+ stop: PlatformParagraphStyle,
+ fraction: Float
+): PlatformParagraphStyle = implementedInJetBrainsFork()
+
+actual fun lerp(
+ start: PlatformSpanStyle,
+ stop: PlatformSpanStyle,
+ fraction: Float
+): PlatformSpanStyle = implementedInJetBrainsFork()
diff --git a/compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/font/DelegatingFontLoaderForDeprecatedUsage.linuxx64Stubs.kt
similarity index 63%
copy from compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt
copy to compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/font/DelegatingFontLoaderForDeprecatedUsage.linuxx64Stubs.kt
index 9df4b05..66bdcf0 100644
--- a/compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt
+++ b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/font/DelegatingFontLoaderForDeprecatedUsage.linuxx64Stubs.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2024 The Android Open Source Project
+ * 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.
@@ -14,10 +14,10 @@
* limitations under the License.
*/
-package androidx.compose.ui.graphics
+package androidx.compose.ui.text.font
-actual class NativePaint
+import androidx.compose.ui.text.implementedInJetBrainsFork
-actual fun Paint(): Paint = implementedInJetBrainsFork()
-
-actual fun BlendMode.isSupported(): Boolean = implementedInJetBrainsFork()
+internal actual fun createFontFamilyResolver(
+ @Suppress("DEPRECATION") fontResourceLoader: Font.ResourceLoader
+): FontFamily.Resolver = implementedInJetBrainsFork()
diff --git a/compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/font/FontSynthesis.linuxx64Stubs.kt
similarity index 62%
copy from compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt
copy to compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/font/FontSynthesis.linuxx64Stubs.kt
index 9df4b05..abd737f 100644
--- a/compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt
+++ b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/font/FontSynthesis.linuxx64Stubs.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2024 The Android Open Source Project
+ * 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.
@@ -14,10 +14,13 @@
* limitations under the License.
*/
-package androidx.compose.ui.graphics
+package androidx.compose.ui.text.font
-actual class NativePaint
+import androidx.compose.ui.text.implementedInJetBrainsFork
-actual fun Paint(): Paint = implementedInJetBrainsFork()
-
-actual fun BlendMode.isSupported(): Boolean = implementedInJetBrainsFork()
+internal actual fun FontSynthesis.synthesizeTypeface(
+ typeface: Any,
+ font: Font,
+ requestedWeight: FontWeight,
+ requestedStyle: FontStyle
+): Any = implementedInJetBrainsFork()
diff --git a/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/font/PlatformFontFamilyTypefaceAdapter.linuxx64Stubs.kt b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/font/PlatformFontFamilyTypefaceAdapter.linuxx64Stubs.kt
new file mode 100644
index 0000000..17a0131
--- /dev/null
+++ b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/font/PlatformFontFamilyTypefaceAdapter.linuxx64Stubs.kt
@@ -0,0 +1,30 @@
+/*
+ * 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.
+ */
+
+package androidx.compose.ui.text.font
+
+import androidx.compose.ui.text.implementedInJetBrainsFork
+
+internal actual class PlatformFontFamilyTypefaceAdapter actual constructor() :
+ FontFamilyTypefaceAdapter {
+
+ actual override fun resolve(
+ typefaceRequest: TypefaceRequest,
+ platformFontLoader: PlatformFontLoader,
+ onAsyncCompletion: (TypefaceResult.Immutable) -> Unit,
+ createDefaultTypeface: (TypefaceRequest) -> Any
+ ): TypefaceResult? = implementedInJetBrainsFork()
+}
diff --git a/compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/input/GapBuffer.linuxx64Stubs.kt
similarity index 63%
copy from compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt
copy to compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/input/GapBuffer.linuxx64Stubs.kt
index 9df4b05..36833b1 100644
--- a/compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt
+++ b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/input/GapBuffer.linuxx64Stubs.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2024 The Android Open Source Project
+ * 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.
@@ -14,10 +14,13 @@
* limitations under the License.
*/
-package androidx.compose.ui.graphics
+package androidx.compose.ui.text.input
-actual class NativePaint
+import androidx.compose.ui.text.implementedInJetBrainsFork
-actual fun Paint(): Paint = implementedInJetBrainsFork()
-
-actual fun BlendMode.isSupported(): Boolean = implementedInJetBrainsFork()
+internal actual fun String.toCharArray(
+ destination: CharArray,
+ destinationOffset: Int,
+ startIndex: Int,
+ endIndex: Int
+): Unit = implementedInJetBrainsFork()
diff --git a/compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/input/PlatformImeOptions.linuxx64Stubs.kt
similarity index 69%
copy from compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt
copy to compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/input/PlatformImeOptions.linuxx64Stubs.kt
index 9df4b05..2764f82 100644
--- a/compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt
+++ b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/input/PlatformImeOptions.linuxx64Stubs.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2024 The Android Open Source Project
+ * Copyright 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,10 +14,9 @@
* limitations under the License.
*/
-package androidx.compose.ui.graphics
+package androidx.compose.ui.text.input
-actual class NativePaint
+import androidx.compose.runtime.Immutable
-actual fun Paint(): Paint = implementedInJetBrainsFork()
-
-actual fun BlendMode.isSupported(): Boolean = implementedInJetBrainsFork()
+/** Used to configure the platform specific IME options. */
+@Immutable actual class PlatformImeOptions
diff --git a/compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/internal/JvmDefaultWithCompatibility.linuxx64Stubs.kt
similarity index 69%
copy from compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt
copy to compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/internal/JvmDefaultWithCompatibility.linuxx64Stubs.kt
index 9df4b05..14e41e4 100644
--- a/compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt
+++ b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/internal/JvmDefaultWithCompatibility.linuxx64Stubs.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2024 The Android Open Source Project
+ * 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.
@@ -14,10 +14,6 @@
* limitations under the License.
*/
-package androidx.compose.ui.graphics
+package androidx.compose.ui.text.internal
-actual class NativePaint
-
-actual fun Paint(): Paint = implementedInJetBrainsFork()
-
-actual fun BlendMode.isSupported(): Boolean = implementedInJetBrainsFork()
+internal actual annotation class JvmDefaultWithCompatibility
diff --git a/compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/intl/DesktopPlatformLocale.linuxx64Stubs.kt
similarity index 68%
copy from compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt
copy to compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/intl/DesktopPlatformLocale.linuxx64Stubs.kt
index 9df4b05..018c860 100644
--- a/compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt
+++ b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/intl/DesktopPlatformLocale.linuxx64Stubs.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2024 The Android Open Source Project
+ * 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.
@@ -14,10 +14,9 @@
* limitations under the License.
*/
-package androidx.compose.ui.graphics
+package androidx.compose.ui.text.intl
-actual class NativePaint
+import androidx.compose.ui.text.implementedInJetBrainsFork
-actual fun Paint(): Paint = implementedInJetBrainsFork()
-
-actual fun BlendMode.isSupported(): Boolean = implementedInJetBrainsFork()
+internal actual fun createPlatformLocaleDelegate(): PlatformLocaleDelegate =
+ implementedInJetBrainsFork()
diff --git a/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/intl/PlatformLocale.linuxx64Stubs.kt b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/intl/PlatformLocale.linuxx64Stubs.kt
new file mode 100644
index 0000000..aea1184
--- /dev/null
+++ b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/intl/PlatformLocale.linuxx64Stubs.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:Suppress("EXTENSION_SHADOWED_BY_MEMBER")
+
+package androidx.compose.ui.text.intl
+
+import androidx.compose.ui.text.implementedInJetBrainsFork
+
+actual class PlatformLocale
+
+internal actual val PlatformLocale.language: String
+ get() = implementedInJetBrainsFork()
+
+internal actual val PlatformLocale.script: String
+ get() = implementedInJetBrainsFork()
+
+internal actual val PlatformLocale.region: String
+ get() = implementedInJetBrainsFork()
+
+internal actual fun PlatformLocale.getLanguageTag(): String = implementedInJetBrainsFork()
diff --git a/compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/platform/DesktopStringDelegate.linuxx64Stubs.kt
similarity index 64%
copy from compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt
copy to compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/platform/DesktopStringDelegate.linuxx64Stubs.kt
index 9df4b05..2a9df32 100644
--- a/compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt
+++ b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/platform/DesktopStringDelegate.linuxx64Stubs.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2024 The Android Open Source Project
+ * 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.
@@ -14,10 +14,9 @@
* limitations under the License.
*/
-package androidx.compose.ui.graphics
+package androidx.compose.ui.text.platform
-actual class NativePaint
+import androidx.compose.ui.text.PlatformStringDelegate
+import androidx.compose.ui.text.implementedInJetBrainsFork
-actual fun Paint(): Paint = implementedInJetBrainsFork()
-
-actual fun BlendMode.isSupported(): Boolean = implementedInJetBrainsFork()
+internal actual fun ActualStringDelegate(): PlatformStringDelegate = implementedInJetBrainsFork()
diff --git a/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/platform/SkiaMultiParagraphDraw.linuxx64Stubs.kt b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/platform/SkiaMultiParagraphDraw.linuxx64Stubs.kt
new file mode 100644
index 0000000..8f9e6b7
--- /dev/null
+++ b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/platform/SkiaMultiParagraphDraw.linuxx64Stubs.kt
@@ -0,0 +1,36 @@
+/*
+ * 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.platform
+
+import androidx.compose.ui.graphics.BlendMode
+import androidx.compose.ui.graphics.Brush
+import androidx.compose.ui.graphics.Canvas
+import androidx.compose.ui.graphics.Shadow
+import androidx.compose.ui.graphics.drawscope.DrawStyle
+import androidx.compose.ui.text.MultiParagraph
+import androidx.compose.ui.text.implementedInJetBrainsFork
+import androidx.compose.ui.text.style.TextDecoration
+
+internal actual fun MultiParagraph.drawMultiParagraph(
+ canvas: Canvas,
+ brush: Brush,
+ alpha: Float,
+ shadow: Shadow?,
+ decoration: TextDecoration?,
+ drawStyle: DrawStyle?,
+ blendMode: BlendMode
+): Unit = implementedInJetBrainsFork()
diff --git a/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/platform/SkiaParagraph.linuxx64Stubs.kt b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/platform/SkiaParagraph.linuxx64Stubs.kt
new file mode 100644
index 0000000..fb9de57
--- /dev/null
+++ b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/platform/SkiaParagraph.linuxx64Stubs.kt
@@ -0,0 +1,59 @@
+/*
+ * 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.
+ */
+package androidx.compose.ui.text.platform
+
+import androidx.compose.ui.text.AnnotatedString.Range
+import androidx.compose.ui.text.Paragraph
+import androidx.compose.ui.text.ParagraphIntrinsics
+import androidx.compose.ui.text.Placeholder
+import androidx.compose.ui.text.SpanStyle
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.Font
+import androidx.compose.ui.text.font.FontFamily
+import androidx.compose.ui.text.implementedInJetBrainsFork
+import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.Density
+
+internal actual fun ActualParagraph(
+ text: String,
+ style: TextStyle,
+ spanStyles: List<Range<SpanStyle>>,
+ placeholders: List<Range<Placeholder>>,
+ maxLines: Int,
+ ellipsis: Boolean,
+ width: Float,
+ density: Density,
+ @Suppress("DEPRECATION") resourceLoader: Font.ResourceLoader
+): Paragraph = implementedInJetBrainsFork()
+
+internal actual fun ActualParagraph(
+ text: String,
+ style: TextStyle,
+ spanStyles: List<Range<SpanStyle>>,
+ placeholders: List<Range<Placeholder>>,
+ maxLines: Int,
+ ellipsis: Boolean,
+ constraints: Constraints,
+ density: Density,
+ fontFamilyResolver: FontFamily.Resolver
+): Paragraph = implementedInJetBrainsFork()
+
+internal actual fun ActualParagraph(
+ paragraphIntrinsics: ParagraphIntrinsics,
+ maxLines: Int,
+ ellipsis: Boolean,
+ constraints: Constraints
+): Paragraph = implementedInJetBrainsFork()
diff --git a/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/platform/SkiaParagraphIntrinsics.linuxx64Stubs.kt b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/platform/SkiaParagraphIntrinsics.linuxx64Stubs.kt
new file mode 100644
index 0000000..8cbb81d
--- /dev/null
+++ b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/platform/SkiaParagraphIntrinsics.linuxx64Stubs.kt
@@ -0,0 +1,34 @@
+/*
+ * 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.
+ */
+package androidx.compose.ui.text.platform
+
+import androidx.compose.ui.text.AnnotatedString.Range
+import androidx.compose.ui.text.ParagraphIntrinsics
+import androidx.compose.ui.text.Placeholder
+import androidx.compose.ui.text.SpanStyle
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.FontFamily
+import androidx.compose.ui.text.implementedInJetBrainsFork
+import androidx.compose.ui.unit.Density
+
+internal actual fun ActualParagraphIntrinsics(
+ text: String,
+ style: TextStyle,
+ spanStyles: List<Range<SpanStyle>>,
+ placeholders: List<Range<Placeholder>>,
+ density: Density,
+ fontFamilyResolver: FontFamily.Resolver
+): ParagraphIntrinsics = implementedInJetBrainsFork()
diff --git a/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/platform/Synchronization.linuxx64Stubs.kt b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/platform/Synchronization.linuxx64Stubs.kt
new file mode 100644
index 0000000..ad58e16e
--- /dev/null
+++ b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/platform/Synchronization.linuxx64Stubs.kt
@@ -0,0 +1,26 @@
+/*
+ * 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.
+ */
+
+package androidx.compose.ui.text.platform
+
+import androidx.compose.ui.text.implementedInJetBrainsFork
+
+@PublishedApi internal actual class SynchronizedObject
+
+internal actual fun createSynchronizedObject(): SynchronizedObject = implementedInJetBrainsFork()
+
+@PublishedApi
+internal actual inline fun <R> synchronized(lock: SynchronizedObject, block: () -> R): R = block()
diff --git a/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/style/LineBreak.linuxx64Stubs.kt b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/style/LineBreak.linuxx64Stubs.kt
new file mode 100644
index 0000000..8a547cd
--- /dev/null
+++ b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/style/LineBreak.linuxx64Stubs.kt
@@ -0,0 +1,34 @@
+/*
+ * 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.style
+
+import androidx.compose.runtime.Immutable
+import androidx.compose.runtime.Stable
+import androidx.compose.ui.text.implementedInJetBrainsFork
+
+@Immutable
+actual value class LineBreak private constructor(internal val mask: Int) {
+ actual companion object {
+ @Stable actual val Simple: LineBreak = implementedInJetBrainsFork()
+
+ @Stable actual val Heading: LineBreak = implementedInJetBrainsFork()
+
+ @Stable actual val Paragraph: LineBreak = implementedInJetBrainsFork()
+
+ @Stable actual val Unspecified: LineBreak = implementedInJetBrainsFork()
+ }
+}
diff --git a/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/style/TextMotion.linuxx64Stubs.kt b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/style/TextMotion.linuxx64Stubs.kt
new file mode 100644
index 0000000..2ef3ee0
--- /dev/null
+++ b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/style/TextMotion.linuxx64Stubs.kt
@@ -0,0 +1,29 @@
+/*
+ * 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.style
+
+import androidx.compose.runtime.Immutable
+import androidx.compose.ui.text.implementedInJetBrainsFork
+
+@Immutable
+actual class TextMotion private constructor() {
+ actual companion object {
+ actual val Static: TextMotion = implementedInJetBrainsFork()
+
+ actual val Animated: TextMotion = implementedInJetBrainsFork()
+ }
+}
diff --git a/compose/ui/ui-tooling-data/build.gradle b/compose/ui/ui-tooling-data/build.gradle
index bb09c87..d7cd9ff 100644
--- a/compose/ui/ui-tooling-data/build.gradle
+++ b/compose/ui/ui-tooling-data/build.gradle
@@ -62,7 +62,7 @@
dependsOn(jvmMain)
dependencies {
api("androidx.annotation:annotation:1.1.0")
- api("androidx.annotation:annotation-experimental:1.4.0")
+ api("androidx.annotation:annotation-experimental:1.4.1")
}
}
diff --git a/compose/ui/ui-unit/build.gradle b/compose/ui/ui-unit/build.gradle
index 49c9961..57ef847 100644
--- a/compose/ui/ui-unit/build.gradle
+++ b/compose/ui/ui-unit/build.gradle
@@ -33,6 +33,7 @@
androidXMultiplatform {
android()
jvmStubs()
+ linuxX64Stubs()
defaultPlatform(PlatformIdentifier.ANDROID)
@@ -64,15 +65,17 @@
dependsOn(jvmMain)
dependencies {
api("androidx.annotation:annotation:1.1.0")
- api("androidx.annotation:annotation-experimental:1.4.0")
+ api("androidx.annotation:annotation-experimental:1.4.1")
implementation('androidx.collection:collection-ktx:1.2.0')
}
}
jvmStubsMain {
dependsOn(jvmMain)
- dependencies {
- }
+ }
+
+ linuxx64StubsMain {
+ dependsOn(commonMain)
}
androidInstrumentedTest {
diff --git a/compose/ui/ui-unit/src/commonMain/kotlin/androidx/compose/ui/unit/Constraints.kt b/compose/ui/ui-unit/src/commonMain/kotlin/androidx/compose/ui/unit/Constraints.kt
index e70985e..5dc4eb6 100644
--- a/compose/ui/ui-unit/src/commonMain/kotlin/androidx/compose/ui/unit/Constraints.kt
+++ b/compose/ui/ui-unit/src/commonMain/kotlin/androidx/compose/ui/unit/Constraints.kt
@@ -19,6 +19,7 @@
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.Stable
import androidx.compose.ui.unit.Constraints.Companion.Infinity
+import kotlin.jvm.JvmInline
import kotlin.math.min
/**
diff --git a/compose/ui/ui-unit/src/commonMain/kotlin/androidx/compose/ui/unit/Dp.kt b/compose/ui/ui-unit/src/commonMain/kotlin/androidx/compose/ui/unit/Dp.kt
index 12868d2..4cc9af8 100644
--- a/compose/ui/ui-unit/src/commonMain/kotlin/androidx/compose/ui/unit/Dp.kt
+++ b/compose/ui/ui-unit/src/commonMain/kotlin/androidx/compose/ui/unit/Dp.kt
@@ -25,6 +25,7 @@
import androidx.compose.ui.util.packFloats
import androidx.compose.ui.util.unpackFloat1
import androidx.compose.ui.util.unpackFloat2
+import kotlin.jvm.JvmInline
import kotlin.math.max
import kotlin.math.min
diff --git a/compose/ui/ui-unit/src/commonMain/kotlin/androidx/compose/ui/unit/IntOffset.kt b/compose/ui/ui-unit/src/commonMain/kotlin/androidx/compose/ui/unit/IntOffset.kt
index 5453fc6..9bafe44 100644
--- a/compose/ui/ui-unit/src/commonMain/kotlin/androidx/compose/ui/unit/IntOffset.kt
+++ b/compose/ui/ui-unit/src/commonMain/kotlin/androidx/compose/ui/unit/IntOffset.kt
@@ -26,6 +26,7 @@
import androidx.compose.ui.util.packInts
import androidx.compose.ui.util.unpackInt1
import androidx.compose.ui.util.unpackInt2
+import kotlin.jvm.JvmInline
/** Constructs a [IntOffset] from [x] and [y] position [Int] values. */
@Stable fun IntOffset(x: Int, y: Int): IntOffset = IntOffset(packInts(x, y))
diff --git a/compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt b/compose/ui/ui-unit/src/linuxx64StubsMain/kotlin/androidx/compose/ui/unit/FontScaling.linuxx64Stubs.kt
similarity index 69%
copy from compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt
copy to compose/ui/ui-unit/src/linuxx64StubsMain/kotlin/androidx/compose/ui/unit/FontScaling.linuxx64Stubs.kt
index 9df4b05..9f1c181 100644
--- a/compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt
+++ b/compose/ui/ui-unit/src/linuxx64StubsMain/kotlin/androidx/compose/ui/unit/FontScaling.linuxx64Stubs.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2024 The Android Open Source Project
+ * Copyright 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,10 +14,7 @@
* limitations under the License.
*/
-package androidx.compose.ui.graphics
+package androidx.compose.ui.unit
-actual class NativePaint
-
-actual fun Paint(): Paint = implementedInJetBrainsFork()
-
-actual fun BlendMode.isSupported(): Boolean = implementedInJetBrainsFork()
+/** Converts [TextUnit] to [Dp] and vice-versa. */
+actual typealias FontScaling = FontScalingLinear
diff --git a/compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt b/compose/ui/ui-unit/src/linuxx64StubsMain/kotlin/androidx/compose/ui/unit/internal/JvmDefaultWithCompatibility.linuxx64Stubs.kt
similarity index 75%
copy from compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt
copy to compose/ui/ui-unit/src/linuxx64StubsMain/kotlin/androidx/compose/ui/unit/internal/JvmDefaultWithCompatibility.linuxx64Stubs.kt
index 9df4b05..44f7772 100644
--- a/compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt
+++ b/compose/ui/ui-unit/src/linuxx64StubsMain/kotlin/androidx/compose/ui/unit/internal/JvmDefaultWithCompatibility.linuxx64Stubs.kt
@@ -14,10 +14,6 @@
* limitations under the License.
*/
-package androidx.compose.ui.graphics
+package androidx.compose.ui.unit.internal
-actual class NativePaint
-
-actual fun Paint(): Paint = implementedInJetBrainsFork()
-
-actual fun BlendMode.isSupported(): Boolean = implementedInJetBrainsFork()
+internal actual annotation class JvmDefaultWithCompatibility actual constructor()
diff --git a/compose/ui/ui-util/build.gradle b/compose/ui/ui-util/build.gradle
index c680394..d07e09f 100644
--- a/compose/ui/ui-util/build.gradle
+++ b/compose/ui/ui-util/build.gradle
@@ -34,6 +34,7 @@
androidXMultiplatform {
android()
jvmStubs()
+ linuxX64Stubs()
defaultPlatform(PlatformIdentifier.ANDROID)
@@ -59,12 +60,21 @@
androidMain {
dependsOn(jvmMain)
dependencies {
- api("androidx.annotation:annotation-experimental:1.4.0")
+ api("androidx.annotation:annotation-experimental:1.4.1")
}
}
+ commonStubsMain {
+ dependsOn(commonMain)
+ }
+
jvmStubsMain {
dependsOn(jvmMain)
+ dependsOn(commonStubsMain)
+ }
+
+ linuxx64StubsMain {
+ dependsOn(commonStubsMain)
}
androidInstrumentedTest {
diff --git a/compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt b/compose/ui/ui-util/src/commonStubsMain/kotlin/androidx/compose/ui/util/NotImplemented.commonStubs.kt
similarity index 64%
copy from compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt
copy to compose/ui/ui-util/src/commonStubsMain/kotlin/androidx/compose/ui/util/NotImplemented.commonStubs.kt
index 9df4b05..8dff401 100644
--- a/compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt
+++ b/compose/ui/ui-util/src/commonStubsMain/kotlin/androidx/compose/ui/util/NotImplemented.commonStubs.kt
@@ -14,10 +14,14 @@
* limitations under the License.
*/
-package androidx.compose.ui.graphics
+package androidx.compose.ui.util
-actual class NativePaint
-
-actual fun Paint(): Paint = implementedInJetBrainsFork()
-
-actual fun BlendMode.isSupported(): Boolean = implementedInJetBrainsFork()
+@Suppress("NOTHING_TO_INLINE")
+internal inline fun implementedInJetBrainsFork(): Nothing =
+ throw NotImplementedError(
+ """
+ Implemented only in JetBrains fork.
+ Please use `org.jetbrains.compose.ui:ui-util` package instead.
+ """
+ .trimIndent()
+ )
diff --git a/compose/ui/ui-util/src/jvmStubsMain/kotlin/androidx/compose/ui/util/Trace.jvmStubs.kt b/compose/ui/ui-util/src/commonStubsMain/kotlin/androidx/compose/ui/util/Trace.commonStubs.kt
similarity index 100%
rename from compose/ui/ui-util/src/jvmStubsMain/kotlin/androidx/compose/ui/util/Trace.jvmStubs.kt
rename to compose/ui/ui-util/src/commonStubsMain/kotlin/androidx/compose/ui/util/Trace.commonStubs.kt
diff --git a/compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt b/compose/ui/ui-util/src/linuxx64StubsMain/kotlin/androidx/compose/ui/util/InlineClassHelper.linuxx64Stubs.kt
similarity index 65%
copy from compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt
copy to compose/ui/ui-util/src/linuxx64StubsMain/kotlin/androidx/compose/ui/util/InlineClassHelper.linuxx64Stubs.kt
index 9df4b05..9a4c815 100644
--- a/compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt
+++ b/compose/ui/ui-util/src/linuxx64StubsMain/kotlin/androidx/compose/ui/util/InlineClassHelper.linuxx64Stubs.kt
@@ -14,10 +14,12 @@
* limitations under the License.
*/
-package androidx.compose.ui.graphics
+package androidx.compose.ui.util
-actual class NativePaint
+actual fun floatFromBits(bits: Int): Float = implementedInJetBrainsFork()
-actual fun Paint(): Paint = implementedInJetBrainsFork()
+actual fun doubleFromBits(bits: Long): Double = implementedInJetBrainsFork()
-actual fun BlendMode.isSupported(): Boolean = implementedInJetBrainsFork()
+actual fun Float.fastRoundToInt(): Int = implementedInJetBrainsFork()
+
+actual fun Double.fastRoundToInt(): Int = implementedInJetBrainsFork()
diff --git a/compose/ui/ui/build.gradle b/compose/ui/ui/build.gradle
index 1d2de81..ef1a521 100644
--- a/compose/ui/ui/build.gradle
+++ b/compose/ui/ui/build.gradle
@@ -74,7 +74,7 @@
androidMain {
dependsOn(jvmMain)
dependencies {
- api("androidx.annotation:annotation-experimental:1.4.0")
+ api("androidx.annotation:annotation-experimental:1.4.1")
// This has stub APIs for access to legacy Android APIs, so we don't want
// any dependency on this module.
compileOnly(project(":compose:ui:ui-android-stubs"))
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/AndroidAccessibilityTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/AndroidAccessibilityTest.kt
index 7a2f9bc95..390127d 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/AndroidAccessibilityTest.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/AndroidAccessibilityTest.kt
@@ -159,6 +159,7 @@
import androidx.compose.ui.test.assert
import androidx.compose.ui.test.assertContentDescriptionEquals
import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsNotDisplayed
import androidx.compose.ui.test.assertIsNotSelected
import androidx.compose.ui.test.assertIsOff
import androidx.compose.ui.test.assertIsOn
@@ -2714,6 +2715,64 @@
}
@Test
+ fun sendClickedAndChangeSubtree_whenDescendantClicked() {
+ val textTag = "text_tag"
+ // Arrange.
+ setContent {
+ var selected by remember { mutableStateOf(true) }
+ Box {
+ Box(Modifier.testTag(textTag)) {
+ if (selected) {
+ BasicText(text = "DisappearingText")
+ }
+ }
+ Box(Modifier.clickable(onClick = { selected = !selected }).testTag(tag)) {
+ BasicText("ToggleableComponent")
+ }
+ }
+ }
+ val toggleableVirtualViewId = rule.onNodeWithTag(tag).assertIsDisplayed().semanticsId
+
+ // Act.
+ val actionPerformed =
+ rule.runOnUiThread {
+ provider.performAction(toggleableVirtualViewId, ACTION_CLICK, null)
+ }
+
+ // Assert that `TYPE_VIEW_CLICKED` event was sent.
+ rule.runOnIdle {
+ assertThat(actionPerformed).isTrue()
+ verify(container, times(1))
+ .requestSendAccessibilityEvent(
+ eq(androidComposeView),
+ argThat(
+ ArgumentMatcher {
+ getAccessibilityEventSourceSemanticsNodeId(it) ==
+ toggleableVirtualViewId && it.eventType == TYPE_VIEW_CLICKED
+ }
+ )
+ )
+ }
+
+ rule.mainClock.advanceTimeBy(accessibilityEventLoopIntervalMs)
+
+ // Assert that `TYPE_WINDOW_CONTENT_CHANGED` event was also sent.
+ rule.onNodeWithTag(textTag).assertIsNotDisplayed()
+ rule.runOnIdle {
+ verify(container, atLeastOnce())
+ .requestSendAccessibilityEvent(
+ eq(androidComposeView),
+ argThat(
+ ArgumentMatcher {
+ it.eventType == TYPE_WINDOW_CONTENT_CHANGED &&
+ it.contentChangeTypes == CONTENT_CHANGE_TYPE_SUBTREE
+ }
+ )
+ )
+ }
+ }
+
+ @Test
fun sendStateChangeEvent_whenSelectedChange() {
// Arrange.
setContent {
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat.android.kt
index e3b46c6..f5e7c2c 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat.android.kt
@@ -2352,13 +2352,6 @@
return
}
- // No need to send an event if an ancestor has also been changed
- for (potentialAncestor in subtreeChangedLayoutNodes.indices) {
- if (subtreeChangedLayoutNodes.valueAt(potentialAncestor).isAncestorOf(layoutNode)) {
- return
- }
- }
-
// When we finally send the event, make sure it is an accessibility-focusable node.
val id =
trace("GetSemanticsNode") {
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/SemanticsUtils.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/SemanticsUtils.android.kt
index 22c4b62..3f890eb 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/SemanticsUtils.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/SemanticsUtils.android.kt
@@ -25,7 +25,6 @@
import androidx.collection.mutableIntSetOf
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.geometry.Rect
-import androidx.compose.ui.node.LayoutNode
import androidx.compose.ui.node.OwnerScope
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.semantics.ScrollAxisRange
@@ -148,11 +147,6 @@
internal fun AndroidViewsHandler.semanticsIdToView(id: Int): View? =
layoutNodeToHolder.entries.firstOrNull { it.key.semanticsId == id }?.value
-internal fun LayoutNode.isAncestorOf(node: LayoutNode): Boolean {
- val p = node.parent ?: return false
- return (p == this) || isAncestorOf(p)
-}
-
// TODO(mnuzen): refactor `currentSemanticsNodes` in the AccessibilityDelegate file to also use
// IntObjectMap's. Then ACVADC can also call `getAllUncoveredSemanticsNodesToIntObjectMap` instead
// of `getAllUncoveredSemanticsNodesToMap` as it does now.
diff --git a/core/core-telecom/build.gradle b/core/core-telecom/build.gradle
index e5b92f4..6600cb1 100644
--- a/core/core-telecom/build.gradle
+++ b/core/core-telecom/build.gradle
@@ -35,7 +35,7 @@
api(libs.guavaListenableFuture)
implementation("androidx.annotation:annotation:1.4.0")
// @OptIn annotations
- api("androidx.annotation:annotation-experimental:1.4.0")
+ api("androidx.annotation:annotation-experimental:1.4.1")
implementation("androidx.core:core:1.9.0")
implementation(libs.kotlinCoroutinesCore)
implementation(libs.kotlinCoroutinesGuava)
diff --git a/core/core-telecom/integration-tests/testapp/build.gradle b/core/core-telecom/integration-tests/testapp/build.gradle
index a998e35..a679012 100644
--- a/core/core-telecom/integration-tests/testapp/build.gradle
+++ b/core/core-telecom/integration-tests/testapp/build.gradle
@@ -50,7 +50,7 @@
implementation('androidx.recyclerview:recyclerview:1.2.1')
// Align dependencies in debugRuntimeClasspath and debugAndroidTestRuntimeClasspath.
- androidTestImplementation("androidx.annotation:annotation-experimental:1.4.0")
+ androidTestImplementation("androidx.annotation:annotation-experimental:1.4.1")
androidTestImplementation(libs.testExtJunit)
androidTestImplementation(libs.testRunner)
diff --git a/core/core/api/current.txt b/core/core/api/current.txt
index 3d8ff29..4ca6ed5 100644
--- a/core/core/api/current.txt
+++ b/core/core/api/current.txt
@@ -72,6 +72,7 @@
method public static androidx.core.app.ActivityOptionsCompat makeThumbnailScaleUpAnimation(android.view.View, android.graphics.Bitmap, int, int);
method public void requestUsageTimeReport(android.app.PendingIntent);
method public androidx.core.app.ActivityOptionsCompat setLaunchBounds(android.graphics.Rect?);
+ method public androidx.core.app.ActivityOptionsCompat setPendingIntentBackgroundActivityStartMode(int);
method public androidx.core.app.ActivityOptionsCompat setShareIdentityEnabled(boolean);
method public android.os.Bundle? toBundle();
method public void update(androidx.core.app.ActivityOptionsCompat);
diff --git a/core/core/api/restricted_current.txt b/core/core/api/restricted_current.txt
index fd3badb..93b61a0 100644
--- a/core/core/api/restricted_current.txt
+++ b/core/core/api/restricted_current.txt
@@ -90,6 +90,7 @@
method public static androidx.core.app.ActivityOptionsCompat makeThumbnailScaleUpAnimation(android.view.View, android.graphics.Bitmap, int, int);
method public void requestUsageTimeReport(android.app.PendingIntent);
method public androidx.core.app.ActivityOptionsCompat setLaunchBounds(android.graphics.Rect?);
+ method public androidx.core.app.ActivityOptionsCompat setPendingIntentBackgroundActivityStartMode(int);
method public androidx.core.app.ActivityOptionsCompat setShareIdentityEnabled(boolean);
method public android.os.Bundle? toBundle();
method public void update(androidx.core.app.ActivityOptionsCompat);
diff --git a/core/core/build.gradle b/core/core/build.gradle
index a646db3..d4cac38 100644
--- a/core/core/build.gradle
+++ b/core/core/build.gradle
@@ -22,7 +22,7 @@
}
api("androidx.annotation:annotation:1.8.0")
- api("androidx.annotation:annotation-experimental:1.4.0")
+ api("androidx.annotation:annotation-experimental:1.4.1")
api("androidx.lifecycle:lifecycle-runtime:2.6.2")
api("androidx.versionedparcelable:versionedparcelable:1.1.1")
implementation("androidx.collection:collection:1.0.0")
diff --git a/core/core/src/androidTest/java/androidx/core/provider/FontsContractCompatTest.java b/core/core/src/androidTest/java/androidx/core/provider/FontsContractCompatTest.java
index ce091a8..5112fed 100644
--- a/core/core/src/androidTest/java/androidx/core/provider/FontsContractCompatTest.java
+++ b/core/core/src/androidTest/java/androidx/core/provider/FontsContractCompatTest.java
@@ -61,7 +61,6 @@
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
/**
@@ -416,39 +415,6 @@
assertNull(callback.mTypeface);
}
- @Test
- public void testRequestFont_loadsOnExecutor() throws InterruptedException {
- CountDownLatch loadingLatch = new CountDownLatch(1);
- Executor loadingExecutor = c -> {
- loadingLatch.countDown();
- c.run();
- };
-
- CountDownLatch callbackExecutorLatch = new CountDownLatch(1);
- Executor callbackExecutor = c -> {
- callbackExecutorLatch.countDown();
- c.run();
- };
-
- // invalid request, we're just checking for using the right threads
- final FontRequest request = new FontRequest(
- AUTHORITY, PACKAGE, MockFontProvider.INVALID_URI, SIGNATURE);
- CountDownLatch cbLatch = new CountDownLatch(1);
- FontCallback cb = new FontCallback(cbLatch);
-
- FontsContractCompat.requestFont(mContext, request, Typeface.BOLD, loadingExecutor,
- callbackExecutor, cb);
-
- // it is not practical to assert the right runnable goes to the right executor, so just
- // assert that both executors are invoked, and that the callback is eventually invoked
- assertTrue("Loading happens on loading executor",
- loadingLatch.await(5L, TimeUnit.SECONDS));
- assertTrue("callback happens on callback executor",
- callbackExecutorLatch.await(5L, TimeUnit.SECONDS));
- assertTrue("callback happens", cbLatch.await(5L, TimeUnit.SECONDS));
-
- }
-
public static class FontCallback extends FontsContractCompat.FontRequestCallback {
private final CountDownLatch mLatch;
Typeface mTypeface;
diff --git a/core/core/src/main/java/androidx/core/app/ActivityOptionsCompat.java b/core/core/src/main/java/androidx/core/app/ActivityOptionsCompat.java
index 6287095..630ccda 100644
--- a/core/core/src/main/java/androidx/core/app/ActivityOptionsCompat.java
+++ b/core/core/src/main/java/androidx/core/app/ActivityOptionsCompat.java
@@ -20,6 +20,7 @@
import android.app.ActivityOptions;
import android.app.PendingIntent;
import android.content.Context;
+import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Rect;
import android.os.Build;
@@ -27,11 +28,16 @@
import android.view.View;
import androidx.annotation.DoNotInline;
+import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
+import androidx.annotation.RestrictTo;
import androidx.core.util.Pair;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* Helper for accessing features in {@link android.app.ActivityOptions} in a backwards compatible
* fashion.
@@ -51,6 +57,20 @@
public static final String EXTRA_USAGE_TIME_REPORT_PACKAGES = "android.usage_time_packages";
/**
+ * Enumeration of background activity start modes.
+ * <p/>
+ * These define if an app wants to grant it's background activity start privileges to a
+ * {@link PendingIntent}.
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ @IntDef(value = {
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED,
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED,
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED})
+ public @interface BackgroundActivityStartMode {}
+
+ /**
* Create an ActivityOptions specifying a custom animation to run when the
* activity is displayed.
*
@@ -289,6 +309,7 @@
return Api24Impl.getLaunchBounds(mActivityOptions);
}
+ @NonNull
@Override
public ActivityOptionsCompat setShareIdentityEnabled(boolean shareIdentity) {
if (Build.VERSION.SDK_INT < 34) {
@@ -297,6 +318,21 @@
return new ActivityOptionsCompatImpl(
Api34Impl.setShareIdentityEnabled(mActivityOptions, shareIdentity));
}
+
+ @NonNull
+ @Override
+ public ActivityOptionsCompat setPendingIntentBackgroundActivityStartMode(
+ @BackgroundActivityStartMode int state) {
+ if (Build.VERSION.SDK_INT >= 34) {
+ Api34Impl.setPendingIntentBackgroundActivityStartMode(mActivityOptions, state);
+ } else if (Build.VERSION.SDK_INT >= 33) {
+ // Matches the behavior of isPendingIntentBackgroundActivityLaunchAllowed().
+ boolean isAllowed = state != ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED;
+ Api33Impl.setPendingIntentBackgroundActivityLaunchAllowed(
+ mActivityOptions, isAllowed);
+ }
+ return this;
+ }
}
protected ActivityOptionsCompat() {
@@ -402,6 +438,21 @@
return this;
}
+ /**
+ * Sets the mode for allowing or denying the senders privileges to start background activities
+ * to the PendingIntent.
+ * <p/>
+ * This is typically used in when executing {@link PendingIntent#send(Context, int, Intent)} or
+ * similar methods. A privileged sender of a PendingIntent should only grant
+ * {@link ActivityOptions#MODE_BACKGROUND_ACTIVITY_START_ALLOWED} if the PendingIntent is from a
+ * trusted source and/or executed on behalf the user.
+ */
+ @NonNull
+ public ActivityOptionsCompat setPendingIntentBackgroundActivityStartMode(
+ @BackgroundActivityStartMode int state) {
+ return this;
+ }
+
@RequiresApi(23)
static class Api23Impl {
private Api23Impl() {
@@ -470,6 +521,20 @@
}
}
+ @RequiresApi(33)
+ static class Api33Impl {
+ private Api33Impl() {
+ // This class is not instantiable.
+ }
+
+ @SuppressWarnings("deprecation")
+ @DoNotInline
+ static void setPendingIntentBackgroundActivityLaunchAllowed(ActivityOptions activityOptions,
+ boolean allowed) {
+ activityOptions.setPendingIntentBackgroundActivityLaunchAllowed(allowed);
+ }
+ }
+
@RequiresApi(34)
static class Api34Impl {
private Api34Impl() {
@@ -481,5 +546,11 @@
boolean shareIdentity) {
return activityOptions.setShareIdentityEnabled(shareIdentity);
}
+
+ @DoNotInline
+ static ActivityOptions setPendingIntentBackgroundActivityStartMode(
+ ActivityOptions activityOptions, int state) {
+ return activityOptions.setPendingIntentBackgroundActivityStartMode(state);
+ }
}
}
diff --git a/core/core/src/test/java/androidx/core/app/ActivityOptionsCompatTest.kt b/core/core/src/test/java/androidx/core/app/ActivityOptionsCompatTest.kt
new file mode 100644
index 0000000..2ba9b7d
--- /dev/null
+++ b/core/core/src/test/java/androidx/core/app/ActivityOptionsCompatTest.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.core.app
+
+import android.app.ActivityOptions
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.RobolectricTestRunner
+import org.robolectric.annotation.Config
+
+@RunWith(RobolectricTestRunner::class)
+class ActivityOptionsCompatTest {
+
+ @Config(minSdk = 33)
+ @Test
+ fun testSetPendingIntentBackgroundActivityStartMode() {
+ val activityOptionsCompat = ActivityOptionsCompat.makeBasic()
+
+ activityOptionsCompat.setPendingIntentBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
+ )
+ activityOptionsCompat.setPendingIntentBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED
+ )
+ }
+}
diff --git a/credentials/credentials-e2ee/src/main/java/androidx/credentials/playservices/e2ee/androidx-credentials-credentials-play-services-e2ee-documentation.md b/credentials/credentials-e2ee/src/main/java/androidx/credentials/playservices/e2ee/androidx-credentials-credentials-play-services-e2ee-documentation.md
new file mode 100644
index 0000000..85f3a1e
--- /dev/null
+++ b/credentials/credentials-e2ee/src/main/java/androidx/credentials/playservices/e2ee/androidx-credentials-credentials-play-services-e2ee-documentation.md
@@ -0,0 +1,7 @@
+# Module root
+
+CREDENTIALS CREDENTIALS PLAY SERVICES E2EE
+
+# Package androidx.credentials.playservices.e2ee
+
+This package allows developers to use E2EE contact keys.
diff --git a/credentials/credentials-play-services-e2ee/OWNERS b/credentials/credentials-play-services-e2ee/OWNERS
new file mode 100644
index 0000000..ce145c2
--- /dev/null
+++ b/credentials/credentials-play-services-e2ee/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 1487500
[email protected]
[email protected]
diff --git a/credentials/credentials-play-services-e2ee/api/current.txt b/credentials/credentials-play-services-e2ee/api/current.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/credentials/credentials-play-services-e2ee/api/current.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/credentials/credentials-play-services-e2ee/api/res-current.txt b/credentials/credentials-play-services-e2ee/api/res-current.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/credentials/credentials-play-services-e2ee/api/res-current.txt
diff --git a/credentials/credentials-play-services-e2ee/api/restricted_current.txt b/credentials/credentials-play-services-e2ee/api/restricted_current.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/credentials/credentials-play-services-e2ee/api/restricted_current.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/credentials/credentials-play-services-e2ee/build.gradle b/credentials/credentials-play-services-e2ee/build.gradle
new file mode 100644
index 0000000..5ff33f5
--- /dev/null
+++ b/credentials/credentials-play-services-e2ee/build.gradle
@@ -0,0 +1,39 @@
+
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import androidx.build.LibraryType
+plugins {
+ id("AndroidXPlugin")
+ id("com.android.library")
+ id("org.jetbrains.kotlin.android")
+}
+dependencies {
+ api(libs.kotlinStdlib)
+ androidTestImplementation(libs.junit)
+ androidTestImplementation(libs.testExtJunit)
+ androidTestImplementation(libs.testCore)
+ androidTestImplementation(libs.testRunner)
+ androidTestImplementation(libs.truth)
+}
+android {
+ namespace "androidx.credentials.playservices.e2ee"
+}
+androidx {
+ name = "androidx.credentials:credentials.playservices.e2ee"
+ type = LibraryType.PUBLISHED_LIBRARY
+ inceptionYear = "2024"
+ description = "Play Services API for E2EE contact keys functionality."
+}
diff --git a/credentials/credentials-play-services-e2ee/src/androidTest/java/androidx/credentials/playservices/e2ee/CredentialsPlayServicesE2eeInstrumentedTest.java b/credentials/credentials-play-services-e2ee/src/androidTest/java/androidx/credentials/playservices/e2ee/CredentialsPlayServicesE2eeInstrumentedTest.java
new file mode 100644
index 0000000..bf3d391
--- /dev/null
+++ b/credentials/credentials-play-services-e2ee/src/androidTest/java/androidx/credentials/playservices/e2ee/CredentialsPlayServicesE2eeInstrumentedTest.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.credentials.playservices.e2ee;
+
+import static org.junit.Assert.assertEquals;
+
+import android.content.Context;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class CredentialsPlayServicesE2eeInstrumentedTest {
+ @Test
+ public void useAppContext() {
+ // Context of the app under test.
+ Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+ assertEquals("androidx.credentials.playservices.e2ee.test", appContext.getPackageName());
+ }
+}
diff --git a/docs-tip-of-tree/build.gradle b/docs-tip-of-tree/build.gradle
index 38c3dfb..50e30d8 100644
--- a/docs-tip-of-tree/build.gradle
+++ b/docs-tip-of-tree/build.gradle
@@ -140,6 +140,7 @@
docs(project(":credentials:credentials-fido"))
docs(project(":credentials:credentials-play-services-auth"))
docs(project(":credentials:credentials-e2ee"))
+ docs(project(":credentials:credentials-play-services-e2ee"))
docs(project(":cursoradapter:cursoradapter"))
docs(project(":customview:customview"))
docs(project(":customview:customview-poolingcontainer"))
diff --git a/fragment/fragment/build.gradle b/fragment/fragment/build.gradle
index 1870df9..6734ce4 100644
--- a/fragment/fragment/build.gradle
+++ b/fragment/fragment/build.gradle
@@ -41,7 +41,7 @@
api("androidx.lifecycle:lifecycle-viewmodel-savedstate:2.6.1")
implementation("androidx.profileinstaller:profileinstaller:1.3.1")
api("androidx.savedstate:savedstate:1.2.1")
- api("androidx.annotation:annotation-experimental:1.4.0")
+ api("androidx.annotation:annotation-experimental:1.4.1")
api(libs.kotlinStdlib)
androidTestImplementation("androidx.appcompat:appcompat:1.1.0", {
diff --git a/glance/glance-template/build.gradle b/glance/glance-template/build.gradle
index b76e014..0db72c3 100644
--- a/glance/glance-template/build.gradle
+++ b/glance/glance-template/build.gradle
@@ -49,7 +49,7 @@
implementation(libs.kotlinStdlib)
// Force upgrade since 1.2.0 is not compatible with latest lint.
- implementation("androidx.annotation:annotation-experimental:1.4.0")
+ implementation("androidx.annotation:annotation-experimental:1.4.1")
testImplementation(libs.robolectric)
testImplementation(libs.testCore)
diff --git a/glance/glance/build.gradle b/glance/glance/build.gradle
index 0a563e2..04462a0 100644
--- a/glance/glance/build.gradle
+++ b/glance/glance/build.gradle
@@ -46,7 +46,7 @@
implementation(libs.kotlinStdlib)
// Force upgrade since 1.2.0 is not compatible with latest lint.
- implementation("androidx.annotation:annotation-experimental:1.4.0")
+ implementation("androidx.annotation:annotation-experimental:1.4.1")
testImplementation(libs.robolectric)
testImplementation(libs.testCore)
diff --git a/graphics/filters/filters/build.gradle b/graphics/filters/filters/build.gradle
index 374bfc3..b6f4fa6 100644
--- a/graphics/filters/filters/build.gradle
+++ b/graphics/filters/filters/build.gradle
@@ -43,7 +43,7 @@
implementation('androidx.media3:media3-transformer:' + media3Version)
// Force upgrade since 1.2.0 is not compatible with latest lint.
- implementation("androidx.annotation:annotation-experimental:1.4.0")
+ implementation("androidx.annotation:annotation-experimental:1.4.1")
// Test dependencies
androidTestImplementation(libs.testExtJunit)
diff --git a/graphics/graphics-core/build.gradle b/graphics/graphics-core/build.gradle
index 7fd55aa..4860a59 100644
--- a/graphics/graphics-core/build.gradle
+++ b/graphics/graphics-core/build.gradle
@@ -33,7 +33,7 @@
dependencies {
api(libs.kotlinStdlib)
implementation(libs.kotlinCoroutinesAndroid)
- implementation("androidx.annotation:annotation-experimental:1.4.0")
+ implementation("androidx.annotation:annotation-experimental:1.4.1")
implementation("androidx.core:core:1.8.0")
androidTestImplementation(libs.testExtJunit)
androidTestImplementation(libs.testCore)
diff --git a/graphics/graphics-shapes/build.gradle b/graphics/graphics-shapes/build.gradle
index a782cc0..a253ff9 100644
--- a/graphics/graphics-shapes/build.gradle
+++ b/graphics/graphics-shapes/build.gradle
@@ -71,7 +71,7 @@
dependsOn(jvmMain)
dependencies {
implementation("androidx.core:core-ktx:1.10.0")
- implementation("androidx.annotation:annotation-experimental:1.4.0-rc01")
+ implementation("androidx.annotation:annotation-experimental:1.4.1")
}
}
diff --git a/lint-checks/integration-tests/build.gradle b/lint-checks/integration-tests/build.gradle
index e5c85f7..4966bb6 100644
--- a/lint-checks/integration-tests/build.gradle
+++ b/lint-checks/integration-tests/build.gradle
@@ -31,7 +31,7 @@
dependencies {
implementation("androidx.annotation:annotation:1.8.0")
- implementation("androidx.annotation:annotation-experimental:1.4.0")
+ implementation("androidx.annotation:annotation-experimental:1.4.1")
implementation(libs.kotlinStdlib)
}
diff --git a/lint-checks/src/main/java/androidx/build/lint/AndroidXIssueRegistry.kt b/lint-checks/src/main/java/androidx/build/lint/AndroidXIssueRegistry.kt
index 319d7c5..b4b5ecc 100644
--- a/lint-checks/src/main/java/androidx/build/lint/AndroidXIssueRegistry.kt
+++ b/lint-checks/src/main/java/androidx/build/lint/AndroidXIssueRegistry.kt
@@ -69,8 +69,6 @@
BanSynchronizedMethods.ISSUE,
MetadataTagInsideApplicationTagDetector.ISSUE,
PrivateConstructorForUtilityClassDetector.ISSUE,
- ClassVerificationFailureDetector.METHOD_CALL_ISSUE,
- ClassVerificationFailureDetector.IMPLICIT_CAST_ISSUE,
IdeaSuppressionDetector.ISSUE,
CameraXQuirksClassDetector.ISSUE,
NullabilityAnnotationsDetector.ISSUE,
diff --git a/mediarouter/mediarouter/build.gradle b/mediarouter/mediarouter/build.gradle
index 019340b1..51397fd 100644
--- a/mediarouter/mediarouter/build.gradle
+++ b/mediarouter/mediarouter/build.gradle
@@ -42,9 +42,7 @@
implementation("androidx.recyclerview:recyclerview:1.1.0")
implementation("androidx.appcompat:appcompat-resources:1.2.0")
- // TODO(b/307906685) Change to the stable version with the next release.
- // See b/307906685#comment6 for details.
- implementation("androidx.annotation:annotation-experimental:1.4.0-rc01")
+ implementation("androidx.annotation:annotation-experimental:1.4.1")
testImplementation(libs.junit)
testImplementation(libs.testCore)
diff --git a/navigation/navigation-runtime/build.gradle b/navigation/navigation-runtime/build.gradle
index 91af7ba..64200c4 100644
--- a/navigation/navigation-runtime/build.gradle
+++ b/navigation/navigation-runtime/build.gradle
@@ -35,7 +35,7 @@
api("androidx.activity:activity-ktx:1.7.1")
api("androidx.lifecycle:lifecycle-runtime-ktx:2.6.2")
api("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2")
- api("androidx.annotation:annotation-experimental:1.4.0")
+ api("androidx.annotation:annotation-experimental:1.4.1")
implementation('androidx.collection:collection:1.1.0')
implementation(libs.kotlinSerializationCore)
diff --git a/navigation/navigation-ui/build.gradle b/navigation/navigation-ui/build.gradle
index baa2420..58e46d0 100644
--- a/navigation/navigation-ui/build.gradle
+++ b/navigation/navigation-ui/build.gradle
@@ -42,7 +42,7 @@
api("androidx.drawerlayout:drawerlayout:1.1.1")
api("com.google.android.material:material:1.4.0")
implementation("androidx.transition:transition:1.3.0")
- api("androidx.annotation:annotation-experimental:1.4.0")
+ api("androidx.annotation:annotation-experimental:1.4.1")
androidTestImplementation(project(":internal-testutils-navigation"), {
exclude group: "androidx.navigation", module: "navigation-common"
diff --git a/paging/paging-common/src/commonMain/kotlin/androidx/paging/PagingDataPresenter.kt b/paging/paging-common/src/commonMain/kotlin/androidx/paging/PagingDataPresenter.kt
index b069102..ceeecc6 100644
--- a/paging/paging-common/src/commonMain/kotlin/androidx/paging/PagingDataPresenter.kt
+++ b/paging/paging-common/src/commonMain/kotlin/androidx/paging/PagingDataPresenter.kt
@@ -34,9 +34,12 @@
import kotlinx.coroutines.channels.BufferOverflow.DROP_OLDEST
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asSharedFlow
+import kotlinx.coroutines.flow.update
import kotlinx.coroutines.withContext
+import kotlinx.coroutines.yield
/**
* The class that connects the UI layer to the underlying Paging operations. Takes input from UI
@@ -160,6 +163,9 @@
)
}
event is Insert -> {
+ if (inGetItem.value) {
+ yield()
+ }
// Process APPEND/PREPEND and send to presenter
presentPagingDataEvent(pageStore.processEvent(event))
@@ -215,6 +221,9 @@
}
}
event is Drop -> {
+ if (inGetItem.value) {
+ yield()
+ }
// Process DROP and send to presenter
presentPagingDataEvent(pageStore.processEvent(event))
@@ -249,6 +258,8 @@
}
}
+ private val inGetItem = MutableStateFlow(false)
+
/**
* Returns the presented item at the specified position, notifying Paging of the item access to
* trigger any loads necessary to fulfill [prefetchDistance][PagingConfig.prefetchDistance].
@@ -258,12 +269,13 @@
*/
@MainThread
public operator fun get(@IntRange(from = 0) index: Int): T? {
+ inGetItem.update { true }
lastAccessedIndexUnfulfilled = true
lastAccessedIndex = index
log(VERBOSE) { "Accessing item index[$index]" }
hintReceiver?.accessHint(pageStore.accessHintForPresenterIndex(index))
- return pageStore.get(index)
+ return pageStore.get(index).also { inGetItem.update { false } }
}
/**
diff --git a/paging/paging-runtime/src/androidTest/java/androidx/paging/AsyncPagingDataDifferTest.kt b/paging/paging-runtime/src/androidTest/java/androidx/paging/AsyncPagingDataDifferTest.kt
index 28116aa..bfc1e11 100644
--- a/paging/paging-runtime/src/androidTest/java/androidx/paging/AsyncPagingDataDifferTest.kt
+++ b/paging/paging-runtime/src/androidTest/java/androidx/paging/AsyncPagingDataDifferTest.kt
@@ -1033,6 +1033,329 @@
}
@Test
+ fun prependYieldsToRecyclerView() {
+ Dispatchers.resetMain() // reset MainDispatcherRule
+ // collection on immediate dispatcher to simulate real lifecycle dispatcher
+ val mainDispatcher = Dispatchers.Main.immediate
+ runTest {
+ val events = mutableListOf<String>()
+ val listUpdateCapture = ListUpdateCapture { event -> events.add(event.toString()) }
+ val asyncDiffer =
+ AsyncPagingDataDiffer(
+ diffCallback =
+ object : DiffUtil.ItemCallback<Int>() {
+ override fun areContentsTheSame(oldItem: Int, newItem: Int): Boolean {
+ return oldItem == newItem
+ }
+
+ override fun areItemsTheSame(oldItem: Int, newItem: Int): Boolean {
+ return oldItem == newItem
+ }
+ },
+ // override default Dispatcher.Main with Dispatchers.main.immediate so that
+ // main tasks run without queueing, we need this to simulate real life order of
+ // events
+ mainDispatcher = mainDispatcher,
+ updateCallback = listUpdateCapture,
+ workerDispatcher = backgroundScope.coroutineContext
+ )
+
+ val pager =
+ Pager(
+ config =
+ PagingConfig(
+ pageSize = 5,
+ enablePlaceholders = false,
+ prefetchDistance = 3,
+ initialLoadSize = 10,
+ ),
+ initialKey = 30
+ ) {
+ TestPagingSource(loadDelay = 0)
+ }
+
+ val collectPager =
+ launch(mainDispatcher) { pager.flow.collectLatest { asyncDiffer.submitData(it) } }
+
+ // wait till we get all expected events
+ asyncDiffer.loadStateFlow.awaitNotLoading()
+
+ assertThat(events).containsExactly("Inserted(position=0, count=10)")
+ events.clear()
+
+ // Simulate RV dispatching layout which calls multi onBind --> getItem. LoadStateUpdates
+ // from upstream should yield until dispatch layout completes or else
+ // LoadState-based RV updates will crash. See original bug b/150162465.
+ withContext(mainDispatcher) {
+ events.add("start dispatchLayout")
+ asyncDiffer.getItem(3)
+ asyncDiffer.getItem(2) // this triggers prepend
+ events.add("end dispatchLayout")
+ }
+
+ // wait till we get all expected events
+ asyncDiffer.loadStateFlow.awaitNotLoading()
+
+ // make sure the prepend was not processed until RV is done with layouts
+ assertThat(events)
+ .containsExactly(
+ "start dispatchLayout",
+ "end dispatchLayout",
+ "Inserted(position=0, count=5)"
+ )
+ .inOrder()
+ collectPager.cancel()
+ }
+ }
+
+ @Test
+ fun appendYieldsToRecyclerView() {
+ Dispatchers.resetMain() // reset MainDispatcherRule
+ // collection on immediate dispatcher to simulate real lifecycle dispatcher
+ val mainDispatcher = Dispatchers.Main.immediate
+ runTest {
+ val events = mutableListOf<String>()
+ val listUpdateCapture = ListUpdateCapture { event -> events.add(event.toString()) }
+ val asyncDiffer =
+ AsyncPagingDataDiffer(
+ diffCallback =
+ object : DiffUtil.ItemCallback<Int>() {
+ override fun areContentsTheSame(oldItem: Int, newItem: Int): Boolean {
+ return oldItem == newItem
+ }
+
+ override fun areItemsTheSame(oldItem: Int, newItem: Int): Boolean {
+ return oldItem == newItem
+ }
+ },
+ // override default Dispatcher.Main with Dispatchers.main.immediate so that
+ // main tasks run without queueing, we need this to simulate real life order of
+ // events
+ mainDispatcher = mainDispatcher,
+ updateCallback = listUpdateCapture,
+ workerDispatcher = backgroundScope.coroutineContext
+ )
+
+ val pager =
+ Pager(
+ config =
+ PagingConfig(
+ pageSize = 5,
+ enablePlaceholders = false,
+ prefetchDistance = 3,
+ initialLoadSize = 10,
+ ),
+ ) {
+ TestPagingSource(loadDelay = 0)
+ }
+
+ val collectPager =
+ launch(mainDispatcher) { pager.flow.collectLatest { asyncDiffer.submitData(it) } }
+
+ // wait till we get all expected events
+ asyncDiffer.loadStateFlow.awaitNotLoading()
+
+ assertThat(events).containsExactly("Inserted(position=0, count=10)")
+ events.clear()
+
+ // Simulate RV dispatching layout which calls multi onBind --> getItem. LoadStateUpdates
+ // from upstream should yield until dispatch layout completes or else
+ // LoadState-based RV updates will crash. See original bug b/150162465.
+ withContext(mainDispatcher) {
+ events.add("start dispatchLayout")
+ asyncDiffer.getItem(6)
+ asyncDiffer.getItem(7) // this triggers append
+ events.add("end dispatchLayout")
+ }
+
+ // wait till we get all expected events
+ asyncDiffer.loadStateFlow.awaitNotLoading()
+
+ // make sure the append was not processed until RV is done with layouts
+ assertThat(events)
+ .containsExactly(
+ "start dispatchLayout",
+ "end dispatchLayout",
+ "Inserted(position=10, count=5)"
+ )
+ .inOrder()
+ collectPager.cancel()
+ }
+ }
+
+ @Test
+ fun dropPrependYieldsToRecyclerView() {
+ Dispatchers.resetMain() // reset MainDispatcherRule
+ // collection on immediate dispatcher to simulate real lifecycle dispatcher
+ val mainDispatcher = Dispatchers.Main.immediate
+ runTest {
+ val events = mutableListOf<String>()
+ val listUpdateCapture = ListUpdateCapture { event -> events.add(event.toString()) }
+ val asyncDiffer =
+ AsyncPagingDataDiffer(
+ diffCallback =
+ object : DiffUtil.ItemCallback<Int>() {
+ override fun areContentsTheSame(oldItem: Int, newItem: Int): Boolean {
+ return oldItem == newItem
+ }
+
+ override fun areItemsTheSame(oldItem: Int, newItem: Int): Boolean {
+ return oldItem == newItem
+ }
+ },
+ // override default Dispatcher.Main with Dispatchers.main.immediate so that
+ // main tasks run without queueing, we need this to simulate real life order of
+ // events
+ mainDispatcher = mainDispatcher,
+ updateCallback = listUpdateCapture,
+ workerDispatcher = backgroundScope.coroutineContext
+ )
+
+ val pager =
+ Pager(
+ config =
+ PagingConfig(
+ pageSize = 5,
+ enablePlaceholders = false,
+ prefetchDistance = 3,
+ initialLoadSize = 10,
+ maxSize = 16
+ ),
+ initialKey = 30,
+ ) {
+ TestPagingSource(loadDelay = 0)
+ }
+
+ val collectPager =
+ launch(mainDispatcher) { pager.flow.collectLatest { asyncDiffer.submitData(it) } }
+
+ // wait till we get all expected events
+ asyncDiffer.loadStateFlow.awaitNotLoading()
+
+ assertThat(events).containsExactly("Inserted(position=0, count=10)")
+ events.clear()
+
+ // trigger a prepend that will be dropped later
+ asyncDiffer.getItem(0)
+ asyncDiffer.loadStateFlow.awaitNotLoading()
+
+ assertThat(events).containsExactly("Inserted(position=0, count=5)")
+ events.clear()
+
+ // Simulate RV dispatching layout which calls multi onBind --> getItem. LoadStateUpdates
+ // from upstream should yield until dispatch layout completes or else
+ // LoadState-based RV updates will crash. See original bug b/150162465.
+ withContext(mainDispatcher) {
+ events.add("start dispatchLayout")
+ asyncDiffer.getItem(11)
+ // this triggers append which will cause prepend to drop
+ asyncDiffer.getItem(12)
+ events.add("end dispatchLayout")
+ }
+
+ // wait till we get all expected events
+ asyncDiffer.loadStateFlow.awaitNotLoading()
+
+ // make sure the append was not processed until RV is done with layouts
+ assertThat(events)
+ .containsExactly(
+ "start dispatchLayout",
+ "end dispatchLayout",
+ "Removed(position=0, count=5)", // drop prepend
+ "Inserted(position=10, count=5)" // append
+ )
+ .inOrder()
+ collectPager.cancel()
+ }
+ }
+
+ @Test
+ fun dropAppendYieldsToRecyclerView() {
+ Dispatchers.resetMain() // reset MainDispatcherRule
+ // collection on immediate dispatcher to simulate real lifecycle dispatcher
+ val mainDispatcher = Dispatchers.Main.immediate
+ runTest {
+ val events = mutableListOf<String>()
+ val listUpdateCapture = ListUpdateCapture { event -> events.add(event.toString()) }
+ val asyncDiffer =
+ AsyncPagingDataDiffer(
+ diffCallback =
+ object : DiffUtil.ItemCallback<Int>() {
+ override fun areContentsTheSame(oldItem: Int, newItem: Int): Boolean {
+ return oldItem == newItem
+ }
+
+ override fun areItemsTheSame(oldItem: Int, newItem: Int): Boolean {
+ return oldItem == newItem
+ }
+ },
+ // override default Dispatcher.Main with Dispatchers.main.immediate so that
+ // main tasks run without queueing, we need this to simulate real life order of
+ // events
+ mainDispatcher = mainDispatcher,
+ updateCallback = listUpdateCapture,
+ workerDispatcher = backgroundScope.coroutineContext
+ )
+
+ val pager =
+ Pager(
+ config =
+ PagingConfig(
+ pageSize = 5,
+ enablePlaceholders = false,
+ prefetchDistance = 3,
+ initialLoadSize = 10,
+ maxSize = 16
+ ),
+ initialKey = 30,
+ ) {
+ TestPagingSource(loadDelay = 0)
+ }
+
+ val collectPager =
+ launch(mainDispatcher) { pager.flow.collectLatest { asyncDiffer.submitData(it) } }
+
+ // wait till we get all expected events
+ asyncDiffer.loadStateFlow.awaitNotLoading()
+
+ assertThat(events).containsExactly("Inserted(position=0, count=10)")
+ events.clear()
+
+ // trigger a prepend that will be dropped later
+ asyncDiffer.getItem(9)
+ asyncDiffer.loadStateFlow.awaitNotLoading()
+
+ assertThat(events).containsExactly("Inserted(position=10, count=5)")
+ events.clear()
+
+ // Simulate RV dispatching layout which calls multi onBind --> getItem. LoadStateUpdates
+ // from upstream should yield until dispatch layout completes or else
+ // LoadState-based RV updates will crash. See original bug b/150162465.
+ withContext(mainDispatcher) {
+ events.add("start dispatchLayout")
+ asyncDiffer.getItem(3)
+ // this triggers prepend which will cause append to drop
+ asyncDiffer.getItem(2)
+ events.add("end dispatchLayout")
+ }
+
+ // wait till we get all expected events
+ asyncDiffer.loadStateFlow.awaitNotLoading()
+
+ // make sure the append was not processed until RV is done with layouts
+ assertThat(events)
+ .containsExactly(
+ "start dispatchLayout",
+ "end dispatchLayout",
+ "Removed(position=10, count=5)", // drop append
+ "Inserted(position=0, count=5)" // prepend
+ )
+ .inOrder()
+ collectPager.cancel()
+ }
+ }
+
+ @Test
fun loadStateListenerYieldsToGetItem() {
Dispatchers.resetMain() // reset MainDispatcherRule
// collection on immediate dispatcher to simulate real lifecycle dispatcher
diff --git a/paging/paging-runtime/src/androidTest/java/androidx/paging/ListUpdateCapture.kt b/paging/paging-runtime/src/androidTest/java/androidx/paging/ListUpdateCapture.kt
index 0f6a1d5..e39527a 100644
--- a/paging/paging-runtime/src/androidTest/java/androidx/paging/ListUpdateCapture.kt
+++ b/paging/paging-runtime/src/androidTest/java/androidx/paging/ListUpdateCapture.kt
@@ -18,25 +18,26 @@
import androidx.recyclerview.widget.ListUpdateCallback
-class ListUpdateCapture : ListUpdateCallback {
+class ListUpdateCapture(
+ private val events: MutableList<ListUpdateEvent> = mutableListOf(),
+ val callBack: (event: ListUpdateEvent) -> Unit = { events.add(it) },
+) : ListUpdateCallback {
private var lastEventsListIndex = -1
- val events = mutableListOf<ListUpdateEvent>()
-
override fun onChanged(position: Int, count: Int, payload: Any?) {
- events.add(ListUpdateEvent.Changed(position, count, payload))
+ callBack(ListUpdateEvent.Changed(position, count, payload))
}
override fun onMoved(fromPosition: Int, toPosition: Int) {
- events.add(ListUpdateEvent.Moved(fromPosition, toPosition))
+ callBack(ListUpdateEvent.Moved(fromPosition, toPosition))
}
override fun onInserted(position: Int, count: Int) {
- events.add(ListUpdateEvent.Inserted(position, count))
+ callBack(ListUpdateEvent.Inserted(position, count))
}
override fun onRemoved(position: Int, count: Int) {
- events.add(ListUpdateEvent.Removed(position, count))
+ callBack(ListUpdateEvent.Removed(position, count))
}
fun newEvents(): List<ListUpdateEvent> {
diff --git a/paging/samples/build.gradle b/paging/samples/build.gradle
index f6bc2c0..a7b20e3 100644
--- a/paging/samples/build.gradle
+++ b/paging/samples/build.gradle
@@ -38,7 +38,7 @@
compileOnly(project(":annotation:annotation-sampled"))
implementation("androidx.appcompat:appcompat:1.2.0")
- implementation("androidx.annotation:annotation-experimental:1.4.0")
+ implementation("androidx.annotation:annotation-experimental:1.4.1")
implementation("androidx.fragment:fragment-ktx:1.3.0")
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.0")
implementation("androidx.recyclerview:recyclerview:1.2.0")
diff --git a/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/java/androidx/privacysandbox/sdkruntime/client/loader/LocalSdkTestUtils.kt b/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/java/androidx/privacysandbox/sdkruntime/client/loader/LocalSdkTestUtils.kt
index f0225b7..67db655 100644
--- a/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/java/androidx/privacysandbox/sdkruntime/client/loader/LocalSdkTestUtils.kt
+++ b/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/java/androidx/privacysandbox/sdkruntime/client/loader/LocalSdkTestUtils.kt
@@ -27,10 +27,11 @@
import androidx.privacysandbox.sdkruntime.core.SandboxedSdkProviderCompat
import androidx.privacysandbox.sdkruntime.core.Versions
import androidx.privacysandbox.sdkruntime.core.activity.SdkSandboxActivityHandlerCompat
+import androidx.privacysandbox.sdkruntime.core.controller.SdkSandboxControllerCompat
import java.lang.reflect.InvocationTargetException
import java.lang.reflect.Proxy
+import java.lang.reflect.UndeclaredThrowableException
import java.util.concurrent.CountDownLatch
-import kotlin.reflect.cast
/** Extract value of [Versions.API_VERSION] from loaded SDK. */
internal fun LocalSdkProvider.extractApiVersion(): Int = extractVersionValue("API_VERSION")
@@ -38,45 +39,34 @@
/** Extract value of [Versions.CLIENT_VERSION] from loaded SDK. */
internal fun LocalSdkProvider.extractClientVersion(): Int = extractVersionValue("CLIENT_VERSION")
+/** Extract value of static [versionFieldName] from [Versions] class. */
+private fun LocalSdkProvider.extractVersionValue(versionFieldName: String): Int =
+ extractSdkProviderClassloader()
+ .getClass(Versions::class.java.name)
+ .getStaticField(versionFieldName) as Int
+
/** Extract [SandboxedSdkProviderCompat.context] from loaded SDK. */
-internal fun LocalSdkProvider.extractSdkContext(): Context {
- val getContextMethod = sdkProvider.javaClass.getMethod("getContext")
-
- val rawContext = getContextMethod.invoke(sdkProvider)
-
- return Context::class.cast(rawContext)
-}
+internal fun LocalSdkProvider.extractSdkContext(): Context =
+ sdkProvider.callMethod("getContext") as Context
/** Extract field value from [SandboxedSdkProviderCompat] */
internal inline fun <reified T> LocalSdkProvider.extractSdkProviderFieldValue(
fieldName: String
-): T {
- return sdkProvider.javaClass.getField(fieldName).get(sdkProvider)!! as T
-}
+): T = sdkProvider.getField(fieldName) as T
/** Extract classloader that was used for loading of [SandboxedSdkProviderCompat]. */
internal fun LocalSdkProvider.extractSdkProviderClassloader(): ClassLoader =
sdkProvider.javaClass.classLoader!!
-/**
- * Reflection wrapper for TestSDK object. Underlying TestSDK should implement and delegate to
- * [androidx.privacysandbox.sdkruntime.core.controller.SdkSandboxControllerCompat]:
- * 1) getSandboxedSdks() : List<SandboxedSdkCompat>
- * 2) getAppOwnedSdkSandboxInterfaces() : List<AppOwnedSdkSandboxInterfaceCompat>
- * 3) registerSdkSandboxActivityHandler(SdkSandboxActivityHandlerCompat) : IBinder
- * 4) unregisterSdkSandboxActivityHandler(SdkSandboxActivityHandlerCompat)
- * 5) loadSdk(sdkName, sdkParams) : SandboxedSdkCompat
- */
-internal class TestSdkWrapper(private val sdk: Any) {
+/** Reflection wrapper for [SdkSandboxControllerCompat] */
+internal class SdkControllerWrapper(private val controller: Any) {
fun loadSdk(sdkName: String, sdkParams: Bundle): SandboxedSdkWrapper {
- val loadSdkMethod =
- sdk.javaClass.getMethod("loadSdk", String::class.java, Bundle::class.java)
-
try {
- val rawSandboxedSdkCompat = loadSdkMethod.invoke(sdk, sdkName, sdkParams) as Any
+ val rawSandboxedSdkCompat =
+ controller.callSuspendMethod("loadSdk", sdkName, sdkParams) as Any
return SandboxedSdkWrapper(rawSandboxedSdkCompat)
- } catch (ex: InvocationTargetException) {
- throw tryRebuildCompatException(ex.targetException)
+ } catch (ex: Exception) {
+ throw tryRebuildCompatException(ex)
}
}
@@ -90,53 +80,34 @@
}
fun getSandboxedSdks(): List<SandboxedSdkWrapper> {
- val sdks = sdk.callMethod(methodName = "getSandboxedSdks") as List<*>
+ val sdks = controller.callMethod(methodName = "getSandboxedSdks") as List<*>
return sdks.map { SandboxedSdkWrapper(it!!) }
}
fun getAppOwnedSdkSandboxInterfaces(): List<AppOwnedSdkWrapper> {
- val sdks = sdk.callMethod(methodName = "getAppOwnedSdkSandboxInterfaces") as List<*>
+ val sdks = controller.callMethod(methodName = "getAppOwnedSdkSandboxInterfaces") as List<*>
return sdks.map { AppOwnedSdkWrapper(it!!) }
}
fun registerSdkSandboxActivityHandler(handler: CatchingSdkActivityHandler): IBinder {
- val classLoader = sdk.javaClass.classLoader!!
- val activityHandlerClass =
- Class.forName(SdkSandboxActivityHandlerCompat::class.java.name, false, classLoader)
+ val classLoader = controller.javaClass.classLoader!!
val proxy =
- Proxy.newProxyInstance(classLoader, arrayOf(activityHandlerClass)) { proxy, method, args
- ->
- when (method.name) {
- "hashCode" -> hashCode()
- "equals" -> proxy === args[0]
- "onActivityCreated" -> handler.setResult(args[0])
- else -> {
- throw UnsupportedOperationException(
- "Unexpected method call object:$proxy, method: $method, args: $args"
- )
- }
- }
+ classLoader.createProxyFor(
+ SdkSandboxActivityHandlerCompat::class.java.name,
+ "onActivityCreated"
+ ) {
+ handler.setResult(it!![0]!!)
}
- val registerMethod =
- sdk.javaClass.getMethod("registerSdkSandboxActivityHandler", activityHandlerClass)
-
- val token = registerMethod.invoke(sdk, proxy) as IBinder
+ val token = controller.callMethod("registerSdkSandboxActivityHandler", proxy) as IBinder
handler.proxy = proxy
return token
}
fun unregisterSdkSandboxActivityHandler(handler: CatchingSdkActivityHandler) {
- val classLoader = sdk.javaClass.classLoader!!
- val activityHandlerClass =
- Class.forName(SdkSandboxActivityHandlerCompat::class.java.name, false, classLoader)
-
- val unregisterMethod =
- sdk.javaClass.getMethod("unregisterSdkSandboxActivityHandler", activityHandlerClass)
-
- unregisterMethod.invoke(sdk, handler.proxy)
+ controller.callMethod("unregisterSdkSandboxActivityHandler", handler.proxy)
handler.proxy = null
}
}
@@ -184,8 +155,8 @@
}
/**
- * ActivityHandler to use with [TestSdkWrapper.registerSdkSandboxActivityHandler]. Store received
- * ActivityHolder.
+ * ActivityHandler to use with [SdkControllerWrapper.registerSdkSandboxActivityHandler]. Store
+ * received ActivityHolder.
*/
internal class CatchingSdkActivityHandler {
var proxy: Any? = null
@@ -217,30 +188,146 @@
}
}
-/**
- * Load SDK and wrap it as TestSDK.
- *
- * @see [TestSdkWrapper]
- */
-internal fun LocalSdkProvider.loadTestSdk(): TestSdkWrapper {
- return onLoadSdk(Bundle()).asTestSdk()
+/** Create [SandboxedSdkWrapper] using SDK context from [SandboxedSdkProviderCompat.context]. */
+internal fun LocalSdkProvider.loadTestSdk(): SdkControllerWrapper {
+ return createSdkControllerWrapperFor(extractSdkProviderClassloader(), extractSdkContext())
}
/**
- * Wrap SandboxedSdkCompat as TestSDK.
+ * Create [SandboxedSdkWrapper] using SDK context from TestSDK.
+ *
+ * TestSDK must expose public "context" property.
*
* @see [SandboxedSdkWrapper]
*/
-internal fun SandboxedSdkCompat.asTestSdk(): TestSdkWrapper {
- return TestSdkWrapper(sdk = getInterface()!!)
+internal fun SandboxedSdkCompat.asTestSdk(): SdkControllerWrapper {
+ val testSdk = getInterface()!!
+ val context = testSdk.callMethod("getContext")!!
+ return createSdkControllerWrapperFor(testSdk.javaClass.classLoader!!, context)
}
-private fun Any.callMethod(methodName: String): Any? {
- return javaClass.getMethod(methodName).invoke(this)
+/**
+ * Creates [SdkControllerWrapper] for instance of [SdkSandboxControllerCompat] class loaded by SDK
+ * classloader.
+ */
+private fun createSdkControllerWrapperFor(
+ classLoader: ClassLoader,
+ sdkContext: Any
+): SdkControllerWrapper {
+ val sdkController =
+ classLoader
+ .getClass(SdkSandboxControllerCompat::class.java.name)
+ .callStaticMethod("from", sdkContext)!!
+ return SdkControllerWrapper(sdkController)
}
-private fun LocalSdkProvider.extractVersionValue(versionFieldName: String): Int {
- val versionsClass =
- Class.forName(Versions::class.java.name, false, extractSdkProviderClassloader())
- return versionsClass.getDeclaredField(versionFieldName).get(null) as Int
+private fun ClassLoader.getClass(className: String): Class<*> =
+ Class.forName(className, false, this)
+
+private fun Class<*>.getStaticField(fieldName: String): Any? = getDeclaredField(fieldName).get(null)
+
+private fun Any.getField(fieldName: String): Any? = javaClass.getField(fieldName).get(this)
+
+/**
+ * Call static method and return result. Unwraps [InvocationTargetException] to actual exception.
+ */
+private fun Class<*>.callStaticMethod(methodName: String, vararg args: Any?): Any? {
+ try {
+ return methods.single { it.name == methodName }.invoke(null, *args)
+ } catch (ex: InvocationTargetException) {
+ throw ex.targetException
+ }
+}
+
+/** Call method and return result. Unwraps [InvocationTargetException] to actual exception. */
+private fun Any.callMethod(methodName: String, vararg args: Any?): Any? {
+ try {
+ return javaClass.methods.single { it.name == methodName }.invoke(this, *args)
+ } catch (ex: InvocationTargetException) {
+ throw ex.targetException
+ }
+}
+
+/**
+ * Call suspend method and wait for result (via runBlocking).
+ *
+ * KCallable#callSuspend can't be used here as it will pass Continuation instance loaded by app
+ * classloader while SDK will expect instance loaded by SDK classloader.
+ *
+ * Instead this method calls runBlocking() using classes loaded by SDK classloader.
+ */
+private fun Any.callSuspendMethod(methodName: String, vararg args: Any?): Any? {
+ val classLoader = javaClass.classLoader!!
+
+ val method = javaClass.methods.single { it.name == methodName }
+
+ val coroutineContextClass = classLoader.getClass("kotlin.coroutines.CoroutineContext")
+ val functionClass = classLoader.getClass("kotlin.jvm.functions.Function2")
+ val runBlockingMethod =
+ classLoader
+ .getClass("kotlinx.coroutines.BuildersKt")
+ .getMethod("runBlocking", coroutineContextClass, functionClass)
+
+ val coroutineContextInstance =
+ classLoader.getClass("kotlin.coroutines.EmptyCoroutineContext").getStaticField("INSTANCE")
+
+ val functionProxy =
+ classLoader.createProxyFor("kotlin.jvm.functions.Function2", "invoke") {
+ try {
+ // Receive Continuation as second function argument and pass it as last method
+ // argument
+ method.invoke(this, *args, it!![1])
+ } catch (ex: InvocationTargetException) {
+ // Rethrow original exception to correctly handle it later.
+ throw ex.targetException
+ }
+ }
+
+ try {
+ return runBlockingMethod.invoke(null, coroutineContextInstance, functionProxy)
+ } catch (ex: InvocationTargetException) {
+ // First unwrap InvocationTargetException to get exception while calling runBlocking
+ val runBlockingException = ex.targetException
+
+ // runBlocking doesn't declare exceptions, need to unwrap actual method exception
+ if (runBlockingException is UndeclaredThrowableException) {
+ throw runBlockingException.undeclaredThrowable
+ } else {
+ throw ex
+ }
+ }
+}
+
+/** Create Dynamic Proxy that handles single [methodName]. */
+private fun ClassLoader.createProxyFor(
+ className: String,
+ methodName: String,
+ handler: (args: Array<Any?>?) -> Any?
+): Any {
+ return createProxyFor(className) { calledMethodName, args ->
+ if (calledMethodName == methodName) {
+ handler.invoke(args)
+ } else {
+ throw UnsupportedOperationException(
+ "Unexpected method call: $calledMethodName, args: $args"
+ )
+ }
+ }
+}
+
+/** Create Dynamic Proxy that handles all methods of [className]. */
+private fun ClassLoader.createProxyFor(
+ className: String,
+ handler: (methodName: String, args: Array<Any?>?) -> Any?
+): Any {
+ return Proxy.newProxyInstance(this, arrayOf(getClass(className))) { proxy, method, args ->
+ when (method.name) {
+ "equals" -> proxy === args?.get(0)
+ "hashCode" -> hashCode()
+ "toString" -> toString()
+ else -> {
+ handler.invoke(method.name, args)
+ }
+ }
+ }
}
diff --git a/privacysandbox/sdkruntime/test-sdks/current/src/main/java/androidx/privacysandbox/sdkruntime/testsdk/CompatProvider.kt b/privacysandbox/sdkruntime/test-sdks/current/src/main/java/androidx/privacysandbox/sdkruntime/testsdk/CompatProvider.kt
index 8fa12f8..a7bcdfc 100644
--- a/privacysandbox/sdkruntime/test-sdks/current/src/main/java/androidx/privacysandbox/sdkruntime/testsdk/CompatProvider.kt
+++ b/privacysandbox/sdkruntime/test-sdks/current/src/main/java/androidx/privacysandbox/sdkruntime/testsdk/CompatProvider.kt
@@ -19,15 +19,10 @@
import android.content.Context
import android.os.Binder
import android.os.Bundle
-import android.os.IBinder
import android.view.View
-import androidx.privacysandbox.sdkruntime.core.AppOwnedSdkSandboxInterfaceCompat
import androidx.privacysandbox.sdkruntime.core.LoadSdkCompatException
import androidx.privacysandbox.sdkruntime.core.SandboxedSdkCompat
import androidx.privacysandbox.sdkruntime.core.SandboxedSdkProviderCompat
-import androidx.privacysandbox.sdkruntime.core.activity.SdkSandboxActivityHandlerCompat
-import androidx.privacysandbox.sdkruntime.core.controller.SdkSandboxControllerCompat
-import kotlinx.coroutines.runBlocking
@Suppress("unused") // Reflection usage from tests in privacysandbox:sdkruntime:sdkruntime-client
class CompatProvider : SandboxedSdkProviderCompat() {
@@ -39,7 +34,7 @@
@Throws(LoadSdkCompatException::class)
override fun onLoadSdk(params: Bundle): SandboxedSdkCompat {
- val result = CurrentVersionSdkTest(context!!)
+ val result = SdkImpl(context!!)
onLoadSdkBinder = result
lastOnLoadSdkParams = params
@@ -57,22 +52,8 @@
return View(windowContext)
}
- internal class CurrentVersionSdkTest(private val context: Context) : Binder() {
- fun getSandboxedSdks(): List<SandboxedSdkCompat> =
- SdkSandboxControllerCompat.from(context).getSandboxedSdks()
-
- fun getAppOwnedSdkSandboxInterfaces(): List<AppOwnedSdkSandboxInterfaceCompat> =
- SdkSandboxControllerCompat.from(context).getAppOwnedSdkSandboxInterfaces()
-
- fun registerSdkSandboxActivityHandler(handler: SdkSandboxActivityHandlerCompat): IBinder =
- SdkSandboxControllerCompat.from(context).registerSdkSandboxActivityHandler(handler)
-
- fun unregisterSdkSandboxActivityHandler(handler: SdkSandboxActivityHandlerCompat) {
- SdkSandboxControllerCompat.from(context).unregisterSdkSandboxActivityHandler(handler)
- }
-
- fun loadSdk(sdkName: String, sdkParams: Bundle): SandboxedSdkCompat = runBlocking {
- SdkSandboxControllerCompat.from(context).loadSdk(sdkName, sdkParams)
- }
- }
+ internal class SdkImpl(
+ @Suppress("MemberVisibilityCanBePrivate") // Reflection usage from LocalSdkTestUtils
+ val context: Context
+ ) : Binder()
}
diff --git a/privacysandbox/sdkruntime/test-sdks/v2/src/main/java/androidx/privacysandbox/sdkruntime/testsdk/CompatProvider.kt b/privacysandbox/sdkruntime/test-sdks/v2/src/main/java/androidx/privacysandbox/sdkruntime/testsdk/CompatProvider.kt
index 3df8a54..a7bcdfc 100644
--- a/privacysandbox/sdkruntime/test-sdks/v2/src/main/java/androidx/privacysandbox/sdkruntime/testsdk/CompatProvider.kt
+++ b/privacysandbox/sdkruntime/test-sdks/v2/src/main/java/androidx/privacysandbox/sdkruntime/testsdk/CompatProvider.kt
@@ -23,7 +23,6 @@
import androidx.privacysandbox.sdkruntime.core.LoadSdkCompatException
import androidx.privacysandbox.sdkruntime.core.SandboxedSdkCompat
import androidx.privacysandbox.sdkruntime.core.SandboxedSdkProviderCompat
-import androidx.privacysandbox.sdkruntime.core.controller.SdkSandboxControllerCompat
@Suppress("unused") // Reflection usage from tests in privacysandbox:sdkruntime:sdkruntime-client
class CompatProvider : SandboxedSdkProviderCompat() {
@@ -53,8 +52,8 @@
return View(windowContext)
}
- internal class SdkImpl(private val context: Context) : Binder() {
- fun getSandboxedSdks(): List<SandboxedSdkCompat> =
- SdkSandboxControllerCompat.from(context).getSandboxedSdks()
- }
+ internal class SdkImpl(
+ @Suppress("MemberVisibilityCanBePrivate") // Reflection usage from LocalSdkTestUtils
+ val context: Context
+ ) : Binder()
}
diff --git a/privacysandbox/sdkruntime/test-sdks/v4/src/main/java/androidx/privacysandbox/sdkruntime/testsdk/CompatProvider.kt b/privacysandbox/sdkruntime/test-sdks/v4/src/main/java/androidx/privacysandbox/sdkruntime/testsdk/CompatProvider.kt
index b115b53..a7bcdfc 100644
--- a/privacysandbox/sdkruntime/test-sdks/v4/src/main/java/androidx/privacysandbox/sdkruntime/testsdk/CompatProvider.kt
+++ b/privacysandbox/sdkruntime/test-sdks/v4/src/main/java/androidx/privacysandbox/sdkruntime/testsdk/CompatProvider.kt
@@ -19,14 +19,10 @@
import android.content.Context
import android.os.Binder
import android.os.Bundle
-import android.os.IBinder
import android.view.View
-import androidx.privacysandbox.sdkruntime.core.AppOwnedSdkSandboxInterfaceCompat
import androidx.privacysandbox.sdkruntime.core.LoadSdkCompatException
import androidx.privacysandbox.sdkruntime.core.SandboxedSdkCompat
import androidx.privacysandbox.sdkruntime.core.SandboxedSdkProviderCompat
-import androidx.privacysandbox.sdkruntime.core.activity.SdkSandboxActivityHandlerCompat
-import androidx.privacysandbox.sdkruntime.core.controller.SdkSandboxControllerCompat
@Suppress("unused") // Reflection usage from tests in privacysandbox:sdkruntime:sdkruntime-client
class CompatProvider : SandboxedSdkProviderCompat() {
@@ -56,18 +52,8 @@
return View(windowContext)
}
- internal class SdkImpl(private val context: Context) : Binder() {
- fun getSandboxedSdks(): List<SandboxedSdkCompat> =
- SdkSandboxControllerCompat.from(context).getSandboxedSdks()
-
- fun getAppOwnedSdkSandboxInterfaces(): List<AppOwnedSdkSandboxInterfaceCompat> =
- SdkSandboxControllerCompat.from(context).getAppOwnedSdkSandboxInterfaces()
-
- fun registerSdkSandboxActivityHandler(handler: SdkSandboxActivityHandlerCompat): IBinder =
- SdkSandboxControllerCompat.from(context).registerSdkSandboxActivityHandler(handler)
-
- fun unregisterSdkSandboxActivityHandler(handler: SdkSandboxActivityHandlerCompat) {
- SdkSandboxControllerCompat.from(context).unregisterSdkSandboxActivityHandler(handler)
- }
- }
+ internal class SdkImpl(
+ @Suppress("MemberVisibilityCanBePrivate") // Reflection usage from LocalSdkTestUtils
+ val context: Context
+ ) : Binder()
}
diff --git a/privacysandbox/sdkruntime/test-sdks/v5/src/main/java/androidx/privacysandbox/sdkruntime/testsdk/CompatProvider.kt b/privacysandbox/sdkruntime/test-sdks/v5/src/main/java/androidx/privacysandbox/sdkruntime/testsdk/CompatProvider.kt
index 62484c6..a7bcdfc 100644
--- a/privacysandbox/sdkruntime/test-sdks/v5/src/main/java/androidx/privacysandbox/sdkruntime/testsdk/CompatProvider.kt
+++ b/privacysandbox/sdkruntime/test-sdks/v5/src/main/java/androidx/privacysandbox/sdkruntime/testsdk/CompatProvider.kt
@@ -19,15 +19,10 @@
import android.content.Context
import android.os.Binder
import android.os.Bundle
-import android.os.IBinder
import android.view.View
-import androidx.privacysandbox.sdkruntime.core.AppOwnedSdkSandboxInterfaceCompat
import androidx.privacysandbox.sdkruntime.core.LoadSdkCompatException
import androidx.privacysandbox.sdkruntime.core.SandboxedSdkCompat
import androidx.privacysandbox.sdkruntime.core.SandboxedSdkProviderCompat
-import androidx.privacysandbox.sdkruntime.core.activity.SdkSandboxActivityHandlerCompat
-import androidx.privacysandbox.sdkruntime.core.controller.SdkSandboxControllerCompat
-import kotlinx.coroutines.runBlocking
@Suppress("unused") // Reflection usage from tests in privacysandbox:sdkruntime:sdkruntime-client
class CompatProvider : SandboxedSdkProviderCompat() {
@@ -57,22 +52,8 @@
return View(windowContext)
}
- internal class SdkImpl(private val context: Context) : Binder() {
- fun getSandboxedSdks(): List<SandboxedSdkCompat> =
- SdkSandboxControllerCompat.from(context).getSandboxedSdks()
-
- fun getAppOwnedSdkSandboxInterfaces(): List<AppOwnedSdkSandboxInterfaceCompat> =
- SdkSandboxControllerCompat.from(context).getAppOwnedSdkSandboxInterfaces()
-
- fun registerSdkSandboxActivityHandler(handler: SdkSandboxActivityHandlerCompat): IBinder =
- SdkSandboxControllerCompat.from(context).registerSdkSandboxActivityHandler(handler)
-
- fun unregisterSdkSandboxActivityHandler(handler: SdkSandboxActivityHandlerCompat) {
- SdkSandboxControllerCompat.from(context).unregisterSdkSandboxActivityHandler(handler)
- }
-
- fun loadSdk(sdkName: String, sdkParams: Bundle): SandboxedSdkCompat = runBlocking {
- SdkSandboxControllerCompat.from(context).loadSdk(sdkName, sdkParams)
- }
- }
+ internal class SdkImpl(
+ @Suppress("MemberVisibilityCanBePrivate") // Reflection usage from LocalSdkTestUtils
+ val context: Context
+ ) : Binder()
}
diff --git a/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/GridLayoutManager.java b/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/GridLayoutManager.java
index d778033..dbc7d6e 100644
--- a/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/GridLayoutManager.java
+++ b/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/GridLayoutManager.java
@@ -28,7 +28,6 @@
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.GridView;
-import androidx.annotation.DoNotInline;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
@@ -2128,7 +2127,6 @@
// This class is not instantiable.
}
- @DoNotInline
static boolean isAccessibilityFocused(@NonNull View view) {
return view.isAccessibilityFocused();
}
diff --git a/room/integration-tests/testapp/lint-baseline.xml b/room/integration-tests/testapp/lint-baseline.xml
index 171a19c..2daddb0 100644
--- a/room/integration-tests/testapp/lint-baseline.xml
+++ b/room/integration-tests/testapp/lint-baseline.xml
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.4.0-alpha12" type="baseline" client="gradle" dependencies="false" name="AGP (8.4.0-alpha12)" variant="all" version="8.4.0-alpha12">
+<issues format="6" by="lint 8.6.0-alpha07" type="baseline" client="gradle" dependencies="false"
+ name="AGP (8.6.0-alpha07)" variant="all" version="8.6.0-alpha07">
<issue
id="BanThreadSleep"
@@ -100,6 +101,13 @@
file="src/androidTest/java/androidx/room/integration/testapp/test/InvalidationTrackerBehavioralTest.java"/>
</issue>
+ <issue id="BanThreadSleep" message="Uses Thread.sleep()"
+ errorLine1=" Thread.sleep(200);"
+ errorLine2=" ~~~~~">
+ <location
+ file="src/androidTest/java/androidx/room/integration/testapp/test/MultiInstanceInvalidationTest.java" />
+ </issue>
+
<issue
id="MissingTestSizeAnnotation"
message="Missing test size annotation"
@@ -181,6 +189,22 @@
file="src/main/java/androidx/room/integration/testapp/database/CustomerDao.java"/>
</issue>
+ <issue id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
+ errorLine1=" boolean contains(int id, String name, String lastName);"
+ errorLine2=" ~~~~~~">
+ <location
+ file="src/main/java/androidx/room/integration/testapp/database/CustomerDao.java" />
+ </issue>
+
+ <issue id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
+ errorLine1=" boolean contains(int id, String name, String lastName);"
+ errorLine2=" ~~~~~~">
+ <location
+ file="src/main/java/androidx/room/integration/testapp/database/CustomerDao.java" />
+ </issue>
+
<issue
id="UnknownNullness"
message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
@@ -301,33 +325,6 @@
<issue
id="UnknownNullness"
message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
- errorLine1=" public static Intent intentFor(Context context, String databaseName) {"
- errorLine2=" ~~~~~~">
- <location
- file="src/main/java/androidx/room/integration/testapp/SampleDatabaseService.java"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
- errorLine1=" public static Intent intentFor(Context context, String databaseName) {"
- errorLine2=" ~~~~~~~">
- <location
- file="src/main/java/androidx/room/integration/testapp/SampleDatabaseService.java"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
- errorLine1=" public static Intent intentFor(Context context, String databaseName) {"
- errorLine2=" ~~~~~~">
- <location
- file="src/main/java/androidx/room/integration/testapp/SampleDatabaseService.java"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
errorLine1=" public abstract ProductFtsDao getProductDao();"
errorLine2=" ~~~~~~~~~~~~~">
<location
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/MultiInstanceInvalidationTest.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/MultiInstanceInvalidationTest.java
index 43e65e1..2df7d0e 100644
--- a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/MultiInstanceInvalidationTest.java
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/MultiInstanceInvalidationTest.java
@@ -16,21 +16,15 @@
package androidx.room.integration.testapp.test;
-import static org.hamcrest.CoreMatchers.hasItem;
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.hasSize;
-import static org.hamcrest.Matchers.lessThan;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
import android.content.Context;
-import android.os.SystemClock;
import androidx.annotation.NonNull;
+import androidx.arch.core.executor.ArchTaskExecutor;
import androidx.arch.core.executor.testing.CountingTaskExecutorRule;
import androidx.collection.SimpleArrayMap;
-import androidx.core.util.Pair;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.Observer;
import androidx.room.InvalidationTracker;
@@ -39,7 +33,6 @@
import androidx.room.integration.testapp.ISampleDatabaseService;
import androidx.room.integration.testapp.SampleDatabaseService;
import androidx.room.integration.testapp.database.Customer;
-import androidx.room.integration.testapp.database.CustomerDao;
import androidx.room.integration.testapp.database.Description;
import androidx.room.integration.testapp.database.Product;
import androidx.room.integration.testapp.database.SampleDatabase;
@@ -52,14 +45,12 @@
import org.junit.After;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestName;
import org.junit.runner.RunWith;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
@@ -119,30 +110,29 @@
if (mService != null) {
serviceRule.unbindService();
}
- for (int i = 0, size = mDatabases.size(); i < size; i++) {
- mDatabases.get(i).close();
+ for (RoomDatabase db : mDatabases) {
+ db.close();
}
- mExecutorRule.drainTasks(2, TimeUnit.SECONDS);
+ mExecutorRule.drainTasks(3, TimeUnit.SECONDS);
+ assertWithMessage("Executor isIdle")
+ .that(mExecutorRule.isIdle())
+ .isTrue();
}
- // TODO(324609478): broken test
- @Ignore
@Test
public void invalidateInAnotherInstance() throws Exception {
final SampleDatabase db1 = openDatabase(true);
final SampleDatabase db2 = openDatabase(true);
final CountDownLatch invalidated1 = prepareTableObserver(db1);
- final CountDownLatch changed1 = prepareLiveDataObserver(db1).first;
db2.getCustomerDao().insert(CUSTOMER_1);
- assertTrue(invalidated1.await(3, TimeUnit.SECONDS));
- assertTrue(changed1.await(3, TimeUnit.SECONDS));
+ assertWithMessage("Observer invalidation")
+ .that(invalidated1.await(3, TimeUnit.SECONDS))
+ .isTrue();
}
- // TODO(324274513): broken test
- @Ignore
@Test
public void invalidateInAnotherInstanceFts() throws Exception {
final SampleFtsDatabase db1 = openFtsDatabase(true);
@@ -162,10 +152,13 @@
db2.getProductDao().addDescription(new Description(1, "Wonderful candy."));
- assertTrue(changed.await(3, TimeUnit.SECONDS));
+ assertWithMessage("Observer invalidation")
+ .that(changed.await(3, TimeUnit.SECONDS))
+ .isTrue();
+
List<Product> result = db1.getProductDao().getProductsWithDescription("candy");
- assertThat(result.size(), is(1));
- assertThat(result.get(0), is(theProduct));
+ assertThat(result).hasSize(1);
+ assertThat(result).containsExactly(theProduct);
}
@Test
@@ -174,18 +167,17 @@
final SampleDatabase db2 = openDatabase(false);
final CountDownLatch invalidated1 = prepareTableObserver(db1);
- final CountDownLatch changed1 = prepareLiveDataObserver(db1).first;
final CountDownLatch invalidated2 = prepareTableObserver(db2);
- final CountDownLatch changed2 = prepareLiveDataObserver(db2).first;
db2.getCustomerDao().insert(CUSTOMER_1);
- assertTrue(invalidated2.await(3, TimeUnit.SECONDS));
- assertTrue(changed2.await(3, TimeUnit.SECONDS));
+ assertWithMessage("Observer invalidation")
+ .that(invalidated1.await(200, TimeUnit.MILLISECONDS))
+ .isFalse();
- assertFalse(invalidated1.await(300, TimeUnit.MILLISECONDS));
- assertFalse(changed1.await(300, TimeUnit.MILLISECONDS));
- assertThat(db1.getCustomerDao().countCustomers(), is(1));
+ assertWithMessage("Observer invalidation")
+ .that(invalidated2.await(3, TimeUnit.SECONDS))
+ .isTrue();
}
@Test
@@ -194,22 +186,19 @@
final SampleDatabase db2 = openDatabase(true); // Enabled only on one side
final CountDownLatch invalidated1 = prepareTableObserver(db1);
- final CountDownLatch changed1 = prepareLiveDataObserver(db1).first;
final CountDownLatch invalidated2 = prepareTableObserver(db2);
- final CountDownLatch changed2 = prepareLiveDataObserver(db2).first;
db2.getCustomerDao().insert(CUSTOMER_1);
- assertTrue(invalidated2.await(3, TimeUnit.SECONDS));
- assertTrue(changed2.await(3, TimeUnit.SECONDS));
+ assertWithMessage("Observer invalidation")
+ .that(invalidated1.await(200, TimeUnit.MILLISECONDS))
+ .isFalse();
- assertFalse(invalidated1.await(300, TimeUnit.MILLISECONDS));
- assertFalse(changed1.await(300, TimeUnit.MILLISECONDS));
- assertThat(db1.getCustomerDao().countCustomers(), is(1));
+ assertWithMessage("Observer invalidation")
+ .that(invalidated2.await(3, TimeUnit.SECONDS))
+ .isTrue();
}
- // TODO(335890993): broken test
- @Ignore
@Test
public void invalidationInAnotherInstance_closed() throws Exception {
final SampleDatabase db1 = openDatabase(true);
@@ -217,179 +206,66 @@
final SampleDatabase db3 = openDatabase(true);
final CountDownLatch invalidated1 = prepareTableObserver(db1);
- final Pair<CountDownLatch, CountDownLatch> changed1 = prepareLiveDataObserver(db1);
final CountDownLatch invalidated2 = prepareTableObserver(db2);
- final Pair<CountDownLatch, CountDownLatch> changed2 = prepareLiveDataObserver(db2);
final CountDownLatch invalidated3 = prepareTableObserver(db3);
- final Pair<CountDownLatch, CountDownLatch> changed3 = prepareLiveDataObserver(db3);
-
- db2.getCustomerDao().insert(CUSTOMER_1);
-
- assertTrue(invalidated1.await(3, TimeUnit.SECONDS));
- assertTrue(changed1.first.await(3, TimeUnit.SECONDS));
- assertTrue(invalidated2.await(3, TimeUnit.SECONDS));
- assertTrue(changed2.first.await(3, TimeUnit.SECONDS));
- assertTrue(invalidated3.await(3, TimeUnit.SECONDS));
- assertTrue(changed3.first.await(3, TimeUnit.SECONDS));
db3.close();
- db2.getCustomerDao().insert(CUSTOMER_2);
-
- assertTrue(changed1.second.await(3, TimeUnit.SECONDS));
- assertTrue(changed2.second.await(3, TimeUnit.SECONDS));
- assertFalse(changed3.second.await(3, TimeUnit.SECONDS));
- }
-
- @Ignore // Flaky b/330519843
- @Test
- public void invalidationCausesNoLoop() throws Exception {
- final SampleDatabase db1 = openDatabase(true);
- final SampleDatabase db2 = openDatabase(true);
-
- final CountDownLatch invalidated1 = prepareTableObserver(db1);
- final CountDownLatch changed1 = prepareLiveDataObserver(db1).first;
-
db2.getCustomerDao().insert(CUSTOMER_1);
- mExecutorRule.drainTasks(300, TimeUnit.MILLISECONDS);
- assertFalse(invalidated1.await(300, TimeUnit.MILLISECONDS));
- assertFalse(changed1.await(300, TimeUnit.MILLISECONDS));
+ assertWithMessage("Observer invalidation")
+ .that(invalidated1.await(3, TimeUnit.SECONDS))
+ .isTrue();
+ assertWithMessage("Observer invalidation")
+ .that(invalidated2.await(3, TimeUnit.SECONDS))
+ .isTrue();
+ assertWithMessage("Observer invalidation")
+ .that(invalidated3.await(200, TimeUnit.MILLISECONDS))
+ .isFalse();
}
@Test
- public void reopen() throws Exception {
- final SampleDatabase db1 = openDatabase(true);
- final Product product = new Product();
- product.setId(1);
- product.setName("A");
- db1.getProductDao().insert(product);
- db1.close();
- final SampleDatabase db2 = openDatabase(true);
- final CountDownLatch invalidated2 = prepareTableObserver(db2);
- final CountDownLatch changed2 = prepareLiveDataObserver(db2).first;
- db2.getCustomerDao().insert(CUSTOMER_1);
- assertTrue(invalidated2.await(3, TimeUnit.SECONDS));
- assertTrue(changed2.await(3, TimeUnit.SECONDS));
- }
-
- // TODO(186405912): broken test
- @Ignore
- @Test
public void invalidatedByAnotherProcess() throws Exception {
bindTestService();
+
final SampleDatabase db = openDatabase(true);
- assertThat(db.getCustomerDao().countCustomers(), is(0));
+ assertThat(db.getCustomerDao().countCustomers()).isEqualTo(0);
final CountDownLatch invalidated = prepareTableObserver(db);
- final CountDownLatch changed = prepareLiveDataObserver(db).first;
mService.insertCustomer(CUSTOMER_1.getId(), CUSTOMER_1.getName(), CUSTOMER_1.getLastName());
- assertTrue(invalidated.await(3, TimeUnit.SECONDS));
- assertTrue(changed.await(3, TimeUnit.SECONDS));
+ assertWithMessage("Observer invalidation")
+ .that(invalidated.await(3, TimeUnit.SECONDS))
+ .isTrue();
}
- // TODO(186405912): broken test
- @Ignore
@Test
public void invalidateAnotherProcess() throws Exception {
bindTestService();
+
final SampleDatabase db = openDatabase(true);
- db.getCustomerDao().insert(CUSTOMER_1);
- assertTrue(mService.waitForCustomer(CUSTOMER_1.getId(), CUSTOMER_1.getName(),
- CUSTOMER_1.getLastName()));
- }
- // TODO: b/72877822 Better performance measurement
- @Ignore
- @Test
- public void performance_oneByOne() {
- final List<Customer> customers = generateCustomers(100);
- final long[] elapsed = measureSeveralTimesEach(false, customers);
- final String message = createMessage(elapsed);
- assertThat(message, (double) elapsed[1], is(lessThan(elapsed[0] * 1.2)));
- }
+ ArchTaskExecutor.getIOThreadExecutor().execute(
+ () -> {
+ // This sleep is needed to wait for the IPC of waitForCustomer() to
+ // reach the other process and to let the observer in the other process
+ // subscribe to invalidation. If we insert before the other process registers
+ // the observer then we'll miss the observer.
+ try {
+ Thread.sleep(200);
+ } catch (InterruptedException e) {
+ // no-op
+ }
+ db.getCustomerDao().insert(CUSTOMER_1);
+ }
+ );
- // TODO: b/72877822 Better performance measurement
- @Ignore
- @Test
- public void performance_bulk() {
- final List<Customer> customers = generateCustomers(10000);
- final long[] elapsed = measureSeveralTimesEach(true, customers);
- final String message = createMessage(elapsed);
- assertThat(message, (double) elapsed[1], is(lessThan(elapsed[0] * 1.2)));
- }
-
- private static String createMessage(long[] elapsed) {
- return "Without multi-instance invalidation: " + elapsed[0] + " ms, "
- + "with multi-instance invalidation: " + elapsed[1] + " ms.";
- }
-
- private List<Customer> generateCustomers(int count) {
- final ArrayList<Customer> customers = new ArrayList<>();
- for (int i = 0; i < count; i++) {
- final Customer customer = new Customer();
- int id = i + 1;
- customer.setId(id);
- customer.setName("Name" + id);
- customer.setLastName("LastName" + id);
- customers.add(customer);
- }
- return customers;
- }
-
- private long[] measureSeveralTimesEach(boolean bulk, List<Customer> customers) {
- final int n = 10;
- final long[] results1 = new long[n];
- final long[] results2 = new long[n];
- for (int i = 0; i < n; i++) {
- results2[i] = measure(true, bulk, customers);
- results1[i] = measure(false, bulk, customers);
- }
-
- // Median
- Arrays.sort(results1);
- Arrays.sort(results2);
- final long result1 = results1[results1.length / 2];
- final long result2 = results2[results2.length / 2];
-
- return new long[]{result1, result2};
- }
-
-
- @SuppressWarnings("deprecation")
- private long measure(boolean multiInstanceInvalidation, boolean bulk,
- List<Customer> customers) {
- final Context context = ApplicationProvider.getApplicationContext();
- context.deleteDatabase(mDatabaseName);
- final SampleDatabase db = openDatabase(multiInstanceInvalidation);
- final CustomerDao dao = db.getCustomerDao();
- final InvalidationTracker.Observer observer = new InvalidationTracker.Observer("Customer") {
- @Override
- public void onInvalidated(Set<String> tables) {
- // This observer is only for creating triggers.
- }
- };
- db.getInvalidationTracker().addObserver(observer);
- final long start = SystemClock.currentThreadTimeMillis();
- if (bulk) {
- db.beginTransaction();
- }
- try {
- for (int i = 0, size = customers.size(); i < size; i++) {
- dao.insert(customers.get(i));
- }
- if (bulk) {
- db.setTransactionSuccessful();
- }
- } finally {
- if (bulk) {
- db.endTransaction();
- }
- }
- long elapsed = SystemClock.currentThreadTimeMillis() - start;
- db.getInvalidationTracker().removeObserver(observer);
- return elapsed;
+ boolean awaitResult = mService.waitForCustomer(
+ CUSTOMER_1.getId(), CUSTOMER_1.getName(), CUSTOMER_1.getLastName());
+ assertWithMessage(
+ "Observer invalidation in another process")
+ .that(awaitResult)
+ .isTrue();
}
private SampleDatabase openDatabase(boolean multiInstanceInvalidation) {
@@ -430,40 +306,12 @@
db.getInvalidationTracker()
.addObserver(new InvalidationTracker.Observer("Customer", "Product") {
@Override
- public void onInvalidated(Set<String> tables) {
- assertThat(tables, hasSize(1));
- assertThat(tables, hasItem("Customer"));
+ public void onInvalidated(@NonNull Set<String> tables) {
+ assertThat(tables).hasSize(1);
+ assertThat(tables).contains("Customer");
invalidated.countDown();
}
});
return invalidated;
}
-
- private Pair<CountDownLatch, CountDownLatch> prepareLiveDataObserver(SampleDatabase db)
- throws InterruptedException {
- final CountDownLatch initialized = new CountDownLatch(1);
- final CountDownLatch changedFirst = new CountDownLatch(1);
- final CountDownLatch changedSecond = new CountDownLatch(1);
- final Observer<List<Customer>> observer = customers -> {
- if (customers == null || customers.isEmpty()) {
- initialized.countDown();
- } else if (changedFirst.getCount() > 0) {
- assertThat(customers, hasSize(1));
- assertThat(customers, hasItem(CUSTOMER_1));
- changedFirst.countDown();
- } else {
- assertThat(customers, hasSize(2));
- assertThat(customers, hasItem(CUSTOMER_1));
- assertThat(customers, hasItem(CUSTOMER_2));
- changedSecond.countDown();
- }
- };
- final LiveData<List<Customer>> customers = db.getCustomerDao().all();
- InstrumentationRegistry.getInstrumentation().runOnMainSync(() ->
- customers.observeForever(observer));
- mObservers.put(customers, observer);
- // Make sure that this observer is ready before inserting an item in another instance.
- initialized.await(3, TimeUnit.SECONDS);
- return new Pair<>(changedFirst, changedSecond);
- }
}
diff --git a/room/integration-tests/testapp/src/main/java/androidx/room/integration/testapp/SampleDatabaseService.java b/room/integration-tests/testapp/src/main/java/androidx/room/integration/testapp/SampleDatabaseService.java
index 38de612..c2b92b4 100644
--- a/room/integration-tests/testapp/src/main/java/androidx/room/integration/testapp/SampleDatabaseService.java
+++ b/room/integration-tests/testapp/src/main/java/androidx/room/integration/testapp/SampleDatabaseService.java
@@ -19,20 +19,18 @@
import android.app.Service;
import android.content.Context;
import android.content.Intent;
-import android.os.Handler;
import android.os.IBinder;
-import android.os.Looper;
import android.os.Process;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import androidx.lifecycle.LiveData;
-import androidx.lifecycle.Observer;
import androidx.room.ExperimentalRoomApi;
+import androidx.room.InvalidationTracker;
import androidx.room.Room;
import androidx.room.integration.testapp.database.Customer;
import androidx.room.integration.testapp.database.SampleDatabase;
-import java.util.List;
+import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -61,25 +59,23 @@
@Override
public boolean waitForCustomer(int id, String name, String lastName) {
- final Customer customer = new Customer();
- customer.setId(id);
- customer.setName(name);
- customer.setLastName(lastName);
final CountDownLatch changed = new CountDownLatch(1);
- final Observer<List<Customer>> observer = list -> {
- if (list != null && list.size() >= 1 && list.contains(customer)) {
- changed.countDown();
+ final InvalidationTracker.Observer observer = new InvalidationTracker.Observer(
+ Customer.class.getSimpleName()) {
+ @Override
+ public void onInvalidated(@NonNull Set<String> tables) {
+ if (mDatabase.getCustomerDao().contains(id, name, lastName)) {
+ changed.countDown();
+ }
}
};
- final LiveData<List<Customer>> customers = mDatabase.getCustomerDao().all();
- final Handler handler = new Handler(Looper.getMainLooper());
- handler.post(() -> customers.observeForever(observer));
+ mDatabase.getInvalidationTracker().addObserver(observer);
try {
return changed.await(3, TimeUnit.SECONDS);
} catch (InterruptedException e) {
return false;
} finally {
- handler.post(() -> customers.removeObserver(observer));
+ mDatabase.getInvalidationTracker().removeObserver(observer);
}
}
};
@@ -93,11 +89,12 @@
/**
* Creates the test service for the given database name
- * @param context The context to creat the intent
+ * @param context The context to create the intent
* @param databaseName The database name to be used
* @return A new intent that can be used to connect to this service
*/
- public static Intent intentFor(Context context, String databaseName) {
+ @NonNull
+ public static Intent intentFor(@NonNull Context context, @NonNull String databaseName) {
Intent intent = new Intent(context, SampleDatabaseService.class);
intent.putExtra(DATABASE_NAME_PARAM, databaseName);
return intent;
@@ -109,7 +106,7 @@
public IBinder onBind(Intent intent) {
String databaseName = intent.getStringExtra(DATABASE_NAME_PARAM);
if (databaseName == null) {
- throw new IllegalArgumentException("must pass database name in the intent");
+ throw new IllegalArgumentException("Must pass database name in the intent");
}
if (mDatabase != null) {
throw new IllegalStateException("Cannot re-use the same service for different tests");
diff --git a/room/integration-tests/testapp/src/main/java/androidx/room/integration/testapp/database/CustomerDao.java b/room/integration-tests/testapp/src/main/java/androidx/room/integration/testapp/database/CustomerDao.java
index 7ab9523..14bc4691 100644
--- a/room/integration-tests/testapp/src/main/java/androidx/room/integration/testapp/database/CustomerDao.java
+++ b/room/integration-tests/testapp/src/main/java/androidx/room/integration/testapp/database/CustomerDao.java
@@ -54,4 +54,10 @@
*/
@Query("SELECT * FROM customer")
LiveData<List<Customer>> all();
+
+ /**
+ * @return True if customer is found
+ */
+ @Query("SELECT 1 FROM customer WHERE mId = :id AND mName = :name AND mLastName = :lastName")
+ boolean contains(int id, String name, String lastName);
}
diff --git a/room/room-runtime/src/androidInstrumentedTest/kotlin/androidx/room/MultiInstanceInvalidationTest.kt b/room/room-runtime/src/androidInstrumentedTest/kotlin/androidx/room/MultiInstanceInvalidationTest.kt
index 10605d2..a5d8e0f 100644
--- a/room/room-runtime/src/androidInstrumentedTest/kotlin/androidx/room/MultiInstanceInvalidationTest.kt
+++ b/room/room-runtime/src/androidInstrumentedTest/kotlin/androidx/room/MultiInstanceInvalidationTest.kt
@@ -19,14 +19,16 @@
import android.app.ActivityManager
import android.content.Context
import android.os.Build
+import androidx.arch.core.executor.testing.CountingTaskExecutorRule
import androidx.kruth.assertThat
+import androidx.kruth.assertWithMessage
import androidx.room.support.AutoClosingRoomOpenHelper
import androidx.test.core.app.ApplicationProvider
import androidx.test.filters.SdkSuppress
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
import kotlin.test.Test
-import org.junit.Before
+import org.junit.Rule
class MultiInstanceInvalidationTest {
@Entity data class SampleEntity(@PrimaryKey val pk: Int)
@@ -34,44 +36,52 @@
@Database(entities = [SampleEntity::class], version = 1, exportSchema = false)
abstract class SampleDatabase : RoomDatabase()
- private lateinit var autoCloseDb: SampleDatabase
+ @get:Rule val countingTaskExecutorRule = CountingTaskExecutorRule()
- @Suppress("DEPRECATION") // For `getRunningServices()`
@Test
- @SdkSuppress(minSdkVersion = Build.VERSION_CODES.N)
- fun invalidateInAnotherInstanceAutoCloser() {
- val latch = CountDownLatch(1)
- val context = ApplicationProvider.getApplicationContext<Context>()
- val manager = context.getSystemService(ActivityManager::class.java)
- val autoCloseHelper = autoCloseDb.openHelper as AutoClosingRoomOpenHelper
- val autoCloser = autoCloseHelper.autoCloser
- autoCloseHelper.writableDatabase
- // Make sure the service is running.
- assertThat(manager.getRunningServices(100)).isNotEmpty()
-
- // Let Room call setAutoCloseCallback
- val trackerCallback = autoCloser.onAutoCloseCallback
- autoCloser.setAutoCloseCallback {
- trackerCallback?.run()
- // At this point in time InvalidationTracker's callback has run and unbind should have
- // been invoked.
- latch.countDown()
- }
- latch.await()
-
- // Make sure the service is no longer running.
- assertThat(manager.getRunningServices(100)).isEmpty()
- autoCloseDb.close()
- }
-
@OptIn(ExperimentalRoomApi::class)
- @Before
- fun initDb() {
- val context: Context = ApplicationProvider.getApplicationContext()
- autoCloseDb =
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.N)
+ @Suppress("DEPRECATION") // For getRunningServices()
+ fun invalidateInAnotherInstanceAutoCloser() {
+ val context = ApplicationProvider.getApplicationContext<Context>()
+ val autoCloseDb =
Room.databaseBuilder(context, SampleDatabase::class.java, "MyDb")
.enableMultiInstanceInvalidation()
.setAutoCloseTimeout(200, TimeUnit.MILLISECONDS)
.build()
+ val manager = context.getSystemService(ActivityManager::class.java)
+ val autoCloseHelper = autoCloseDb.openHelper as AutoClosingRoomOpenHelper
+
+ // Force open the database causing the multi-instance invalidation service to start
+ autoCloseHelper.writableDatabase
+
+ // Assert multi-instance invalidation service is running.
+ assertThat(manager.getRunningServices(100)).isNotEmpty()
+
+ // Remember the current auto close callback, it should be the one installed by the
+ // invalidation tracker
+ val trackerCallback = autoCloseHelper.autoCloser.onAutoCloseCallback!!
+
+ // Set a new callback, intercepting when DB is auto-closed
+ val latch = CountDownLatch(1)
+ autoCloseHelper.autoCloser.setAutoCloseCallback {
+ // Run the remember auto close callback
+ trackerCallback.run()
+ // At this point in time InvalidationTracker's callback has run and unbind should have
+ // been invoked.
+ latch.countDown()
+ }
+
+ assertWithMessage("Auto close callback latch await")
+ .that(latch.await(2, TimeUnit.SECONDS))
+ .isTrue()
+
+ countingTaskExecutorRule.drainTasks(2, TimeUnit.SECONDS)
+ assertWithMessage("Executor isIdle").that(countingTaskExecutorRule.isIdle).isTrue()
+
+ // Assert multi-instance invalidation service is no longer running.
+ assertThat(manager.getRunningServices(100)).isEmpty()
+
+ autoCloseDb.close()
}
}
diff --git a/room/room-runtime/src/androidMain/kotlin/androidx/room/InvalidationTracker.android.kt b/room/room-runtime/src/androidMain/kotlin/androidx/room/InvalidationTracker.android.kt
index e3d33a4..f4b7057 100644
--- a/room/room-runtime/src/androidMain/kotlin/androidx/room/InvalidationTracker.android.kt
+++ b/room/room-runtime/src/androidMain/kotlin/androidx/room/InvalidationTracker.android.kt
@@ -166,12 +166,11 @@
val state = checkNotNull(multiInstanceClientInitState)
multiInstanceInvalidationClient =
MultiInstanceInvalidationClient(
- context = state.context,
- name = state.name,
- serviceIntent = state.serviceIntent,
- invalidationTracker = this,
- executor = database.queryExecutor
- )
+ context = state.context,
+ name = state.name,
+ invalidationTracker = this,
+ )
+ .apply { start(state.serviceIntent) }
}
private fun stopMultiInstanceInvalidation() {
@@ -216,6 +215,12 @@
implementation.addObserver(observer)
}
+ /** An internal [addObserver] for remote observer only that skips trigger syncing. */
+ internal fun addRemoteObserver(observer: Observer): Unit {
+ check(observer.isRemote) { "isRemote was false of observer argument" }
+ implementation.addObserverOnly(observer)
+ }
+
/**
* Adds an observer but keeps a weak reference back to it.
*
diff --git a/room/room-runtime/src/androidMain/kotlin/androidx/room/MultiInstanceInvalidationClient.android.kt b/room/room-runtime/src/androidMain/kotlin/androidx/room/MultiInstanceInvalidationClient.android.kt
index 491e677..9a023dc 100644
--- a/room/room-runtime/src/androidMain/kotlin/androidx/room/MultiInstanceInvalidationClient.android.kt
+++ b/room/room-runtime/src/androidMain/kotlin/androidx/room/MultiInstanceInvalidationClient.android.kt
@@ -23,8 +23,8 @@
import android.os.RemoteException
import android.util.Log
import androidx.room.Room.LOG_TAG
-import java.util.concurrent.Executor
import java.util.concurrent.atomic.AtomicBoolean
+import kotlinx.coroutines.launch
/**
* Handles all the communication from [RoomDatabase] and [InvalidationTracker] to
@@ -32,88 +32,80 @@
*
* @param context The Context to be used for binding [IMultiInstanceInvalidationService].
* @param name The name of the database file.
- * @param serviceIntent The [Intent] used for binding [IMultiInstanceInvalidationService].
* @param invalidationTracker The [InvalidationTracker]
- * @param executor The background executor.
*/
internal class MultiInstanceInvalidationClient(
context: Context,
val name: String,
- serviceIntent: Intent,
val invalidationTracker: InvalidationTracker,
- val executor: Executor
) {
private val appContext = context.applicationContext
+ private val coroutineScope = invalidationTracker.database.getCoroutineScope()
+
+ private val stopped = AtomicBoolean(true)
/** The client ID assigned by [MultiInstanceInvalidationService]. */
- var clientId = 0
- lateinit var observer: InvalidationTracker.Observer
- var service: IMultiInstanceInvalidationService? = null
+ private var clientId = 0
+ private var invalidationService: IMultiInstanceInvalidationService? = null
- val callback: IMultiInstanceInvalidationCallback =
+ /** All table observer to notify service of changes. */
+ private val observer =
+ object : InvalidationTracker.Observer(invalidationTracker.tableNames) {
+ override fun onInvalidated(tables: Set<String>) {
+ if (stopped.get()) {
+ return
+ }
+
+ try {
+ invalidationService?.broadcastInvalidation(clientId, tables.toTypedArray())
+ } catch (e: RemoteException) {
+ Log.w(LOG_TAG, "Cannot broadcast invalidation", e)
+ }
+ }
+
+ override val isRemote: Boolean
+ get() = true
+ }
+
+ private val invalidationCallback: IMultiInstanceInvalidationCallback =
object : IMultiInstanceInvalidationCallback.Stub() {
override fun onInvalidation(tables: Array<out String>) {
- executor.execute { invalidationTracker.notifyObserversByTableNames(*tables) }
+ coroutineScope.launch { invalidationTracker.notifyObserversByTableNames(*tables) }
}
}
- val stopped = AtomicBoolean(false)
-
- val serviceConnection: ServiceConnection =
+ private val serviceConnection: ServiceConnection =
object : ServiceConnection {
override fun onServiceConnected(name: ComponentName, service: IBinder) {
- [email protected] =
- IMultiInstanceInvalidationService.Stub.asInterface(service)
- executor.execute(setUpRunnable)
+ invalidationService = IMultiInstanceInvalidationService.Stub.asInterface(service)
+ registerCallback()
}
override fun onServiceDisconnected(name: ComponentName) {
- executor.execute(removeObserverRunnable)
- service = null
+ invalidationService = null
}
}
- val setUpRunnable = Runnable {
+ private fun registerCallback() {
try {
- service?.let {
- clientId = it.registerCallback(callback, name)
- invalidationTracker.addObserver(observer)
- }
+ invalidationService?.let { clientId = it.registerCallback(invalidationCallback, name) }
} catch (e: RemoteException) {
Log.w(LOG_TAG, "Cannot register multi-instance invalidation callback", e)
}
}
- val removeObserverRunnable = Runnable { invalidationTracker.removeObserver(observer) }
-
- init {
- // Use all tables names for observer.
- val tableNames = invalidationTracker.tableNames
- observer =
- object : InvalidationTracker.Observer(tableNames) {
- override fun onInvalidated(tables: Set<String>) {
- if (stopped.get()) {
- return
- }
-
- try {
- service?.broadcastInvalidation(clientId, tables.toTypedArray())
- } catch (e: RemoteException) {
- Log.w(LOG_TAG, "Cannot broadcast invalidation", e)
- }
- }
-
- override val isRemote: Boolean
- get() = true
- }
- appContext.bindService(serviceIntent, serviceConnection, Context.BIND_AUTO_CREATE)
+ fun start(serviceIntent: Intent) {
+ if (stopped.compareAndSet(true, false)) {
+ appContext.bindService(serviceIntent, serviceConnection, Context.BIND_AUTO_CREATE)
+ invalidationTracker.addRemoteObserver(observer)
+ }
}
fun stop() {
if (stopped.compareAndSet(false, true)) {
invalidationTracker.removeObserver(observer)
try {
- service?.unregisterCallback(callback, clientId)
+ invalidationService?.unregisterCallback(invalidationCallback, clientId)
} catch (e: RemoteException) {
Log.w(LOG_TAG, "Cannot unregister multi-instance invalidation callback", e)
}
diff --git a/room/room-runtime/src/commonMain/kotlin/androidx/room/InvalidationTracker.kt b/room/room-runtime/src/commonMain/kotlin/androidx/room/InvalidationTracker.kt
index 07d6ba4..16c09cb 100644
--- a/room/room-runtime/src/commonMain/kotlin/androidx/room/InvalidationTracker.kt
+++ b/room/room-runtime/src/commonMain/kotlin/androidx/room/InvalidationTracker.kt
@@ -201,8 +201,20 @@
connection.execSQL(CREATE_TRACKING_TABLE_SQL)
}
- /** Add an observer and return true if it was actually added, or false if already added. */
+ /**
+ * Add an observer, sync triggers and return true if it was actually added, or false if already
+ * added.
+ */
internal suspend fun addObserver(observer: Observer): Boolean {
+ val shouldSync = addObserverOnly(observer)
+ if (shouldSync) {
+ syncTriggers()
+ }
+ return shouldSync
+ }
+
+ /** Add an observer and return true if it was actually added, or false if already added. */
+ internal fun addObserverOnly(observer: Observer): Boolean {
val (resolvedTableNames, tableIds) = validateTableNames(observer.tables)
val wrapper =
ObserverWrapper(
@@ -219,7 +231,15 @@
observerMap.put(observer, wrapper)
}
}
- val shouldSync = currentObserver == null && observedTableStates.onObserverAdded(tableIds)
+ return currentObserver == null && observedTableStates.onObserverAdded(tableIds)
+ }
+
+ /**
+ * Removes an observer, sync triggers and return true if it was actually removed, or false if it
+ * was not found.
+ */
+ internal suspend fun removeObserver(observer: Observer): Boolean {
+ val shouldSync = removeObserverOnly(observer)
if (shouldSync) {
syncTriggers()
}
@@ -229,13 +249,9 @@
/**
* Removes an observer and return true if it was actually removed, or false if it was not found.
*/
- internal suspend fun removeObserver(observer: Observer): Boolean {
+ private fun removeObserverOnly(observer: Observer): Boolean {
val wrapper = observerMapLock.withLock { observerMap.remove(observer) }
- val shouldSync = wrapper != null && observedTableStates.onObserverRemoved(wrapper.tableIds)
- if (shouldSync) {
- syncTriggers()
- }
- return shouldSync
+ return wrapper != null && observedTableStates.onObserverRemoved(wrapper.tableIds)
}
/** Resolves the list of tables and views into unique table names and ids. */
diff --git a/samples/MediaRoutingDemo/src/main/java/com/example/androidx/mediarouting/activities/MainActivity.java b/samples/MediaRoutingDemo/src/main/java/com/example/androidx/mediarouting/activities/MainActivity.java
index e360241..65e89dd 100644
--- a/samples/MediaRoutingDemo/src/main/java/com/example/androidx/mediarouting/activities/MainActivity.java
+++ b/samples/MediaRoutingDemo/src/main/java/com/example/androidx/mediarouting/activities/MainActivity.java
@@ -47,7 +47,6 @@
import android.widget.TextView;
import android.widget.Toast;
-import androidx.annotation.DoNotInline;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
@@ -772,17 +771,14 @@
// This class is not instantiable.
}
- @DoNotInline
static boolean canDrawOverlays(Context context) {
return Settings.canDrawOverlays(context);
}
- @DoNotInline
static boolean shouldShowRequestPermissionRationale(Activity activity, String permission) {
return activity.shouldShowRequestPermissionRationale(permission);
}
- @DoNotInline
static void requestPermissions(Activity activity, String[] permissions, int requestCode) {
activity.requestPermissions(permissions, requestCode);
}
diff --git a/samples/MediaRoutingDemo/src/main/java/com/example/androidx/mediarouting/activities/systemrouting/source/AudioManagerSystemRoutesSource.java b/samples/MediaRoutingDemo/src/main/java/com/example/androidx/mediarouting/activities/systemrouting/source/AudioManagerSystemRoutesSource.java
index 80d6e33..096ebfd 100644
--- a/samples/MediaRoutingDemo/src/main/java/com/example/androidx/mediarouting/activities/systemrouting/source/AudioManagerSystemRoutesSource.java
+++ b/samples/MediaRoutingDemo/src/main/java/com/example/androidx/mediarouting/activities/systemrouting/source/AudioManagerSystemRoutesSource.java
@@ -22,7 +22,6 @@
import android.media.AudioManager;
import android.os.Build;
-import androidx.annotation.DoNotInline;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
@@ -115,7 +114,6 @@
// This class is not instantiable.
}
- @DoNotInline
static String getAddress(AudioDeviceInfo audioDeviceInfo) {
return audioDeviceInfo.getAddress();
}
diff --git a/samples/MediaRoutingDemo/src/main/java/com/example/androidx/mediarouting/player/Player.java b/samples/MediaRoutingDemo/src/main/java/com/example/androidx/mediarouting/player/Player.java
index 4080f47..51b8acd 100644
--- a/samples/MediaRoutingDemo/src/main/java/com/example/androidx/mediarouting/player/Player.java
+++ b/samples/MediaRoutingDemo/src/main/java/com/example/androidx/mediarouting/player/Player.java
@@ -38,7 +38,6 @@
import android.widget.Toast;
import androidx.annotation.CallSuper;
-import androidx.annotation.DoNotInline;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
@@ -397,24 +396,20 @@
// This class is not instantiable.
}
- @DoNotInline
static NotificationChannel createNotificationChannel(String notificationChannelId,
String name, int importance) {
return new NotificationChannel(notificationChannelId, name, importance);
}
- @DoNotInline
static void createNotificationChannel(NotificationManager notificationManager,
NotificationChannel channel) {
notificationManager.createNotificationChannel(channel);
}
- @DoNotInline
static void setDescription(NotificationChannel notificationChannel, String description) {
notificationChannel.setDescription(description);
}
- @DoNotInline
static NotificationManager getSystemServiceReturnsNotificationManager(Context context) {
return context.getSystemService(NotificationManager.class);
}
diff --git a/samples/MediaRoutingDemo/src/main/java/com/example/androidx/mediarouting/providers/SampleMediaRouteProvider.java b/samples/MediaRoutingDemo/src/main/java/com/example/androidx/mediarouting/providers/SampleMediaRouteProvider.java
index b10fabb..4db6b6b 100644
--- a/samples/MediaRoutingDemo/src/main/java/com/example/androidx/mediarouting/providers/SampleMediaRouteProvider.java
+++ b/samples/MediaRoutingDemo/src/main/java/com/example/androidx/mediarouting/providers/SampleMediaRouteProvider.java
@@ -31,7 +31,6 @@
import android.provider.Settings;
import android.util.Log;
-import androidx.annotation.DoNotInline;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
@@ -735,7 +734,6 @@
// This class is not instantiable.
}
- @DoNotInline
static boolean canDrawOverlays(Context context) {
return Settings.canDrawOverlays(context);
}
diff --git a/security/security-crypto/src/main/java/androidx/security/crypto/MasterKey.java b/security/security-crypto/src/main/java/androidx/security/crypto/MasterKey.java
index 4c1946c..0ac7512 100644
--- a/security/security-crypto/src/main/java/androidx/security/crypto/MasterKey.java
+++ b/security/security-crypto/src/main/java/androidx/security/crypto/MasterKey.java
@@ -26,7 +26,6 @@
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
-import androidx.annotation.DoNotInline;
import androidx.annotation.IntRange;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -339,7 +338,6 @@
// This class is not instantiable.
}
- @DoNotInline
static String getKeystoreAlias(KeyGenParameterSpec keyGenParameterSpec) {
return keyGenParameterSpec.getKeystoreAlias();
}
@@ -397,7 +395,6 @@
// This class is not instantiable.
}
- @DoNotInline
static void setIsStrongBoxBacked(KeyGenParameterSpec.Builder builder) {
builder.setIsStrongBoxBacked(true);
}
@@ -409,7 +406,6 @@
// This class is not instantiable.
}
- @DoNotInline
static void setUserAuthenticationParameters(KeyGenParameterSpec.Builder builder,
int timeout,
int type) {
@@ -426,12 +422,10 @@
// This class is not instantiable.
}
- @DoNotInline
static boolean isUserAuthenticationRequired(KeyGenParameterSpec keyGenParameterSpec) {
return keyGenParameterSpec.isUserAuthenticationRequired();
}
- @DoNotInline
static int getUserAuthenticationValidityDurationSeconds(
KeyGenParameterSpec keyGenParameterSpec) {
return keyGenParameterSpec.getUserAuthenticationValidityDurationSeconds();
@@ -444,7 +438,6 @@
// This class is not instantiable.
}
- @DoNotInline
static boolean isStrongBoxBacked(KeyGenParameterSpec keyGenParameterSpec) {
return keyGenParameterSpec.isStrongBoxBacked();
}
diff --git a/security/security-identity-credential/src/main/java/androidx/security/identity/HardwareIdentityCredential.java b/security/security-identity-credential/src/main/java/androidx/security/identity/HardwareIdentityCredential.java
index 096366c..76c8677 100644
--- a/security/security-identity-credential/src/main/java/androidx/security/identity/HardwareIdentityCredential.java
+++ b/security/security-identity-credential/src/main/java/androidx/security/identity/HardwareIdentityCredential.java
@@ -19,7 +19,6 @@
import android.icu.util.Calendar;
import android.os.Build;
-import androidx.annotation.DoNotInline;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
@@ -282,14 +281,12 @@
@RequiresApi(Build.VERSION_CODES.S)
private static class ApiImplS {
@SuppressWarnings("deprecation")
- @DoNotInline
static void callSetAllowUsingExpiredKeys(
@NonNull android.security.identity.IdentityCredential credential,
boolean allowUsingExpiredKeys) {
credential.setAllowUsingExpiredKeys(allowUsingExpiredKeys);
}
- @DoNotInline
static void callStoreStaticAuthenticationData(
@NonNull android.security.identity.IdentityCredential credential,
@NonNull X509Certificate authenticationKey,
@@ -301,21 +298,18 @@
staticAuthData);
}
- @DoNotInline
static @NonNull byte[] callProveOwnership(
@NonNull android.security.identity.IdentityCredential credential,
@NonNull byte[] challenge) {
return credential.proveOwnership(challenge);
}
- @DoNotInline
static @NonNull byte[] callDelete(
@NonNull android.security.identity.IdentityCredential credential,
@NonNull byte[] challenge) {
return credential.delete(challenge);
}
- @DoNotInline
static @NonNull byte[] callUpdate(
@NonNull android.security.identity.IdentityCredential credential,
@NonNull android.security.identity.PersonalizationData personalizationData) {
diff --git a/settings.gradle b/settings.gradle
index 3006362..00df100 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -623,6 +623,7 @@
includeProject(":credentials:credentials-play-services-auth", [BuildType.MAIN])
includeProject(":credentials:credentials-provider", [BuildType.MAIN])
includeProject(":credentials:credentials-e2ee", [BuildType.MAIN])
+includeProject(":credentials:credentials-play-services-e2ee", [BuildType.MAIN])
includeProject(":cursoradapter:cursoradapter", [BuildType.MAIN])
includeProject(":customview:customview", [BuildType.MAIN])
includeProject(":customview:customview-poolingcontainer", [BuildType.MAIN, BuildType.COMPOSE])
diff --git a/slice/slice-core/src/main/java/androidx/slice/Slice.java b/slice/slice-core/src/main/java/androidx/slice/Slice.java
index 93e5561..5cab35a 100644
--- a/slice/slice-core/src/main/java/androidx/slice/Slice.java
+++ b/slice/slice-core/src/main/java/androidx/slice/Slice.java
@@ -61,7 +61,6 @@
import android.os.Bundle;
import android.os.Parcelable;
-import androidx.annotation.DoNotInline;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
@@ -653,7 +652,6 @@
// This class is not instantiable.
}
- @DoNotInline
static <T> T getSystemService(Context context, Class<T> serviceClass) {
return context.getSystemService(serviceClass);
}
@@ -666,7 +664,6 @@
// This class is not instantiable.
}
- @DoNotInline
static android.app.slice.Slice bindSlice(SliceManager sliceManager, Uri uri,
Set<android.app.slice.SliceSpec> supportedSpecs) {
return sliceManager.bindSlice(uri, supportedSpecs);
diff --git a/slice/slice-core/src/main/java/androidx/slice/compat/SliceProviderCompat.java b/slice/slice-core/src/main/java/androidx/slice/compat/SliceProviderCompat.java
index 9f327fc..e607f74 100644
--- a/slice/slice-core/src/main/java/androidx/slice/compat/SliceProviderCompat.java
+++ b/slice/slice-core/src/main/java/androidx/slice/compat/SliceProviderCompat.java
@@ -42,7 +42,6 @@
import android.os.StrictMode;
import android.util.Log;
-import androidx.annotation.DoNotInline;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
@@ -728,7 +727,6 @@
// This class is not instantiable.
}
- @DoNotInline
static void close(ContentProviderClient contentProviderClient) {
contentProviderClient.close();
}
diff --git a/sqlite/sqlite-framework/src/androidMain/kotlin/androidx/sqlite/db/framework/FrameworkSQLiteDatabase.android.kt b/sqlite/sqlite-framework/src/androidMain/kotlin/androidx/sqlite/db/framework/FrameworkSQLiteDatabase.android.kt
index d86497a..e999766 100644
--- a/sqlite/sqlite-framework/src/androidMain/kotlin/androidx/sqlite/db/framework/FrameworkSQLiteDatabase.android.kt
+++ b/sqlite/sqlite-framework/src/androidMain/kotlin/androidx/sqlite/db/framework/FrameworkSQLiteDatabase.android.kt
@@ -28,7 +28,6 @@
import android.os.CancellationSignal
import android.text.TextUtils
import android.util.Pair
-import androidx.annotation.DoNotInline
import androidx.annotation.RequiresApi
import androidx.sqlite.db.SimpleSQLiteQuery
import androidx.sqlite.db.SupportSQLiteDatabase
@@ -324,7 +323,6 @@
@RequiresApi(30)
internal object Api30Impl {
- @DoNotInline
fun execPerConnectionSQL(
sQLiteDatabase: SQLiteDatabase,
sql: String,
diff --git a/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/AccessibilityNodeInfoDumper.java b/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/AccessibilityNodeInfoDumper.java
index f4d86fa..951f4bb 100644
--- a/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/AccessibilityNodeInfoDumper.java
+++ b/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/AccessibilityNodeInfoDumper.java
@@ -23,7 +23,6 @@
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityWindowInfo;
-import androidx.annotation.DoNotInline;
import androidx.annotation.RequiresApi;
import androidx.test.uiautomator.util.Traces;
import androidx.test.uiautomator.util.Traces.Section;
@@ -216,7 +215,6 @@
private Api24Impl() {
}
- @DoNotInline
static int getDrawingOrder(AccessibilityNodeInfo accessibilityNodeInfo) {
return accessibilityNodeInfo.getDrawingOrder();
}
@@ -227,7 +225,6 @@
private Api26Impl() {
}
- @DoNotInline
static String getHintText(AccessibilityNodeInfo accessibilityNodeInfo) {
CharSequence chars = accessibilityNodeInfo.getHintText();
return chars != null ? chars.toString() : null;
@@ -239,7 +236,6 @@
private Api30Impl() {
}
- @DoNotInline
static int getDisplayId(AccessibilityNodeInfo accessibilityNodeInfo) {
AccessibilityWindowInfo accessibilityWindowInfo = accessibilityNodeInfo.getWindow();
return accessibilityWindowInfo == null ? Display.DEFAULT_DISPLAY :
diff --git a/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/ByMatcher.java b/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/ByMatcher.java
index 46ffa92..5a63da9 100644
--- a/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/ByMatcher.java
+++ b/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/ByMatcher.java
@@ -22,7 +22,6 @@
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityWindowInfo;
-import androidx.annotation.DoNotInline;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import androidx.test.uiautomator.util.Traces;
@@ -365,7 +364,6 @@
private Api26Impl() {
}
- @DoNotInline
static String getHintText(AccessibilityNodeInfo accessibilityNodeInfo) {
CharSequence chars = accessibilityNodeInfo.getHintText();
return chars != null ? chars.toString() : null;
@@ -427,7 +425,6 @@
private Api30Impl() {
}
- @DoNotInline
static int getDisplayId(AccessibilityNodeInfo accessibilityNodeInfo) {
AccessibilityWindowInfo accessibilityWindowInfo = accessibilityNodeInfo.getWindow();
return accessibilityWindowInfo == null ? Display.DEFAULT_DISPLAY :
diff --git a/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/UiDevice.java b/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/UiDevice.java
index a105be1..1c189a3 100644
--- a/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/UiDevice.java
+++ b/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/UiDevice.java
@@ -46,7 +46,6 @@
import android.view.accessibility.AccessibilityWindowInfo;
import androidx.annotation.Discouraged;
-import androidx.annotation.DoNotInline;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.Px;
@@ -1519,7 +1518,6 @@
private Api24Impl() {
}
- @DoNotInline
static UiAutomation getUiAutomationWithRetry(Instrumentation instrumentation, int flags) {
UiAutomation uiAutomation = null;
for (int i = 0; i < MAX_UIAUTOMATION_RETRY; i++) {
@@ -1541,7 +1539,6 @@
private Api30Impl() {
}
- @DoNotInline
static SparseArray<List<AccessibilityWindowInfo>> getWindowsOnAllDisplays(
UiAutomation uiAutomation) {
return uiAutomation.getWindowsOnAllDisplays();
@@ -1553,7 +1550,6 @@
private Api31Impl() {
}
- @DoNotInline
static Context createWindowContext(Context context, Display display) {
return context.createWindowContext(display,
WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY, null);
diff --git a/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/UiObject2.java b/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/UiObject2.java
index 2da3b2a..c50fb1f 100644
--- a/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/UiObject2.java
+++ b/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/UiObject2.java
@@ -33,7 +33,6 @@
import android.widget.Checkable;
import android.widget.TextView;
-import androidx.annotation.DoNotInline;
import androidx.annotation.FloatRange;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -1032,7 +1031,6 @@
private Api24Impl() {
}
- @DoNotInline
static int getDrawingOrder(AccessibilityNodeInfo accessibilityNodeInfo) {
return accessibilityNodeInfo.getDrawingOrder();
}
@@ -1043,7 +1041,6 @@
private Api26Impl() {
}
- @DoNotInline
static String getHintText(AccessibilityNodeInfo accessibilityNodeInfo) {
CharSequence chars = accessibilityNodeInfo.getHintText();
return chars != null ? chars.toString() : null;
@@ -1055,7 +1052,6 @@
private Api30Impl() {
}
- @DoNotInline
static int getDisplayId(AccessibilityWindowInfo accessibilityWindowInfo) {
return accessibilityWindowInfo.getDisplayId();
}
diff --git a/testutils/testutils-appcompat/build.gradle b/testutils/testutils-appcompat/build.gradle
index eb47b7a..5c26d7f 100644
--- a/testutils/testutils-appcompat/build.gradle
+++ b/testutils/testutils-appcompat/build.gradle
@@ -32,7 +32,7 @@
dependencies {
api(project(":internal-testutils-runtime"))
api(project(":appcompat:appcompat"))
- api("androidx.annotation:annotation-experimental:1.4.0")
+ api("androidx.annotation:annotation-experimental:1.4.1")
implementation(libs.testExtJunit)
implementation(libs.testCore)
diff --git a/testutils/testutils-runtime/src/main/java/androidx/testutils/AndroidFontScaleHelper.kt b/testutils/testutils-runtime/src/main/java/androidx/testutils/AndroidFontScaleHelper.kt
index 865ca5f..a5dab67 100644
--- a/testutils/testutils-runtime/src/main/java/androidx/testutils/AndroidFontScaleHelper.kt
+++ b/testutils/testutils-runtime/src/main/java/androidx/testutils/AndroidFontScaleHelper.kt
@@ -19,7 +19,6 @@
import android.app.Activity
import android.os.Build
import android.provider.Settings
-import androidx.annotation.DoNotInline
import androidx.annotation.RequiresApi
import androidx.test.core.app.ActivityScenario
import androidx.test.platform.app.InstrumentationRegistry
@@ -70,7 +69,6 @@
@RequiresApi(Build.VERSION_CODES.Q)
private object Api29Impl {
@JvmStatic
- @DoNotInline
fun <A : Activity> setSystemFontScale(
fontScale: Float,
activityScenario: ActivityScenario<A>
@@ -101,7 +99,6 @@
/** Runs the given function as root, with all shell permissions granted. */
@JvmStatic
- @DoNotInline
private fun invokeWithShellPermissions(runnable: () -> Unit) {
val uiAutomation = InstrumentationRegistry.getInstrumentation().uiAutomation
try {
diff --git a/text/text/src/main/java/androidx/compose/ui/text/android/BoringLayoutFactory.android.kt b/text/text/src/main/java/androidx/compose/ui/text/android/BoringLayoutFactory.android.kt
index 16f27ac..287c6c9 100644
--- a/text/text/src/main/java/androidx/compose/ui/text/android/BoringLayoutFactory.android.kt
+++ b/text/text/src/main/java/androidx/compose/ui/text/android/BoringLayoutFactory.android.kt
@@ -23,7 +23,6 @@
import android.text.TextDirectionHeuristic
import android.text.TextPaint
import android.text.TextUtils.TruncateAt
-import androidx.annotation.DoNotInline
import androidx.annotation.RequiresApi
/** Factory Class for BoringLayout */
@@ -123,7 +122,6 @@
private object BoringLayoutFactory33 {
@JvmStatic
- @DoNotInline
fun isBoring(text: CharSequence, paint: TextPaint, textDir: TextDirectionHeuristic): Metrics? {
return BoringLayout.isBoring(
text,
@@ -135,7 +133,6 @@
}
@JvmStatic
- @DoNotInline
fun create(
text: CharSequence,
paint: TextPaint,
@@ -165,7 +162,6 @@
}
@JvmStatic
- @DoNotInline
fun isFallbackLineSpacingEnabled(layout: BoringLayout): Boolean {
return layout.isFallbackLineSpacingEnabled
}
@@ -173,7 +169,6 @@
private object BoringLayoutFactoryDefault {
@JvmStatic
- @DoNotInline
fun isBoring(text: CharSequence, paint: TextPaint, textDir: TextDirectionHeuristic): Metrics? {
return if (!textDir.isRtl(text, 0, text.length)) {
return BoringLayout.isBoring(text, paint, null /* metrics */)
@@ -183,7 +178,6 @@
}
@JvmStatic
- @DoNotInline
fun create(
text: CharSequence,
paint: TextPaint,
diff --git a/text/text/src/main/java/androidx/compose/ui/text/android/LayoutIntrinsics.android.kt b/text/text/src/main/java/androidx/compose/ui/text/android/LayoutIntrinsics.android.kt
index feda869..0928bb4b 100644
--- a/text/text/src/main/java/androidx/compose/ui/text/android/LayoutIntrinsics.android.kt
+++ b/text/text/src/main/java/androidx/compose/ui/text/android/LayoutIntrinsics.android.kt
@@ -18,8 +18,11 @@
import android.text.BoringLayout
import android.text.Layout
+import android.text.SpannableString
import android.text.Spanned
import android.text.TextPaint
+import android.text.style.CharacterStyle
+import android.text.style.MetricAffectingSpan
import androidx.compose.ui.text.android.style.LetterSpacingSpanEm
import androidx.compose.ui.text.android.style.LetterSpacingSpanPx
import java.text.BreakIterator
@@ -79,15 +82,18 @@
var desiredWidth = (boringMetrics?.width ?: -1).toFloat()
// boring metrics doesn't cover RTL text so we fallback to different calculation
- // when boring
- // metrics can't be calculated
+ // when boring metrics can't be calculated
if (desiredWidth < 0) {
// b/233856978, apply `ceil` function here to be consistent with the boring
- // metrics
- // width calculation that does it under the hood, too
+ // metrics width calculation that does it under the hood, too
desiredWidth =
ceil(
- Layout.getDesiredWidth(charSequence, 0, charSequence.length, textPaint)
+ Layout.getDesiredWidth(
+ stripNonMetricAffectingCharacterStyleSpans(charSequence),
+ 0,
+ charSequence.length,
+ textPaint
+ )
)
}
if (shouldIncreaseMaxIntrinsic(desiredWidth, charSequence, textPaint)) {
@@ -142,7 +148,13 @@
var minWidth = 0f
longestWordCandidates.forEach { (start, end) ->
- val width = Layout.getDesiredWidth(text, start, end, paint)
+ val width =
+ Layout.getDesiredWidth(
+ stripNonMetricAffectingCharacterStyleSpans(text),
+ start,
+ end,
+ paint
+ )
minWidth = maxOf(minWidth, width)
}
@@ -150,6 +162,27 @@
}
/**
+ * See [b/346918500#comment7](https://issuetracker.google.com/346918500#comment7).
+ *
+ * Remove all character styling spans for measuring intrinsic width. [CharacterStyle] spans may
+ * affect the intrinsic width, even though they aren't supposed to, resulting in a width that
+ * doesn't actually fit the text. This can cause the line to unexpectedly wrap, even if `maxLines`
+ * was set to `1` or `softWrap` was `false`.
+ *
+ * [MetricAffectingSpan] extends [CharacterStyle], but [MetricAffectingSpan]s are allowed to affect
+ * the width, so only remove spans that **do** extend [CharacterStyle] but **don't** extend
+ * [MetricAffectingSpan].
+ */
+private fun stripNonMetricAffectingCharacterStyleSpans(charSequence: CharSequence): CharSequence {
+ if (charSequence !is Spanned) return charSequence
+ val spans = charSequence.getSpans(0, charSequence.length, CharacterStyle::class.java)
+ if (spans.isNullOrEmpty()) return charSequence
+ return SpannableString(charSequence).apply {
+ spans.onEach { if (it !is MetricAffectingSpan) removeSpan(it) }
+ }
+}
+
+/**
* b/173574230 on Android 11 and above, creating a StaticLayout when
* - desiredWidth is an Integer,
* - letterSpacing is set
@@ -157,7 +190,6 @@
*
* This function checks if those conditions are met.
*/
-@OptIn(InternalPlatformTextApi::class)
private fun shouldIncreaseMaxIntrinsic(
desiredWidth: Float,
charSequence: CharSequence,
diff --git a/text/text/src/main/java/androidx/compose/ui/text/android/PaintExtensions.android.kt b/text/text/src/main/java/androidx/compose/ui/text/android/PaintExtensions.android.kt
index bc28ba3..cfbdc69 100644
--- a/text/text/src/main/java/androidx/compose/ui/text/android/PaintExtensions.android.kt
+++ b/text/text/src/main/java/androidx/compose/ui/text/android/PaintExtensions.android.kt
@@ -22,7 +22,6 @@
import android.text.Spanned
import android.text.TextPaint
import android.text.style.MetricAffectingSpan
-import androidx.annotation.DoNotInline
import androidx.annotation.RequiresApi
import androidx.annotation.VisibleForTesting
import kotlin.math.max
@@ -101,7 +100,6 @@
@RequiresApi(29)
private object Paint29 {
@JvmStatic
- @DoNotInline
fun getTextBounds(paint: Paint, text: CharSequence, start: Int, end: Int, rect: Rect) {
paint.getTextBounds(text, start, end, rect)
}
diff --git a/text/text/src/main/java/androidx/compose/ui/text/android/StaticLayoutFactory.android.kt b/text/text/src/main/java/androidx/compose/ui/text/android/StaticLayoutFactory.android.kt
index 19699201e..256e746 100644
--- a/text/text/src/main/java/androidx/compose/ui/text/android/StaticLayoutFactory.android.kt
+++ b/text/text/src/main/java/androidx/compose/ui/text/android/StaticLayoutFactory.android.kt
@@ -24,7 +24,6 @@
import android.text.TextPaint
import android.text.TextUtils.TruncateAt
import android.util.Log
-import androidx.annotation.DoNotInline
import androidx.annotation.FloatRange
import androidx.annotation.IntRange
import androidx.annotation.RequiresApi
@@ -151,7 +150,7 @@
private interface StaticLayoutFactoryImpl {
- @DoNotInline // API level specific, do not inline to prevent ART class verification breakages
+ // API level specific, do not inline to prevent ART class verification breakages
fun create(params: StaticLayoutParams): StaticLayout
fun isFallbackLineSpacingEnabled(layout: StaticLayout, useFallbackLineSpacing: Boolean): Boolean
@@ -160,7 +159,6 @@
@RequiresApi(23)
private class StaticLayoutFactory23 : StaticLayoutFactoryImpl {
- @DoNotInline
override fun create(params: StaticLayoutParams): StaticLayout {
return Builder.obtain(params.text, params.start, params.end, params.paint, params.width)
.apply {
@@ -211,7 +209,6 @@
@RequiresApi(26)
private object StaticLayoutFactory26 {
@JvmStatic
- @DoNotInline
fun setJustificationMode(builder: Builder, justificationMode: Int) {
builder.setJustificationMode(justificationMode)
}
@@ -220,7 +217,6 @@
@RequiresApi(28)
private object StaticLayoutFactory28 {
@JvmStatic
- @DoNotInline
fun setUseLineSpacingFromFallbacks(builder: Builder, useFallbackLineSpacing: Boolean) {
builder.setUseLineSpacingFromFallbacks(useFallbackLineSpacing)
}
@@ -229,13 +225,11 @@
@RequiresApi(33)
private object StaticLayoutFactory33 {
@JvmStatic
- @DoNotInline
fun isFallbackLineSpacingEnabled(layout: StaticLayout): Boolean {
return layout.isFallbackLineSpacingEnabled
}
@JvmStatic
- @DoNotInline
fun setLineBreakConfig(builder: Builder, lineBreakStyle: Int, lineBreakWordStyle: Int) {
val lineBreakConfig =
LineBreakConfig.Builder()
@@ -283,7 +277,6 @@
}
}
- @DoNotInline
override fun create(params: StaticLayoutParams): StaticLayout {
// On API 21 to 23, try to call the StaticLayoutConstructor which supports the
// textDir and maxLines.
diff --git a/text/text/src/main/java/androidx/compose/ui/text/android/TextAndroidCanvas.android.kt b/text/text/src/main/java/androidx/compose/ui/text/android/TextAndroidCanvas.android.kt
index cdc118e..917198b 100644
--- a/text/text/src/main/java/androidx/compose/ui/text/android/TextAndroidCanvas.android.kt
+++ b/text/text/src/main/java/androidx/compose/ui/text/android/TextAndroidCanvas.android.kt
@@ -33,7 +33,6 @@
import android.graphics.fonts.Font
import android.graphics.text.MeasuredText
import android.os.Build
-import androidx.annotation.DoNotInline
import androidx.annotation.RequiresApi
/**
@@ -781,7 +780,6 @@
@RequiresApi(Build.VERSION_CODES.M)
private object CanvasCompatM {
- @DoNotInline
fun drawTextRun(
canvas: Canvas,
text: CharArray,
@@ -797,7 +795,6 @@
canvas.drawTextRun(text, index, count, contextIndex, contextCount, x, y, isRtl, paint)
}
- @DoNotInline
fun drawTextRun(
canvas: Canvas,
text: CharSequence,
@@ -817,27 +814,22 @@
@RequiresApi(Build.VERSION_CODES.O)
private object CanvasCompatO {
- @DoNotInline
fun clipOutRect(canvas: Canvas, rect: RectF): Boolean {
return canvas.clipOutRect(rect)
}
- @DoNotInline
fun clipOutRect(canvas: Canvas, rect: Rect): Boolean {
return canvas.clipOutRect(rect)
}
- @DoNotInline
fun clipOutRect(canvas: Canvas, left: Float, top: Float, right: Float, bottom: Float): Boolean {
return canvas.clipOutRect(left, top, right, bottom)
}
- @DoNotInline
fun clipOutRect(canvas: Canvas, left: Int, top: Int, right: Int, bottom: Int): Boolean {
return canvas.clipOutRect(left, top, right, bottom)
}
- @DoNotInline
fun clipOutPath(canvas: Canvas, path: Path): Boolean {
return canvas.clipOutPath(path)
}
@@ -846,32 +838,26 @@
@RequiresApi(Build.VERSION_CODES.Q)
private object CanvasCompatQ {
- @DoNotInline
fun enableZ(canvas: Canvas) {
canvas.enableZ()
}
- @DoNotInline
fun disableZ(canvas: Canvas) {
canvas.disableZ()
}
- @DoNotInline
fun drawColor(canvas: Canvas, color: Long) {
canvas.drawColor(color)
}
- @DoNotInline
fun drawColor(canvas: Canvas, color: Int, mode: BlendMode) {
canvas.drawColor(color, mode)
}
- @DoNotInline
fun drawColor(canvas: Canvas, color: Long, mode: BlendMode) {
canvas.drawColor(color, mode)
}
- @DoNotInline
fun drawDoubleRoundRect(
canvas: Canvas,
outer: RectF,
@@ -885,7 +871,6 @@
canvas.drawDoubleRoundRect(outer, outerRx, outerRy, inner, innerRx, innerRy, paint)
}
- @DoNotInline
fun drawDoubleRoundRect(
canvas: Canvas,
outer: RectF,
@@ -897,7 +882,6 @@
canvas.drawDoubleRoundRect(outer, outerRadii, inner, innerRadii, paint)
}
- @DoNotInline
fun drawTextRun(
canvas: Canvas,
text: MeasuredText,
@@ -913,7 +897,6 @@
canvas.drawTextRun(text, start, end, contextStart, contextEnd, x, y, isRtl, paint)
}
- @DoNotInline
fun drawRenderNode(canvas: Canvas, renderNode: RenderNode) {
canvas.drawRenderNode(renderNode)
}
@@ -922,17 +905,14 @@
@RequiresApi(Build.VERSION_CODES.R)
private object CanvasCompatR {
- @DoNotInline
fun quickReject(canvas: Canvas, rect: RectF): Boolean {
return canvas.quickReject(rect)
}
- @DoNotInline
fun quickReject(canvas: Canvas, path: Path): Boolean {
return canvas.quickReject(path)
}
- @DoNotInline
fun quickReject(canvas: Canvas, left: Float, top: Float, right: Float, bottom: Float): Boolean {
return canvas.quickReject(left, top, right, bottom)
}
@@ -940,17 +920,14 @@
@RequiresApi(Build.VERSION_CODES.S)
private object CanvasCompatS {
- @DoNotInline
fun drawPatch(canvas: Canvas, patch: NinePatch, dst: Rect, paint: Paint?) {
canvas.drawPatch(patch, dst, paint)
}
- @DoNotInline
fun drawPatch(canvas: Canvas, patch: NinePatch, dst: RectF, paint: Paint?) {
canvas.drawPatch(patch, dst, paint)
}
- @DoNotInline
fun drawGlyphs(
canvas: Canvas,
glyphIds: IntArray,
diff --git a/text/text/src/main/java/androidx/compose/ui/text/android/TextLayout.android.kt b/text/text/src/main/java/androidx/compose/ui/text/android/TextLayout.android.kt
index 1fe9dd3..06e3aef 100644
--- a/text/text/src/main/java/androidx/compose/ui/text/android/TextLayout.android.kt
+++ b/text/text/src/main/java/androidx/compose/ui/text/android/TextLayout.android.kt
@@ -32,7 +32,6 @@
import android.text.TextDirectionHeuristics
import android.text.TextPaint
import android.text.TextUtils
-import androidx.annotation.DoNotInline
import androidx.annotation.Px
import androidx.annotation.RequiresApi
import androidx.annotation.VisibleForTesting
@@ -1099,7 +1098,6 @@
@RequiresApi(34)
internal object AndroidLayoutApi34 {
- @DoNotInline
internal fun getRangeForRect(
layout: TextLayout,
rectF: RectF,
diff --git a/text/text/src/main/java/androidx/compose/ui/text/android/selection/SegmentFinder.android.kt b/text/text/src/main/java/androidx/compose/ui/text/android/selection/SegmentFinder.android.kt
index 878a6c7..cac8111 100644
--- a/text/text/src/main/java/androidx/compose/ui/text/android/selection/SegmentFinder.android.kt
+++ b/text/text/src/main/java/androidx/compose/ui/text/android/selection/SegmentFinder.android.kt
@@ -19,7 +19,6 @@
import android.graphics.Paint
import android.os.Build
import android.text.TextPaint
-import androidx.annotation.DoNotInline
import androidx.annotation.RequiresApi
import androidx.compose.ui.text.android.selection.SegmentFinder.Companion.DONE
import java.text.BreakIterator
@@ -222,7 +221,6 @@
@RequiresApi(34)
internal object Api34SegmentFinder {
- @DoNotInline
internal fun SegmentFinder.toAndroidSegmentFinder(): android.text.SegmentFinder {
return object : android.text.SegmentFinder() {
override fun previousStartBoundary(offset: Int): Int =
diff --git a/tracing/tracing/src/main/java/androidx/tracing/TraceApi29Impl.java b/tracing/tracing/src/main/java/androidx/tracing/TraceApi29Impl.java
index 14ba157..6c51b18 100644
--- a/tracing/tracing/src/main/java/androidx/tracing/TraceApi29Impl.java
+++ b/tracing/tracing/src/main/java/androidx/tracing/TraceApi29Impl.java
@@ -16,7 +16,6 @@
package androidx.tracing;
-import androidx.annotation.DoNotInline;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
@@ -38,7 +37,6 @@
/**
* Checks whether or not tracing is currently enabled.
*/
- @DoNotInline
public static boolean isEnabled() {
return android.os.Trace.isEnabled();
}
diff --git a/transition/transition/src/main/java/androidx/transition/CanvasUtils.java b/transition/transition/src/main/java/androidx/transition/CanvasUtils.java
index e9d7452..6c058c5 100644
--- a/transition/transition/src/main/java/androidx/transition/CanvasUtils.java
+++ b/transition/transition/src/main/java/androidx/transition/CanvasUtils.java
@@ -20,7 +20,6 @@
import android.graphics.Canvas;
import android.os.Build;
-import androidx.annotation.DoNotInline;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
@@ -88,12 +87,10 @@
// This class is not instantiable.
}
- @DoNotInline
static void enableZ(Canvas canvas) {
canvas.enableZ();
}
- @DoNotInline
static void disableZ(Canvas canvas) {
canvas.disableZ();
}
diff --git a/transition/transition/src/main/java/androidx/transition/GhostViewHolder.java b/transition/transition/src/main/java/androidx/transition/GhostViewHolder.java
index eccefad..95667f4 100644
--- a/transition/transition/src/main/java/androidx/transition/GhostViewHolder.java
+++ b/transition/transition/src/main/java/androidx/transition/GhostViewHolder.java
@@ -23,7 +23,6 @@
import android.view.ViewParent;
import android.widget.FrameLayout;
-import androidx.annotation.DoNotInline;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
@@ -205,7 +204,6 @@
// This class is not instantiable.
}
- @DoNotInline
static float getZ(View view) {
return view.getZ();
}
diff --git a/transition/transition/src/main/java/androidx/transition/ImageViewUtils.java b/transition/transition/src/main/java/androidx/transition/ImageViewUtils.java
index 453d015..58c435a 100644
--- a/transition/transition/src/main/java/androidx/transition/ImageViewUtils.java
+++ b/transition/transition/src/main/java/androidx/transition/ImageViewUtils.java
@@ -22,7 +22,6 @@
import android.os.Build;
import android.widget.ImageView;
-import androidx.annotation.DoNotInline;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
@@ -118,7 +117,6 @@
// This class is not instantiable.
}
- @DoNotInline
static void animateTransform(ImageView imageView, Matrix matrix) {
imageView.animateTransform(matrix);
}
diff --git a/transition/transition/src/main/java/androidx/transition/ObjectAnimatorUtils.java b/transition/transition/src/main/java/androidx/transition/ObjectAnimatorUtils.java
index ecf4989..2a4a6bd 100644
--- a/transition/transition/src/main/java/androidx/transition/ObjectAnimatorUtils.java
+++ b/transition/transition/src/main/java/androidx/transition/ObjectAnimatorUtils.java
@@ -22,7 +22,6 @@
import android.os.Build;
import android.util.Property;
-import androidx.annotation.DoNotInline;
import androidx.annotation.RequiresApi;
class ObjectAnimatorUtils {
@@ -42,7 +41,6 @@
// This class is not instantiable.
}
- @DoNotInline
static <T, V> ObjectAnimator ofObject(T target, Property<T, V> property, Path path) {
return ObjectAnimator.ofObject(target, property, null, path);
}
diff --git a/transition/transition/src/main/java/androidx/transition/PropertyValuesHolderUtils.java b/transition/transition/src/main/java/androidx/transition/PropertyValuesHolderUtils.java
index f8ccc90..913d56d 100644
--- a/transition/transition/src/main/java/androidx/transition/PropertyValuesHolderUtils.java
+++ b/transition/transition/src/main/java/androidx/transition/PropertyValuesHolderUtils.java
@@ -22,7 +22,6 @@
import android.os.Build;
import android.util.Property;
-import androidx.annotation.DoNotInline;
import androidx.annotation.RequiresApi;
class PropertyValuesHolderUtils {
@@ -52,7 +51,6 @@
// This class is not instantiable.
}
- @DoNotInline
static <V> PropertyValuesHolder ofObject(Property<?, V> property, Path path) {
return PropertyValuesHolder.ofObject(property, null, path);
}
diff --git a/transition/transition/src/main/java/androidx/transition/Transition.java b/transition/transition/src/main/java/androidx/transition/Transition.java
index 745f2b0..460cd83 100644
--- a/transition/transition/src/main/java/androidx/transition/Transition.java
+++ b/transition/transition/src/main/java/androidx/transition/Transition.java
@@ -42,7 +42,6 @@
import android.widget.ListView;
import android.widget.Spinner;
-import androidx.annotation.DoNotInline;
import androidx.annotation.IdRes;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
@@ -2711,12 +2710,10 @@
@RequiresApi(Build.VERSION_CODES.O)
private static class Impl26 {
- @DoNotInline
static long getTotalDuration(Animator animator) {
return animator.getTotalDuration();
}
- @DoNotInline
static void setCurrentPlayTime(Animator animator, long playTimeMillis) {
((AnimatorSet) animator).setCurrentPlayTime(playTimeMillis);
}
diff --git a/transition/transition/src/main/java/androidx/transition/TransitionUtils.java b/transition/transition/src/main/java/androidx/transition/TransitionUtils.java
index 3e323a0d..f4359b2 100644
--- a/transition/transition/src/main/java/androidx/transition/TransitionUtils.java
+++ b/transition/transition/src/main/java/androidx/transition/TransitionUtils.java
@@ -30,7 +30,6 @@
import android.view.ViewGroupOverlay;
import android.widget.ImageView;
-import androidx.annotation.DoNotInline;
import androidx.annotation.RequiresApi;
class TransitionUtils {
@@ -179,7 +178,6 @@
// This class is not instantiable.
}
- @DoNotInline
static Bitmap createBitmap(Picture source) {
return Bitmap.createBitmap(source);
}
diff --git a/transition/transition/src/main/java/androidx/transition/ViewGroupUtils.java b/transition/transition/src/main/java/androidx/transition/ViewGroupUtils.java
index 66a310a..20b4000 100644
--- a/transition/transition/src/main/java/androidx/transition/ViewGroupUtils.java
+++ b/transition/transition/src/main/java/androidx/transition/ViewGroupUtils.java
@@ -20,7 +20,6 @@
import android.os.Build;
import android.view.ViewGroup;
-import androidx.annotation.DoNotInline;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
@@ -103,12 +102,10 @@
// This class is not instantiable.
}
- @DoNotInline
static void suppressLayout(ViewGroup viewGroup, boolean suppress) {
viewGroup.suppressLayout(suppress);
}
- @DoNotInline
static int getChildDrawingOrder(ViewGroup viewGroup, int drawingPosition) {
return viewGroup.getChildDrawingOrder(drawingPosition);
}
diff --git a/transition/transition/src/main/java/androidx/transition/ViewUtilsApi19.java b/transition/transition/src/main/java/androidx/transition/ViewUtilsApi19.java
index 448ba3a..f7c5b7d 100644
--- a/transition/transition/src/main/java/androidx/transition/ViewUtilsApi19.java
+++ b/transition/transition/src/main/java/androidx/transition/ViewUtilsApi19.java
@@ -22,7 +22,6 @@
import android.view.View;
import android.view.ViewParent;
-import androidx.annotation.DoNotInline;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
@@ -206,12 +205,10 @@
// This class is not instantiable.
}
- @DoNotInline
static void setTransitionAlpha(View view, float alpha) {
view.setTransitionAlpha(alpha);
}
- @DoNotInline
static float getTransitionAlpha(View view) {
return view.getTransitionAlpha();
}
diff --git a/transition/transition/src/main/java/androidx/transition/ViewUtilsApi21.java b/transition/transition/src/main/java/androidx/transition/ViewUtilsApi21.java
index e4d7acd..3a4adeb 100644
--- a/transition/transition/src/main/java/androidx/transition/ViewUtilsApi21.java
+++ b/transition/transition/src/main/java/androidx/transition/ViewUtilsApi21.java
@@ -20,7 +20,6 @@
import android.graphics.Matrix;
import android.view.View;
-import androidx.annotation.DoNotInline;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
@@ -89,17 +88,14 @@
// This class is not instantiable.
}
- @DoNotInline
static void transformMatrixToGlobal(View view, Matrix matrix) {
view.transformMatrixToGlobal(matrix);
}
- @DoNotInline
static void transformMatrixToLocal(View view, Matrix matrix) {
view.transformMatrixToLocal(matrix);
}
- @DoNotInline
static void setAnimationMatrix(View view, Matrix matrix) {
view.setAnimationMatrix(matrix);
}
diff --git a/transition/transition/src/main/java/androidx/transition/ViewUtilsApi22.java b/transition/transition/src/main/java/androidx/transition/ViewUtilsApi22.java
index b74893c..e0330a7 100644
--- a/transition/transition/src/main/java/androidx/transition/ViewUtilsApi22.java
+++ b/transition/transition/src/main/java/androidx/transition/ViewUtilsApi22.java
@@ -19,7 +19,6 @@
import android.annotation.SuppressLint;
import android.view.View;
-import androidx.annotation.DoNotInline;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
@@ -51,7 +50,6 @@
// This class is not instantiable.
}
- @DoNotInline
static void setLeftTopRightBottom(View view, int left, int top, int right, int bottom) {
view.setLeftTopRightBottom(left, top, right, bottom);
}
diff --git a/transition/transition/src/main/java/androidx/transition/ViewUtilsApi23.java b/transition/transition/src/main/java/androidx/transition/ViewUtilsApi23.java
index b76944a..fc1d27b 100644
--- a/transition/transition/src/main/java/androidx/transition/ViewUtilsApi23.java
+++ b/transition/transition/src/main/java/androidx/transition/ViewUtilsApi23.java
@@ -20,7 +20,6 @@
import android.os.Build;
import android.view.View;
-import androidx.annotation.DoNotInline;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
@@ -57,7 +56,6 @@
// This class is not instantiable.
}
- @DoNotInline
static void setTransitionVisibility(View view, int visibility) {
view.setTransitionVisibility(visibility);
}
diff --git a/vectordrawable/vectordrawable-animated/src/main/java/androidx/vectordrawable/graphics/drawable/AnimatedVectorDrawableCompat.java b/vectordrawable/vectordrawable-animated/src/main/java/androidx/vectordrawable/graphics/drawable/AnimatedVectorDrawableCompat.java
index 7c743a9..4a6e255 100644
--- a/vectordrawable/vectordrawable-animated/src/main/java/androidx/vectordrawable/graphics/drawable/AnimatedVectorDrawableCompat.java
+++ b/vectordrawable/vectordrawable-animated/src/main/java/androidx/vectordrawable/graphics/drawable/AnimatedVectorDrawableCompat.java
@@ -40,7 +40,6 @@
import android.util.Log;
import android.util.Xml;
-import androidx.annotation.DoNotInline;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -950,19 +949,16 @@
// This class is not instantiable.
}
- @DoNotInline
static boolean unregisterAnimationCallback(Object animatedVectorDrawable,
Object callback) {
return ((AnimatedVectorDrawable) animatedVectorDrawable).unregisterAnimationCallback(
(Animatable2.AnimationCallback) callback);
}
- @DoNotInline
static void clearAnimationCallbacks(Object animatedVectorDrawable) {
((AnimatedVectorDrawable) animatedVectorDrawable).clearAnimationCallbacks();
}
- @DoNotInline
static void registerAnimationCallback(Object animatedVectorDrawable, Object callback) {
((AnimatedVectorDrawable) animatedVectorDrawable).registerAnimationCallback(
(Animatable2.AnimationCallback) callback);
diff --git a/versionedparcelable/versionedparcelable/src/main/java/androidx/versionedparcelable/VersionedParcel.java b/versionedparcelable/versionedparcelable/src/main/java/androidx/versionedparcelable/VersionedParcel.java
index 83db0db..b796da3 100644
--- a/versionedparcelable/versionedparcelable/src/main/java/androidx/versionedparcelable/VersionedParcel.java
+++ b/versionedparcelable/versionedparcelable/src/main/java/androidx/versionedparcelable/VersionedParcel.java
@@ -29,7 +29,6 @@
import android.util.SizeF;
import android.util.SparseBooleanArray;
-import androidx.annotation.DoNotInline;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
@@ -1686,7 +1685,6 @@
@RequiresApi(21)
private static final class Api21Impl {
- @DoNotInline
static void writeSize(@NonNull VersionedParcel self, @Nullable Size val) {
self.writeBoolean(val != null);
if (val != null) {
@@ -1695,7 +1693,6 @@
}
}
- @DoNotInline
static void writeSizeF(@NonNull VersionedParcel self, @Nullable SizeF val) {
self.writeBoolean(val != null);
if (val != null) {
@@ -1704,7 +1701,6 @@
}
}
- @DoNotInline
@Nullable
static Size readSize(@NonNull VersionedParcel self) {
if (self.readBoolean()) {
@@ -1715,7 +1711,6 @@
return null;
}
- @DoNotInline
@Nullable
static SizeF readSizeF(@NonNull VersionedParcel self) {
if (self.readBoolean()) {
diff --git a/viewpager2/viewpager2/build.gradle b/viewpager2/viewpager2/build.gradle
index 56499ac..abddf8a 100644
--- a/viewpager2/viewpager2/build.gradle
+++ b/viewpager2/viewpager2/build.gradle
@@ -31,7 +31,7 @@
dependencies {
api("androidx.annotation:annotation:1.1.0")
- api("androidx.annotation:annotation-experimental:1.4.0")
+ api("androidx.annotation:annotation-experimental:1.4.1")
implementation("androidx.core:core:1.3.2")
api("androidx.fragment:fragment:1.1.0")
api("androidx.recyclerview:recyclerview:1.3.1")
diff --git a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/Haptics.kt b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/Haptics.kt
new file mode 100644
index 0000000..cd8d533
--- /dev/null
+++ b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/Haptics.kt
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.compose.material3.demos
+
+import android.view.HapticFeedbackConstants
+import android.view.View
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalView
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.dp
+import androidx.wear.compose.material3.Button
+import androidx.wear.compose.material3.Text
+
+@Composable
+fun HapticsDemos() {
+ val haptics = HapticFeedbackProvider(LocalView.current)
+
+ // Show a Button to trigger each haptic constant when clicked:
+ // https://developer.android.com/reference/android/view/HapticFeedbackConstants
+ val hapticConstants =
+ listOf(
+ Pair(HapticFeedbackConstants.CLOCK_TICK, "Clock Tick"),
+ Pair(HapticFeedbackConstants.CONFIRM, "Confirm"),
+ Pair(HapticFeedbackConstants.CONTEXT_CLICK, "Context Click"),
+ Pair(HapticFeedbackConstants.DRAG_START, "Drag Start"),
+ // NB HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING has been deprecated, so omit it
+ Pair(HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING, "Flag Ignore"),
+ Pair(HapticFeedbackConstants.GESTURE_END, "Gesture End"),
+ Pair(HapticFeedbackConstants.GESTURE_START, "Gesture Start"),
+ Pair(HapticFeedbackConstants.GESTURE_THRESHOLD_ACTIVATE, "Gesture Threshold Activate"),
+ Pair(
+ HapticFeedbackConstants.GESTURE_THRESHOLD_DEACTIVATE,
+ "Gesture Threshold Deactivate"
+ ),
+ Pair(HapticFeedbackConstants.KEYBOARD_PRESS, "Keyboard Press"),
+ Pair(HapticFeedbackConstants.KEYBOARD_RELEASE, "Keyboard Release"),
+ Pair(HapticFeedbackConstants.KEYBOARD_TAP, "Keyboard Tap"),
+ Pair(HapticFeedbackConstants.LONG_PRESS, "Long Press"),
+ Pair(HapticFeedbackConstants.NO_HAPTICS, "No haptics"),
+ Pair(HapticFeedbackConstants.REJECT, "Reject"),
+ Pair(HapticFeedbackConstants.SEGMENT_FREQUENT_TICK, "Segment Frequent Tick"),
+ Pair(HapticFeedbackConstants.SEGMENT_TICK, "Segment Tick"),
+ Pair(HapticFeedbackConstants.TEXT_HANDLE_MOVE, "Text Handle Move"),
+ Pair(HapticFeedbackConstants.TOGGLE_OFF, "Toggle Off"),
+ Pair(HapticFeedbackConstants.TOGGLE_ON, "Toggle On"),
+ Pair(HapticFeedbackConstants.VIRTUAL_KEY, "Virtual Key"),
+ Pair(HapticFeedbackConstants.VIRTUAL_KEY_RELEASE, "Virtual Key Release"),
+ )
+
+ ScalingLazyDemo(contentPadding = PaddingValues(horizontal = 20.dp)) {
+ items(hapticConstants.size) { index ->
+ val (constant, name) = hapticConstants[index]
+ HapticsDemo(haptics, constant, name)
+ }
+ }
+}
+
+@Composable
+private fun HapticsDemo(
+ hapticFeedbackProvider: HapticFeedbackProvider,
+ feedbackConstant: Int,
+ demoName: String
+) {
+ Button(
+ onClick = { hapticFeedbackProvider.performHapticFeedback(feedbackConstant) },
+ label = {
+ Text(demoName, textAlign = TextAlign.Center, modifier = Modifier.fillMaxWidth())
+ },
+ modifier = Modifier.fillMaxWidth()
+ )
+}
+
+private class HapticFeedbackProvider(private val view: View) {
+ fun performHapticFeedback(feedbackConstant: Int) {
+ view.let { view -> view.performHapticFeedback(feedbackConstant) }
+ }
+}
diff --git a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/Material3Demos.kt b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/Material3Demos.kt
index e2f16b1..3d39cb3 100644
--- a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/Material3Demos.kt
+++ b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/Material3Demos.kt
@@ -16,6 +16,7 @@
package androidx.wear.compose.material3.demos
+import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
@@ -26,13 +27,17 @@
import androidx.wear.compose.material3.ScreenScaffold
@Composable
-fun ScalingLazyDemo(content: ScalingLazyListScope.() -> Unit) {
+fun ScalingLazyDemo(
+ contentPadding: PaddingValues = PaddingValues(),
+ content: ScalingLazyListScope.() -> Unit
+) {
val scrollState = rememberScalingLazyListState()
ScreenScaffold(scrollState = scrollState) {
ScalingLazyColumn(
state = scrollState,
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
+ contentPadding = contentPadding,
content = content
)
}
diff --git a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/WearMaterial3Demos.kt b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/WearMaterial3Demos.kt
index b7e97ab..7c13585 100644
--- a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/WearMaterial3Demos.kt
+++ b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/WearMaterial3Demos.kt
@@ -43,6 +43,7 @@
listOf(
ComposableDemo("Scaffold") { ScaffoldSample() },
Material3DemoCategory("ScrollAway", ScrollAwayDemos),
+ ComposableDemo("Haptics") { Centralize { HapticsDemos() } },
Material3DemoCategory(
"Button",
listOf(
diff --git a/wear/compose/integration-tests/demos/build.gradle b/wear/compose/integration-tests/demos/build.gradle
index 5a629cc..e43fb98 100644
--- a/wear/compose/integration-tests/demos/build.gradle
+++ b/wear/compose/integration-tests/demos/build.gradle
@@ -25,8 +25,8 @@
defaultConfig {
applicationId "androidx.wear.compose.integration.demos"
minSdk 25
- versionCode 31
- versionName "1.31"
+ versionCode 32
+ versionName "1.32"
}
buildTypes {
diff --git a/wear/protolayout/protolayout-expression-pipeline/build.gradle b/wear/protolayout/protolayout-expression-pipeline/build.gradle
index d82751b..0a5898e 100644
--- a/wear/protolayout/protolayout-expression-pipeline/build.gradle
+++ b/wear/protolayout/protolayout-expression-pipeline/build.gradle
@@ -35,7 +35,7 @@
implementation("androidx.collection:collection:1.2.0")
implementation("androidx.core:core:1.7.0")
implementation("androidx.concurrent:concurrent-futures:1.1.0")
- implementation("androidx.annotation:annotation-experimental:1.4.0")
+ implementation("androidx.annotation:annotation-experimental:1.4.1")
implementation(project(":wear:protolayout:protolayout-proto"))
implementation(project(":wear:protolayout:protolayout-expression"))
diff --git a/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/NumberFormatter.java b/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/NumberFormatter.java
index a1b629b..36152d5 100644
--- a/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/NumberFormatter.java
+++ b/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/NumberFormatter.java
@@ -29,7 +29,6 @@
import android.os.Build.VERSION_CODES;
import android.util.Log;
-import androidx.annotation.DoNotInline;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import androidx.annotation.VisibleForTesting;
@@ -123,19 +122,16 @@
@RequiresApi(VERSION_CODES.R)
private static class Api30Impl {
@NonNull
- @DoNotInline
static String callFormatToString(LocalizedNumberFormatter mFmt, int value) {
return mFmt.format(value).toString();
}
@NonNull
- @DoNotInline
static String callFormatToString(LocalizedNumberFormatter mFmt, float value) {
return mFmt.format(value).toString();
}
@NonNull
- @DoNotInline
static LocalizedNumberFormatter buildLocalizedNumberFormatter(
int minIntegerDigits,
int minFractionDigits,
diff --git a/wear/protolayout/protolayout-expression/build.gradle b/wear/protolayout/protolayout-expression/build.gradle
index 4b3638f..e21ab55 100644
--- a/wear/protolayout/protolayout-expression/build.gradle
+++ b/wear/protolayout/protolayout-expression/build.gradle
@@ -32,7 +32,7 @@
annotationProcessor(libs.nullaway)
api("androidx.annotation:annotation:1.2.0")
- implementation("androidx.annotation:annotation-experimental:1.4.0")
+ implementation("androidx.annotation:annotation-experimental:1.4.1")
implementation("androidx.collection:collection:1.2.0")
implementation(project(":wear:protolayout:protolayout-proto"))
diff --git a/wear/protolayout/protolayout-material-core/build.gradle b/wear/protolayout/protolayout-material-core/build.gradle
index dcf081a..ca1740a 100644
--- a/wear/protolayout/protolayout-material-core/build.gradle
+++ b/wear/protolayout/protolayout-material-core/build.gradle
@@ -26,6 +26,7 @@
plugins {
id("AndroidXPlugin")
id("com.android.library")
+ id("kotlin-android")
}
android {
@@ -55,6 +56,7 @@
testImplementation(libs.testRunner)
testImplementation(libs.testRules)
testImplementation(libs.truth)
+ testImplementation("androidx.core:core-ktx:1.13.1")
}
androidx {
diff --git a/wear/protolayout/protolayout-material-core/src/main/java/androidx/wear/protolayout/materialcore/fontscaling/FontScaleConverter.java b/wear/protolayout/protolayout-material-core/src/main/java/androidx/wear/protolayout/materialcore/fontscaling/FontScaleConverter.java
new file mode 100644
index 0000000..98351ea
--- /dev/null
+++ b/wear/protolayout/protolayout-material-core/src/main/java/androidx/wear/protolayout/materialcore/fontscaling/FontScaleConverter.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.protolayout.materialcore.fontscaling;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
+
+import java.util.Arrays;
+
+/**
+ * A lookup table for non-linear font scaling. Converts font sizes given in "sp" dimensions to a
+ * "dp" dimension according to a non-linear curve.
+ *
+ * <p>This is meant to improve readability at larger font scales: larger fonts will scale up more
+ * slowly than smaller fonts, so we don't get ridiculously huge fonts that don't fit on the screen.
+ *
+ * <p>The thinking here is that large fonts are already big enough to read, but we still want to
+ * scale them slightly to preserve the visual hierarchy when compared to smaller fonts.
+ */
+// This is copied from
+// https://cs.android.com/android/_/android/platform/frameworks/base/+/2a4e99a798cc69944f64d54b81aee987fbea45d6:core/java/android/content/res/FontScaleConverter.java
+// TODO: b/342359552 - Use Android Platform api instead when it becomes public.
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public class FontScaleConverter {
+
+ final float[] mFromSpValues;
+ final float[] mToDpValues;
+
+ /**
+ * Creates a lookup table for the given conversions.
+ *
+ * <p>Any "sp" value not in the lookup table will be derived via linear interpolation.
+ *
+ * <p>The arrays must be sorted ascending and monotonically increasing.
+ *
+ * @param fromSp array of dimensions in SP
+ * @param toDp array of dimensions in DP that correspond to an SP value in fromSp
+ * @throws IllegalArgumentException if the array lengths don't match or are empty
+ */
+ FontScaleConverter(@NonNull float[] fromSp, @NonNull float[] toDp) {
+ if (fromSp.length != toDp.length || fromSp.length == 0) {
+ throw new IllegalArgumentException("Array lengths must match and be nonzero");
+ }
+
+ mFromSpValues = fromSp;
+ mToDpValues = toDp;
+ }
+
+ /** Convert a dimension in "dp" back to "sp" using the lookup table. */
+ public float convertDpToSp(float dp) {
+ return lookupAndInterpolate(dp, mToDpValues, mFromSpValues);
+ }
+
+ /** Convert a dimension in "sp" to "dp" using the lookup table. */
+ public float convertSpToDp(float sp) {
+ return lookupAndInterpolate(sp, mFromSpValues, mToDpValues);
+ }
+
+ private static float lookupAndInterpolate(
+ float sourceValue, float[] sourceValues, float[] targetValues) {
+ final float sourceValuePositive = Math.abs(sourceValue);
+ // TODO(b/247861374): find a match at a higher index?
+ final float sign = Math.signum(sourceValue);
+ // We search for exact matches only, even if it's just a little off. The interpolation will
+ // handle any non-exact matches.
+ final int index = Arrays.binarySearch(sourceValues, sourceValuePositive);
+ if (index >= 0) {
+ // exact match, return the matching dp
+ return sign * targetValues[index];
+ } else {
+ // must be a value in between index and index + 1: interpolate.
+ final int lowerIndex = -(index + 1) - 1;
+
+ final float startSp;
+ final float endSp;
+ final float startDp;
+ final float endDp;
+
+ if (lowerIndex >= sourceValues.length - 1) {
+ // It's past our lookup table. Determine the last elements' scaling factor and use.
+ startSp = sourceValues[sourceValues.length - 1];
+ startDp = targetValues[sourceValues.length - 1];
+
+ if (startSp == 0) {
+ return 0;
+ }
+
+ final float scalingFactor = startDp / startSp;
+ return sourceValue * scalingFactor;
+ } else if (lowerIndex == -1) {
+ // It's smaller than the smallest value in our table. Interpolate from 0.
+ startSp = 0;
+ startDp = 0;
+ endSp = sourceValues[0];
+ endDp = targetValues[0];
+ } else {
+ startSp = sourceValues[lowerIndex];
+ endSp = sourceValues[lowerIndex + 1];
+ startDp = targetValues[lowerIndex];
+ endDp = targetValues[lowerIndex + 1];
+ }
+
+ return sign
+ * MathUtils.constrainedMap(startDp, endDp, startSp, endSp, sourceValuePositive);
+ }
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null) {
+ return false;
+ }
+ if (!(o instanceof FontScaleConverter)) {
+ return false;
+ }
+ FontScaleConverter that = (FontScaleConverter) o;
+ return Arrays.equals(mFromSpValues, that.mFromSpValues)
+ && Arrays.equals(mToDpValues, that.mToDpValues);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = Arrays.hashCode(mFromSpValues);
+ result = 31 * result + Arrays.hashCode(mToDpValues);
+ return result;
+ }
+
+ @NonNull
+ @Override
+ public String toString() {
+ return "FontScaleConverter{"
+ + "fromSpValues="
+ + Arrays.toString(mFromSpValues)
+ + ", toDpValues="
+ + Arrays.toString(mToDpValues)
+ + '}';
+ }
+}
diff --git a/wear/protolayout/protolayout-material-core/src/main/java/androidx/wear/protolayout/materialcore/fontscaling/FontScaleConverterFactory.java b/wear/protolayout/protolayout-material-core/src/main/java/androidx/wear/protolayout/materialcore/fontscaling/FontScaleConverterFactory.java
new file mode 100644
index 0000000..5b8b3f7
--- /dev/null
+++ b/wear/protolayout/protolayout-material-core/src/main/java/androidx/wear/protolayout/materialcore/fontscaling/FontScaleConverterFactory.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.protolayout.materialcore.fontscaling;
+
+import android.content.res.Configuration;
+import android.util.SparseArray;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
+import androidx.annotation.VisibleForTesting;
+
+/** Stores lookup tables for creating {@link FontScaleConverter}s at various scales. */
+// This is copied from
+// https://cs.android.com/android/_/android/platform/frameworks/base/+/2a4e99a798cc69944f64d54b81aee987fbea45d6:core/java/android/content/res/FontScaleConverterFactory.java
+// TODO: b/342359552 - Use Android Platform api instead when it becomes public.
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public class FontScaleConverterFactory {
+ private static final float SCALE_KEY_MULTIPLIER = 100f;
+
+ @VisibleForTesting
+ static final SparseArray<FontScaleConverter> LOOKUP_TABLES = new SparseArray<>();
+
+ @SuppressWarnings("NonFinalStaticField")
+ private static float sMinScaleBeforeCurvesApplied = 1.05f;
+
+ static {
+ // These were generated by frameworks/base/tools/fonts/font-scaling-array-generator.js and
+ // manually tweaked for optimum readability.
+ put(
+ /* scaleKey= */ 1.15f,
+ new FontScaleConverter(
+ /* fromSp= */ new float[] {8f, 10f, 12f, 14f, 18f, 20f, 24f, 30f, 100},
+ /* toDp= */ new float[] {
+ 9.2f, 11.5f, 13.8f, 16.4f, 19.8f, 21.8f, 25.2f, 30f, 100
+ }));
+
+ put(
+ /* scaleKey= */ 1.3f,
+ new FontScaleConverter(
+ /* fromSp= */ new float[] {8f, 10f, 12f, 14f, 18f, 20f, 24f, 30f, 100},
+ /* toDp= */ new float[] {
+ 10.4f, 13f, 15.6f, 18.8f, 21.6f, 23.6f, 26.4f, 30f, 100
+ }));
+
+ put(
+ /* scaleKey= */ 1.5f,
+ new FontScaleConverter(
+ /* fromSp= */ new float[] {8f, 10f, 12f, 14f, 18f, 20f, 24f, 30f, 100},
+ /* toDp= */ new float[] {12f, 15f, 18f, 22f, 24f, 26f, 28f, 30f, 100}));
+
+ put(
+ /* scaleKey= */ 1.8f,
+ new FontScaleConverter(
+ /* fromSp= */ new float[] {8f, 10f, 12f, 14f, 18f, 20f, 24f, 30f, 100},
+ /* toDp= */ new float[] {
+ 14.4f, 18f, 21.6f, 24.4f, 27.6f, 30.8f, 32.8f, 34.8f, 100
+ }));
+
+ put(
+ /* scaleKey= */ 2f,
+ new FontScaleConverter(
+ /* fromSp= */ new float[] {8f, 10f, 12f, 14f, 18f, 20f, 24f, 30f, 100},
+ /* toDp= */ new float[] {16f, 20f, 24f, 26f, 30f, 34f, 36f, 38f, 100}));
+
+ sMinScaleBeforeCurvesApplied = getScaleFromKey(LOOKUP_TABLES.keyAt(0)) - 0.02f;
+ if (sMinScaleBeforeCurvesApplied <= 1.0f) {
+ throw new IllegalStateException(
+ "You should only apply non-linear scaling to font scales > 1");
+ }
+ }
+
+ private FontScaleConverterFactory() {}
+
+ /**
+ * Returns true if non-linear font scaling curves would be in effect for the given scale, false
+ * if the scaling would follow a linear curve or for no scaling.
+ *
+ * <p>Example usage: <code>
+ * isNonLinearFontScalingActive(getResources().getConfiguration().fontScale)</code>
+ */
+ public static boolean isNonLinearFontScalingActive(float fontScale) {
+ return fontScale >= sMinScaleBeforeCurvesApplied;
+ }
+
+ /**
+ * Finds a matching FontScaleConverter for the given fontScale factor.
+ *
+ * @param fontScale the scale factor, usually from {@link Configuration#fontScale}.
+ * @return a converter for the given scale, or null if non-linear scaling should not be used.
+ */
+ @Nullable
+ public static FontScaleConverter forScale(float fontScale) {
+ if (!isNonLinearFontScalingActive(fontScale)) {
+ return null;
+ }
+
+ FontScaleConverter lookupTable = get(fontScale);
+ if (lookupTable != null) {
+ return lookupTable;
+ }
+
+ // Didn't find an exact match: interpolate between two existing tables
+ final int index = LOOKUP_TABLES.indexOfKey(getKey(fontScale));
+ if (index >= 0) {
+ // This should never happen, should have been covered by get() above.
+ return LOOKUP_TABLES.valueAt(index);
+ }
+ // Didn't find an exact match: interpolate between two existing tables
+ final int lowerIndex = -(index + 1) - 1;
+ final int higherIndex = lowerIndex + 1;
+ if (lowerIndex < 0 || higherIndex >= LOOKUP_TABLES.size()) {
+ // We have gone beyond our bounds and have nothing to interpolate between. Just give
+ // them a straight linear table instead.
+ // This works because when FontScaleConverter encounters a size beyond its bounds, it
+ // calculates a linear fontScale factor using the ratio of the last element pair.
+ return new FontScaleConverter(new float[] {1f}, new float[] {fontScale});
+ } else {
+ float startScale = getScaleFromKey(LOOKUP_TABLES.keyAt(lowerIndex));
+ float endScale = getScaleFromKey(LOOKUP_TABLES.keyAt(higherIndex));
+ float interpolationPoint =
+ MathUtils.constrainedMap(
+ /* rangeMin= */ 0f,
+ /* rangeMax= */ 1f,
+ startScale,
+ endScale,
+ fontScale);
+ return createInterpolatedTableBetween(
+ LOOKUP_TABLES.valueAt(lowerIndex),
+ LOOKUP_TABLES.valueAt(higherIndex),
+ interpolationPoint);
+ }
+ }
+
+ @NonNull
+ private static FontScaleConverter createInterpolatedTableBetween(
+ FontScaleConverter start, FontScaleConverter end, float interpolationPoint) {
+ float[] commonSpSizes = new float[] {8f, 10f, 12f, 14f, 18f, 20f, 24f, 30f, 100f};
+ float[] dpInterpolated = new float[commonSpSizes.length];
+
+ for (int i = 0; i < commonSpSizes.length; i++) {
+ float sp = commonSpSizes[i];
+ float startDp = start.convertSpToDp(sp);
+ float endDp = end.convertSpToDp(sp);
+ dpInterpolated[i] = MathUtils.lerp(startDp, endDp, interpolationPoint);
+ }
+
+ return new FontScaleConverter(commonSpSizes, dpInterpolated);
+ }
+
+ private static int getKey(float fontScale) {
+ return (int) (fontScale * SCALE_KEY_MULTIPLIER);
+ }
+
+ private static float getScaleFromKey(int key) {
+ return (float) key / SCALE_KEY_MULTIPLIER;
+ }
+
+ private static void put(float scaleKey, @NonNull FontScaleConverter fontScaleConverter) {
+ LOOKUP_TABLES.put(getKey(scaleKey), fontScaleConverter);
+ }
+
+ @Nullable
+ private static FontScaleConverter get(float scaleKey) {
+ return LOOKUP_TABLES.get(getKey(scaleKey));
+ }
+}
diff --git a/wear/protolayout/protolayout-material-core/src/main/java/androidx/wear/protolayout/materialcore/fontscaling/MathUtils.java b/wear/protolayout/protolayout-material-core/src/main/java/androidx/wear/protolayout/materialcore/fontscaling/MathUtils.java
new file mode 100644
index 0000000..5773d03
--- /dev/null
+++ b/wear/protolayout/protolayout-material-core/src/main/java/androidx/wear/protolayout/materialcore/fontscaling/MathUtils.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.wear.protolayout.materialcore.fontscaling;
+
+import static java.lang.Math.min;
+
+/** A class that contains utility methods related to numbers. */
+final class MathUtils {
+ private MathUtils() {}
+
+ /**
+ * Returns the linear interpolation of {@code amount} between {@code start} and {@code stop}.
+ */
+ static float lerp(float start, float stop, float amount) {
+ return start + (stop - start) * amount;
+ }
+
+ /**
+ * Returns the interpolation scalar (s) that satisfies the equation: {@code value = }{@link
+ * #lerp}{@code (a, b, s)}
+ *
+ * <p>If {@code a == b}, then this function will return 0.
+ */
+ static float lerpInv(float a, float b, float value) {
+ return a != b ? ((value - a) / (b - a)) : 0.0f;
+ }
+
+ /** Returns the single argument constrained between [0.0, 1.0]. */
+ static float saturate(float value) {
+ return value < 0.0f ? 0.0f : min(1.0f, value);
+ }
+
+ /** Returns the saturated (constrained between [0, 1]) result of {@link #lerpInv}. */
+ static float lerpInvSat(float a, float b, float value) {
+ return saturate(lerpInv(a, b, value));
+ }
+
+ /**
+ * Calculates a value in [rangeMin, rangeMax] that maps value in [valueMin, valueMax] to
+ * returnVal in [rangeMin, rangeMax].
+ *
+ * <p>Always returns a constrained value in the range [rangeMin, rangeMax], even if value is
+ * outside [valueMin, valueMax].
+ *
+ * <p>Eg: constrainedMap(0f, 100f, 0f, 1f, 0.5f) = 50f constrainedMap(20f, 200f, 10f, 20f, 20f)
+ * = 200f constrainedMap(20f, 200f, 10f, 20f, 50f) = 200f constrainedMap(10f, 50f, 10f, 20f, 5f)
+ * = 10f
+ *
+ * @param rangeMin minimum of the range that should be returned.
+ * @param rangeMax maximum of the range that should be returned.
+ * @param valueMin minimum of range to map {@code value} to.
+ * @param valueMax maximum of range to map {@code value} to.
+ * @param value to map to the range [{@code valueMin}, {@code valueMax}]. Note, can be outside
+ * this range, resulting in a clamped value.
+ * @return the mapped value, constrained to [{@code rangeMin}, {@code rangeMax}.
+ */
+ static float constrainedMap(
+ float rangeMin, float rangeMax, float valueMin, float valueMax, float value) {
+ return lerp(rangeMin, rangeMax, lerpInvSat(valueMin, valueMax, value));
+ }
+}
diff --git a/wear/protolayout/protolayout-material-core/src/test/java/androidx/wear/protolayout/materialcore/fontscaling/FontScaleConverterFactoryTest.kt b/wear/protolayout/protolayout-material-core/src/test/java/androidx/wear/protolayout/materialcore/fontscaling/FontScaleConverterFactoryTest.kt
new file mode 100644
index 0000000..841825b
--- /dev/null
+++ b/wear/protolayout/protolayout-material-core/src/test/java/androidx/wear/protolayout/materialcore/fontscaling/FontScaleConverterFactoryTest.kt
@@ -0,0 +1,232 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.protolayout.materialcore.fontscaling
+
+import androidx.core.util.forEach
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import kotlin.math.ceil
+import kotlin.math.floor
+import kotlin.random.Random.Default.nextFloat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class FontScaleConverterFactoryTest {
+
+ @Test
+ fun scale200IsTwiceAtSmallSizes() {
+ val table = FontScaleConverterFactory.forScale(2F)!!
+ assertThat(table.convertSpToDp(1F)).isWithin(CONVERSION_TOLERANCE).of(2f)
+ assertThat(table.convertSpToDp(8F)).isWithin(CONVERSION_TOLERANCE).of(16f)
+ assertThat(table.convertSpToDp(10F)).isWithin(CONVERSION_TOLERANCE).of(20f)
+ assertThat(table.convertSpToDp(5F)).isWithin(CONVERSION_TOLERANCE).of(10f)
+ assertThat(table.convertSpToDp(0F)).isWithin(CONVERSION_TOLERANCE).of(0f)
+ }
+
+ @LargeTest
+ @Test
+ fun missingLookupTablePastEnd_returnsLinear() {
+ val table = FontScaleConverterFactory.forScale(3F)!!
+ generateSequenceOfFractions(-10000f..10000f, step = 0.01f).map {
+ assertThat(table.convertSpToDp(it)).isWithin(CONVERSION_TOLERANCE).of(it * 3f)
+ }
+ assertThat(table.convertSpToDp(1F)).isWithin(CONVERSION_TOLERANCE).of(3f)
+ assertThat(table.convertSpToDp(8F)).isWithin(CONVERSION_TOLERANCE).of(24f)
+ assertThat(table.convertSpToDp(10F)).isWithin(CONVERSION_TOLERANCE).of(30f)
+ assertThat(table.convertSpToDp(5F)).isWithin(CONVERSION_TOLERANCE).of(15f)
+ assertThat(table.convertSpToDp(0F)).isWithin(CONVERSION_TOLERANCE).of(0f)
+ assertThat(table.convertSpToDp(50F)).isWithin(CONVERSION_TOLERANCE).of(150f)
+ assertThat(table.convertSpToDp(100F)).isWithin(CONVERSION_TOLERANCE).of(300f)
+ }
+
+ @SmallTest
+ fun missingLookupTable110_returnsInterpolated() {
+ val table = FontScaleConverterFactory.forScale(1.1F)!!
+
+ assertThat(table.convertSpToDp(1F)).isWithin(CONVERSION_TOLERANCE).of(1.1f)
+ assertThat(table.convertSpToDp(8F)).isWithin(CONVERSION_TOLERANCE).of(8f * 1.1f)
+ assertThat(table.convertSpToDp(10F)).isWithin(CONVERSION_TOLERANCE).of(11f)
+ assertThat(table.convertSpToDp(5F)).isWithin(CONVERSION_TOLERANCE).of(5f * 1.1f)
+ assertThat(table.convertSpToDp(0F)).isWithin(CONVERSION_TOLERANCE).of(0f)
+ assertThat(table.convertSpToDp(50F)).isLessThan(50f * 1.1f)
+ assertThat(table.convertSpToDp(100F)).isLessThan(100f * 1.1f)
+ }
+
+ @Test
+ fun missingLookupTable199_returnsInterpolated() {
+ val table = FontScaleConverterFactory.forScale(1.9999F)!!
+ assertThat(table.convertSpToDp(1F)).isWithin(CONVERSION_TOLERANCE).of(2f)
+ assertThat(table.convertSpToDp(8F)).isWithin(CONVERSION_TOLERANCE).of(16f)
+ assertThat(table.convertSpToDp(10F)).isWithin(CONVERSION_TOLERANCE).of(20f)
+ assertThat(table.convertSpToDp(5F)).isWithin(CONVERSION_TOLERANCE).of(10f)
+ assertThat(table.convertSpToDp(0F)).isWithin(CONVERSION_TOLERANCE).of(0f)
+ }
+
+ @Test
+ fun missingLookupTable160_returnsInterpolated() {
+ val table = FontScaleConverterFactory.forScale(1.6F)!!
+ assertThat(table.convertSpToDp(1F)).isWithin(CONVERSION_TOLERANCE).of(1f * 1.6F)
+ assertThat(table.convertSpToDp(8F)).isWithin(CONVERSION_TOLERANCE).of(8f * 1.6F)
+ assertThat(table.convertSpToDp(10F)).isWithin(CONVERSION_TOLERANCE).of(10f * 1.6F)
+ assertThat(table.convertSpToDp(20F)).isLessThan(20f * 1.6F)
+ assertThat(table.convertSpToDp(100F)).isLessThan(100f * 1.6F)
+ assertThat(table.convertSpToDp(5F)).isWithin(CONVERSION_TOLERANCE).of(5f * 1.6F)
+ assertThat(table.convertSpToDp(0F)).isWithin(CONVERSION_TOLERANCE).of(0f)
+ }
+
+ @SmallTest
+ fun missingLookupTableNegativeReturnsNull() {
+ assertThat(FontScaleConverterFactory.forScale(-1F)).isNull()
+ }
+
+ @SmallTest
+ fun unnecessaryFontScalesReturnsNull() {
+ assertThat(FontScaleConverterFactory.forScale(0F)).isNull()
+ assertThat(FontScaleConverterFactory.forScale(1F)).isNull()
+ assertThat(FontScaleConverterFactory.forScale(0.85F)).isNull()
+ }
+
+ @SmallTest
+ fun tablesMatchAndAreMonotonicallyIncreasing() {
+ FontScaleConverterFactory.LOOKUP_TABLES.forEach { _, lookupTable ->
+ assertThat(lookupTable.mToDpValues).hasLength(lookupTable.mFromSpValues.size)
+ assertThat(lookupTable.mToDpValues).isNotEmpty()
+
+ assertThat(lookupTable.mFromSpValues.asList()).isInStrictOrder()
+ assertThat(lookupTable.mToDpValues.asList()).isInStrictOrder()
+
+ assertThat(lookupTable.mFromSpValues.asList()).containsNoDuplicates()
+ assertThat(lookupTable.mToDpValues.asList()).containsNoDuplicates()
+ }
+ }
+
+ @SmallTest
+ fun testIsNonLinearFontScalingActive() {
+ assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(1f)).isFalse()
+ assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(0f)).isFalse()
+ assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(-1f)).isFalse()
+ assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(0.85f)).isFalse()
+ assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(1.02f)).isFalse()
+ assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(1.10f)).isFalse()
+ assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(1.15f)).isTrue()
+ assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(1.1499999f)).isTrue()
+ assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(1.5f)).isTrue()
+ assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(2f)).isTrue()
+ assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(3f)).isTrue()
+ }
+
+ @LargeTest
+ @Test
+ fun allFeasibleScalesAndConversionsDoNotCrash() {
+ generateSequenceOfFractions(-10f..10f, step = 0.1f)
+ .fuzzFractions()
+ .mapNotNull { FontScaleConverterFactory.forScale(it) }
+ .flatMap { table ->
+ generateSequenceOfFractions(-2000f..2000f, step = 0.1f).fuzzFractions().map {
+ Pair(table, it)
+ }
+ }
+ .forEach { (table, sp) ->
+ try {
+ // Truth is slow because it creates a bunch of
+ // objects. Don't use it unless we need to.
+ if (!table.convertSpToDp(sp).isFinite()) {
+ assertWithMessage("convertSpToDp(%s) on table: %s", sp, table)
+ .that(table.convertSpToDp(sp))
+ .isFinite()
+ }
+ } catch (e: Exception) {
+ throw AssertionError("Exception during convertSpToDp($sp) on table: $table", e)
+ }
+ }
+ }
+
+ @Test
+ fun testGenerateSequenceOfFractions() {
+ val fractions = generateSequenceOfFractions(-1000f..1000f, step = 0.1f).toList()
+ fractions.forEach {
+ assertThat(it).isAtLeast(-1000f)
+ assertThat(it).isAtMost(1000f)
+ }
+
+ assertThat(fractions).isInStrictOrder()
+ assertThat(fractions).hasSize(1000 * 2 * 10 + 1) // Don't forget the 0 in the middle!
+
+ assertThat(fractions).contains(100f)
+ assertThat(fractions).contains(500.1f)
+ assertThat(fractions).contains(500.2f)
+ assertThat(fractions).contains(0.2f)
+ assertThat(fractions).contains(0f)
+ assertThat(fractions).contains(-10f)
+ assertThat(fractions).contains(-10f)
+ assertThat(fractions).contains(-10.3f)
+
+ assertThat(fractions).doesNotContain(-10.31f)
+ assertThat(fractions).doesNotContain(0.35f)
+ assertThat(fractions).doesNotContain(0.31f)
+ assertThat(fractions).doesNotContain(-.35f)
+ }
+
+ @Test
+ fun testFuzzFractions() {
+ val numFuzzedFractions = 6
+ val fractions =
+ generateSequenceOfFractions(-1000f..1000f, step = 0.1f).fuzzFractions().toList()
+ fractions.forEach {
+ assertThat(it).isAtLeast(-1000f)
+ assertThat(it).isLessThan(1001f)
+ }
+
+ val numGeneratedFractions = 1000 * 2 * 10 + 1 // Don't forget the 0 in the middle!
+ assertThat(fractions).hasSize(numGeneratedFractions * numFuzzedFractions)
+
+ assertThat(fractions).contains(100f)
+ assertThat(fractions).contains(500.1f)
+ assertThat(fractions).contains(500.2f)
+ assertThat(fractions).contains(0.2f)
+ assertThat(fractions).contains(0f)
+ assertThat(fractions).contains(-10f)
+ assertThat(fractions).contains(-10f)
+ assertThat(fractions).contains(-10.3f)
+ }
+
+ companion object {
+ private const val CONVERSION_TOLERANCE = 0.05f
+ }
+}
+
+fun generateSequenceOfFractions(
+ range: ClosedFloatingPointRange<Float>,
+ step: Float
+): Sequence<Float> {
+ val multiplier = 1f / step
+ val start = floor(range.start * multiplier).toInt()
+ val endInclusive = ceil(range.endInclusive * multiplier).toInt()
+ return generateSequence(start) { it + 1 }
+ .takeWhile { it <= endInclusive }
+ .map { it.toFloat() / multiplier }
+}
+
+private fun Sequence<Float>.fuzzFractions(): Sequence<Float> {
+ return flatMap { i ->
+ listOf(i, i + 0.01f, i + 0.054f, i + 0.099f, i + nextFloat(), i + nextFloat())
+ }
+}
diff --git a/wear/protolayout/protolayout-material-core/src/test/java/androidx/wear/protolayout/materialcore/fontscaling/FontScaleConverterTest.kt b/wear/protolayout/protolayout-material-core/src/test/java/androidx/wear/protolayout/materialcore/fontscaling/FontScaleConverterTest.kt
new file mode 100644
index 0000000..7dc342a
--- /dev/null
+++ b/wear/protolayout/protolayout-material-core/src/test/java/androidx/wear/protolayout/materialcore/fontscaling/FontScaleConverterTest.kt
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.protolayout.materialcore.fontscaling
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertWithMessage
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class FontScaleConverterTest {
+
+ @Test
+ fun straightInterpolation() {
+ val table = createTable(8f to 8f, 10f to 10f, 20f to 20f)
+ verifyConversionBothWays(table, 1f, 1F)
+ verifyConversionBothWays(table, 8f, 8F)
+ verifyConversionBothWays(table, 10f, 10F)
+ verifyConversionBothWays(table, 30f, 30F)
+ verifyConversionBothWays(table, 20f, 20F)
+ verifyConversionBothWays(table, 5f, 5F)
+ verifyConversionBothWays(table, 0f, 0F)
+ }
+
+ @Test
+ fun interpolate200Percent() {
+ val table = createTable(8f to 16f, 10f to 20f, 30f to 60f)
+ verifyConversionBothWays(table, 2f, 1F)
+ verifyConversionBothWays(table, 16f, 8F)
+ verifyConversionBothWays(table, 20f, 10F)
+ verifyConversionBothWays(table, 60f, 30F)
+ verifyConversionBothWays(table, 40f, 20F)
+ verifyConversionBothWays(table, 10f, 5F)
+ verifyConversionBothWays(table, 0f, 0F)
+ }
+
+ @Test
+ fun interpolate150Percent() {
+ val table = createTable(2f to 3f, 10f to 15f, 20f to 30f, 100f to 150f)
+ verifyConversionBothWays(table, 3f, 2F)
+ verifyConversionBothWays(table, 1.5f, 1F)
+ verifyConversionBothWays(table, 12f, 8F)
+ verifyConversionBothWays(table, 15f, 10F)
+ verifyConversionBothWays(table, 30f, 20F)
+ verifyConversionBothWays(table, 75f, 50F)
+ verifyConversionBothWays(table, 7.5f, 5F)
+ verifyConversionBothWays(table, 0f, 0F)
+ }
+
+ @Test
+ fun pastEndsUsesLastScalingFactor() {
+ val table = createTable(8f to 16f, 10f to 20f, 30f to 60f)
+ verifyConversionBothWays(table, 200f, 100F)
+ verifyConversionBothWays(table, 62f, 31F)
+ verifyConversionBothWays(table, 2000f, 1000F)
+ verifyConversionBothWays(table, 4000f, 2000F)
+ verifyConversionBothWays(table, 20000f, 10000F)
+ }
+
+ @Test
+ fun negativeSpIsNegativeDp() {
+ val table = createTable(8f to 16f, 10f to 20f, 30f to 60f)
+ verifyConversionBothWays(table, -2f, -1F)
+ verifyConversionBothWays(table, -16f, -8F)
+ verifyConversionBothWays(table, -20f, -10F)
+ verifyConversionBothWays(table, -60f, -30F)
+ verifyConversionBothWays(table, -40f, -20F)
+ verifyConversionBothWays(table, -10f, -5F)
+ verifyConversionBothWays(table, 0f, -0F)
+ }
+
+ private fun createTable(vararg pairs: Pair<Float, Float>) =
+ FontScaleConverter(
+ pairs.map { it.first }.toFloatArray(),
+ pairs.map { it.second }.toFloatArray()
+ )
+
+ private fun verifyConversionBothWays(
+ table: FontScaleConverter,
+ expectedDp: Float,
+ spToConvert: Float
+ ) {
+ assertWithMessage("convertSpToDp")
+ .that(table.convertSpToDp(spToConvert))
+ .isWithin(CONVERSION_TOLERANCE)
+ .of(expectedDp)
+
+ assertWithMessage("inverse: convertDpToSp")
+ .that(table.convertDpToSp(expectedDp))
+ .isWithin(CONVERSION_TOLERANCE)
+ .of(spToConvert)
+ }
+
+ companion object {
+ private const val CONVERSION_TOLERANCE = 0.05f
+ }
+}
diff --git a/wear/protolayout/protolayout-material/build.gradle b/wear/protolayout/protolayout-material/build.gradle
index be951af..a979b44 100644
--- a/wear/protolayout/protolayout-material/build.gradle
+++ b/wear/protolayout/protolayout-material/build.gradle
@@ -36,7 +36,7 @@
implementation(project(":wear:protolayout:protolayout-material-core"))
implementation(project(":wear:protolayout:protolayout-proto"))
- implementation("androidx.annotation:annotation-experimental:1.4.0")
+ implementation("androidx.annotation:annotation-experimental:1.4.1")
lintChecks(project(":wear:protolayout:protolayout-lint"))
lintPublish(project(":wear:protolayout:protolayout-lint"))
diff --git a/wear/protolayout/protolayout-material/src/androidTest/java/androidx/wear/protolayout/material/MaterialGoldenTest.java b/wear/protolayout/protolayout-material/src/androidTest/java/androidx/wear/protolayout/material/MaterialGoldenTest.java
index 0299154..7dd4feb 100644
--- a/wear/protolayout/protolayout-material/src/androidTest/java/androidx/wear/protolayout/material/MaterialGoldenTest.java
+++ b/wear/protolayout/protolayout-material/src/androidTest/java/androidx/wear/protolayout/material/MaterialGoldenTest.java
@@ -67,21 +67,10 @@
@Parameterized.Parameters(name = "{0}")
public static Collection<Object[]> data() {
- Context context = InstrumentationRegistry.getInstrumentation().getContext();
+ Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
float scale = displayMetrics.density;
- InstrumentationRegistry.getInstrumentation()
- .getContext()
- .getResources()
- .getDisplayMetrics()
- .setTo(displayMetrics);
- InstrumentationRegistry.getInstrumentation()
- .getTargetContext()
- .getResources()
- .getDisplayMetrics()
- .setTo(displayMetrics);
-
DeviceParameters deviceParameters =
new DeviceParameters.Builder()
.setScreenWidthDp(pxToDp(SCREEN_WIDTH, scale))
diff --git a/wear/protolayout/protolayout-material/src/androidTest/java/androidx/wear/protolayout/material/MaterialGoldenXLTest.java b/wear/protolayout/protolayout-material/src/androidTest/java/androidx/wear/protolayout/material/MaterialGoldenXLTest.java
index 61734b9..ef36a3d 100644
--- a/wear/protolayout/protolayout-material/src/androidTest/java/androidx/wear/protolayout/material/MaterialGoldenXLTest.java
+++ b/wear/protolayout/protolayout-material/src/androidTest/java/androidx/wear/protolayout/material/MaterialGoldenXLTest.java
@@ -16,11 +16,12 @@
package androidx.wear.protolayout.material;
-import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
import static androidx.wear.protolayout.material.RunnerUtils.SCREEN_HEIGHT;
import static androidx.wear.protolayout.material.RunnerUtils.SCREEN_WIDTH;
import static androidx.wear.protolayout.material.RunnerUtils.convertToTestParameters;
+import static androidx.wear.protolayout.material.RunnerUtils.getFontScale;
import static androidx.wear.protolayout.material.RunnerUtils.runSingleScreenshotTest;
+import static androidx.wear.protolayout.material.RunnerUtils.setFontScale;
import static androidx.wear.protolayout.material.RunnerUtils.waitForNotificationToDisappears;
import static androidx.wear.protolayout.material.TestCasesGenerator.XXXL_SCALE_SUFFIX;
import static androidx.wear.protolayout.material.TestCasesGenerator.generateTestCases;
@@ -38,6 +39,8 @@
import androidx.wear.protolayout.DeviceParametersBuilders.DeviceParameters;
import androidx.wear.protolayout.material.RunnerUtils.TestCase;
+import org.junit.After;
+import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -50,15 +53,10 @@
@RunWith(Parameterized.class)
@LargeTest
public class MaterialGoldenXLTest {
- /* We set DisplayMetrics in the data() method for creating test cases. However, when running all
- tests together, first all parametrization (data()) methods are called, and then individual
- tests, causing that actual DisplayMetrics will be different. So we need to restore it before
- each test. */
- private static final DisplayMetrics DISPLAY_METRICS_FOR_TEST = new DisplayMetrics();
- private static final DisplayMetrics OLD_DISPLAY_METRICS = new DisplayMetrics();
-
private static final float FONT_SCALE_XXXL = 1.24f;
+ private static float originalFontScale;
+
private final TestCase mTestCase;
private final String mExpected;
@@ -76,27 +74,18 @@
return (int) ((px - 0.5f) / scale);
}
- @SuppressWarnings("deprecation")
@Parameterized.Parameters(name = "{0}")
- public static Collection<Object[]> data() {
+ public static Collection<Object[]> data() throws Exception {
+ // These "parameters" methods are called before any parameterized test (from any class)
+ // executes. We set and later reset the font here to have the correct context during test
+ // generation. We later set and reset the font for the actual test in BeforeClass/AfterClass
+ // methods.
Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
- DisplayMetrics currentDisplayMetrics = new DisplayMetrics();
+ originalFontScale =
+ getFontScale(InstrumentationRegistry.getInstrumentation().getTargetContext());
+ setFontScale(context, FONT_SCALE_XXXL);
+
DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
- currentDisplayMetrics.setTo(displayMetrics);
- displayMetrics.scaledDensity *= FONT_SCALE_XXXL;
-
- InstrumentationRegistry.getInstrumentation()
- .getContext()
- .getResources()
- .getDisplayMetrics()
- .setTo(displayMetrics);
- InstrumentationRegistry.getInstrumentation()
- .getTargetContext()
- .getResources()
- .getDisplayMetrics()
- .setTo(displayMetrics);
-
- DISPLAY_METRICS_FOR_TEST.setTo(displayMetrics);
float scale = displayMetrics.density;
DeviceParameters deviceParameters =
@@ -104,6 +93,7 @@
.setScreenWidthDp(pxToDp(SCREEN_WIDTH, scale))
.setScreenHeightDp(pxToDp(SCREEN_HEIGHT, scale))
.setScreenDensity(displayMetrics.density)
+ .setFontScale(context.getResources().getConfiguration().fontScale)
// Not important for components.
.setScreenShape(DeviceParametersBuilders.SCREEN_SHAPE_RECT)
.build();
@@ -126,31 +116,22 @@
/* isForLtr= */ true));
// Restore state before this method, so other test have correct context.
- InstrumentationRegistry.getInstrumentation()
- .getContext()
- .getResources()
- .getDisplayMetrics()
- .setTo(currentDisplayMetrics);
- InstrumentationRegistry.getInstrumentation()
- .getTargetContext()
- .getResources()
- .getDisplayMetrics()
- .setTo(currentDisplayMetrics);
-
+ setFontScale(context, originalFontScale);
waitForNotificationToDisappears();
return testCaseList;
}
- @Parameterized.BeforeParam
- public static void restoreBefore() {
- OLD_DISPLAY_METRICS.setTo(getApplicationContext().getResources().getDisplayMetrics());
- getApplicationContext().getResources().getDisplayMetrics().setTo(DISPLAY_METRICS_FOR_TEST);
+ @Before
+ public void setUp() {
+ setFontScale(
+ InstrumentationRegistry.getInstrumentation().getTargetContext(), FONT_SCALE_XXXL);
}
- @Parameterized.AfterParam
- public static void restoreAfter() {
- getApplicationContext().getResources().getDisplayMetrics().setTo(OLD_DISPLAY_METRICS);
+ @After
+ public void tearDown() {
+ setFontScale(
+ InstrumentationRegistry.getInstrumentation().getTargetContext(), originalFontScale);
}
@Test
diff --git a/wear/protolayout/protolayout-material/src/androidTest/java/androidx/wear/protolayout/material/RunnerUtils.java b/wear/protolayout/protolayout-material/src/androidTest/java/androidx/wear/protolayout/material/RunnerUtils.java
index 0fe42d3..dd901e5 100644
--- a/wear/protolayout/protolayout-material/src/androidTest/java/androidx/wear/protolayout/material/RunnerUtils.java
+++ b/wear/protolayout/protolayout-material/src/androidTest/java/androidx/wear/protolayout/material/RunnerUtils.java
@@ -17,8 +17,9 @@
package androidx.wear.protolayout.material;
import android.annotation.SuppressLint;
-import android.app.Activity;
+import android.content.Context;
import android.content.Intent;
+import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.util.DisplayMetrics;
import android.util.Log;
@@ -70,53 +71,39 @@
startIntent.putExtra("layout", layoutPayload);
startIntent.putExtra(GoldenTestActivity.USE_RTL_DIRECTION, isRtlDirection);
- ActivityScenario<GoldenTestActivity> scenario = ActivityScenario.launch(startIntent);
- InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+ try (ActivityScenario<GoldenTestActivity> scenario = ActivityScenario.launch(startIntent)) {
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync();
- try {
- // Wait 1s after launching the activity. This allows for the old white layout in the
- // bootstrap activity to fully go away before proceeding.
- Thread.sleep(1000);
- } catch (Exception ex) {
- if (ex instanceof InterruptedException) {
- Thread.currentThread().interrupt();
+ try {
+ // Wait 1s after launching the activity. This allows for the old white layout in the
+ // bootstrap activity to fully go away before proceeding.
+ Thread.sleep(100);
+ } catch (Exception ex) {
+ if (ex instanceof InterruptedException) {
+ Thread.currentThread().interrupt();
+ }
+ Log.e("MaterialGoldenTest", "Error sleeping", ex);
}
- Log.e("MaterialGoldenTest", "Error sleeping", ex);
- }
- DisplayMetrics displayMetrics =
- InstrumentationRegistry.getInstrumentation()
- .getTargetContext()
- .getResources()
- .getDisplayMetrics();
+ DisplayMetrics displayMetrics =
+ InstrumentationRegistry.getInstrumentation()
+ .getTargetContext()
+ .getResources()
+ .getDisplayMetrics();
- // RTL will put the View on the right side.
- int screenWidthStart = isRtlDirection ? displayMetrics.widthPixels - SCREEN_WIDTH : 0;
+ // RTL will put the View on the right side.
+ int screenWidthStart = isRtlDirection ? displayMetrics.widthPixels - SCREEN_WIDTH : 0;
- Bitmap bitmap =
- Bitmap.createBitmap(
- InstrumentationRegistry.getInstrumentation()
- .getUiAutomation()
- .takeScreenshot(),
- screenWidthStart,
- 0,
- SCREEN_WIDTH,
- SCREEN_HEIGHT);
- rule.assertBitmapAgainstGolden(bitmap, expected, new MSSIMMatcher());
-
- // There's a weird bug (related to b/159805732) where, when calling .close() on
- // ActivityScenario or calling finish() and immediately exiting the test, the test can hang
- // on a white screen for 45s. Closing the activity here and waiting for 1s seems to fix
- // this.
- scenario.onActivity(Activity::finish);
-
- try {
- Thread.sleep(1000);
- } catch (Exception ex) {
- if (ex instanceof InterruptedException) {
- Thread.currentThread().interrupt();
- }
- Log.e("MaterialGoldenTest", "Error sleeping", ex);
+ Bitmap bitmap =
+ Bitmap.createBitmap(
+ InstrumentationRegistry.getInstrumentation()
+ .getUiAutomation()
+ .takeScreenshot(),
+ screenWidthStart,
+ 0,
+ SCREEN_WIDTH,
+ SCREEN_HEIGHT);
+ rule.assertBitmapAgainstGolden(bitmap, expected, new MSSIMMatcher());
}
}
@@ -153,4 +140,17 @@
this.isForLtr = isForLtr;
}
}
+
+ public static float getFontScale(Context context) {
+ return context.getResources().getConfiguration().fontScale;
+ }
+
+ @SuppressWarnings("deprecation")
+ public static void setFontScale(Context context, float fontScale) {
+ Configuration newConfiguration =
+ new Configuration(context.getResources().getConfiguration());
+ newConfiguration.fontScale = fontScale;
+ context.getResources()
+ .updateConfiguration(newConfiguration, context.getResources().getDisplayMetrics());
+ }
}
diff --git a/wear/protolayout/protolayout-material/src/androidTest/java/androidx/wear/protolayout/material/layouts/LayoutsGoldenTest.java b/wear/protolayout/protolayout-material/src/androidTest/java/androidx/wear/protolayout/material/layouts/LayoutsGoldenTest.java
index c613323..9685084e 100644
--- a/wear/protolayout/protolayout-material/src/androidTest/java/androidx/wear/protolayout/material/layouts/LayoutsGoldenTest.java
+++ b/wear/protolayout/protolayout-material/src/androidTest/java/androidx/wear/protolayout/material/layouts/LayoutsGoldenTest.java
@@ -65,21 +65,10 @@
@Parameterized.Parameters(name = "{0}")
public static Collection<Object[]> data() {
- Context context = InstrumentationRegistry.getInstrumentation().getContext();
+ Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
float scale = displayMetrics.density;
- InstrumentationRegistry.getInstrumentation()
- .getContext()
- .getResources()
- .getDisplayMetrics()
- .setTo(displayMetrics);
- InstrumentationRegistry.getInstrumentation()
- .getTargetContext()
- .getResources()
- .getDisplayMetrics()
- .setTo(displayMetrics);
-
DeviceParameters deviceParameters =
new DeviceParameters.Builder()
.setScreenWidthDp(pxToDp(SCREEN_WIDTH, scale))
diff --git a/wear/protolayout/protolayout-material/src/androidTest/java/androidx/wear/protolayout/material/layouts/LayoutsGoldenXLTest.java b/wear/protolayout/protolayout-material/src/androidTest/java/androidx/wear/protolayout/material/layouts/LayoutsGoldenXLTest.java
index 3c49e92..dc82303 100644
--- a/wear/protolayout/protolayout-material/src/androidTest/java/androidx/wear/protolayout/material/layouts/LayoutsGoldenXLTest.java
+++ b/wear/protolayout/protolayout-material/src/androidTest/java/androidx/wear/protolayout/material/layouts/LayoutsGoldenXLTest.java
@@ -16,11 +16,12 @@
package androidx.wear.protolayout.material.layouts;
-import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
import static androidx.wear.protolayout.material.RunnerUtils.SCREEN_HEIGHT;
import static androidx.wear.protolayout.material.RunnerUtils.SCREEN_WIDTH;
import static androidx.wear.protolayout.material.RunnerUtils.convertToTestParameters;
+import static androidx.wear.protolayout.material.RunnerUtils.getFontScale;
import static androidx.wear.protolayout.material.RunnerUtils.runSingleScreenshotTest;
+import static androidx.wear.protolayout.material.RunnerUtils.setFontScale;
import static androidx.wear.protolayout.material.RunnerUtils.waitForNotificationToDisappears;
import static androidx.wear.protolayout.material.layouts.TestCasesGenerator.XXXL_SCALE_SUFFIX;
import static androidx.wear.protolayout.material.layouts.TestCasesGenerator.generateTestCases;
@@ -36,6 +37,8 @@
import androidx.wear.protolayout.DeviceParametersBuilders.DeviceParameters;
import androidx.wear.protolayout.material.RunnerUtils.TestCase;
+import org.junit.After;
+import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -47,15 +50,10 @@
@RunWith(Parameterized.class)
@LargeTest
public class LayoutsGoldenXLTest {
- /* We set DisplayMetrics in the data() method for creating test cases. However, when running all
- tests together, first all parametrization (data()) methods are called, and then individual
- tests, causing that actual DisplayMetrics will be different. So we need to restore it before
- each test. */
- private static final DisplayMetrics DISPLAY_METRICS_FOR_TEST = new DisplayMetrics();
- private static final DisplayMetrics OLD_DISPLAY_METRICS = new DisplayMetrics();
-
private static final float FONT_SCALE_XXXL = 1.24f;
+ private static float originalFontScale;
+
private final TestCase mTestCase;
private final String mExpected;
@@ -73,27 +71,18 @@
return (int) ((px - 0.5f) / scale);
}
- @SuppressWarnings("deprecation")
@Parameterized.Parameters(name = "{0}")
- public static Collection<Object[]> data() {
+ public static Collection<Object[]> data() throws Exception {
+ // These "parameters" methods are called before any parameterized test (from any class)
+ // executes. We set and later reset the font here to have the correct context during test
+ // generation. We later set and reset the font for the actual test in BeforeClass/AfterClass
+ // methods.
Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
- DisplayMetrics currentDisplayMetrics = new DisplayMetrics();
+ originalFontScale =
+ getFontScale(InstrumentationRegistry.getInstrumentation().getTargetContext());
+ setFontScale(context, FONT_SCALE_XXXL);
+
DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
- currentDisplayMetrics.setTo(displayMetrics);
- displayMetrics.scaledDensity *= FONT_SCALE_XXXL;
-
- InstrumentationRegistry.getInstrumentation()
- .getContext()
- .getResources()
- .getDisplayMetrics()
- .setTo(displayMetrics);
- InstrumentationRegistry.getInstrumentation()
- .getTargetContext()
- .getResources()
- .getDisplayMetrics()
- .setTo(displayMetrics);
-
- DISPLAY_METRICS_FOR_TEST.setTo(displayMetrics);
float scale = displayMetrics.density;
DeviceParameters deviceParameters =
@@ -101,6 +90,7 @@
.setScreenWidthDp(pxToDp(SCREEN_WIDTH, scale))
.setScreenHeightDp(pxToDp(SCREEN_HEIGHT, scale))
.setScreenDensity(displayMetrics.density)
+ .setFontScale(context.getResources().getConfiguration().fontScale)
// TODO(b/231543947): Add test cases for round screen.
.setScreenShape(DeviceParametersBuilders.SCREEN_SHAPE_RECT)
.build();
@@ -111,40 +101,23 @@
/* isForRtl= */ true,
/* isForLtr= */ true);
- // Restore state before this method, so other test have correct context. This is needed here
- // too, besides in restoreBefore and restoreAfter as the test cases builder uses the context
- // to apply font scaling, so we need that display metrics passed in. However, after
- // generating cases we need to restore the state as other data() methods in this package can
- // work correctly with the default state, as when the tests are run, first all data() static
- // methods are called, and then parameterized test cases.
- InstrumentationRegistry.getInstrumentation()
- .getContext()
- .getResources()
- .getDisplayMetrics()
- .setTo(currentDisplayMetrics);
- InstrumentationRegistry.getInstrumentation()
- .getTargetContext()
- .getResources()
- .getDisplayMetrics()
- .setTo(currentDisplayMetrics);
+ // Restore state before this method, so other test have correct context.
+ setFontScale(context, originalFontScale);
waitForNotificationToDisappears();
return testCaseList;
}
- @Parameterized.BeforeParam
- public static void restoreBefore() {
- // Set the state as it was in data() method when we generated test cases. This was
- // overridden by other static data() methods, so we need to restore it.
- OLD_DISPLAY_METRICS.setTo(getApplicationContext().getResources().getDisplayMetrics());
- getApplicationContext().getResources().getDisplayMetrics().setTo(DISPLAY_METRICS_FOR_TEST);
+ @Before
+ public void setUp() {
+ setFontScale(
+ InstrumentationRegistry.getInstrumentation().getTargetContext(), FONT_SCALE_XXXL);
}
- @Parameterized.AfterParam
- public static void restoreAfter() {
- // Restore the state to default, so the other tests and emulator have the correct starter
- // state.
- getApplicationContext().getResources().getDisplayMetrics().setTo(OLD_DISPLAY_METRICS);
+ @After
+ public void tearDown() {
+ setFontScale(
+ InstrumentationRegistry.getInstrumentation().getTargetContext(), originalFontScale);
}
@Test
diff --git a/wear/protolayout/protolayout-material/src/androidTest/java/androidx/wear/protolayout/material/test/GoldenTestActivity.java b/wear/protolayout/protolayout-material/src/androidTest/java/androidx/wear/protolayout/material/test/GoldenTestActivity.java
index 10c457c..8717539d 100644
--- a/wear/protolayout/protolayout-material/src/androidTest/java/androidx/wear/protolayout/material/test/GoldenTestActivity.java
+++ b/wear/protolayout/protolayout-material/src/androidTest/java/androidx/wear/protolayout/material/test/GoldenTestActivity.java
@@ -45,12 +45,14 @@
import com.google.common.util.concurrent.MoreExecutors;
import java.util.Locale;
+import java.util.concurrent.ExecutionException;
@SuppressWarnings("deprecation")
public class GoldenTestActivity extends Activity {
/** Extra to be put in the intent if test should use RTL direction on parent View. */
public static final String USE_RTL_DIRECTION = "using_rtl";
+
private static final String ICON_ID = "icon";
private static final String ICON_ID_SMALL = "icon_small";
private static final String AVATAR = "avatar_image";
@@ -81,7 +83,15 @@
.setIsViewFullyVisible(true)
.build());
- instance.renderAndAttach(checkNotNull(layout).toProto(), resources.toProto(), root);
+ try {
+ instance.renderAndAttach(checkNotNull(layout).toProto(), resources.toProto(), root)
+ .get();
+ } catch (ExecutionException e) {
+ throw new RuntimeException(e);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ throw new RuntimeException(e);
+ }
View firstChild = root.getChildAt(0);
@@ -136,7 +146,7 @@
Locale.setDefault(locale);
Configuration config = new Configuration();
config.setLocale(locale);
- context.getResources().updateConfiguration(
- config, context.getResources().getDisplayMetrics());
+ context.getResources()
+ .updateConfiguration(config, context.getResources().getDisplayMetrics());
}
}
diff --git a/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/Typography.java b/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/Typography.java
index 9e1613e..da9219f 100644
--- a/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/Typography.java
+++ b/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/Typography.java
@@ -16,6 +16,9 @@
package androidx.wear.protolayout.material;
+import static android.os.Build.VERSION.SDK_INT;
+import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
+
import static androidx.annotation.Dimension.DP;
import static androidx.annotation.Dimension.SP;
import static androidx.wear.protolayout.DimensionBuilders.sp;
@@ -28,7 +31,6 @@
import android.annotation.SuppressLint;
import android.content.Context;
-import android.util.DisplayMetrics;
import androidx.annotation.Dimension;
import androidx.annotation.IntDef;
@@ -40,6 +42,8 @@
import androidx.wear.protolayout.LayoutElementBuilders.FontStyle;
import androidx.wear.protolayout.LayoutElementBuilders.FontVariant;
import androidx.wear.protolayout.LayoutElementBuilders.FontWeight;
+import androidx.wear.protolayout.materialcore.fontscaling.FontScaleConverter;
+import androidx.wear.protolayout.materialcore.fontscaling.FontScaleConverterFactory;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -120,6 +124,9 @@
TYPOGRAPHY_TO_LINE_HEIGHT_SP.put(TYPOGRAPHY_CAPTION2, 16f);
TYPOGRAPHY_TO_LINE_HEIGHT_SP.put(TYPOGRAPHY_CAPTION3, 14f);
}
+
+ private Typography() {}
+
/**
* Returns the {@link FontStyle.Builder} for the given FontStyle code with the recommended size,
* weight and letter spacing. Font will be scalable.
@@ -130,8 +137,6 @@
return getFontStyleBuilder(fontStyleCode, context, true);
}
- private Typography() {}
-
/**
* Returns the {@link FontStyle.Builder} for the given Typography code with the recommended
* size, weight and letter spacing, with the option to make this font not scalable.
@@ -183,17 +188,29 @@
return sp(checkNotNull(TYPOGRAPHY_TO_LINE_HEIGHT_SP.get(typography)).intValue());
}
- @NonNull
- @SuppressLint("ResourceType")
- @SuppressWarnings("deprecation") // scaledDensity, b/335215227
- // This is a helper function to make the font not scalable. It should interpret in value as DP
- // and convert it to SP which is needed to be passed in as a font size. However, we will pass an
- // SP object to it, because the default style is defined in it, but for the case when the font
- // size on device in 1, so the DP is equal to SP.
- private static SpProp dpToSp(@NonNull Context context, @Dimension(unit = DP) float valueDp) {
- DisplayMetrics metrics = context.getResources().getDisplayMetrics();
- float scaledSp = (valueDp / metrics.scaledDensity) * metrics.density;
- return sp(scaledSp);
+ /**
+ * This is a helper function to make the font not scalable. It should interpret in value as DP
+ * and convert it to SP which is needed to be passed in as a font size. However, we will pass an
+ * SP object to it, because the default style is defined in it, but for the case when the font
+ * size on device is 1, so the DP is equal to SP.
+ */
+ @Dimension(unit = SP)
+ private static float dpToSp(float fontScale, @Dimension(unit = DP) float valueDp) {
+ FontScaleConverter converter =
+ (SDK_INT >= UPSIDE_DOWN_CAKE)
+ ? FontScaleConverterFactory.forScale(fontScale)
+ : null;
+
+ if (converter == null) {
+ return dpToSpLinear(fontScale, valueDp);
+ }
+
+ return converter.convertDpToSp(valueDp);
+ }
+
+ @Dimension(unit = SP)
+ private static float dpToSpLinear(float fontScale, @Dimension(unit = DP) float valueDp) {
+ return valueDp / fontScale;
}
// The @Dimension(unit = SP) on sp() is seemingly being ignored, so lint complains that we're
@@ -206,8 +223,9 @@
float letterSpacing,
boolean isScalable,
@NonNull Context context) {
+ float fontScale = context.getResources().getConfiguration().fontScale;
return new FontStyle.Builder()
- .setSize(isScalable ? DimensionBuilders.sp(size) : dpToSp(context, size))
+ .setSize(DimensionBuilders.sp(isScalable ? size : dpToSp(fontScale, size)))
.setLetterSpacing(DimensionBuilders.em(letterSpacing))
.setVariant(variant)
.setWeight(weight);
diff --git a/wear/protolayout/protolayout/build.gradle b/wear/protolayout/protolayout/build.gradle
index 5d37b2ad..8b883b5 100644
--- a/wear/protolayout/protolayout/build.gradle
+++ b/wear/protolayout/protolayout/build.gradle
@@ -33,7 +33,7 @@
api("androidx.annotation:annotation:1.2.0")
api(project(":wear:protolayout:protolayout-expression"))
- implementation("androidx.annotation:annotation-experimental:1.4.0")
+ implementation("androidx.annotation:annotation-experimental:1.4.1")
implementation(project(":wear:protolayout:protolayout-proto"))
lintChecks(project(":wear:protolayout:protolayout-lint"))
diff --git a/wear/tiles/tiles/build.gradle b/wear/tiles/tiles/build.gradle
index 8b85e94..9d64b88 100644
--- a/wear/tiles/tiles/build.gradle
+++ b/wear/tiles/tiles/build.gradle
@@ -36,7 +36,7 @@
compileOnly files("../../wear_sdk/wear-sdk.jar")
- implementation("androidx.annotation:annotation-experimental:1.4.0")
+ implementation("androidx.annotation:annotation-experimental:1.4.1")
implementation("androidx.concurrent:concurrent-futures:1.1.0")
implementation(project(":wear:tiles:tiles-proto"))
diff --git a/wear/tiles/tiles/src/main/java/androidx/wear/tiles/TileService.java b/wear/tiles/tiles/src/main/java/androidx/wear/tiles/TileService.java
index 36a655c..ab30109 100644
--- a/wear/tiles/tiles/src/main/java/androidx/wear/tiles/TileService.java
+++ b/wear/tiles/tiles/src/main/java/androidx/wear/tiles/TileService.java
@@ -28,7 +28,6 @@
import android.os.RemoteException;
import android.util.Log;
-import androidx.annotation.DoNotInline;
import androidx.annotation.MainThread;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -684,7 +683,6 @@
@RequiresApi(34)
private static class Api34Impl {
- @DoNotInline
@NonNull
static ListenableFuture<List<ActiveTileIdentifier>> getActiveTilesAsync(
@NonNull TilesManager tilesManager, @NonNull Executor executor) {
diff --git a/wear/watchface/watchface-complications-data/src/main/java/androidx/wear/watchface/utility/TraceEvent.kt b/wear/watchface/watchface-complications-data/src/main/java/androidx/wear/watchface/utility/TraceEvent.kt
index 18e1f0f..395f381 100644
--- a/wear/watchface/watchface-complications-data/src/main/java/androidx/wear/watchface/utility/TraceEvent.kt
+++ b/wear/watchface/watchface-complications-data/src/main/java/androidx/wear/watchface/utility/TraceEvent.kt
@@ -18,7 +18,6 @@
import android.os.Build
import android.os.Trace
-import androidx.annotation.DoNotInline
import androidx.annotation.RequiresApi
import androidx.annotation.RestrictTo
import java.io.Closeable
@@ -57,13 +56,11 @@
@RequiresApi(Build.VERSION_CODES.Q)
private object Api29Impl {
@JvmStatic
- @DoNotInline
fun callBeginAsyncSection(traceName: String, traceId: Int) {
Trace.beginAsyncSection(traceName, traceId)
}
@JvmStatic
- @DoNotInline
fun callEndAsyncSection(traceName: String, traceId: Int) {
Trace.endAsyncSection(traceName, traceId)
}
diff --git a/wear/wear-remote-interactions/src/main/java/androidx/wear/remote/interactions/RemoteInteractionsUtil.kt b/wear/wear-remote-interactions/src/main/java/androidx/wear/remote/interactions/RemoteInteractionsUtil.kt
index d79b16d..9719a8c 100644
--- a/wear/wear-remote-interactions/src/main/java/androidx/wear/remote/interactions/RemoteInteractionsUtil.kt
+++ b/wear/wear-remote-interactions/src/main/java/androidx/wear/remote/interactions/RemoteInteractionsUtil.kt
@@ -18,7 +18,6 @@
import android.content.Context
import android.os.Build
-import androidx.annotation.DoNotInline
import androidx.annotation.RequiresApi
internal object RemoteInteractionsUtil {
@@ -30,7 +29,6 @@
@RequiresApi(Build.VERSION_CODES.N)
private object Api24Impl {
@JvmStatic
- @DoNotInline
fun hasSystemFeature(context: Context) =
context.packageManager.hasSystemFeature(SYSTEM_FEATURE_WATCH)
}
diff --git a/webkit/webkit/src/main/java/androidx/webkit/internal/ApiHelperForM.java b/webkit/webkit/src/main/java/androidx/webkit/internal/ApiHelperForM.java
index cb66c5b..d2caca4 100644
--- a/webkit/webkit/src/main/java/androidx/webkit/internal/ApiHelperForM.java
+++ b/webkit/webkit/src/main/java/androidx/webkit/internal/ApiHelperForM.java
@@ -25,7 +25,6 @@
import android.webkit.WebSettings;
import android.webkit.WebView;
-import androidx.annotation.DoNotInline;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
@@ -46,7 +45,6 @@
/**
* @see WebMessagePort#postMessage(WebMessage)
*/
- @DoNotInline
public static void postMessage(@NonNull WebMessagePort webMessagePort,
@NonNull WebMessage webMessage) {
webMessagePort.postMessage(webMessage);
@@ -55,7 +53,6 @@
/**
* @see WebMessagePort#close()
*/
- @DoNotInline
public static void close(@NonNull WebMessagePort webMessagePort) {
webMessagePort.close();
}
@@ -64,7 +61,6 @@
* Wraps the passed callback in the framework callback type to isolate new types in this class.
* @see WebMessagePort#setWebMessageCallback(WebMessagePort.WebMessageCallback)
*/
- @DoNotInline
public static void setWebMessageCallback(@NonNull WebMessagePort frameworksImpl,
@NonNull WebMessagePortCompat.WebMessageCallbackCompat callback) {
frameworksImpl.setWebMessageCallback(new WebMessagePort.WebMessageCallback() {
@@ -81,7 +77,6 @@
* {@link WebMessagePort#setWebMessageCallback(WebMessagePort.WebMessageCallback, Handler)}
* Wraps the passed callback in the framework callback type to isolate new types in this class.
*/
- @DoNotInline
public static void setWebMessageCallback(@NonNull WebMessagePort frameworksImpl,
@NonNull WebMessagePortCompat.WebMessageCallbackCompat callback,
@Nullable Handler handler) {
@@ -97,7 +92,6 @@
/**
* @see WebMessage#WebMessage(String, WebMessagePort[])} WebMessage
*/
- @DoNotInline
@NonNull
public static WebMessage createWebMessage(@NonNull WebMessageCompat message) {
return new WebMessage(message.getData(),
@@ -107,7 +101,6 @@
/**
* @see WebMessageCompat#WebMessageCompat(String, WebMessagePortCompat[])
*/
- @DoNotInline
@NonNull
public static WebMessageCompat createWebMessageCompat(@NonNull WebMessage webMessage) {
return new WebMessageCompat(webMessage.getData(),
@@ -117,7 +110,6 @@
/**
* @see WebResourceError#getErrorCode()
*/
- @DoNotInline
public static int getErrorCode(@NonNull WebResourceError webResourceError) {
return webResourceError.getErrorCode();
}
@@ -126,7 +118,6 @@
/**
* @see WebResourceError#getDescription()
*/
- @DoNotInline
@NonNull
public static CharSequence getDescription(@NonNull WebResourceError webResourceError) {
return webResourceError.getDescription();
@@ -135,7 +126,6 @@
/**
* @see WebSettings#setOffscreenPreRaster(boolean)
*/
- @DoNotInline
public static void setOffscreenPreRaster(@NonNull WebSettings webSettings, boolean b) {
webSettings.setOffscreenPreRaster(b);
}
@@ -143,7 +133,6 @@
/**
* @see WebSettings#getOffscreenPreRaster()
*/
- @DoNotInline
public static boolean getOffscreenPreRaster(@NonNull WebSettings webSettings) {
return webSettings.getOffscreenPreRaster();
}
@@ -152,7 +141,6 @@
* Wraps the passed callback in the framework callback type to isolate new types in this class.
* @see WebView#postVisualStateCallback(long, WebView.VisualStateCallback)
*/
- @DoNotInline
public static void postVisualStateCallback(@NonNull WebView webView, long requestId,
final @NonNull WebViewCompat.VisualStateCallback callback) {
webView.postVisualStateCallback(requestId, new WebView.VisualStateCallback() {
@@ -166,7 +154,6 @@
/**
* @see WebView#postWebMessage(WebMessage, Uri)
*/
- @DoNotInline
public static void postWebMessage(@NonNull WebView webView, @NonNull WebMessage message,
@NonNull Uri targetOrigin) {
webView.postWebMessage(message, targetOrigin);
@@ -175,7 +162,6 @@
/**
* @see WebView#createWebMessageChannel()
*/
- @DoNotInline
@NonNull
public static WebMessagePort[] createWebMessageChannel(@NonNull WebView webView) {
return webView.createWebMessageChannel();
diff --git a/webkit/webkit/src/main/java/androidx/webkit/internal/ApiHelperForN.java b/webkit/webkit/src/main/java/androidx/webkit/internal/ApiHelperForN.java
index 76260d7..a8c61b2 100644
--- a/webkit/webkit/src/main/java/androidx/webkit/internal/ApiHelperForN.java
+++ b/webkit/webkit/src/main/java/androidx/webkit/internal/ApiHelperForN.java
@@ -24,7 +24,6 @@
import android.webkit.WebResourceRequest;
import android.webkit.WebSettings;
-import androidx.annotation.DoNotInline;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
@@ -45,7 +44,6 @@
/**
* @see Context#getDataDir()
*/
- @DoNotInline
@NonNull
public static File getDataDir(@NonNull Context context) {
return context.getDataDir();
@@ -54,7 +52,6 @@
/**
* @see ServiceWorkerController#getInstance()
*/
- @DoNotInline
@NonNull
public static ServiceWorkerController getServiceWorkerControllerInstance() {
return ServiceWorkerController.getInstance();
@@ -63,7 +60,6 @@
/**
* @see ServiceWorkerController#getServiceWorkerWebSettings()
*/
- @DoNotInline
@NonNull
public static ServiceWorkerWebSettings getServiceWorkerWebSettings(
@NonNull ServiceWorkerController serviceWorkerController) {
@@ -73,7 +69,6 @@
/**
* @see ServiceWorkerController#getServiceWorkerWebSettings()
*/
- @DoNotInline
@NonNull
public static ServiceWorkerWebSettingsImpl getServiceWorkerWebSettingsImpl(
@NonNull ServiceWorkerController serviceWorkerController) {
@@ -84,7 +79,6 @@
/**
* @see ServiceWorkerController#setServiceWorkerClient(ServiceWorkerClient)
*/
- @DoNotInline
public static void setServiceWorkerClient(
@NonNull ServiceWorkerController serviceWorkerController,
@Nullable ServiceWorkerClient serviceWorkerClient) {
@@ -94,7 +88,6 @@
/**
* @see ServiceWorkerController#setServiceWorkerClient(ServiceWorkerClient)
*/
- @DoNotInline
public static void setServiceWorkerClientCompat(
@NonNull ServiceWorkerController serviceWorkerController,
@NonNull ServiceWorkerClientCompat serviceWorkerClientCompat) {
@@ -105,7 +98,6 @@
/**
* @see ServiceWorkerWebSettings#setCacheMode(int)
*/
- @DoNotInline
public static void setCacheMode(@NonNull ServiceWorkerWebSettings serviceWorkerWebSettings,
int cacheMode) {
serviceWorkerWebSettings.setCacheMode(cacheMode);
@@ -114,7 +106,6 @@
/**
* @see ServiceWorkerWebSettings#getCacheMode()
*/
- @DoNotInline
public static int getCacheMode(@NonNull ServiceWorkerWebSettings serviceWorkerWebSettings) {
return serviceWorkerWebSettings.getCacheMode();
}
@@ -122,7 +113,6 @@
/**
* @see ServiceWorkerWebSettings#setAllowContentAccess(boolean)
*/
- @DoNotInline
public static void setAllowContentAccess(
@NonNull ServiceWorkerWebSettings serviceWorkerWebSettings,
boolean allowContentAccess) {
@@ -132,7 +122,6 @@
/**
* @see ServiceWorkerWebSettings#getAllowContentAccess()
*/
- @DoNotInline
public static boolean getAllowContentAccess(
@NonNull ServiceWorkerWebSettings serviceWorkerWebSettings) {
return serviceWorkerWebSettings.getAllowContentAccess();
@@ -141,7 +130,6 @@
/**
* @see ServiceWorkerWebSettings#setAllowFileAccess(boolean)
*/
- @DoNotInline
public static void setAllowFileAccess(
@NonNull ServiceWorkerWebSettings serviceWorkerWebSettings, boolean allowFileAccess) {
serviceWorkerWebSettings.setAllowFileAccess(allowFileAccess);
@@ -150,7 +138,6 @@
/**
* @see ServiceWorkerWebSettings#getAllowFileAccess()
*/
- @DoNotInline
public static boolean getAllowFileAccess(
@NonNull ServiceWorkerWebSettings serviceWorkerWebSettings) {
return serviceWorkerWebSettings.getAllowFileAccess();
@@ -159,7 +146,6 @@
/**
* @see ServiceWorkerWebSettings#setBlockNetworkLoads(boolean)
*/
- @DoNotInline
public static void setBlockNetworkLoads(
@NonNull ServiceWorkerWebSettings serviceWorkerWebSettings, boolean blockNetworkLoads) {
serviceWorkerWebSettings.setBlockNetworkLoads(blockNetworkLoads);
@@ -168,7 +154,6 @@
/**
* @see ServiceWorkerWebSettings#getBlockNetworkLoads()
*/
- @DoNotInline
public static boolean getBlockNetworkLoads(
@NonNull ServiceWorkerWebSettings serviceWorkerWebSettings) {
return serviceWorkerWebSettings.getBlockNetworkLoads();
@@ -177,7 +162,6 @@
/**
* @see WebResourceRequest#isRedirect()
*/
- @DoNotInline
public static boolean isRedirect(@NonNull WebResourceRequest webResourceRequest) {
return webResourceRequest.isRedirect();
}
@@ -185,7 +169,6 @@
/**
* @see WebSettings#setDisabledActionModeMenuItems(int)
*/
- @DoNotInline
public static void setDisabledActionModeMenuItems(@NonNull WebSettings webSettings, int i) {
webSettings.setDisabledActionModeMenuItems(i);
}
@@ -193,7 +176,6 @@
/**
* @see WebSettings#getDisabledActionModeMenuItems()
*/
- @DoNotInline
public static int getDisabledActionModeMenuItems(@NonNull WebSettings webSettings) {
return webSettings.getDisabledActionModeMenuItems();
}
diff --git a/webkit/webkit/src/main/java/androidx/webkit/internal/ApiHelperForO.java b/webkit/webkit/src/main/java/androidx/webkit/internal/ApiHelperForO.java
index b4efe99..5c0ce09 100644
--- a/webkit/webkit/src/main/java/androidx/webkit/internal/ApiHelperForO.java
+++ b/webkit/webkit/src/main/java/androidx/webkit/internal/ApiHelperForO.java
@@ -23,7 +23,6 @@
import android.webkit.WebView;
import android.webkit.WebViewClient;
-import androidx.annotation.DoNotInline;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
@@ -41,7 +40,6 @@
/**
* @see WebSettings#setSafeBrowsingEnabled(boolean)
*/
- @DoNotInline
public static void setSafeBrowsingEnabled(@NonNull WebSettings webSettings, boolean b) {
webSettings.setSafeBrowsingEnabled(b);
}
@@ -49,7 +47,6 @@
/**
* @see WebSettings#getSafeBrowsingEnabled()
*/
- @DoNotInline
public static boolean getSafeBrowsingEnabled(@NonNull WebSettings webSettings) {
return webSettings.getSafeBrowsingEnabled();
}
@@ -57,7 +54,6 @@
/**
* @see WebView#getWebViewClient()
*/
- @DoNotInline
@Nullable
public static WebViewClient getWebViewClient(@NonNull WebView webView) {
return webView.getWebViewClient();
@@ -66,7 +62,6 @@
/**
* @see WebView#getWebChromeClient()
*/
- @DoNotInline
@Nullable
public static WebChromeClient getWebChromeClient(@NonNull WebView webView) {
return webView.getWebChromeClient();
@@ -75,7 +70,6 @@
/**
* @see WebView#getCurrentWebViewPackage()
*/
- @DoNotInline
@NonNull
public static PackageInfo getCurrentWebViewPackage() {
return WebView.getCurrentWebViewPackage();
diff --git a/webkit/webkit/src/main/java/androidx/webkit/internal/ApiHelperForOMR1.java b/webkit/webkit/src/main/java/androidx/webkit/internal/ApiHelperForOMR1.java
index 0630f2f..85ab81a 100644
--- a/webkit/webkit/src/main/java/androidx/webkit/internal/ApiHelperForOMR1.java
+++ b/webkit/webkit/src/main/java/androidx/webkit/internal/ApiHelperForOMR1.java
@@ -23,7 +23,6 @@
import android.webkit.ValueCallback;
import android.webkit.WebView;
-import androidx.annotation.DoNotInline;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
@@ -44,7 +43,6 @@
/**
* @see SafeBrowsingResponse#showInterstitial(boolean)
*/
- @DoNotInline
public static void showInterstitial(@NonNull SafeBrowsingResponse safeBrowsingResponse,
boolean showInterstitial) {
safeBrowsingResponse.showInterstitial(showInterstitial);
@@ -53,7 +51,6 @@
/**
* @see SafeBrowsingResponse#proceed(boolean)
*/
- @DoNotInline
public static void proceed(@NonNull SafeBrowsingResponse frameworksImpl, boolean proceed) {
frameworksImpl.proceed(proceed);
}
@@ -61,7 +58,6 @@
/**
* @see SafeBrowsingResponse#backToSafety(boolean)
*/
- @DoNotInline
public static void backToSafety(@NonNull SafeBrowsingResponse safeBrowsingResponse,
boolean backToSafety) {
safeBrowsingResponse.backToSafety(backToSafety);
@@ -70,7 +66,6 @@
/**
* @see WebView#startSafeBrowsing(Context, ValueCallback)
*/
- @DoNotInline
public static void startSafeBrowsing(@NonNull Context context,
@Nullable ValueCallback<Boolean> callback) {
WebView.startSafeBrowsing(context, callback);
@@ -79,7 +74,6 @@
/**
* @see WebView#setSafeBrowsingWhitelist(List, ValueCallback)
*/
- @DoNotInline
public static void setSafeBrowsingWhitelist(@NonNull List<String> hosts,
@Nullable ValueCallback<Boolean> callback) {
WebView.setSafeBrowsingWhitelist(hosts, callback);
@@ -88,7 +82,6 @@
/**
* @see WebView#getSafeBrowsingPrivacyPolicyUrl()
*/
- @DoNotInline
@NonNull
public static Uri getSafeBrowsingPrivacyPolicyUrl() {
return WebView.getSafeBrowsingPrivacyPolicyUrl();
diff --git a/webkit/webkit/src/main/java/androidx/webkit/internal/ApiHelperForP.java b/webkit/webkit/src/main/java/androidx/webkit/internal/ApiHelperForP.java
index a495e22..b9d7643 100644
--- a/webkit/webkit/src/main/java/androidx/webkit/internal/ApiHelperForP.java
+++ b/webkit/webkit/src/main/java/androidx/webkit/internal/ApiHelperForP.java
@@ -21,7 +21,6 @@
import android.webkit.TracingController;
import android.webkit.WebView;
-import androidx.annotation.DoNotInline;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
@@ -43,7 +42,6 @@
/**
* @see TracingController#getInstance()
*/
- @DoNotInline
@NonNull
public static TracingController getTracingControllerInstance() {
return TracingController.getInstance();
@@ -52,7 +50,6 @@
/**
* @see TracingController#isTracing()
*/
- @DoNotInline
public static boolean isTracing(@NonNull TracingController tracingController) {
return tracingController.isTracing();
}
@@ -62,7 +59,6 @@
* isolate new types in this class.
* @see TracingController#start(android.webkit.TracingConfig)
*/
- @DoNotInline
public static void start(@NonNull TracingController tracingController,
@NonNull TracingConfig tracingConfig) {
android.webkit.TracingConfig config =
@@ -77,7 +73,6 @@
/**
* @see TracingController#stop(OutputStream, Executor)
*/
- @DoNotInline
public static boolean stop(@NonNull TracingController tracingController,
@Nullable OutputStream os, @NonNull Executor ex) {
return tracingController.stop(os, ex);
@@ -86,7 +81,6 @@
/**
* @see WebView#getWebViewClassLoader()
*/
- @DoNotInline
@NonNull
public static ClassLoader getWebViewClassLoader() {
return WebView.getWebViewClassLoader();
@@ -95,7 +89,6 @@
/**
* @see WebView#getWebViewLooper()
*/
- @DoNotInline
@NonNull
public static Looper getWebViewLooper(@NonNull WebView webView) {
return webView.getWebViewLooper();
@@ -105,7 +98,6 @@
/**
* @see WebView#setDataDirectorySuffix(String)
*/
- @DoNotInline
public static void setDataDirectorySuffix(@NonNull String suffix) {
WebView.setDataDirectorySuffix(suffix);
}
diff --git a/webkit/webkit/src/main/java/androidx/webkit/internal/ApiHelperForQ.java b/webkit/webkit/src/main/java/androidx/webkit/internal/ApiHelperForQ.java
index 4e93fca..5917b04 100644
--- a/webkit/webkit/src/main/java/androidx/webkit/internal/ApiHelperForQ.java
+++ b/webkit/webkit/src/main/java/androidx/webkit/internal/ApiHelperForQ.java
@@ -21,7 +21,6 @@
import android.webkit.WebView;
import android.webkit.WebViewRenderProcess;
-import androidx.annotation.DoNotInline;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
@@ -43,7 +42,6 @@
* @see WebSettings#setForceDark(int)
* @deprecated in 31
*/
- @DoNotInline
@Deprecated
public static void setForceDark(@NonNull WebSettings webSettings, int forceDark) {
webSettings.setForceDark(forceDark);
@@ -53,7 +51,6 @@
* @see WebSettings#getForceDark()
* @deprecated in 31
*/
- @DoNotInline
@Deprecated
public static int getForceDark(@NonNull WebSettings webSettings) {
return webSettings.getForceDark();
@@ -62,7 +59,6 @@
/**
* @see WebView#getWebViewRenderProcess()
*/
- @DoNotInline
@Nullable
public static WebViewRenderProcess getWebViewRenderProcess(@NonNull WebView webView) {
return webView.getWebViewRenderProcess();
@@ -71,7 +67,6 @@
/**
* @see WebViewRenderProcess#terminate()
*/
- @DoNotInline
public static boolean terminate(@NonNull WebViewRenderProcess webViewRenderProcess) {
return webViewRenderProcess.terminate();
}
@@ -80,7 +75,6 @@
* @see WebView#setWebViewRenderProcessClient(Executor,
* android.webkit.WebViewRenderProcessClient)
*/
- @DoNotInline
public static void setWebViewRenderProcessClient(@NonNull WebView webView,
@NonNull Executor executor,
@Nullable WebViewRenderProcessClient client) {
@@ -92,7 +86,6 @@
/**
* @see WebView#setWebViewRenderProcessClient(android.webkit.WebViewRenderProcessClient)
*/
- @DoNotInline
public static void setWebViewRenderProcessClient(@NonNull WebView webView,
@Nullable WebViewRenderProcessClient client) {
WebViewRenderProcessClientFrameworkAdapter clientAdapter =
@@ -103,7 +96,6 @@
/**
* @see WebView#getWebViewRenderProcessClient()
*/
- @DoNotInline
@Nullable
public static android.webkit.WebViewRenderProcessClient getWebViewRenderProcessClient(
@NonNull WebView webView) {
diff --git a/webkit/webkit/src/main/java/androidx/webkit/internal/ApiHelperForTiramisu.java b/webkit/webkit/src/main/java/androidx/webkit/internal/ApiHelperForTiramisu.java
index ff59197..1ad8075 100644
--- a/webkit/webkit/src/main/java/androidx/webkit/internal/ApiHelperForTiramisu.java
+++ b/webkit/webkit/src/main/java/androidx/webkit/internal/ApiHelperForTiramisu.java
@@ -21,7 +21,6 @@
import android.content.pm.ServiceInfo;
import android.os.Build;
-import androidx.annotation.DoNotInline;
import androidx.annotation.RequiresApi;
/**
@@ -34,12 +33,10 @@
private ApiHelperForTiramisu() {
}
- @DoNotInline
static PackageManager.ComponentInfoFlags of(long value) {
return PackageManager.ComponentInfoFlags.of(value);
}
- @DoNotInline
static ServiceInfo getServiceInfo(PackageManager packageManager, ComponentName component,
PackageManager.ComponentInfoFlags flags)
throws PackageManager.NameNotFoundException {
diff --git a/window/extensions/extensions/build.gradle b/window/extensions/extensions/build.gradle
index ac53d85..4ad8662 100644
--- a/window/extensions/extensions/build.gradle
+++ b/window/extensions/extensions/build.gradle
@@ -31,7 +31,7 @@
dependencies {
implementation("androidx.annotation:annotation:1.6.0")
- implementation("androidx.annotation:annotation-experimental:1.4.0")
+ implementation("androidx.annotation:annotation-experimental:1.4.1")
compileOnly("androidx.window.extensions.core:core:1.0.0")
testImplementation(libs.robolectric)
diff --git a/window/window/src/main/java/androidx/window/embedding/ExtensionEmbeddingBackend.kt b/window/window/src/main/java/androidx/window/embedding/ExtensionEmbeddingBackend.kt
index 8b57faf..11e0f41 100644
--- a/window/window/src/main/java/androidx/window/embedding/ExtensionEmbeddingBackend.kt
+++ b/window/window/src/main/java/androidx/window/embedding/ExtensionEmbeddingBackend.kt
@@ -23,7 +23,6 @@
import android.os.Build
import android.os.IBinder
import android.util.Log
-import androidx.annotation.DoNotInline
import androidx.annotation.GuardedBy
import androidx.annotation.RequiresApi
import androidx.annotation.VisibleForTesting
@@ -385,7 +384,6 @@
@RequiresApi(31)
private object Api31Impl {
- @DoNotInline
fun isSplitPropertyEnabled(context: Context): SplitController.SplitSupportStatus {
val property =
try {
diff --git a/window/window/src/main/java/androidx/window/embedding/SplitRule.kt b/window/window/src/main/java/androidx/window/embedding/SplitRule.kt
index 13cfa6b..adc8732 100644
--- a/window/window/src/main/java/androidx/window/embedding/SplitRule.kt
+++ b/window/window/src/main/java/androidx/window/embedding/SplitRule.kt
@@ -20,7 +20,6 @@
import android.graphics.Rect
import android.os.Build
import android.view.WindowMetrics
-import androidx.annotation.DoNotInline
import androidx.annotation.IntRange
import androidx.annotation.RequiresApi
import androidx.core.util.Preconditions
@@ -277,7 +276,6 @@
@RequiresApi(30)
internal object Api30Impl {
- @DoNotInline
fun getBounds(windowMetrics: WindowMetrics): Rect {
return windowMetrics.bounds
}
@@ -285,7 +283,6 @@
@RequiresApi(34)
internal object Api34Impl {
- @DoNotInline
fun getDensity(windowMetrics: WindowMetrics, context: Context): Float {
// TODO(b/265089843) remove the try catch after U is finalized.
return try {
diff --git a/window/window/src/main/java/androidx/window/layout/util/ContextCompatHelper.kt b/window/window/src/main/java/androidx/window/layout/util/ContextCompatHelper.kt
index 3f3217b..36fc60b 100644
--- a/window/window/src/main/java/androidx/window/layout/util/ContextCompatHelper.kt
+++ b/window/window/src/main/java/androidx/window/layout/util/ContextCompatHelper.kt
@@ -23,7 +23,6 @@
import android.inputmethodservice.InputMethodService
import android.os.Build
import android.view.WindowManager
-import androidx.annotation.DoNotInline
import androidx.annotation.RequiresApi
import androidx.annotation.UiContext
import androidx.core.view.WindowInsetsCompat
@@ -90,10 +89,7 @@
* Computes the [WindowInsetsCompat] for platforms above [Build.VERSION_CODES.R], inclusive.
*
* @see androidx.window.layout.WindowMetrics.getWindowInsets
- * @DoNotInline required for implementation-specific class method to prevent it from being
- * inlined.
*/
- @DoNotInline
fun currentWindowInsets(@UiContext context: Context): WindowInsetsCompat {
val platformInsets =
context.getSystemService(WindowManager::class.java).currentWindowMetrics.windowInsets
diff --git a/work/work-multiprocess/src/main/java/androidx/work/multiprocess/parcelable/ParcelableRuntimeExtras.java b/work/work-multiprocess/src/main/java/androidx/work/multiprocess/parcelable/ParcelableRuntimeExtras.java
index 410e540..4180a34 100644
--- a/work/work-multiprocess/src/main/java/androidx/work/multiprocess/parcelable/ParcelableRuntimeExtras.java
+++ b/work/work-multiprocess/src/main/java/androidx/work/multiprocess/parcelable/ParcelableRuntimeExtras.java
@@ -26,7 +26,6 @@
import android.os.Parcel;
import android.os.Parcelable;
-import androidx.annotation.DoNotInline;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import androidx.annotation.RestrictTo;
@@ -156,7 +155,6 @@
// This class is not instantiable.
}
- @DoNotInline
static Parcelable castToParcelable(Network network) {
return network;
}
diff --git a/work/work-runtime/build.gradle b/work/work-runtime/build.gradle
index 1545264..004fffe 100644
--- a/work/work-runtime/build.gradle
+++ b/work/work-runtime/build.gradle
@@ -66,7 +66,7 @@
implementation("androidx.core:core:1.12.0")
implementation("androidx.room:room-ktx:2.6.1")
implementation("androidx.concurrent:concurrent-futures-ktx:1.1.0")
- api("androidx.annotation:annotation-experimental:1.4.0")
+ api("androidx.annotation:annotation-experimental:1.4.1")
api(libs.guavaListenableFuture)
api("androidx.lifecycle:lifecycle-livedata:2.6.2")
api("androidx.startup:startup-runtime:1.1.1")
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/WorkDatabasePathHelper.kt b/work/work-runtime/src/main/java/androidx/work/impl/WorkDatabasePathHelper.kt
index c6965c9..319eefd 100644
--- a/work/work-runtime/src/main/java/androidx/work/impl/WorkDatabasePathHelper.kt
+++ b/work/work-runtime/src/main/java/androidx/work/impl/WorkDatabasePathHelper.kt
@@ -17,7 +17,6 @@
import android.content.Context
import android.os.Build
-import androidx.annotation.DoNotInline
import androidx.annotation.RequiresApi
import androidx.annotation.RestrictTo
import androidx.work.Logger
@@ -116,7 +115,6 @@
@RequiresApi(21)
internal object Api21Impl {
- @DoNotInline
fun getNoBackupFilesDir(context: Context): File {
return context.noBackupFilesDir
}
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/WorkManagerImpl.java b/work/work-runtime/src/main/java/androidx/work/impl/WorkManagerImpl.java
index 14201d2..2e8c1b7 100644
--- a/work/work-runtime/src/main/java/androidx/work/impl/WorkManagerImpl.java
+++ b/work/work-runtime/src/main/java/androidx/work/impl/WorkManagerImpl.java
@@ -37,7 +37,6 @@
import android.content.Intent;
import android.os.Build;
-import androidx.annotation.DoNotInline;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
@@ -716,7 +715,6 @@
// This class is not instantiable.
}
- @DoNotInline
static boolean isDeviceProtectedStorage(Context context) {
return context.isDeviceProtectedStorage();
}
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/background/systemjob/SystemJobService.java b/work/work-runtime/src/main/java/androidx/work/impl/background/systemjob/SystemJobService.java
index dc44577..5e5159b 100644
--- a/work/work-runtime/src/main/java/androidx/work/impl/background/systemjob/SystemJobService.java
+++ b/work/work-runtime/src/main/java/androidx/work/impl/background/systemjob/SystemJobService.java
@@ -45,7 +45,6 @@
import android.os.Build;
import android.os.PersistableBundle;
-import androidx.annotation.DoNotInline;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
@@ -245,12 +244,10 @@
// This class is not instantiable.
}
- @DoNotInline
static Uri[] getTriggeredContentUris(JobParameters jobParameters) {
return jobParameters.getTriggeredContentUris();
}
- @DoNotInline
static String[] getTriggeredContentAuthorities(JobParameters jobParameters) {
return jobParameters.getTriggeredContentAuthorities();
}
@@ -262,7 +259,6 @@
// This class is not instantiable.
}
- @DoNotInline
static Network getNetwork(JobParameters jobParameters) {
return jobParameters.getNetwork();
}
@@ -274,7 +270,6 @@
// This class is not instantiable.
}
- @DoNotInline
static int getStopReason(JobParameters jobParameters) {
return stopReason(jobParameters.getStopReason());
}
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/foreground/SystemForegroundService.java b/work/work-runtime/src/main/java/androidx/work/impl/foreground/SystemForegroundService.java
index 62efc86..9b1ac79 100644
--- a/work/work-runtime/src/main/java/androidx/work/impl/foreground/SystemForegroundService.java
+++ b/work/work-runtime/src/main/java/androidx/work/impl/foreground/SystemForegroundService.java
@@ -25,7 +25,6 @@
import android.content.Intent;
import android.os.Build;
-import androidx.annotation.DoNotInline;
import androidx.annotation.MainThread;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -155,7 +154,6 @@
// This class is not instantiable.
}
- @DoNotInline
static void startForeground(Service service, int id, Notification notification,
int foregroundServiceType) {
service.startForeground(id, notification, foregroundServiceType);
@@ -168,7 +166,6 @@
// This class is not instantiable.
}
- @DoNotInline
static void startForeground(Service service, int id, Notification notification,
int foregroundServiceType) {
try {
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/utils/DurationApi26.kt b/work/work-runtime/src/main/java/androidx/work/impl/utils/DurationApi26.kt
index 0ff8bbc..9f53082 100644
--- a/work/work-runtime/src/main/java/androidx/work/impl/utils/DurationApi26.kt
+++ b/work/work-runtime/src/main/java/androidx/work/impl/utils/DurationApi26.kt
@@ -18,11 +18,9 @@
package androidx.work.impl.utils
-import androidx.annotation.DoNotInline
import androidx.annotation.RequiresApi
import java.time.Duration
-@DoNotInline
internal fun Duration.toMillisCompat(): Long {
return this.toMillis()
}
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/utils/NetworkApi21.kt b/work/work-runtime/src/main/java/androidx/work/impl/utils/NetworkApi21.kt
index 7c8dbaf..e8afb1b 100644
--- a/work/work-runtime/src/main/java/androidx/work/impl/utils/NetworkApi21.kt
+++ b/work/work-runtime/src/main/java/androidx/work/impl/utils/NetworkApi21.kt
@@ -22,16 +22,12 @@
import android.net.ConnectivityManager.NetworkCallback
import android.net.Network
import android.net.NetworkCapabilities
-import androidx.annotation.DoNotInline
import androidx.annotation.RequiresApi
-@DoNotInline
internal fun ConnectivityManager.unregisterNetworkCallbackCompat(networkCallback: NetworkCallback) =
unregisterNetworkCallback(networkCallback)
-@DoNotInline
internal fun ConnectivityManager.getNetworkCapabilitiesCompat(network: Network?) =
getNetworkCapabilities(network)
-@DoNotInline
internal fun NetworkCapabilities.hasCapabilityCompat(capability: Int) = hasCapability(capability)
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/utils/NetworkApi23.kt b/work/work-runtime/src/main/java/androidx/work/impl/utils/NetworkApi23.kt
index bf57873..6b0bee95 100644
--- a/work/work-runtime/src/main/java/androidx/work/impl/utils/NetworkApi23.kt
+++ b/work/work-runtime/src/main/java/androidx/work/impl/utils/NetworkApi23.kt
@@ -19,7 +19,6 @@
package androidx.work.impl.utils
import android.net.ConnectivityManager
-import androidx.annotation.DoNotInline
import androidx.annotation.RequiresApi
-@DoNotInline fun ConnectivityManager.getActiveNetworkCompat() = activeNetwork
+fun ConnectivityManager.getActiveNetworkCompat() = activeNetwork
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/utils/NetworkApi24.kt b/work/work-runtime/src/main/java/androidx/work/impl/utils/NetworkApi24.kt
index 8e27226..3a23709 100644
--- a/work/work-runtime/src/main/java/androidx/work/impl/utils/NetworkApi24.kt
+++ b/work/work-runtime/src/main/java/androidx/work/impl/utils/NetworkApi24.kt
@@ -19,10 +19,8 @@
package androidx.work.impl.utils
import android.net.ConnectivityManager
-import androidx.annotation.DoNotInline
import androidx.annotation.RequiresApi
-@DoNotInline
fun ConnectivityManager.registerDefaultNetworkCallbackCompat(
networkCallback: ConnectivityManager.NetworkCallback
) = registerDefaultNetworkCallback(networkCallback)
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/utils/NetworkRequestCompat.kt b/work/work-runtime/src/main/java/androidx/work/impl/utils/NetworkRequestCompat.kt
index e3af8a5..d928d8b 100644
--- a/work/work-runtime/src/main/java/androidx/work/impl/utils/NetworkRequestCompat.kt
+++ b/work/work-runtime/src/main/java/androidx/work/impl/utils/NetworkRequestCompat.kt
@@ -19,7 +19,6 @@
import android.net.NetworkCapabilities
import android.net.NetworkRequest
import android.os.Build
-import androidx.annotation.DoNotInline
import androidx.annotation.RequiresApi
internal data class NetworkRequestCompat(val wrapped: Any? = null) {
@@ -92,16 +91,13 @@
@RequiresApi(28)
object NetworkRequest28 {
- @DoNotInline
internal fun hasCapability(request: NetworkRequest, capability: Int) =
request.hasCapability(capability)
- @DoNotInline
internal fun hasTransport(request: NetworkRequest, transport: Int) =
request.hasTransport(transport)
@JvmStatic
- @DoNotInline
fun createNetworkRequest(capabilities: IntArray, transports: IntArray): NetworkRequest {
val networkRequest = NetworkRequest.Builder()
capabilities.forEach { networkRequest.addCapability(it) }
@@ -109,7 +105,6 @@
return networkRequest.build()
}
- @DoNotInline
internal fun createNetworkRequestCompat(
capabilities: IntArray,
transports: IntArray
@@ -120,12 +115,12 @@
@RequiresApi(31)
private object NetworkRequest31 {
- @DoNotInline fun capabilities(request: NetworkRequest) = request.capabilities
+ fun capabilities(request: NetworkRequest) = request.capabilities
- @DoNotInline fun transportTypes(request: NetworkRequest) = request.transportTypes
+ fun transportTypes(request: NetworkRequest) = request.transportTypes
}
@RequiresApi(30)
internal object NetworkRequest30 {
- @DoNotInline fun getNetworkSpecifier(request: NetworkRequest) = request.networkSpecifier
+ fun getNetworkSpecifier(request: NetworkRequest) = request.networkSpecifier
}
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/utils/ProcessUtils.kt b/work/work-runtime/src/main/java/androidx/work/impl/utils/ProcessUtils.kt
index 952cf90..d987b19 100644
--- a/work/work-runtime/src/main/java/androidx/work/impl/utils/ProcessUtils.kt
+++ b/work/work-runtime/src/main/java/androidx/work/impl/utils/ProcessUtils.kt
@@ -23,7 +23,6 @@
import android.content.Context
import android.os.Build
import android.os.Process
-import androidx.annotation.DoNotInline
import androidx.annotation.RequiresApi
import androidx.work.Configuration
import androidx.work.Logger
@@ -68,7 +67,6 @@
@RequiresApi(28)
private object Api28Impl {
- @get:DoNotInline
val processName: String
get() = Application.getProcessName()
}