Merge "Refactor Plot benchmarks." into androidx-main
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index 8a971d6..a760252 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -6,7 +6,7 @@
# Owners for each library group:
/activity* @sanura-njaka @jbw0033 @ianhanniballake
/appcompat/* @alanv
-/biometric/* @dlam
+/biometric/* @jbolinger
/collection/* @dlam
/compose/compiler/* @jimgoog @lelandrichardson
/compose/runtime/* @jimgoog @lelandrichardson
diff --git a/.github/ci-control/ci-config.json b/.github/ci-control/ci-config.json
index dfb2af6..b7c25eb 100644
--- a/.github/ci-control/ci-config.json
+++ b/.github/ci-control/ci-config.json
@@ -14,6 +14,7 @@
"main" : {
"exclude" : [
"compose-runtime",
+ "core",
"room"
],
"default": true
diff --git a/appcompat/appcompat/src/androidTest/AndroidManifest.xml b/appcompat/appcompat/src/androidTest/AndroidManifest.xml
index fb17a4b..aa16fad 100644
--- a/appcompat/appcompat/src/androidTest/AndroidManifest.xml
+++ b/appcompat/appcompat/src/androidTest/AndroidManifest.xml
@@ -131,6 +131,11 @@
android:theme="@style/Theme.TextColors"/>
<activity
+ android:name="androidx.appcompat.widget.AppCompatTextViewFontScalingTest$TextViewFontScalingActivity"
+ android:label="@string/app_compat_text_view_activity"
+ android:theme="@style/Theme.TextColors" />
+
+ <activity
android:name="androidx.appcompat.widget.AppCompatTextViewAutoSizeActivity"
android:label="@string/app_compat_text_view_auto_size_activity"
android:theme="@style/Theme.AppCompat.Light"/>
diff --git a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatTextViewFontScalingTest.kt b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatTextViewFontScalingTest.kt
new file mode 100644
index 0000000..a87d598
--- /dev/null
+++ b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatTextViewFontScalingTest.kt
@@ -0,0 +1,197 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.appcompat.widget
+
+import android.app.Activity
+import android.os.Build
+import android.os.Bundle
+import android.util.TypedValue
+import android.widget.TextView
+import androidx.appcompat.test.R
+import androidx.test.ext.junit.rules.ActivityScenarioRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import androidx.testutils.AndroidFontScaleHelper.resetSystemFontScale
+import androidx.testutils.AndroidFontScaleHelper.setSystemFontScale
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Test {@link AppCompatTextView} under non-linear font scaling.
+ */
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+class AppCompatTextViewFontScalingTest {
+ @get:Rule
+ val scenarioRule = ActivityScenarioRule(TextViewFontScalingActivity::class.java)
+
+ @After
+ fun teardown() {
+ // Have to manually check the version here because if we try to rely on the assumeTrue() in
+ // resetSystemFontScale(), it is called twice (again in setSystemFontScale()) and the test
+ // fails with a "TestCouldNotBeSkippedException: Test could not be skipped due to other
+ // failures" because it thinks the second assumeTrue() was a separate error.
+ // tl;dr avoids a bug in jUnit when multiple assumeTrue()s happen in @Test and @After
+ if (Build.VERSION.SDK_INT >= 29) {
+ resetSystemFontScale(scenarioRule.scenario)
+ }
+ }
+
+ @Test
+ @Throws(Throwable::class)
+ fun testNonLinearFontScaling_testSetLineHeightSpAndSetTextSizeSp() {
+ setSystemFontScale(2f, scenarioRule.scenario)
+ scenarioRule.scenario.onActivity { activity ->
+ assertThat(activity.resources.configuration.fontScale).isWithin(0.02f).of(2f)
+
+ val textView = AppCompatTextView(activity)
+ val textSizeSp = 20f
+ val lineHeightSp = 40f
+
+ textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, textSizeSp)
+ textView.setLineHeight(TypedValue.COMPLEX_UNIT_SP, lineHeightSp)
+
+ verifyLineHeightIsIntendedProportions(lineHeightSp, textSizeSp, activity, textView)
+ }
+ }
+
+ @Test
+ @Throws(Throwable::class)
+ fun testNonLinearFontScaling_overwriteXml_testSetLineHeightSpAndSetTextSizeSp() {
+ setSystemFontScale(2f, scenarioRule.scenario)
+ scenarioRule.scenario.onActivity { activity ->
+ assertThat(activity.resources.configuration.fontScale).isWithin(0.02f).of(2f)
+
+ val textView = findTextView(activity, R.id.textview_lineheight2x)
+ val textSizeSp = 20f
+ val lineHeightSp = 40f
+
+ textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, textSizeSp)
+ textView.setLineHeight(TypedValue.COMPLEX_UNIT_SP, lineHeightSp)
+
+ verifyLineHeightIsIntendedProportions(lineHeightSp, textSizeSp, activity, textView)
+ }
+ }
+
+ @Test
+ @Throws(Throwable::class)
+ fun testNonLinearFontScaling_xml_testLineHeightAttrSpAndTextSizeAttrSp() {
+ setSystemFontScale(2f, scenarioRule.scenario)
+ scenarioRule.scenario.onActivity { activity ->
+ assertThat(activity.resources.configuration.fontScale).isWithin(0.02f).of(2f)
+
+ val textView = findTextView(activity, R.id.textview_lineheight2x)
+ val textSizeSp = 20f
+ val lineHeightSp = 40f
+
+ verifyLineHeightIsIntendedProportions(lineHeightSp, textSizeSp, activity, textView)
+ }
+ }
+
+ @Test
+ @Throws(Throwable::class)
+ fun testNonLinearFontScaling_dimenXml_testLineHeightAttrSpAndTextSizeAttrSp() {
+ setSystemFontScale(2f, scenarioRule.scenario)
+ scenarioRule.scenario.onActivity { activity ->
+ assertThat(activity.resources.configuration.fontScale).isWithin(0.02f).of(2f)
+
+ val textView = findTextView(activity, R.id.textview_lineheight_dimen3x)
+ val textSizeSp = 20f
+ val lineHeightSp = 60f
+
+ verifyLineHeightIsIntendedProportions(lineHeightSp, textSizeSp, activity, textView)
+ }
+ }
+
+ @Test
+ @Throws(Throwable::class)
+ fun testNonLinearFontScaling_styleXml_testLineHeightAttrSpAndTextSizeAttrSp() {
+ setSystemFontScale(2f, scenarioRule.scenario)
+ scenarioRule.scenario.onActivity { activity ->
+ assertThat(activity.resources.configuration.fontScale).isWithin(0.02f).of(2f)
+
+ val textView = findTextView(activity, R.id.textview_lineheight_style3x)
+ val textSizeSp = 20f
+ val lineHeightSp = 60f
+
+ verifyLineHeightIsIntendedProportions(lineHeightSp, textSizeSp, activity, textView)
+ }
+ }
+
+ @Test
+ @Throws(Throwable::class)
+ fun testNonLinearFontScaling_dimenXml_testSetLineHeightSpAndTextSizeAttrSp() {
+ setSystemFontScale(2f, scenarioRule.scenario)
+ scenarioRule.scenario.onActivity { activity ->
+ assertThat(activity.resources.configuration.fontScale).isWithin(0.02f).of(2f)
+
+ val textView = findTextView(activity, R.id.textview_lineheight_dimen3x)
+ val textSizeSp = 20f
+ val lineHeightSp = 30f
+
+ textView.setLineHeight(TypedValue.COMPLEX_UNIT_SP, lineHeightSp)
+
+ verifyLineHeightIsIntendedProportions(lineHeightSp, textSizeSp, activity, textView)
+ }
+ }
+
+ private fun findTextView(activity: Activity, id: Int): AppCompatTextView {
+ return activity.findViewById(id)!!
+ }
+
+ class TextViewFontScalingActivity : Activity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.appcompat_textview_fontscaling_activity)
+ }
+ }
+
+ companion object {
+ /**
+ * Tolerance for comparing expected float lineHeight to the integer one returned by
+ * getLineHeight(). It is pretty lenient to account for integer rounding when text size is
+ * loaded from an attribute. (When loading an SP resource from an attribute for textSize,
+ * it is rounded to the nearest pixel, which can throw off calculations quite a lot. Not
+ * enough to make much of a difference to the user, but enough to need a wide tolerance in
+ * tests. See b/279456702 for more details.)
+ */
+ private const val TOLERANCE = 5f
+
+ private fun verifyLineHeightIsIntendedProportions(
+ lineHeightSp: Float,
+ textSizeSp: Float,
+ activity: Activity,
+ textView: TextView
+ ) {
+ val lineHeightMultiplier = lineHeightSp / textSizeSp
+ // Calculate what line height would be without non-linear font scaling compressing it.
+ // The trick is multiplying afterwards (by the pixel value) instead of before (by the SP
+ // value)
+ val expectedLineHeightPx = lineHeightMultiplier * TypedValue.applyDimension(
+ TypedValue.COMPLEX_UNIT_SP,
+ textSizeSp,
+ activity.resources.displayMetrics
+ )
+ assertThat(textView.lineHeight.toFloat())
+ .isWithin(TOLERANCE)
+ .of(expectedLineHeightPx)
+ }
+ }
+}
diff --git a/appcompat/appcompat/src/androidTest/res/layout/appcompat_textview_fontscaling_activity.xml b/appcompat/appcompat/src/androidTest/res/layout/appcompat_textview_fontscaling_activity.xml
new file mode 100644
index 0000000..fadabf4
--- /dev/null
+++ b/appcompat/appcompat/src/androidTest/res/layout/appcompat_textview_fontscaling_activity.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<ScrollView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <LinearLayout
+ android:id="@+id/layout_textviewtest"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <!-- Tests line height 2x the text size -->
+ <androidx.appcompat.widget.AppCompatTextView
+ android:id="@+id/textview_lineheight2x"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/sample_text_long"
+ android:textSize="20sp"
+ android:lineHeight="40sp" />
+
+ <!-- Tests line height 3x the text size -->
+ <androidx.appcompat.widget.AppCompatTextView
+ android:id="@+id/textview_lineheight_dimen3x"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/sample_text_long"
+ android:textSize="@dimen/textview_fontScaling_textSize"
+ android:lineHeight="@dimen/textview_fontScaling_lineHeight" />
+
+ <!-- Tests line height 3x the text size -->
+ <androidx.appcompat.widget.AppCompatTextView
+ android:id="@+id/textview_lineheight_style3x"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/sample_text_long"
+ style="@style/TextAppearance.FontScaling" />
+
+ </LinearLayout>
+
+</ScrollView>
diff --git a/appcompat/appcompat/src/androidTest/res/values/dimens.xml b/appcompat/appcompat/src/androidTest/res/values/dimens.xml
index d4a8a0e..aed0b5d 100644
--- a/appcompat/appcompat/src/androidTest/res/values/dimens.xml
+++ b/appcompat/appcompat/src/androidTest/res/values/dimens.xml
@@ -27,4 +27,7 @@
<dimen name="textview_firstBaselineToTopHeight">100dp</dimen>
<dimen name="textview_lastBaselineToBottomHeight">30dp</dimen>
<dimen name="textview_lineHeight">60dp</dimen>
+
+ <dimen name="textview_fontScaling_textSize">20sp</dimen>
+ <dimen name="textview_fontScaling_lineHeight">60sp</dimen>
</resources>
\ No newline at end of file
diff --git a/appcompat/appcompat/src/androidTest/res/values/donottranslate-strings.xml b/appcompat/appcompat/src/androidTest/res/values/donottranslate-strings.xml
index 151400a..7a07538 100644
--- a/appcompat/appcompat/src/androidTest/res/values/donottranslate-strings.xml
+++ b/appcompat/appcompat/src/androidTest/res/values/donottranslate-strings.xml
@@ -71,6 +71,16 @@
<string name="app_compat_button_auto_size_activity">AppCompat button auto-size</string>
<string name="sample_text1">Sample text 1</string>
<string name="sample_text2">Sample text 2</string>
+ <string name="sample_text_long">This is a really long string which exceeds the width of the
+view. New devices have a much larger screen which actually enables long strings to be displayed
+with no fading. I have made this string longer to fix this case. If you are correcting this
+text, I would love to see the kind of devices you guys now use! Guys, maybe some devices need longer
+string! I think so, so how about double this string, like copy and paste!
+This is a really long string which exceeds the width of the view.
+New devices have a much larger screen which actually enables long strings to be displayed
+with no fading. I have made this string longer to fix this case. If you are correcting this
+text, I would love to see the kind of devices you guys now use! Guys, maybe some devices need longer
+string! I think so, so how about double this string, like copy and paste!</string>
<string name="app_compat_image_button_activity">AppCompat image button</string>
<string name="app_compat_image_view_activity">AppCompat image view</string>
<string name="app_compat_button_activity">AppCompat button</string>
diff --git a/appcompat/appcompat/src/androidTest/res/values/styles.xml b/appcompat/appcompat/src/androidTest/res/values/styles.xml
index 0b540bf..a076019 100644
--- a/appcompat/appcompat/src/androidTest/res/values/styles.xml
+++ b/appcompat/appcompat/src/androidTest/res/values/styles.xml
@@ -146,4 +146,9 @@
<item name="android:colorForeground">@color/color_state_list_lilac</item>
</style>
+ <!-- Tests line height 3x the text size -->
+ <style name="TextAppearance.FontScaling">
+ <item name="android:textSize">@dimen/textview_fontScaling_textSize</item>
+ <item name="android:lineHeight">@dimen/textview_fontScaling_lineHeight</item>
+ </style>
</resources>
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 fda4335..4eb3371 100644
--- a/appcompat/appcompat/src/main/java/androidx/appcompat/widget/AppCompatTextHelper.java
+++ b/appcompat/appcompat/src/main/java/androidx/appcompat/widget/AppCompatTextHelper.java
@@ -42,6 +42,7 @@
import androidx.annotation.RestrictTo;
import androidx.appcompat.R;
import androidx.core.content.res.ResourcesCompat;
+import androidx.core.util.TypedValueCompat;
import androidx.core.view.ViewCompat;
import androidx.core.view.inputmethod.EditorInfoCompat;
import androidx.core.widget.TextViewCompat;
@@ -326,8 +327,20 @@
R.styleable.AppCompatTextView_firstBaselineToTopHeight, -1);
final int lastBaselineToBottomHeight = a.getDimensionPixelSize(
R.styleable.AppCompatTextView_lastBaselineToBottomHeight, -1);
- final int lineHeight = a.getDimensionPixelSize(
- R.styleable.AppCompatTextView_lineHeight, -1);
+ float lineHeight = -1;
+ int lineHeightUnit = -1;
+ if (a.hasValue(R.styleable.AppCompatTextView_lineHeight)) {
+ TypedValue peekValue = a.peekValue(R.styleable.AppCompatTextView_lineHeight);
+ if (peekValue != null && peekValue.type == TypedValue.TYPE_DIMENSION) {
+ lineHeightUnit = TypedValueCompat.getUnitFromComplexDimension(peekValue.data);
+ lineHeight = TypedValue.complexToFloat(peekValue.data);
+ } else {
+ lineHeight = a.getDimensionPixelSize(
+ R.styleable.AppCompatTextView_lineHeight,
+ -1
+ );
+ }
+ }
a.recycle();
if (firstBaselineToTopHeight != -1) {
@@ -337,7 +350,11 @@
TextViewCompat.setLastBaselineToBottomHeight(mView, lastBaselineToBottomHeight);
}
if (lineHeight != -1) {
- TextViewCompat.setLineHeight(mView, lineHeight);
+ if (lineHeightUnit == -1) {
+ TextViewCompat.setLineHeight(mView, (int) lineHeight);
+ } else {
+ TextViewCompat.setLineHeight(mView, lineHeightUnit, lineHeight);
+ }
}
}
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 1e92e4a..1f0e15a 100644
--- a/appcompat/appcompat/src/main/java/androidx/appcompat/widget/AppCompatTextView.java
+++ b/appcompat/appcompat/src/main/java/androidx/appcompat/widget/AppCompatTextView.java
@@ -37,6 +37,7 @@
import android.widget.TextView;
import androidx.annotation.DrawableRes;
+import androidx.annotation.FloatRange;
import androidx.annotation.IntRange;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -477,6 +478,15 @@
TextViewCompat.setLineHeight(this, lineHeight);
}
+ @Override
+ public void setLineHeight(int unit, @FloatRange(from = 0) float lineHeight) {
+ if (Build.VERSION.SDK_INT >= 34) {
+ getSuperCaller().setLineHeight(unit, lineHeight);
+ } else {
+ TextViewCompat.setLineHeight(this, unit, lineHeight);
+ }
+ }
+
/**
* See
* {@link TextViewCompat#setCustomSelectionActionModeCallback(TextView, ActionMode.Callback)}
@@ -789,7 +799,9 @@
@RequiresApi(api = 26)
SuperCaller getSuperCaller() {
if (mSuperCaller == null) {
- if (Build.VERSION.SDK_INT >= 28) {
+ if (Build.VERSION.SDK_INT >= 34) {
+ mSuperCaller = new SuperCallerApi34();
+ } else if (Build.VERSION.SDK_INT >= 28) {
mSuperCaller = new SuperCallerApi28();
} else if (Build.VERSION.SDK_INT >= 26) {
mSuperCaller = new SuperCallerApi26();
@@ -817,6 +829,9 @@
// api 28
void setFirstBaselineToTopHeight(@Px int firstBaselineToTopHeight);
void setLastBaselineToBottomHeight(@Px int lastBaselineToBottomHeight);
+
+ // api 34
+ void setLineHeight(int unit, @FloatRange(from = 0) float lineHeight);
}
@RequiresApi(api = 26)
@@ -878,6 +893,9 @@
@Override
public void setLastBaselineToBottomHeight(int lastBaselineToBottomHeight) {}
+
+ @Override
+ public void setLineHeight(int unit, float lineHeight) {}
}
@RequiresApi(api = 28)
@@ -893,4 +911,12 @@
AppCompatTextView.super.setLastBaselineToBottomHeight(lastBaselineToBottomHeight);
}
}
+
+ @RequiresApi(api = 34)
+ class SuperCallerApi34 extends SuperCallerApi28 {
+ @Override
+ public void setLineHeight(int unit, float lineHeight) {
+ AppCompatTextView.super.setLineHeight(unit, lineHeight);
+ }
+ }
}
diff --git a/appsearch/appsearch-builtin-types/api/current.txt b/appsearch/appsearch-builtin-types/api/current.txt
index 1e295d6..ceffd8c 100644
--- a/appsearch/appsearch-builtin-types/api/current.txt
+++ b/appsearch/appsearch-builtin-types/api/current.txt
@@ -15,6 +15,7 @@
@androidx.appsearch.annotation.Document(name="builtin:Alarm") public class Alarm extends androidx.appsearch.builtintypes.Thing {
method public String? getBlackoutPeriodEndDate();
method public String? getBlackoutPeriodStartDate();
+ method public int getComputingDevice();
method public int[]? getDaysOfWeek();
method @IntRange(from=0, to=23) public int getHour();
method @IntRange(from=0, to=59) public int getMinute();
@@ -23,6 +24,9 @@
method public String? getRingtone();
method public boolean isEnabled();
method public boolean shouldVibrate();
+ field public static final int COMPUTING_DEVICE_SMART_PHONE = 1; // 0x1
+ field public static final int COMPUTING_DEVICE_SMART_WATCH = 2; // 0x2
+ field public static final int COMPUTING_DEVICE_UNKNOWN = 0; // 0x0
}
public static final class Alarm.Builder {
@@ -35,6 +39,7 @@
method public androidx.appsearch.builtintypes.Alarm.Builder clearPotentialActions();
method public androidx.appsearch.builtintypes.Alarm.Builder setBlackoutPeriodEndDate(String?);
method public androidx.appsearch.builtintypes.Alarm.Builder setBlackoutPeriodStartDate(String?);
+ method public androidx.appsearch.builtintypes.Alarm.Builder setComputingDevice(int);
method public androidx.appsearch.builtintypes.Alarm.Builder setCreationTimestampMillis(long);
method public androidx.appsearch.builtintypes.Alarm.Builder setDaysOfWeek(@IntRange(from=java.util.Calendar.SUNDAY, to=java.util.Calendar.SATURDAY) int...);
method public androidx.appsearch.builtintypes.Alarm.Builder setDescription(String?);
diff --git a/appsearch/appsearch-builtin-types/api/restricted_current.txt b/appsearch/appsearch-builtin-types/api/restricted_current.txt
index e402ed8c..4f35050 100644
--- a/appsearch/appsearch-builtin-types/api/restricted_current.txt
+++ b/appsearch/appsearch-builtin-types/api/restricted_current.txt
@@ -17,6 +17,7 @@
@androidx.appsearch.annotation.Document(name="builtin:Alarm") public class Alarm extends androidx.appsearch.builtintypes.Thing {
method public String? getBlackoutPeriodEndDate();
method public String? getBlackoutPeriodStartDate();
+ method public int getComputingDevice();
method public int[]? getDaysOfWeek();
method @IntRange(from=0, to=23) public int getHour();
method @IntRange(from=0, to=59) public int getMinute();
@@ -25,6 +26,9 @@
method public String? getRingtone();
method public boolean isEnabled();
method public boolean shouldVibrate();
+ field public static final int COMPUTING_DEVICE_SMART_PHONE = 1; // 0x1
+ field public static final int COMPUTING_DEVICE_SMART_WATCH = 2; // 0x2
+ field public static final int COMPUTING_DEVICE_UNKNOWN = 0; // 0x0
}
public static final class Alarm.Builder {
@@ -37,6 +41,7 @@
method public androidx.appsearch.builtintypes.Alarm.Builder clearPotentialActions();
method public androidx.appsearch.builtintypes.Alarm.Builder setBlackoutPeriodEndDate(String?);
method public androidx.appsearch.builtintypes.Alarm.Builder setBlackoutPeriodStartDate(String?);
+ method public androidx.appsearch.builtintypes.Alarm.Builder setComputingDevice(int);
method public androidx.appsearch.builtintypes.Alarm.Builder setCreationTimestampMillis(long);
method public androidx.appsearch.builtintypes.Alarm.Builder setDaysOfWeek(@IntRange(from=java.util.Calendar.SUNDAY, to=java.util.Calendar.SATURDAY) int...);
method public androidx.appsearch.builtintypes.Alarm.Builder setDescription(String?);
diff --git a/appsearch/appsearch-builtin-types/src/androidTest/java/androidx/appsearch/builtintypes/AlarmTest.java b/appsearch/appsearch-builtin-types/src/androidTest/java/androidx/appsearch/builtintypes/AlarmTest.java
index 4747d51..5becd28 100644
--- a/appsearch/appsearch-builtin-types/src/androidTest/java/androidx/appsearch/builtintypes/AlarmTest.java
+++ b/appsearch/appsearch-builtin-types/src/androidTest/java/androidx/appsearch/builtintypes/AlarmTest.java
@@ -57,6 +57,7 @@
.setShouldVibrate(true)
.setPreviousInstance(alarmInstance1)
.setNextInstance(alarmInstance2)
+ .setComputingDevice(Alarm.COMPUTING_DEVICE_SMART_WATCH)
.build();
assertThat(alarm.getNamespace()).isEqualTo("namespace");
@@ -82,6 +83,7 @@
assertThat(alarm.shouldVibrate()).isTrue();
assertThat(alarm.getPreviousInstance()).isEqualTo(alarmInstance1);
assertThat(alarm.getNextInstance()).isEqualTo(alarmInstance2);
+ assertThat(alarm.getComputingDevice()).isEqualTo(Alarm.COMPUTING_DEVICE_SMART_WATCH);
}
@Test
@@ -113,6 +115,7 @@
.setShouldVibrate(true)
.setPreviousInstance(alarmInstance1)
.setNextInstance(alarmInstance2)
+ .setComputingDevice(Alarm.COMPUTING_DEVICE_SMART_WATCH)
.build();
Alarm alarm2 = new Alarm.Builder(alarm1).build();
@@ -140,6 +143,7 @@
assertThat(alarm1.shouldVibrate()).isEqualTo(alarm2.shouldVibrate());
assertThat(alarm1.getPreviousInstance()).isEqualTo(alarm2.getPreviousInstance());
assertThat(alarm1.getNextInstance()).isEqualTo(alarm2.getNextInstance());
+ assertThat(alarm1.getComputingDevice()).isEqualTo(alarm2.getComputingDevice());
}
@Test
@@ -173,6 +177,7 @@
.setShouldVibrate(true)
.setPreviousInstance(alarmInstance1)
.setNextInstance(alarmInstance2)
+ .setComputingDevice(Alarm.COMPUTING_DEVICE_SMART_WATCH)
.build();
GenericDocument genericDocument = GenericDocument.fromDocumentClass(alarm);
@@ -207,6 +212,8 @@
.isEqualTo(GenericDocument.fromDocumentClass(alarmInstance1));
assertThat(genericDocument.getPropertyDocument("nextInstance"))
.isEqualTo(GenericDocument.fromDocumentClass(alarmInstance2));
+ assertThat(genericDocument.getPropertyLong("computingDevice"))
+ .isEqualTo(Alarm.COMPUTING_DEVICE_SMART_WATCH);
}
@Test
diff --git a/appsearch/appsearch-builtin-types/src/main/java/androidx/appsearch/builtintypes/Alarm.java b/appsearch/appsearch-builtin-types/src/main/java/androidx/appsearch/builtintypes/Alarm.java
index 4c4a2bb..56336cf 100644
--- a/appsearch/appsearch-builtin-types/src/main/java/androidx/appsearch/builtintypes/Alarm.java
+++ b/appsearch/appsearch-builtin-types/src/main/java/androidx/appsearch/builtintypes/Alarm.java
@@ -16,13 +16,17 @@
package androidx.appsearch.builtintypes;
+import androidx.annotation.IntDef;
import androidx.annotation.IntRange;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
import androidx.appsearch.annotation.Document;
import androidx.appsearch.utils.DateTimeFormatValidator;
import androidx.core.util.Preconditions;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.Calendar;
import java.util.List;
@@ -31,6 +35,18 @@
*/
@Document(name = "builtin:Alarm")
public class Alarm extends Thing {
+ /** The device that this {@link Alarm} belongs to. */
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ @IntDef({COMPUTING_DEVICE_UNKNOWN, COMPUTING_DEVICE_SMART_PHONE, COMPUTING_DEVICE_SMART_WATCH})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ComputingDevice {}
+ /** The {@link Alarm} belongs to an unknown device. */
+ public static final int COMPUTING_DEVICE_UNKNOWN = 0;
+ /** The {@link Alarm} belongs to a smart phone device. */
+ public static final int COMPUTING_DEVICE_SMART_PHONE = 1;
+ /** The {@link Alarm} belongs to a smart watch device. */
+ public static final int COMPUTING_DEVICE_SMART_WATCH = 2;
+
@Document.BooleanProperty
private final boolean mEnabled;
@@ -61,6 +77,9 @@
@Document.DocumentProperty
private final AlarmInstance mNextInstance;
+ @Document.LongProperty
+ private final int mComputingDevice;
+
Alarm(@NonNull String namespace, @NonNull String id, int documentScore,
long creationTimestampMillis, long documentTtlMillis, @Nullable String name,
@Nullable List<String> alternateNames, @Nullable String description,
@@ -69,7 +88,8 @@
boolean enabled, @Nullable int[] daysOfWeek, int hour, int minute,
@Nullable String blackoutPeriodStartDate, @Nullable String blackoutPeriodEndDate,
@Nullable String ringtone, boolean shouldVibrate,
- @Nullable AlarmInstance previousInstance, @Nullable AlarmInstance nextInstance) {
+ @Nullable AlarmInstance previousInstance, @Nullable AlarmInstance nextInstance,
+ int computingDevice) {
super(namespace, id, documentScore, creationTimestampMillis, documentTtlMillis, name,
alternateNames, description, image, url, potentialActions);
mEnabled = enabled;
@@ -82,6 +102,7 @@
mShouldVibrate = shouldVibrate;
mPreviousInstance = previousInstance;
mNextInstance = nextInstance;
+ mComputingDevice = computingDevice;
}
/** Returns whether or not the {@link Alarm} is active. */
@@ -196,6 +217,12 @@
return mNextInstance;
}
+ /** Returns the {@link ComputingDevice} this alarm belongs to. */
+ @ComputingDevice
+ public int getComputingDevice() {
+ return mComputingDevice;
+ }
+
/** Builder for {@link Alarm}. */
public static final class Builder extends BuilderImpl<Builder> {
/**
@@ -229,6 +256,7 @@
protected boolean mShouldVibrate;
protected AlarmInstance mPreviousInstance;
protected AlarmInstance mNextInstance;
+ protected int mComputingDevice;
BuilderImpl(@NonNull String namespace, @NonNull String id) {
super(namespace, id);
@@ -246,6 +274,7 @@
mShouldVibrate = alarm.shouldVibrate();
mPreviousInstance = alarm.getPreviousInstance();
mNextInstance = alarm.getNextInstance();
+ mComputingDevice = alarm.getComputingDevice();
}
/** Sets whether or not the {@link Alarm} is active. */
@@ -391,6 +420,13 @@
return (T) this;
}
+ /** Sets the {@link ComputingDevice} this alarm belongs to. */
+ @NonNull
+ public T setComputingDevice(@ComputingDevice int computingDevice) {
+ mComputingDevice = computingDevice;
+ return (T) this;
+ }
+
/** Builds the {@link Alarm}. */
@NonNull
@Override
@@ -400,7 +436,7 @@
mPotentialActions,
mEnabled, mDaysOfWeek, mHour, mMinute, mBlackoutPeriodStartDate,
mBlackoutPeriodEndDate, mRingtone, mShouldVibrate, mPreviousInstance,
- mNextInstance);
+ mNextInstance, mComputingDevice);
}
}
}
diff --git a/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/converter/SchemaToProtoConverterTest.java b/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/converter/SchemaToProtoConverterTest.java
index 5c98d3e..92a87de 100644
--- a/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/converter/SchemaToProtoConverterTest.java
+++ b/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/converter/SchemaToProtoConverterTest.java
@@ -128,6 +128,8 @@
.setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
.setJoinableValueType(AppSearchSchema.StringPropertyConfig
.JOINABLE_VALUE_TYPE_QUALIFIED_ID)
+ // TODO(b/274157614): Export this to framework when we can access hidden
+ // APIs.
// @exportToFramework:startStrip()
// TODO(b/274157614) start exporting this when it is unhidden in framework
.setDeletionPropagation(true)
@@ -137,7 +139,9 @@
JoinableConfig joinableConfig = JoinableConfig.newBuilder()
.setValueType(JoinableConfig.ValueType.Code.QUALIFIED_ID)
+ // @exportToFramework:startStrip()
.setPropagateDelete(true)
+ // @exportToFramework:endStrip()
.build();
SchemaTypeConfigProto expectedAlbumProto = SchemaTypeConfigProto.newBuilder()
diff --git a/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/converter/SearchSpecToProtoConverterTest.java b/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/converter/SearchSpecToProtoConverterTest.java
index cbfeea9..de56b69 100644
--- a/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/converter/SearchSpecToProtoConverterTest.java
+++ b/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/converter/SearchSpecToProtoConverterTest.java
@@ -645,6 +645,8 @@
PrefixUtil.removePrefix(grouping2.getEntryGroupings(1).getNamespace()));
}
+ // @exportToFramework:startStrip()
+ // TODO(b/258715421) start exporting this when it is unhidden in framework
@Test
public void testToResultSpecProto_groupBySchema() throws Exception {
SearchSpec searchSpec = new SearchSpec.Builder()
@@ -691,6 +693,7 @@
.isEqualTo(
PrefixUtil.removePrefix(grouping2.getEntryGroupings(1).getSchema()));
}
+ // @exportToFramework:endStrip()
@Test
public void testToResultSpecProto_groupByNamespaceAndPackage() throws Exception {
@@ -727,6 +730,8 @@
assertThat(resultSpecProto.getResultGroupings(3).getEntryGroupingsList()).hasSize(1);
}
+ // @exportToFramework:startStrip()
+ // TODO(b/258715421) start exporting this when it is unhidden in framework
@Test
public void testToResultSpecProto_groupBySchemaAndPackage() throws Exception {
SearchSpec searchSpec = new SearchSpec.Builder()
@@ -964,6 +969,7 @@
assertThat(grouping8.getEntryGroupings(0).getSchema())
.isEqualTo("package1$database/typeB");
}
+ // @exportToFramework:endStrip()
@Test
public void testGetTargetNamespaceFilters_emptySearchingFilter() {
diff --git a/appsearch/appsearch-platform-storage/src/main/java/androidx/appsearch/platformstorage/FeaturesImpl.java b/appsearch/appsearch-platform-storage/src/main/java/androidx/appsearch/platformstorage/FeaturesImpl.java
index 794ed1f..ed3a8f6 100644
--- a/appsearch/appsearch-platform-storage/src/main/java/androidx/appsearch/platformstorage/FeaturesImpl.java
+++ b/appsearch/appsearch-platform-storage/src/main/java/androidx/appsearch/platformstorage/FeaturesImpl.java
@@ -63,7 +63,7 @@
// Beyond Android U features
case Features.SEARCH_SPEC_GROUPING_TYPE_PER_SCHEMA:
- // TODO(b/258715421): Update to reflect support in Android U+ once this feature has
+ // TODO(b/258715421) : Update to reflect support in Android U+ once this feature has
// an extservices sdk that includes it.
// fall through
case Features.SCHEMA_SET_DELETION_PROPAGATION:
diff --git a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/AppSearchSessionInternalTestBase.java b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/AppSearchSessionInternalTestBase.java
index 2078a53..87f4f43 100644
--- a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/AppSearchSessionInternalTestBase.java
+++ b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/AppSearchSessionInternalTestBase.java
@@ -233,7 +233,11 @@
.setJoinableValueType(
StringPropertyConfig
.JOINABLE_VALUE_TYPE_QUALIFIED_ID)
+ // TODO(b/274157614): Export this to framework when we
+ // can access hidden APIs.
+ // @exportToFramework:startStrip()
.setDeletionPropagation(true)
+ // @exportToFramework:endStrip()
.build())
.build();
diff --git a/benchmark/benchmark-common/api/1.2.0-beta01.txt b/benchmark/benchmark-common/api/1.2.0-beta01.txt
index 6369afc..eb18450 100644
--- a/benchmark/benchmark-common/api/1.2.0-beta01.txt
+++ b/benchmark/benchmark-common/api/1.2.0-beta01.txt
@@ -18,9 +18,53 @@
@SuppressCompatibility @kotlin.RequiresOptIn @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets=kotlin.annotation.AnnotationTarget.FUNCTION) public static @interface BenchmarkState.Companion.ExperimentalExternalReport {
}
+ @SuppressCompatibility @kotlin.RequiresOptIn @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) public @interface ExperimentalBenchmarkConfigApi {
+ }
+
@SuppressCompatibility @kotlin.RequiresOptIn @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) public @interface ExperimentalBenchmarkStateApi {
}
+ @SuppressCompatibility @androidx.benchmark.ExperimentalBenchmarkConfigApi public abstract class MetricCapture {
+ ctor public MetricCapture(java.util.List<java.lang.String> names);
+ method public abstract void capturePaused();
+ method public abstract void captureResumed();
+ method public abstract void captureStart(long timeNs);
+ method public abstract void captureStop(long timeNs, long[] output, int offset);
+ method public final java.util.List<java.lang.String> getNames();
+ property public final java.util.List<java.lang.String> names;
+ }
+
+ @SuppressCompatibility @androidx.benchmark.ExperimentalBenchmarkConfigApi public final class MicrobenchmarkConfig {
+ ctor public MicrobenchmarkConfig(optional java.util.List<? extends androidx.benchmark.MetricCapture> metrics, optional boolean shouldEnableTraceAppTag, optional boolean shouldEnablePerfettoSdkTracing, optional androidx.benchmark.ProfilerConfig? profiler);
+ method public java.util.List<androidx.benchmark.MetricCapture> getMetrics();
+ method public androidx.benchmark.ProfilerConfig? getProfiler();
+ method public boolean getShouldEnablePerfettoSdkTracing();
+ method public boolean getShouldEnableTraceAppTag();
+ property public final java.util.List<androidx.benchmark.MetricCapture> metrics;
+ property public final androidx.benchmark.ProfilerConfig? profiler;
+ property public final boolean shouldEnablePerfettoSdkTracing;
+ property public final boolean shouldEnableTraceAppTag;
+ }
+
+ @SuppressCompatibility @androidx.benchmark.ExperimentalBenchmarkConfigApi public abstract sealed class ProfilerConfig {
+ }
+
+ public static final class ProfilerConfig.MethodTracing extends androidx.benchmark.ProfilerConfig {
+ ctor public ProfilerConfig.MethodTracing();
+ }
+
+ public static final class ProfilerConfig.StackSampling extends androidx.benchmark.ProfilerConfig {
+ ctor public ProfilerConfig.StackSampling();
+ }
+
+ @SuppressCompatibility @androidx.benchmark.ExperimentalBenchmarkConfigApi public final class TimeCapture extends androidx.benchmark.MetricCapture {
+ ctor public TimeCapture();
+ method public void capturePaused();
+ method public void captureResumed();
+ method public void captureStart(long timeNs);
+ method public void captureStop(long timeNs, long[] output, int offset);
+ }
+
}
package androidx.benchmark.perfetto {
diff --git a/benchmark/benchmark-common/api/current.ignore b/benchmark/benchmark-common/api/current.ignore
deleted file mode 100644
index f150239..0000000
--- a/benchmark/benchmark-common/api/current.ignore
+++ /dev/null
@@ -1,59 +0,0 @@
-// Baseline format: 1.0
-BecameUnchecked: androidx.benchmark.BenchmarkState#reportData(String, String, long, java.util.List<java.lang.Long>, int, long, int):
- Removed method androidx.benchmark.BenchmarkState.reportData(String,String,long,java.util.List<java.lang.Long>,int,long,int) from compatibility checked API surface
-BecameUnchecked: androidx.benchmark.BenchmarkState#reportData(String, String, long, java.util.List<java.lang.Long>, int, long, int) parameter #0:
- Removed parameter className in androidx.benchmark.BenchmarkState.reportData(String className, String testName, long totalRunTimeNs, java.util.List<java.lang.Long> dataNs, int warmupIterations, long thermalThrottleSleepSeconds, int repeatIterations) from compatibility checked API surface
-BecameUnchecked: androidx.benchmark.BenchmarkState#reportData(String, String, long, java.util.List<java.lang.Long>, int, long, int) parameter #1:
- Removed parameter testName in androidx.benchmark.BenchmarkState.reportData(String className, String testName, long totalRunTimeNs, java.util.List<java.lang.Long> dataNs, int warmupIterations, long thermalThrottleSleepSeconds, int repeatIterations) from compatibility checked API surface
-BecameUnchecked: androidx.benchmark.BenchmarkState#reportData(String, String, long, java.util.List<java.lang.Long>, int, long, int) parameter #2:
- Removed parameter totalRunTimeNs in androidx.benchmark.BenchmarkState.reportData(String className, String testName, long totalRunTimeNs, java.util.List<java.lang.Long> dataNs, int warmupIterations, long thermalThrottleSleepSeconds, int repeatIterations) from compatibility checked API surface
-BecameUnchecked: androidx.benchmark.BenchmarkState#reportData(String, String, long, java.util.List<java.lang.Long>, int, long, int) parameter #3:
- Removed parameter dataNs in androidx.benchmark.BenchmarkState.reportData(String className, String testName, long totalRunTimeNs, java.util.List<java.lang.Long> dataNs, int warmupIterations, long thermalThrottleSleepSeconds, int repeatIterations) from compatibility checked API surface
-BecameUnchecked: androidx.benchmark.BenchmarkState#reportData(String, String, long, java.util.List<java.lang.Long>, int, long, int) parameter #4:
- Removed parameter warmupIterations in androidx.benchmark.BenchmarkState.reportData(String className, String testName, long totalRunTimeNs, java.util.List<java.lang.Long> dataNs, int warmupIterations, long thermalThrottleSleepSeconds, int repeatIterations) from compatibility checked API surface
-BecameUnchecked: androidx.benchmark.BenchmarkState#reportData(String, String, long, java.util.List<java.lang.Long>, int, long, int) parameter #5:
- Removed parameter thermalThrottleSleepSeconds in androidx.benchmark.BenchmarkState.reportData(String className, String testName, long totalRunTimeNs, java.util.List<java.lang.Long> dataNs, int warmupIterations, long thermalThrottleSleepSeconds, int repeatIterations) from compatibility checked API surface
-BecameUnchecked: androidx.benchmark.BenchmarkState#reportData(String, String, long, java.util.List<java.lang.Long>, int, long, int) parameter #6:
- Removed parameter repeatIterations in androidx.benchmark.BenchmarkState.reportData(String className, String testName, long totalRunTimeNs, java.util.List<java.lang.Long> dataNs, int warmupIterations, long thermalThrottleSleepSeconds, int repeatIterations) from compatibility checked API surface
-BecameUnchecked: androidx.benchmark.BenchmarkState.Companion#reportData(String, String, long, java.util.List<java.lang.Long>, int, long, int):
- Removed method androidx.benchmark.BenchmarkState.Companion.reportData(String,String,long,java.util.List<java.lang.Long>,int,long,int) from compatibility checked API surface
-BecameUnchecked: androidx.benchmark.BenchmarkState.Companion#reportData(String, String, long, java.util.List<java.lang.Long>, int, long, int) parameter #0:
- Removed parameter className in androidx.benchmark.BenchmarkState.Companion.reportData(String className, String testName, long totalRunTimeNs, java.util.List<java.lang.Long> dataNs, int warmupIterations, long thermalThrottleSleepSeconds, int repeatIterations) from compatibility checked API surface
-BecameUnchecked: androidx.benchmark.BenchmarkState.Companion#reportData(String, String, long, java.util.List<java.lang.Long>, int, long, int) parameter #1:
- Removed parameter testName in androidx.benchmark.BenchmarkState.Companion.reportData(String className, String testName, long totalRunTimeNs, java.util.List<java.lang.Long> dataNs, int warmupIterations, long thermalThrottleSleepSeconds, int repeatIterations) from compatibility checked API surface
-BecameUnchecked: androidx.benchmark.BenchmarkState.Companion#reportData(String, String, long, java.util.List<java.lang.Long>, int, long, int) parameter #2:
- Removed parameter totalRunTimeNs in androidx.benchmark.BenchmarkState.Companion.reportData(String className, String testName, long totalRunTimeNs, java.util.List<java.lang.Long> dataNs, int warmupIterations, long thermalThrottleSleepSeconds, int repeatIterations) from compatibility checked API surface
-BecameUnchecked: androidx.benchmark.BenchmarkState.Companion#reportData(String, String, long, java.util.List<java.lang.Long>, int, long, int) parameter #3:
- Removed parameter dataNs in androidx.benchmark.BenchmarkState.Companion.reportData(String className, String testName, long totalRunTimeNs, java.util.List<java.lang.Long> dataNs, int warmupIterations, long thermalThrottleSleepSeconds, int repeatIterations) from compatibility checked API surface
-BecameUnchecked: androidx.benchmark.BenchmarkState.Companion#reportData(String, String, long, java.util.List<java.lang.Long>, int, long, int) parameter #4:
- Removed parameter warmupIterations in androidx.benchmark.BenchmarkState.Companion.reportData(String className, String testName, long totalRunTimeNs, java.util.List<java.lang.Long> dataNs, int warmupIterations, long thermalThrottleSleepSeconds, int repeatIterations) from compatibility checked API surface
-BecameUnchecked: androidx.benchmark.BenchmarkState.Companion#reportData(String, String, long, java.util.List<java.lang.Long>, int, long, int) parameter #5:
- Removed parameter thermalThrottleSleepSeconds in androidx.benchmark.BenchmarkState.Companion.reportData(String className, String testName, long totalRunTimeNs, java.util.List<java.lang.Long> dataNs, int warmupIterations, long thermalThrottleSleepSeconds, int repeatIterations) from compatibility checked API surface
-BecameUnchecked: androidx.benchmark.BenchmarkState.Companion#reportData(String, String, long, java.util.List<java.lang.Long>, int, long, int) parameter #6:
- Removed parameter repeatIterations in androidx.benchmark.BenchmarkState.Companion.reportData(String className, String testName, long totalRunTimeNs, java.util.List<java.lang.Long> dataNs, int warmupIterations, long thermalThrottleSleepSeconds, int repeatIterations) from compatibility checked API surface
-BecameUnchecked: androidx.benchmark.BenchmarkState.Companion.ExperimentalExternalReport:
- Removed class androidx.benchmark.BenchmarkState.Companion.ExperimentalExternalReport from compatibility checked API surface
-
-
-RemovedClass: androidx.benchmark.Api21Kt:
- Removed class androidx.benchmark.Api21Kt
-RemovedClass: androidx.benchmark.Api24Kt:
- Removed class androidx.benchmark.Api24Kt
-RemovedClass: androidx.benchmark.Api27Kt:
- Removed class androidx.benchmark.Api27Kt
-RemovedClass: androidx.benchmark.Api29Kt:
- Removed class androidx.benchmark.Api29Kt
-RemovedClass: androidx.benchmark.ArgumentsKt:
- Removed class androidx.benchmark.ArgumentsKt
-RemovedClass: androidx.benchmark.ConfigurationErrorKt:
- Removed class androidx.benchmark.ConfigurationErrorKt
-RemovedClass: androidx.benchmark.MetricNameUtilsKt:
- Removed class androidx.benchmark.MetricNameUtilsKt
-RemovedClass: androidx.benchmark.ProfilerKt:
- Removed class androidx.benchmark.ProfilerKt
-RemovedClass: androidx.benchmark.UserspaceTracingKt:
- Removed class androidx.benchmark.UserspaceTracingKt
-RemovedClass: androidx.benchmark.perfetto.PerfettoConfigKt:
- Removed class androidx.benchmark.perfetto.PerfettoConfigKt
-RemovedClass: androidx.benchmark.perfetto.UiStateKt:
- Removed class androidx.benchmark.perfetto.UiStateKt
diff --git a/benchmark/benchmark-common/api/current.txt b/benchmark/benchmark-common/api/current.txt
index 6369afc..eb18450 100644
--- a/benchmark/benchmark-common/api/current.txt
+++ b/benchmark/benchmark-common/api/current.txt
@@ -18,9 +18,53 @@
@SuppressCompatibility @kotlin.RequiresOptIn @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets=kotlin.annotation.AnnotationTarget.FUNCTION) public static @interface BenchmarkState.Companion.ExperimentalExternalReport {
}
+ @SuppressCompatibility @kotlin.RequiresOptIn @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) public @interface ExperimentalBenchmarkConfigApi {
+ }
+
@SuppressCompatibility @kotlin.RequiresOptIn @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) public @interface ExperimentalBenchmarkStateApi {
}
+ @SuppressCompatibility @androidx.benchmark.ExperimentalBenchmarkConfigApi public abstract class MetricCapture {
+ ctor public MetricCapture(java.util.List<java.lang.String> names);
+ method public abstract void capturePaused();
+ method public abstract void captureResumed();
+ method public abstract void captureStart(long timeNs);
+ method public abstract void captureStop(long timeNs, long[] output, int offset);
+ method public final java.util.List<java.lang.String> getNames();
+ property public final java.util.List<java.lang.String> names;
+ }
+
+ @SuppressCompatibility @androidx.benchmark.ExperimentalBenchmarkConfigApi public final class MicrobenchmarkConfig {
+ ctor public MicrobenchmarkConfig(optional java.util.List<? extends androidx.benchmark.MetricCapture> metrics, optional boolean shouldEnableTraceAppTag, optional boolean shouldEnablePerfettoSdkTracing, optional androidx.benchmark.ProfilerConfig? profiler);
+ method public java.util.List<androidx.benchmark.MetricCapture> getMetrics();
+ method public androidx.benchmark.ProfilerConfig? getProfiler();
+ method public boolean getShouldEnablePerfettoSdkTracing();
+ method public boolean getShouldEnableTraceAppTag();
+ property public final java.util.List<androidx.benchmark.MetricCapture> metrics;
+ property public final androidx.benchmark.ProfilerConfig? profiler;
+ property public final boolean shouldEnablePerfettoSdkTracing;
+ property public final boolean shouldEnableTraceAppTag;
+ }
+
+ @SuppressCompatibility @androidx.benchmark.ExperimentalBenchmarkConfigApi public abstract sealed class ProfilerConfig {
+ }
+
+ public static final class ProfilerConfig.MethodTracing extends androidx.benchmark.ProfilerConfig {
+ ctor public ProfilerConfig.MethodTracing();
+ }
+
+ public static final class ProfilerConfig.StackSampling extends androidx.benchmark.ProfilerConfig {
+ ctor public ProfilerConfig.StackSampling();
+ }
+
+ @SuppressCompatibility @androidx.benchmark.ExperimentalBenchmarkConfigApi public final class TimeCapture extends androidx.benchmark.MetricCapture {
+ ctor public TimeCapture();
+ method public void capturePaused();
+ method public void captureResumed();
+ method public void captureStart(long timeNs);
+ method public void captureStop(long timeNs, long[] output, int offset);
+ }
+
}
package androidx.benchmark.perfetto {
diff --git a/benchmark/benchmark-common/api/restricted_1.2.0-beta01.txt b/benchmark/benchmark-common/api/restricted_1.2.0-beta01.txt
index 883a688..fb75b90 100644
--- a/benchmark/benchmark-common/api/restricted_1.2.0-beta01.txt
+++ b/benchmark/benchmark-common/api/restricted_1.2.0-beta01.txt
@@ -20,9 +20,53 @@
@SuppressCompatibility @kotlin.RequiresOptIn @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets=kotlin.annotation.AnnotationTarget.FUNCTION) public static @interface BenchmarkState.Companion.ExperimentalExternalReport {
}
+ @SuppressCompatibility @kotlin.RequiresOptIn @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) public @interface ExperimentalBenchmarkConfigApi {
+ }
+
@SuppressCompatibility @kotlin.RequiresOptIn @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) public @interface ExperimentalBenchmarkStateApi {
}
+ @SuppressCompatibility @androidx.benchmark.ExperimentalBenchmarkConfigApi public abstract class MetricCapture {
+ ctor public MetricCapture(java.util.List<java.lang.String> names);
+ method public abstract void capturePaused();
+ method public abstract void captureResumed();
+ method public abstract void captureStart(long timeNs);
+ method public abstract void captureStop(long timeNs, long[] output, int offset);
+ method public final java.util.List<java.lang.String> getNames();
+ property public final java.util.List<java.lang.String> names;
+ }
+
+ @SuppressCompatibility @androidx.benchmark.ExperimentalBenchmarkConfigApi public final class MicrobenchmarkConfig {
+ ctor public MicrobenchmarkConfig(optional java.util.List<? extends androidx.benchmark.MetricCapture> metrics, optional boolean shouldEnableTraceAppTag, optional boolean shouldEnablePerfettoSdkTracing, optional androidx.benchmark.ProfilerConfig? profiler);
+ method public java.util.List<androidx.benchmark.MetricCapture> getMetrics();
+ method public androidx.benchmark.ProfilerConfig? getProfiler();
+ method public boolean getShouldEnablePerfettoSdkTracing();
+ method public boolean getShouldEnableTraceAppTag();
+ property public final java.util.List<androidx.benchmark.MetricCapture> metrics;
+ property public final androidx.benchmark.ProfilerConfig? profiler;
+ property public final boolean shouldEnablePerfettoSdkTracing;
+ property public final boolean shouldEnableTraceAppTag;
+ }
+
+ @SuppressCompatibility @androidx.benchmark.ExperimentalBenchmarkConfigApi public abstract sealed class ProfilerConfig {
+ }
+
+ public static final class ProfilerConfig.MethodTracing extends androidx.benchmark.ProfilerConfig {
+ ctor public ProfilerConfig.MethodTracing();
+ }
+
+ public static final class ProfilerConfig.StackSampling extends androidx.benchmark.ProfilerConfig {
+ ctor public ProfilerConfig.StackSampling();
+ }
+
+ @SuppressCompatibility @androidx.benchmark.ExperimentalBenchmarkConfigApi public final class TimeCapture extends androidx.benchmark.MetricCapture {
+ ctor public TimeCapture();
+ method public void capturePaused();
+ method public void captureResumed();
+ method public void captureStart(long timeNs);
+ method public void captureStop(long timeNs, long[] output, int offset);
+ }
+
}
package androidx.benchmark.perfetto {
diff --git a/benchmark/benchmark-common/api/restricted_current.ignore b/benchmark/benchmark-common/api/restricted_current.ignore
deleted file mode 100644
index bd6e5a8..0000000
--- a/benchmark/benchmark-common/api/restricted_current.ignore
+++ /dev/null
@@ -1,23 +0,0 @@
-// Baseline format: 1.0
-RemovedClass: androidx.benchmark.Api21Kt:
- Removed class androidx.benchmark.Api21Kt
-RemovedClass: androidx.benchmark.Api24Kt:
- Removed class androidx.benchmark.Api24Kt
-RemovedClass: androidx.benchmark.Api27Kt:
- Removed class androidx.benchmark.Api27Kt
-RemovedClass: androidx.benchmark.Api29Kt:
- Removed class androidx.benchmark.Api29Kt
-RemovedClass: androidx.benchmark.ArgumentsKt:
- Removed class androidx.benchmark.ArgumentsKt
-RemovedClass: androidx.benchmark.ConfigurationErrorKt:
- Removed class androidx.benchmark.ConfigurationErrorKt
-RemovedClass: androidx.benchmark.MetricNameUtilsKt:
- Removed class androidx.benchmark.MetricNameUtilsKt
-RemovedClass: androidx.benchmark.ProfilerKt:
- Removed class androidx.benchmark.ProfilerKt
-RemovedClass: androidx.benchmark.UserspaceTracingKt:
- Removed class androidx.benchmark.UserspaceTracingKt
-RemovedClass: androidx.benchmark.perfetto.PerfettoConfigKt:
- Removed class androidx.benchmark.perfetto.PerfettoConfigKt
-RemovedClass: androidx.benchmark.perfetto.UiStateKt:
- Removed class androidx.benchmark.perfetto.UiStateKt
diff --git a/benchmark/benchmark-common/api/restricted_current.txt b/benchmark/benchmark-common/api/restricted_current.txt
index 883a688..fb75b90 100644
--- a/benchmark/benchmark-common/api/restricted_current.txt
+++ b/benchmark/benchmark-common/api/restricted_current.txt
@@ -20,9 +20,53 @@
@SuppressCompatibility @kotlin.RequiresOptIn @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets=kotlin.annotation.AnnotationTarget.FUNCTION) public static @interface BenchmarkState.Companion.ExperimentalExternalReport {
}
+ @SuppressCompatibility @kotlin.RequiresOptIn @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) public @interface ExperimentalBenchmarkConfigApi {
+ }
+
@SuppressCompatibility @kotlin.RequiresOptIn @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) public @interface ExperimentalBenchmarkStateApi {
}
+ @SuppressCompatibility @androidx.benchmark.ExperimentalBenchmarkConfigApi public abstract class MetricCapture {
+ ctor public MetricCapture(java.util.List<java.lang.String> names);
+ method public abstract void capturePaused();
+ method public abstract void captureResumed();
+ method public abstract void captureStart(long timeNs);
+ method public abstract void captureStop(long timeNs, long[] output, int offset);
+ method public final java.util.List<java.lang.String> getNames();
+ property public final java.util.List<java.lang.String> names;
+ }
+
+ @SuppressCompatibility @androidx.benchmark.ExperimentalBenchmarkConfigApi public final class MicrobenchmarkConfig {
+ ctor public MicrobenchmarkConfig(optional java.util.List<? extends androidx.benchmark.MetricCapture> metrics, optional boolean shouldEnableTraceAppTag, optional boolean shouldEnablePerfettoSdkTracing, optional androidx.benchmark.ProfilerConfig? profiler);
+ method public java.util.List<androidx.benchmark.MetricCapture> getMetrics();
+ method public androidx.benchmark.ProfilerConfig? getProfiler();
+ method public boolean getShouldEnablePerfettoSdkTracing();
+ method public boolean getShouldEnableTraceAppTag();
+ property public final java.util.List<androidx.benchmark.MetricCapture> metrics;
+ property public final androidx.benchmark.ProfilerConfig? profiler;
+ property public final boolean shouldEnablePerfettoSdkTracing;
+ property public final boolean shouldEnableTraceAppTag;
+ }
+
+ @SuppressCompatibility @androidx.benchmark.ExperimentalBenchmarkConfigApi public abstract sealed class ProfilerConfig {
+ }
+
+ public static final class ProfilerConfig.MethodTracing extends androidx.benchmark.ProfilerConfig {
+ ctor public ProfilerConfig.MethodTracing();
+ }
+
+ public static final class ProfilerConfig.StackSampling extends androidx.benchmark.ProfilerConfig {
+ ctor public ProfilerConfig.StackSampling();
+ }
+
+ @SuppressCompatibility @androidx.benchmark.ExperimentalBenchmarkConfigApi public final class TimeCapture extends androidx.benchmark.MetricCapture {
+ ctor public TimeCapture();
+ method public void capturePaused();
+ method public void captureResumed();
+ method public void captureStart(long timeNs);
+ method public void captureStop(long timeNs, long[] output, int offset);
+ }
+
}
package androidx.benchmark.perfetto {
diff --git a/benchmark/benchmark-common/build.gradle b/benchmark/benchmark-common/build.gradle
index bda3d75..e8d4694 100644
--- a/benchmark/benchmark-common/build.gradle
+++ b/benchmark/benchmark-common/build.gradle
@@ -89,7 +89,8 @@
kotlinOptions {
// Enable using experimental APIs from within same version group
freeCompilerArgs += [
- "-opt-in=androidx.benchmark.perfetto.ExperimentalPerfettoCaptureApi"
+ "-opt-in=androidx.benchmark.ExperimentalBenchmarkConfigApi",
+ "-opt-in=androidx.benchmark.perfetto.ExperimentalPerfettoCaptureApi",
]
}
}
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 9782080..100979c 100644
--- a/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/BenchmarkStateConfigTest.kt
+++ b/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/BenchmarkStateConfigTest.kt
@@ -80,7 +80,7 @@
profiler = null,
warmupCount = 100,
measurementCount = 1000,
- cpuEventCountersMask = 0x0,
+ metrics = arrayOf(TimeCapture()),
),
expectedWarmups = 0,
expectedMeasurements = 1,
@@ -96,7 +96,7 @@
profiler = null,
warmupCount = 100,
measurementCount = 1000,
- cpuEventCountersMask = 0x0,
+ metrics = arrayOf(TimeCapture()),
),
expectedWarmups = 0,
expectedMeasurements = 10,
@@ -113,7 +113,7 @@
profiler = null,
warmupCount = null,
measurementCount = null,
- cpuEventCountersMask = 0x0,
+ metrics = arrayOf(TimeCapture()),
),
expectedWarmups = null,
expectedMeasurements = 55, // includes allocations
@@ -129,7 +129,7 @@
profiler = null,
warmupCount = 10,
measurementCount = 100,
- cpuEventCountersMask = 0x0,
+ metrics = arrayOf(TimeCapture()),
),
expectedWarmups = 10,
expectedMeasurements = 105, // includes allocations
@@ -145,7 +145,7 @@
profiler = MethodTracing,
warmupCount = 5,
measurementCount = 10,
- cpuEventCountersMask = 0x0,
+ metrics = arrayOf(TimeCapture()),
),
expectedWarmups = 5,
expectedMeasurements = 15, // profiler not measured, not accounted for here
@@ -162,7 +162,7 @@
profiler = MethodTracing,
warmupCount = 100,
measurementCount = 10,
- cpuEventCountersMask = 0x0,
+ metrics = arrayOf(TimeCapture()),
),
expectedWarmups = 100,
expectedMeasurements = 10,
diff --git a/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/BenchmarkStateTest.kt b/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/BenchmarkStateTest.kt
index 393f8df..c2ddc3a 100644
--- a/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/BenchmarkStateTest.kt
+++ b/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/BenchmarkStateTest.kt
@@ -287,47 +287,46 @@
}
private fun validateProfilerUsage(simplifiedTimingOnlyMode: Boolean?) {
- try {
- profilerOverride = StackSamplingLegacy
+ val config = MicrobenchmarkConfig(profiler = ProfilerConfig.StackSamplingLegacy())
- val benchmarkState = if (simplifiedTimingOnlyMode != null) {
- BenchmarkState(simplifiedTimingOnlyMode = simplifiedTimingOnlyMode)
+ val benchmarkState = if (simplifiedTimingOnlyMode != null) {
+ BenchmarkState(
+ config = config,
+ simplifiedTimingOnlyMode = simplifiedTimingOnlyMode
+ )
+ } else {
+ BenchmarkState(config)
+ }
+
+ // count iters with profiler enabled vs disabled
+ var profilerDisabledIterations = 0
+ var profilerEnabledIterations = 0
+ var profilerAllocationIterations = 0
+ while (benchmarkState.keepRunning()) {
+ if (StackSamplingLegacy.isRunning) {
+ profilerEnabledIterations++
} else {
- BenchmarkState()
- }
+ profilerDisabledIterations++
- // count iters with profiler enabled vs disabled
- var profilerDisabledIterations = 0
- var profilerEnabledIterations = 0
- var profilerAllocationIterations = 0
- while (benchmarkState.keepRunning()) {
- if (StackSamplingLegacy.isRunning) {
- profilerEnabledIterations++
- } else {
- profilerDisabledIterations++
-
- if (profilerEnabledIterations != 0) {
- // profiler will only be disabled after running during allocation phase
- profilerAllocationIterations++
- }
+ if (profilerEnabledIterations != 0) {
+ // profiler will only be disabled after running during allocation phase
+ profilerAllocationIterations++
}
}
+ }
- if (simplifiedTimingOnlyMode == true) {
- // profiler should be always disabled
- assertNotEquals(0, profilerDisabledIterations)
- assertEquals(0, profilerEnabledIterations)
- assertEquals(0, profilerAllocationIterations)
- } else {
- // first, profiler disabled (timing) ...
- assertNotEquals(0, profilerDisabledIterations)
- // then enabled (profiling) ...
- assertNotEquals(0, profilerEnabledIterations)
- // then disabled again (allocs)
- assertNotEquals(0, profilerAllocationIterations)
- }
- } finally {
- profilerOverride = null
+ if (simplifiedTimingOnlyMode == true) {
+ // profiler should be always disabled
+ assertNotEquals(0, profilerDisabledIterations)
+ assertEquals(0, profilerEnabledIterations)
+ assertEquals(0, profilerAllocationIterations)
+ } else {
+ // first, profiler disabled (timing) ...
+ assertNotEquals(0, profilerDisabledIterations)
+ // then enabled (profiling) ...
+ assertNotEquals(0, profilerEnabledIterations)
+ // then disabled again (allocs)
+ assertNotEquals(0, profilerAllocationIterations)
}
}
diff --git a/benchmark/benchmark-common/src/main/java/androidx/benchmark/Arguments.kt b/benchmark/benchmark-common/src/main/java/androidx/benchmark/Arguments.kt
index 47a2342..f82a89b 100644
--- a/benchmark/benchmark-common/src/main/java/androidx/benchmark/Arguments.kt
+++ b/benchmark/benchmark-common/src/main/java/androidx/benchmark/Arguments.kt
@@ -31,12 +31,6 @@
@VisibleForTesting
public var argumentSource: Bundle? = null
-/**
- * Allows tests to override profiler
- */
-@VisibleForTesting
-internal var profilerOverride: Profiler? = null
-
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
object Arguments {
// public properties are shared by micro + macro benchmarks
@@ -76,9 +70,7 @@
internal val outputEnable: Boolean
internal val startupMode: Boolean
internal val iterations: Int?
- private val _profiler: Profiler?
internal val profiler: Profiler?
- get() = if (profilerOverride != null) profilerOverride else _profiler
internal val profilerSampleFrequency: Int
internal val profilerSampleDurationSeconds: Long
internal val thermalThrottleSleepDurationSeconds: Long
@@ -171,7 +163,7 @@
enableCompilation =
arguments.getBenchmarkArgument("compilation.enabled")?.toBoolean() ?: !dryRunMode
- _profiler = arguments.getProfiler(outputEnable)
+ profiler = arguments.getProfiler(outputEnable)
profilerSampleFrequency =
arguments.getBenchmarkArgument("profiling.sampleFrequency")?.ifBlank { null }
?.toInt()
@@ -180,10 +172,10 @@
arguments.getBenchmarkArgument("profiling.sampleDurationSeconds")?.ifBlank { null }
?.toLong()
?: 5
- if (_profiler != null) {
+ if (profiler != null) {
Log.d(
BenchmarkState.TAG,
- "Profiler ${_profiler.javaClass.simpleName}, freq " +
+ "Profiler ${profiler.javaClass.simpleName}, freq " +
"$profilerSampleFrequency, duration $profilerSampleDurationSeconds"
)
}
@@ -222,7 +214,7 @@
fun methodTracingEnabled(): Boolean {
return when {
dryRunMode -> false
- _profiler == MethodTracing -> true
+ profiler == MethodTracing -> true
else -> false
}
}
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 22c18e8..b6ab1fc 100644
--- a/benchmark/benchmark-common/src/main/java/androidx/benchmark/BenchmarkState.kt
+++ b/benchmark/benchmark-common/src/main/java/androidx/benchmark/BenchmarkState.kt
@@ -77,21 +77,28 @@
* Constructor used for standard uses of BenchmarkState, e.g. in BenchmarkRule
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
- constructor() : this(warmupCount = null, simplifiedTimingOnlyMode = false)
+ constructor(
+ config: MicrobenchmarkConfig? = null
+ ) : this(
+ warmupCount = null,
+ simplifiedTimingOnlyMode = false,
+ config = config
+ )
internal constructor(
warmupCount: Int? = null,
measurementCount: Int? = null,
- simplifiedTimingOnlyMode: Boolean = false
+ simplifiedTimingOnlyMode: Boolean = false,
+ config: MicrobenchmarkConfig? = null
) : this(
MicrobenchmarkPhase.Config(
dryRunMode = Arguments.dryRunMode,
startupMode = Arguments.startupMode,
- profiler = Arguments.profiler,
+ profiler = config?.profiler?.profiler ?: Arguments.profiler,
warmupCount = warmupCount,
measurementCount = Arguments.iterations ?: measurementCount,
simplifiedTimingOnlyMode = simplifiedTimingOnlyMode,
- cpuEventCountersMask = Arguments.cpuEventCounterMask
+ metrics = config?.metrics?.toTypedArray() ?: DEFAULT_METRICS
)
)
@@ -544,6 +551,19 @@
private var firstBenchmark = true
+ private val DEFAULT_METRICS: Array<MetricCapture> =
+ if (Arguments.cpuEventCounterMask != 0) {
+ arrayOf(
+ TimeCapture(),
+ CpuEventCounterCapture(
+ MicrobenchmarkPhase.cpuEventCounter,
+ Arguments.cpuEventCounterMask
+ )
+ )
+ } else {
+ arrayOf(TimeCapture())
+ }
+
@RequiresOptIn
@Retention(AnnotationRetention.BINARY)
@Target(AnnotationTarget.FUNCTION)
diff --git a/benchmark/benchmark-common/src/main/java/androidx/benchmark/ExperimentalBenchmarkConfigApi.kt b/benchmark/benchmark-common/src/main/java/androidx/benchmark/ExperimentalBenchmarkConfigApi.kt
new file mode 100644
index 0000000..30314c7
--- /dev/null
+++ b/benchmark/benchmark-common/src/main/java/androidx/benchmark/ExperimentalBenchmarkConfigApi.kt
@@ -0,0 +1,26 @@
+/*
+ * 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.benchmark
+
+/**
+ * Annotation indicating experimental API primarily intended to allow configuration of
+ * microbenchmarks from code, overriding instrumentation arguments
+ * like `androidx.benchmark.profiling.mode`, and custom microbenchmark metrics.
+ */
+@RequiresOptIn
+@Retention(AnnotationRetention.BINARY)
+public annotation class ExperimentalBenchmarkConfigApi
diff --git a/benchmark/benchmark-common/src/main/java/androidx/benchmark/MetricCapture.kt b/benchmark/benchmark-common/src/main/java/androidx/benchmark/MetricCapture.kt
index 5ef654e..2d7a00f 100644
--- a/benchmark/benchmark-common/src/main/java/androidx/benchmark/MetricCapture.kt
+++ b/benchmark/benchmark-common/src/main/java/androidx/benchmark/MetricCapture.kt
@@ -18,35 +18,56 @@
import android.os.Debug
-internal abstract class MetricCapture(
+/**
+ * Microbenchmark metric.
+ *
+ * Note that the API is designed around low overhead, even in the case of multiple submetrics (such
+ * as cpu perf event counters) that must be started/stopped together for efficiency.
+ *
+ * This class may be initialized on a different thread from where measurement occurs, but all
+ * `capture` methods must be invoked from the same thread.
+ */
+@ExperimentalBenchmarkConfigApi
+abstract class MetricCapture(
val names: List<String>
) {
/**
* Starts collecting data for a run.
*
- * Must be called at the start of each run.
+ * Called at the start of each run.
*/
abstract fun captureStart(timeNs: Long)
/**
- * Marks the end of a run, and stores one metric value in the output array since the last call
- * to start.
+ * Mark the end of a run, and store offset metrics in the output array, per sub metric.
*
- * Should be called when a run stops.
+ * To output values, store them in the output array offset by both the parameter offset,
+ * and their submetric index, for example:
+ *
+ * ```
+ * class MyMetricCapture("firstSubMetricName", "secondSubMetricName") {
+ * //...
+ * override fun captureStop(timeNs: Long, output: LongArray, offset: Int) {
+ * output[offset + 0] = firstSubMetricValue
+ * output[offset + 1] = secondSubMetricValue
+ * }
+ * }
+ * ```
+ *
+ * @param timeNs Time of metric capture start, in monotonic time (System..
+ * @param output LongArray sized to hold all simultaneous sub metric outputs, use `offset` as
+ * the initial position in `output` to start writing submetrics.
+ * @param offset Offset into the output array to start writing sub metrics.
*/
abstract fun captureStop(timeNs: Long, output: LongArray, offset: Int)
/**
- * Pauses data collection.
- *
- * Call when you want to not capture the following part of a run.
+ * Pause data collection.
*/
abstract fun capturePaused()
/**
- * Resumes data collection.
- *
- * Call when you want to resume capturing a capturePaused-ed run.
+ * Resume data collection
*/
abstract fun captureResumed()
@@ -59,7 +80,13 @@
}
}
-internal class TimeCapture : MetricCapture(
+/**
+ * Time metric, which reports time in nanos, based on the time passed to [captureStop].
+ *
+ * Reports "timeNs"
+ */
+@ExperimentalBenchmarkConfigApi
+public class TimeCapture : MetricCapture(
names = listOf("timeNs")
) {
private var currentStarted = 0L
diff --git a/benchmark/benchmark-common/src/main/java/androidx/benchmark/MicrobenchmarkConfig.kt b/benchmark/benchmark-common/src/main/java/androidx/benchmark/MicrobenchmarkConfig.kt
new file mode 100644
index 0000000..52e5c3e
--- /dev/null
+++ b/benchmark/benchmark-common/src/main/java/androidx/benchmark/MicrobenchmarkConfig.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.benchmark
+
+/**
+ * Experimental config object for microbenchmarks for defining custom metrics, tracing behavior,
+ * and profiling, which overrides options set in
+ * [instrumentation arguments](https://developer.android.com/topic/performance/benchmarking/microbenchmark-instrumentation-args).
+ */
+@ExperimentalBenchmarkConfigApi
+class MicrobenchmarkConfig constructor(
+ /**
+ * Timing metrics for primary phase, post-warmup
+ *
+ * Defaults to [TimeCapture].
+ */
+ val metrics: List<MetricCapture> = listOf(TimeCapture()),
+
+ /**
+ * Set to true to enable capture of `trace("foo") {}` blocks in the output Perfetto trace.
+ *
+ * Defaults to false to minimize interference.
+ */
+ val shouldEnableTraceAppTag: Boolean = false,
+
+ /**
+ * Set to true to enable capture of tracing-perfetto trace events, such as in Compose
+ * composition tracing.
+ *
+ * Defaults to false to minimize interference.
+ */
+ val shouldEnablePerfettoSdkTracing: Boolean = false,
+
+ /**
+ * Optional profiler to be used after the primary timing phase.
+ */
+ val profiler: ProfilerConfig? = null,
+)
diff --git a/benchmark/benchmark-common/src/main/java/androidx/benchmark/MicrobenchmarkPhase.kt b/benchmark/benchmark-common/src/main/java/androidx/benchmark/MicrobenchmarkPhase.kt
index eb6bf1f1..e8cba451 100644
--- a/benchmark/benchmark-common/src/main/java/androidx/benchmark/MicrobenchmarkPhase.kt
+++ b/benchmark/benchmark-common/src/main/java/androidx/benchmark/MicrobenchmarkPhase.kt
@@ -103,7 +103,7 @@
private val THROTTLE_BACKOFF_S = Arguments.thermalThrottleSleepDurationSeconds
// static instance ensures there's only one, and we don't leak native memory
- private val cpuEventCounter: CpuEventCounter by lazy {
+ internal val cpuEventCounter: CpuEventCounter by lazy {
// As this is only ever enabled by experimental arguments, we force enable this
// permanently once the first benchmark uses it, for local runs only.
CpuEventCounter.forceEnable()?.let { errorMessage ->
@@ -146,19 +146,12 @@
loopMode: LoopMode,
measurementCount: Int,
simplifiedTimingOnlyMode: Boolean,
- cpuEventCountersMask: Int,
+ metrics: Array<MetricCapture>
) = MicrobenchmarkPhase(
label = "Benchmark Time",
measurementCount = measurementCount,
loopMode = loopMode,
- metrics = if (cpuEventCountersMask != 0) {
- arrayOf(
- TimeCapture(),
- CpuEventCounterCapture(cpuEventCounter, cpuEventCountersMask)
- )
- } else {
- arrayOf(TimeCapture())
- },
+ metrics = metrics,
thermalThrottleSleepsMax = if (simplifiedTimingOnlyMode) 0 else 2
)
@@ -205,7 +198,7 @@
val profiler: Profiler?,
val warmupCount: Int?,
val measurementCount: Int?,
- val cpuEventCountersMask: Int,
+ val metrics: Array<MetricCapture>,
) {
val warmupManager = WarmupManager(overrideCount = warmupCount)
init {
@@ -237,14 +230,16 @@
// only timing phase has a complex impl of pause/resume, then behavior
// changes drastically, and the warmupManager will estimate a far faster
// impl of `measureRepeated { runWithTimingDisabled }`
- collectCpuEventInstructions = cpuEventCountersMask != 0
+ collectCpuEventInstructions = metrics.any {
+ it is CpuEventCounterCapture && it.names.isNotEmpty()
+ }
),
// Regular timing phase
timingMeasurementPhase(
measurementCount = measurementCount ?: 50,
loopMode = loopMode,
- simplifiedTimingOnlyMode = simplifiedTimingOnlyMode,
- cpuEventCountersMask = cpuEventCountersMask
+ metrics = metrics,
+ simplifiedTimingOnlyMode = simplifiedTimingOnlyMode
),
if (simplifiedTimingOnlyMode || profiler == null) {
null
diff --git a/benchmark/benchmark-common/src/main/java/androidx/benchmark/ProfilerConfig.kt b/benchmark/benchmark-common/src/main/java/androidx/benchmark/ProfilerConfig.kt
new file mode 100644
index 0000000..1038397
--- /dev/null
+++ b/benchmark/benchmark-common/src/main/java/androidx/benchmark/ProfilerConfig.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.benchmark
+
+import android.os.Build
+
+/**
+ * Profiler configuration object.
+ *
+ * Will eventually allow further configuration, e.g. sampling frequency.
+ */
+@ExperimentalBenchmarkConfigApi
+sealed class ProfilerConfig(
+ internal val profiler: Profiler
+) {
+ class StackSampling() : ProfilerConfig(
+ profiler = if (Build.VERSION.SDK_INT >= 29) {
+ StackSamplingSimpleperf
+ } else {
+ StackSamplingLegacy
+ }
+ )
+
+ class MethodTracing() : ProfilerConfig(MethodTracing)
+
+ // used for tests
+ internal class StackSamplingLegacy() : ProfilerConfig(profiler = StackSamplingLegacy)
+}
diff --git a/benchmark/benchmark-junit4/api/1.2.0-beta01.txt b/benchmark/benchmark-junit4/api/1.2.0-beta01.txt
index 14173a7..aea3355 100644
--- a/benchmark/benchmark-junit4/api/1.2.0-beta01.txt
+++ b/benchmark/benchmark-junit4/api/1.2.0-beta01.txt
@@ -7,6 +7,7 @@
public final class BenchmarkRule implements org.junit.rules.TestRule {
ctor public BenchmarkRule();
+ ctor @SuppressCompatibility @androidx.benchmark.ExperimentalBenchmarkConfigApi public BenchmarkRule(androidx.benchmark.MicrobenchmarkConfig config);
method public org.junit.runners.model.Statement apply(org.junit.runners.model.Statement base, org.junit.runner.Description description);
method public androidx.benchmark.BenchmarkState getState();
}
diff --git a/benchmark/benchmark-junit4/api/current.txt b/benchmark/benchmark-junit4/api/current.txt
index 14173a7..aea3355 100644
--- a/benchmark/benchmark-junit4/api/current.txt
+++ b/benchmark/benchmark-junit4/api/current.txt
@@ -7,6 +7,7 @@
public final class BenchmarkRule implements org.junit.rules.TestRule {
ctor public BenchmarkRule();
+ ctor @SuppressCompatibility @androidx.benchmark.ExperimentalBenchmarkConfigApi public BenchmarkRule(androidx.benchmark.MicrobenchmarkConfig config);
method public org.junit.runners.model.Statement apply(org.junit.runners.model.Statement base, org.junit.runner.Description description);
method public androidx.benchmark.BenchmarkState getState();
}
diff --git a/benchmark/benchmark-junit4/api/restricted_1.2.0-beta01.txt b/benchmark/benchmark-junit4/api/restricted_1.2.0-beta01.txt
index 09ef59c..0dab2ea 100644
--- a/benchmark/benchmark-junit4/api/restricted_1.2.0-beta01.txt
+++ b/benchmark/benchmark-junit4/api/restricted_1.2.0-beta01.txt
@@ -7,6 +7,7 @@
public final class BenchmarkRule implements org.junit.rules.TestRule {
ctor public BenchmarkRule();
+ ctor @SuppressCompatibility @androidx.benchmark.ExperimentalBenchmarkConfigApi public BenchmarkRule(androidx.benchmark.MicrobenchmarkConfig config);
method public org.junit.runners.model.Statement apply(org.junit.runners.model.Statement base, org.junit.runner.Description description);
method public androidx.benchmark.BenchmarkState getState();
}
diff --git a/benchmark/benchmark-junit4/api/restricted_current.ignore b/benchmark/benchmark-junit4/api/restricted_current.ignore
deleted file mode 100644
index b2f8308..0000000
--- a/benchmark/benchmark-junit4/api/restricted_current.ignore
+++ /dev/null
@@ -1,3 +0,0 @@
-// Baseline format: 1.0
-RemovedClass: androidx.benchmark.junit4.PerfettoRule:
- Removed class androidx.benchmark.junit4.PerfettoRule
diff --git a/benchmark/benchmark-junit4/api/restricted_current.txt b/benchmark/benchmark-junit4/api/restricted_current.txt
index 09ef59c..0dab2ea 100644
--- a/benchmark/benchmark-junit4/api/restricted_current.txt
+++ b/benchmark/benchmark-junit4/api/restricted_current.txt
@@ -7,6 +7,7 @@
public final class BenchmarkRule implements org.junit.rules.TestRule {
ctor public BenchmarkRule();
+ ctor @SuppressCompatibility @androidx.benchmark.ExperimentalBenchmarkConfigApi public BenchmarkRule(androidx.benchmark.MicrobenchmarkConfig config);
method public org.junit.runners.model.Statement apply(org.junit.runners.model.Statement base, org.junit.runner.Description description);
method public androidx.benchmark.BenchmarkState getState();
}
diff --git a/benchmark/benchmark-junit4/build.gradle b/benchmark/benchmark-junit4/build.gradle
index cfb08c2..ca453a3 100644
--- a/benchmark/benchmark-junit4/build.gradle
+++ b/benchmark/benchmark-junit4/build.gradle
@@ -57,7 +57,8 @@
kotlinOptions {
// Enable using experimental APIs from within same version group
freeCompilerArgs += [
- "-opt-in=androidx.benchmark.perfetto.ExperimentalPerfettoCaptureApi"
+ "-opt-in=androidx.benchmark.ExperimentalBenchmarkConfigApi",
+ "-opt-in=androidx.benchmark.perfetto.ExperimentalPerfettoCaptureApi",
]
}
}
diff --git a/benchmark/benchmark-junit4/src/androidTest/java/androidx/benchmark/junit4/ActivityBenchmarkTests.kt b/benchmark/benchmark-junit4/src/androidTest/java/androidx/benchmark/junit4/ActivityBenchmarkTests.kt
index 5a4185b5..d1d337c 100644
--- a/benchmark/benchmark-junit4/src/androidTest/java/androidx/benchmark/junit4/ActivityBenchmarkTests.kt
+++ b/benchmark/benchmark-junit4/src/androidTest/java/androidx/benchmark/junit4/ActivityBenchmarkTests.kt
@@ -41,7 +41,7 @@
@RunWith(AndroidJUnit4::class)
class ActivityScenarioTest {
@get:Rule
- val benchmarkRule = BenchmarkRule(enableReport = false)
+ val benchmarkRule = BenchmarkRule()
private lateinit var activityScenario: ActivityScenario<Activity>
@@ -62,7 +62,7 @@
@RunWith(AndroidJUnit4::class)
class ActivityScenarioRuleTest {
@get:Rule
- val benchmarkRule = BenchmarkRule(enableReport = false)
+ val benchmarkRule = BenchmarkRule()
@get:Rule
val activityRule = ActivityScenarioRule(Activity::class.java)
@@ -79,7 +79,7 @@
@RunWith(AndroidJUnit4::class)
class ActivityTestRuleTest {
@get:Rule
- val benchmarkRule = BenchmarkRule(enableReport = false)
+ val benchmarkRule = BenchmarkRule()
@Suppress("DEPRECATION")
@get:Rule
diff --git a/benchmark/benchmark-junit4/src/androidTest/java/androidx/benchmark/junit4/BenchmarkRuleAnnotationTest.kt b/benchmark/benchmark-junit4/src/androidTest/java/androidx/benchmark/junit4/BenchmarkRuleAnnotationTest.kt
index 887ffc4..2e8ee5d 100644
--- a/benchmark/benchmark-junit4/src/androidTest/java/androidx/benchmark/junit4/BenchmarkRuleAnnotationTest.kt
+++ b/benchmark/benchmark-junit4/src/androidTest/java/androidx/benchmark/junit4/BenchmarkRuleAnnotationTest.kt
@@ -26,7 +26,7 @@
class BenchmarkRuleAnnotationTest {
@Suppress("MemberVisibilityCanBePrivate") // intentionally public
// NOTE: not annotated, so will throw when state is accessed
- val unannotatedRule = BenchmarkRule(enableReport = false)
+ val unannotatedRule = BenchmarkRule()
@Test(expected = IllegalStateException::class)
fun throwsIfNotAnnotated() {
diff --git a/benchmark/benchmark-junit4/src/androidTest/java/androidx/benchmark/junit4/BenchmarkRuleNotUsedTest.kt b/benchmark/benchmark-junit4/src/androidTest/java/androidx/benchmark/junit4/BenchmarkRuleNotUsedTest.kt
index fc57f68..f45ad61 100644
--- a/benchmark/benchmark-junit4/src/androidTest/java/androidx/benchmark/junit4/BenchmarkRuleNotUsedTest.kt
+++ b/benchmark/benchmark-junit4/src/androidTest/java/androidx/benchmark/junit4/BenchmarkRuleNotUsedTest.kt
@@ -26,7 +26,7 @@
@RunWith(AndroidJUnit4::class)
public class BenchmarkRuleNotUsedTest {
@get:Rule
- public val benchmarkRule: BenchmarkRule = BenchmarkRule(enableReport = true)
+ public val benchmarkRule: BenchmarkRule = BenchmarkRule()
/**
* Previously this test would fail, because BenchmarkState().report(...) used in the
diff --git a/benchmark/benchmark-junit4/src/androidTest/java/androidx/benchmark/junit4/BenchmarkRuleTest.kt b/benchmark/benchmark-junit4/src/androidTest/java/androidx/benchmark/junit4/BenchmarkRuleTest.kt
index 3c34893..2e3f44e 100644
--- a/benchmark/benchmark-junit4/src/androidTest/java/androidx/benchmark/junit4/BenchmarkRuleTest.kt
+++ b/benchmark/benchmark-junit4/src/androidTest/java/androidx/benchmark/junit4/BenchmarkRuleTest.kt
@@ -28,7 +28,7 @@
@RunWith(AndroidJUnit4::class)
public class BenchmarkRuleTest {
@get:Rule
- public val benchmarkRule: BenchmarkRule = BenchmarkRule(enableReport = false)
+ public val benchmarkRule: BenchmarkRule = BenchmarkRule()
@Test
public fun runWithTimingDisabled() {
diff --git a/benchmark/benchmark-junit4/src/main/java/androidx/benchmark/junit4/BenchmarkRule.kt b/benchmark/benchmark-junit4/src/main/java/androidx/benchmark/junit4/BenchmarkRule.kt
index 5234cb6..5fcf6977 100644
--- a/benchmark/benchmark-junit4/src/main/java/androidx/benchmark/junit4/BenchmarkRule.kt
+++ b/benchmark/benchmark-junit4/src/main/java/androidx/benchmark/junit4/BenchmarkRule.kt
@@ -17,12 +17,16 @@
package androidx.benchmark.junit4
import android.Manifest
+import android.os.Build
import android.util.Log
import androidx.annotation.RestrictTo
import androidx.benchmark.Arguments
import androidx.benchmark.BenchmarkState
import androidx.benchmark.DeviceInfo
+import androidx.benchmark.ExperimentalBenchmarkConfigApi
+import androidx.benchmark.MicrobenchmarkConfig
import androidx.benchmark.UserspaceTracing
+import androidx.benchmark.perfetto.PerfettoCapture
import androidx.benchmark.perfetto.PerfettoCaptureWrapper
import androidx.benchmark.perfetto.PerfettoConfig
import androidx.benchmark.perfetto.UiState
@@ -81,21 +85,25 @@
* See the [Benchmark Guide](https://developer.android.com/studio/profile/benchmark)
* for more information on writing Benchmarks.
*/
-public class BenchmarkRule internal constructor(
+public class BenchmarkRule private constructor(
+ private val config: MicrobenchmarkConfig?,
/**
- * Used to disable reporting, for correctness tests that shouldn't report values
- * (and would trigger warnings if they did, e.g. debuggable=true)
- * Is always true when called non-internally.
+ * This param is ignored, and just present to disambiguate the internal (nullable) vs external
+ * (non-null) variants of the constructor, since a lint failure occurs if they have the same
+ * signature, even if the external variant uses `this(config as MicrobenchmarkConfig?)`.
+ *
+ * In the future, we should just always pass a "default" config object, which can reference
+ * default values from Arguments, but that's a deeper change.
*/
- private val enableReport: Boolean,
- private val packages: List<String> = emptyList() // TODO: revisit if needed
+ @Suppress("UNUSED_PARAMETER") ignored: Boolean = true
) : TestRule {
- public constructor() : this(true)
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
- public constructor(packages: List<String>) : this(true, packages)
+ constructor() : this(config = null, ignored = true)
+
+ @ExperimentalBenchmarkConfigApi
+ constructor(config: MicrobenchmarkConfig) : this(config, ignored = true)
internal // synthetic access
- val internalState = BenchmarkState()
+ var internalState = BenchmarkState(config)
/**
* Object used for benchmarking in Java.
@@ -214,11 +222,25 @@
val tracePath = PerfettoCaptureWrapper().record(
fileLabel = uniqueName,
config = PerfettoConfig.Benchmark(
- appTagPackages = packages,
+ appTagPackages = if (config?.shouldEnableTraceAppTag == true) {
+ listOf(InstrumentationRegistry.getInstrumentation().context.packageName)
+ } else {
+ emptyList()
+ },
useStackSamplingConfig = false
),
- // TODO(290918736): add support for Perfetto SDK Tracing in Microbenchmark
- perfettoSdkConfig = null,
+ // TODO(290918736): add support for Perfetto SDK Tracing in
+ // Microbenchmark in other cases, outside of MicrobenchmarkConfig
+ perfettoSdkConfig = if (config?.shouldEnablePerfettoSdkTracing == true &&
+ Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
+ ) {
+ PerfettoCapture.PerfettoSdkConfig(
+ InstrumentationRegistry.getInstrumentation().context.packageName,
+ PerfettoCapture.PerfettoSdkConfig.InitialProcessState.Alive
+ )
+ } else {
+ null
+ },
// Optimize throughput in dryRunMode, since trace isn't useful, and extremely
// expensive on some emulators. Could alternately use UserspaceTracing if desired
@@ -246,14 +268,12 @@
)
}
- if (enableReport) {
- internalState.report(
- fullClassName = description.className,
- simpleClassName = description.testClass.simpleName,
- methodName = invokeMethodName,
- tracePath = tracePath
- )
- }
+ internalState.report(
+ fullClassName = description.className,
+ simpleClassName = description.testClass.simpleName,
+ methodName = invokeMethodName,
+ tracePath = tracePath
+ )
}
internal companion object {
diff --git a/benchmark/benchmark-macro-junit4/build.gradle b/benchmark/benchmark-macro-junit4/build.gradle
index 5fa8211..75bd5ec 100644
--- a/benchmark/benchmark-macro-junit4/build.gradle
+++ b/benchmark/benchmark-macro-junit4/build.gradle
@@ -36,10 +36,10 @@
api(libs.kotlinStdlib)
api("androidx.annotation:annotation:1.1.0")
api(project(":benchmark:benchmark-macro"))
+ api("androidx.test.uiautomator:uiautomator:2.2.0")
implementation(project(":benchmark:benchmark-common"))
implementation("androidx.test:rules:1.5.0")
implementation("androidx.test:runner:1.5.0")
- implementation("androidx.test.uiautomator:uiautomator:2.2.0")
androidTestImplementation(project(":internal-testutils-ktx"))
androidTestImplementation(libs.testExtJunit)
diff --git a/benchmark/benchmark/src/androidTest/java/androidx/benchmark/benchmark/PerfettoOverheadBenchmark.kt b/benchmark/benchmark/src/androidTest/java/androidx/benchmark/benchmark/PerfettoOverheadBenchmark.kt
index 449e495..57f6360 100644
--- a/benchmark/benchmark/src/androidTest/java/androidx/benchmark/benchmark/PerfettoOverheadBenchmark.kt
+++ b/benchmark/benchmark/src/androidTest/java/androidx/benchmark/benchmark/PerfettoOverheadBenchmark.kt
@@ -16,25 +16,25 @@
package androidx.benchmark.benchmark
+import androidx.benchmark.ExperimentalBenchmarkConfigApi
+import androidx.benchmark.MicrobenchmarkConfig
import androidx.benchmark.junit4.BenchmarkRule
import androidx.benchmark.junit4.measureRepeated
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
-import androidx.test.platform.app.InstrumentationRegistry
import androidx.tracing.Trace
import androidx.tracing.trace
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
+@OptIn(ExperimentalBenchmarkConfigApi::class)
@LargeTest
@RunWith(AndroidJUnit4::class)
class PerfettoOverheadBenchmark {
- private val targetPackage =
- InstrumentationRegistry.getInstrumentation().targetContext.packageName
@get:Rule
- val benchmarkRule = BenchmarkRule(packages = listOf(targetPackage))
+ val benchmarkRule = BenchmarkRule(MicrobenchmarkConfig(shouldEnableTraceAppTag = true))
/**
* Empty baseline, no tracing. Expect similar results to [TrivialJavaBenchmark.nothing].
@@ -48,7 +48,7 @@
*/
@Test
fun runWithTimingDisabled() = benchmarkRule.measureRepeated {
- runWithTimingDisabled { /* nothing*/ }
+ runWithTimingDisabled { /* nothing */ }
}
/**
diff --git a/benchmark/benchmark/src/androidTest/java/androidx/benchmark/benchmark/PerfettoSdkOverheadBenchmark.kt b/benchmark/benchmark/src/androidTest/java/androidx/benchmark/benchmark/PerfettoSdkOverheadBenchmark.kt
index 5467d51..8388e3d 100644
--- a/benchmark/benchmark/src/androidTest/java/androidx/benchmark/benchmark/PerfettoSdkOverheadBenchmark.kt
+++ b/benchmark/benchmark/src/androidTest/java/androidx/benchmark/benchmark/PerfettoSdkOverheadBenchmark.kt
@@ -17,6 +17,8 @@
package androidx.benchmark.benchmark
import android.os.Build
+import androidx.benchmark.ExperimentalBenchmarkConfigApi
+import androidx.benchmark.MicrobenchmarkConfig
import androidx.benchmark.junit4.BenchmarkRule
import androidx.benchmark.junit4.measureRepeated
import androidx.benchmark.perfetto.PerfettoCapture
@@ -35,6 +37,7 @@
import org.junit.Test
import org.junit.runner.RunWith
+@OptIn(ExperimentalBenchmarkConfigApi::class)
@LargeTest
@RunWith(AndroidJUnit4::class)
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.R) // TODO(234351579): Support API < 30
@@ -48,7 +51,7 @@
InstrumentationRegistry.getInstrumentation().targetContext.packageName
@get:Rule
- val benchmarkRule = BenchmarkRule(packages = listOf(targetPackage))
+ val benchmarkRule = BenchmarkRule(MicrobenchmarkConfig(shouldEnableTraceAppTag = true))
private val testData = Array(50_000) { UUID.randomUUID().toString() }
diff --git a/benchmark/benchmark/src/androidTest/java/androidx/benchmark/benchmark/SampleCustomBenchmark.kt b/benchmark/benchmark/src/androidTest/java/androidx/benchmark/benchmark/SampleCustomBenchmark.kt
new file mode 100644
index 0000000..8a2d967
--- /dev/null
+++ b/benchmark/benchmark/src/androidTest/java/androidx/benchmark/benchmark/SampleCustomBenchmark.kt
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.benchmark.benchmark
+
+import androidx.benchmark.ExperimentalBenchmarkConfigApi
+import androidx.benchmark.MetricCapture
+import androidx.benchmark.MicrobenchmarkConfig
+import androidx.benchmark.ProfilerConfig
+import androidx.benchmark.TimeCapture
+import androidx.benchmark.junit4.BenchmarkRule
+import androidx.benchmark.junit4.measureRepeated
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalBenchmarkConfigApi::class)
+@LargeTest
+@RunWith(AndroidJUnit4::class)
+class SampleCustomBenchmark {
+ var counter: Long = 0
+
+ @get:Rule
+ val benchmarkRule = BenchmarkRule(
+ // Ideally, this config object would be specified per test, but that requires non-trivial
+ // changes to BenchmarkRule/BenchmarkState, and can be explored later
+ MicrobenchmarkConfig(
+ metrics = listOf(
+ TimeCapture(), // put TimeCapture early to prioritize it (starts last, stops first)
+ object : MetricCapture(
+ // list of names of submetrics this capture will produce
+ listOf("customCounter", "surpriseZero")
+ ) {
+ var valueAtPause: Long = 0
+ var pausedOffset: Long = 0
+
+ override fun captureStart(timeNs: Long) {
+ counter = 0
+ }
+
+ override fun captureStop(timeNs: Long, output: LongArray, offset: Int) {
+ output[offset] = counter - pausedOffset
+
+ // note that this number that's reported isn't the metric for one iter, but
+ // for N iters, and is divided on your behalf. This means with a quick
+ // benchmark like the one below, you might see results like:
+ // SampleCustomBenchmark.sample
+ // timeNs min 25.1, median 25.1, max 25.4
+ // customCounter min 20.0, median 20.0, max 20.0
+ // surpriseZero min 0.0, median 0.0, max 0.0
+ // allocationCount min 0.0, median 0.0, max 0.0
+ output[offset + 1] = 1000
+ }
+
+ override fun capturePaused() {
+ valueAtPause = counter
+ }
+
+ override fun captureResumed() {
+ pausedOffset += counter - valueAtPause
+ }
+ }
+ ),
+ profiler = ProfilerConfig.MethodTracing()
+ )
+ )
+ @Test
+ fun sample() {
+ benchmarkRule.measureRepeated {
+ repeat(20) {
+ counter++
+ }
+ runWithTimingDisabled {
+ counter++ // this is ignored, so customCounter output is simply 20
+ }
+ }
+ }
+}
diff --git a/benchmark/gradle-plugin/src/main/kotlin/androidx/benchmark/gradle/BenchmarkPlugin.kt b/benchmark/gradle-plugin/src/main/kotlin/androidx/benchmark/gradle/BenchmarkPlugin.kt
index 5f656dc..64086f5 100644
--- a/benchmark/gradle-plugin/src/main/kotlin/androidx/benchmark/gradle/BenchmarkPlugin.kt
+++ b/benchmark/gradle-plugin/src/main/kotlin/androidx/benchmark/gradle/BenchmarkPlugin.kt
@@ -137,13 +137,18 @@
if (!applied) {
applied = true
+ // Note, this directory is hard-coded in AGP
+ val outputDir = project.layout.buildDirectory.dir(
+ "outputs/connected_android_test_additional_output"
+ )
if (!project.properties[ADDITIONAL_TEST_OUTPUT_KEY].toString().toBoolean()) {
// Only enable pulling benchmark data through this plugin on older versions of
// AGP that do not yet enable this flag.
project.tasks.register("benchmarkReport", BenchmarkReportTask::class.java)
- .configure {
- it.adbPath.set(adbPathProvider)
- it.dependsOn(project.tasks.named("connectedAndroidTest"))
+ .configure { reportTask ->
+ reportTask.benchmarkReportDir.set(outputDir)
+ reportTask.adbPath.set(adbPathProvider)
+ reportTask.dependsOn(project.tasks.named("connectedAndroidTest"))
}
project.tasks.named("connectedAndroidTest").configure {
@@ -153,13 +158,12 @@
it.finalizedBy("benchmarkReport")
}
} else {
- val projectBuildDir = project.buildDir.path
project.tasks.named("connectedAndroidTest").configure {
it.doLast {
it.logger.info(
"Benchmark",
- "Benchmark report files generated at $projectBuildDir" +
- "/outputs/connected_android_test_additional_output"
+ "Benchmark report files generated at " +
+ outputDir.get().asFile.absolutePath
)
}
}
diff --git a/benchmark/gradle-plugin/src/main/kotlin/androidx/benchmark/gradle/BenchmarkReportTask.kt b/benchmark/gradle-plugin/src/main/kotlin/androidx/benchmark/gradle/BenchmarkReportTask.kt
index e84bb7c..482292a 100644
--- a/benchmark/gradle-plugin/src/main/kotlin/androidx/benchmark/gradle/BenchmarkReportTask.kt
+++ b/benchmark/gradle-plugin/src/main/kotlin/androidx/benchmark/gradle/BenchmarkReportTask.kt
@@ -18,8 +18,10 @@
import java.io.File
import org.gradle.api.DefaultTask
+import org.gradle.api.file.DirectoryProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Input
+import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.StopExecutionException
import org.gradle.api.tasks.TaskAction
import org.gradle.work.DisableCachingByDefault
@@ -27,18 +29,12 @@
@Suppress("UnstableApiUsage")
@DisableCachingByDefault(because = "Benchmark measurements are performed each task execution.")
abstract class BenchmarkReportTask : DefaultTask() {
- private val benchmarkReportDir: File
init {
group = "Android"
description = "Run benchmarks found in the current project and output reports to the " +
"benchmark_reports folder under the project's build directory."
- benchmarkReportDir = File(
- "${project.buildDir}/outputs", "connected_android_test_additional_output"
- )
- outputs.dir(benchmarkReportDir)
-
// This task should mirror the upToDate behavior of connectedAndroidTest as we always want
// this task to run after connectedAndroidTest is run to pull the most recent benchmark
// report data, even when tests are triggered multiple times in a row without source
@@ -46,7 +42,10 @@
outputs.upToDateWhen { false }
}
- @get: Input
+ @get:OutputDirectory
+ abstract val benchmarkReportDir: DirectoryProperty
+
+ @get:Input
abstract val adbPath: Property<String>
@TaskAction
@@ -57,10 +56,11 @@
}
private fun getReportsForDevices(adb: Adb) {
- if (benchmarkReportDir.exists()) {
- benchmarkReportDir.deleteRecursively()
+ val reportDir = benchmarkReportDir.asFile.get()
+ if (reportDir.exists()) {
+ reportDir.deleteRecursively()
}
- benchmarkReportDir.mkdirs()
+ reportDir.mkdirs()
val deviceIds = adb.execSync("devices -l").stdout
.split("\n")
@@ -75,12 +75,12 @@
throw StopExecutionException("Failed to find benchmark report on device: $deviceId")
}
- val outDir = File(benchmarkReportDir, deviceId)
+ val outDir = File(reportDir, deviceId)
outDir.mkdirs()
getReportsForDevice(adb, outDir, dataDir, deviceId)
logger.info(
"Benchmark",
- "Benchmark report files generated at ${benchmarkReportDir.absolutePath}"
+ "Benchmark report files generated at ${reportDir.absolutePath}"
)
}
}
diff --git a/bluetooth/bluetooth/api/current.txt b/bluetooth/bluetooth/api/current.txt
index c549d1d..1c84661 100644
--- a/bluetooth/bluetooth/api/current.txt
+++ b/bluetooth/bluetooth/api/current.txt
@@ -55,8 +55,9 @@
}
public final class ScanFilter {
- ctor public ScanFilter(optional androidx.bluetooth.BluetoothAddress? deviceAddress, optional int manufacturerId, optional byte[]? manufacturerData, optional byte[]? manufacturerDataMask, optional java.util.UUID? serviceDataUuid, optional byte[]? serviceData, optional byte[]? serviceDataMask, optional java.util.UUID? serviceUuid, optional java.util.UUID? serviceUuidMask);
+ ctor public ScanFilter(optional androidx.bluetooth.BluetoothAddress? deviceAddress, optional String? deviceName, optional int manufacturerId, optional byte[]? manufacturerData, optional byte[]? manufacturerDataMask, optional java.util.UUID? serviceDataUuid, optional byte[]? serviceData, optional byte[]? serviceDataMask, optional java.util.UUID? serviceUuid, optional java.util.UUID? serviceUuidMask);
method public androidx.bluetooth.BluetoothAddress? getDeviceAddress();
+ method public String? getDeviceName();
method public byte[]? getManufacturerData();
method public byte[]? getManufacturerDataMask();
method public int getManufacturerId();
@@ -66,6 +67,7 @@
method public java.util.UUID? getServiceUuid();
method public java.util.UUID? getServiceUuidMask();
property public final androidx.bluetooth.BluetoothAddress? deviceAddress;
+ property public final String? deviceName;
property public final byte[]? manufacturerData;
property public final byte[]? manufacturerDataMask;
property public final int manufacturerId;
diff --git a/bluetooth/bluetooth/api/restricted_current.txt b/bluetooth/bluetooth/api/restricted_current.txt
index c549d1d..1c84661 100644
--- a/bluetooth/bluetooth/api/restricted_current.txt
+++ b/bluetooth/bluetooth/api/restricted_current.txt
@@ -55,8 +55,9 @@
}
public final class ScanFilter {
- ctor public ScanFilter(optional androidx.bluetooth.BluetoothAddress? deviceAddress, optional int manufacturerId, optional byte[]? manufacturerData, optional byte[]? manufacturerDataMask, optional java.util.UUID? serviceDataUuid, optional byte[]? serviceData, optional byte[]? serviceDataMask, optional java.util.UUID? serviceUuid, optional java.util.UUID? serviceUuidMask);
+ ctor public ScanFilter(optional androidx.bluetooth.BluetoothAddress? deviceAddress, optional String? deviceName, optional int manufacturerId, optional byte[]? manufacturerData, optional byte[]? manufacturerDataMask, optional java.util.UUID? serviceDataUuid, optional byte[]? serviceData, optional byte[]? serviceDataMask, optional java.util.UUID? serviceUuid, optional java.util.UUID? serviceUuidMask);
method public androidx.bluetooth.BluetoothAddress? getDeviceAddress();
+ method public String? getDeviceName();
method public byte[]? getManufacturerData();
method public byte[]? getManufacturerDataMask();
method public int getManufacturerId();
@@ -66,6 +67,7 @@
method public java.util.UUID? getServiceUuid();
method public java.util.UUID? getServiceUuidMask();
property public final androidx.bluetooth.BluetoothAddress? deviceAddress;
+ property public final String? deviceName;
property public final byte[]? manufacturerData;
property public final byte[]? manufacturerDataMask;
property public final int manufacturerId;
diff --git a/bluetooth/bluetooth/src/main/java/androidx/bluetooth/ScanFilter.kt b/bluetooth/bluetooth/src/main/java/androidx/bluetooth/ScanFilter.kt
index 74eb868..0111f0bd 100644
--- a/bluetooth/bluetooth/src/main/java/androidx/bluetooth/ScanFilter.kt
+++ b/bluetooth/bluetooth/src/main/java/androidx/bluetooth/ScanFilter.kt
@@ -30,6 +30,9 @@
/** The scan filter for the remote device address. `null` if filter is not set. */
val deviceAddress: BluetoothAddress? = null,
+ /** The scan filter for the remote device name. `null` if filter is not set. */
+ val deviceName: String? = null,
+
/** The scan filter for manufacturer id. [MANUFACTURER_FILTER_NONE] if filter is not set. */
val manufacturerId: Int = MANUFACTURER_FILTER_NONE,
@@ -97,6 +100,8 @@
FwkScanFilter.Builder().run {
deviceAddress?.let { setDeviceAddress(it.address) }
+ deviceName?.let { setDeviceName(it) }
+
if (manufacturerId != MANUFACTURER_FILTER_NONE && manufacturerData != null) {
if (Build.VERSION.SDK_INT >= 33) {
setManufacturerData(
diff --git a/buildSrc-tests/build.gradle b/buildSrc-tests/build.gradle
index 5a72509..dcf2b1d 100644
--- a/buildSrc-tests/build.gradle
+++ b/buildSrc-tests/build.gradle
@@ -109,8 +109,3 @@
t.overrideDirectory = ktlintDir
t.overrideSubdirectories = subdirs
})
-
-// Broken in AGP 7.0-alpha15 due to b/180408027
-tasks["lint"].configure { t ->
- t.enabled = false
-}
diff --git a/buildSrc-tests/src/test/java/androidx/build/testConfiguration/AndroidTestConfigBuilderTest.kt b/buildSrc-tests/src/test/java/androidx/build/testConfiguration/AndroidTestConfigBuilderTest.kt
index 3d8531a..e4d7736 100644
--- a/buildSrc-tests/src/test/java/androidx/build/testConfiguration/AndroidTestConfigBuilderTest.kt
+++ b/buildSrc-tests/src/test/java/androidx/build/testConfiguration/AndroidTestConfigBuilderTest.kt
@@ -39,7 +39,7 @@
fun init() {
builder = ConfigBuilder()
builder.configName("placeHolderAndroidTest.xml")
- .isBenchmark(false)
+ .isMicrobenchmark(false)
.applicationId("com.androidx.placeholder.Placeholder")
.isPostsubmit(true)
.minSdk("15")
@@ -59,7 +59,7 @@
@Test
fun testXmlAgainstGoldenDefaultBenchmark() {
- builder.isBenchmark(true)
+ builder.isMicrobenchmark(true)
MatcherAssert.assertThat(
builder.buildXml(),
CoreMatchers.`is`(goldenDefaultConfigBenchmark)
@@ -123,7 +123,7 @@
@Test
fun testJsonAgainstGoldenPresubmitBenchmark() {
- builder.isBenchmark(true)
+ builder.isMicrobenchmark(true)
.isPostsubmit(false)
MatcherAssert.assertThat(
builder.buildJson(),
@@ -241,7 +241,7 @@
@Test
fun testValidTestConfigXml_benchmarkTrue() {
- builder.isBenchmark(true)
+ builder.isMicrobenchmark(true)
validate(builder.buildXml())
}
@@ -279,7 +279,7 @@
@Test
fun testValidTestConfigXml_presubmitBenchmark() {
builder.isPostsubmit(false)
- .isBenchmark(true)
+ .isMicrobenchmark(true)
validate(builder.buildXml())
}
@@ -349,10 +349,6 @@
<option name="config-descriptor:metadata" key="applicationId" value="com.androidx.placeholder.Placeholder" />
<option name="wifi:disable" value="true" />
<option name="instrumentation-arg" key="notAnnotation" value="androidx.test.filters.FlakyTest" />
- <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
- <option name="run-command" value="cmd package compile -f -m speed com.androidx.placeholder.Placeholder" />
- </target_preparer>
- <option name="instrumentation-arg" key="androidx.benchmark.output.enable" value="true" />
<option name="instrumentation-arg" key="listener" value="androidx.benchmark.junit4.InstrumentationResultsRunListener" />
<include name="google/unbundled/common/setup" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
@@ -360,6 +356,9 @@
<option name="install-arg" value="-t" />
<option name="test-file-name" value="placeholder.apk" />
</target_preparer>
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="run-command" value="cmd package compile -f -m speed com.androidx.placeholder.Placeholder" />
+ </target_preparer>
<test class="com.android.tradefed.testtype.AndroidJUnitTest">
<option name="runner" value="com.example.Runner"/>
<option name="package" value="com.androidx.placeholder.Placeholder" />
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXComposeImplPlugin.kt b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXComposeImplPlugin.kt
index 282bd32..1d045d0 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXComposeImplPlugin.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXComposeImplPlugin.kt
@@ -23,27 +23,17 @@
import com.android.build.gradle.LibraryExtension
import com.android.build.gradle.LibraryPlugin
import com.android.build.gradle.TestedExtension
-import com.android.build.gradle.internal.lint.AndroidLintAnalysisTask
-import com.android.build.gradle.internal.lint.AndroidLintTask
-import com.android.build.gradle.internal.lint.LintModelWriterTask
-import com.android.build.gradle.internal.lint.VariantInputs
import java.io.File
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.artifacts.type.ArtifactTypeDefinition
import org.gradle.api.attributes.Attribute
-import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.plugins.ExtraPropertiesExtension
import org.gradle.api.tasks.ClasspathNormalizer
import org.gradle.kotlin.dsl.create
-import org.gradle.kotlin.dsl.findByType
-import org.gradle.kotlin.dsl.withType
-import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
import org.jetbrains.kotlin.gradle.plugin.KotlinBasePluginWrapper
import org.jetbrains.kotlin.gradle.plugin.KotlinMultiplatformPluginWrapper
-import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
-import org.jetbrains.kotlin.tooling.core.withClosure
const val composeSourceOption =
"plugin:androidx.compose.compiler.plugins.kotlin:sourceInformation=true"
@@ -187,8 +177,6 @@
it.languageSettings.apply { optIn("kotlin.ExperimentalMultiplatform") }
}
- configureLintForMultiplatformLibrary(multiplatformExtension)
-
afterEvaluate {
if (multiplatformExtension.targets.findByName("jvm") != null) {
tasks.named("jvmTestClasses").also(::addToBuildOnServer)
@@ -276,70 +264,3 @@
}
}
}
-
-/**
- * Adds missing MPP sourcesets (such as commonMain) to the Lint tasks
- *
- * TODO: b/195329463 Lint is not aware of MPP, and MPP doesn't configure Lint. There is no built-in
- * API to adjust the default Lint task's sources, so we use this hack to manually add sources for
- * MPP source sets. In the future with the new Kotlin Project Model
- * (https://youtrack.jetbrains.com/issue/KT-42572) and an AGP / MPP integration plugin this will
- * no longer be needed.
- */
-private fun Project.configureLintForMultiplatformLibrary(
- multiplatformExtension: KotlinMultiplatformExtension
-) {
- afterEvaluate {
- // This workaround only works for libraries (apps would require changes to a different
- // task). Given that we currently do not have any MPP app projects, this should never
- // happen.
- project.extensions.findByType<LibraryExtension>() ?: return@afterEvaluate
- val androidMain =
- multiplatformExtension.sourceSets.findByName("androidMain") ?: return@afterEvaluate
- // Get all the sourcesets androidMain transitively / directly depends on
- val dependencies = androidMain.withClosure(KotlinSourceSet::dependsOn)
-
- /** Helper function to add the missing sourcesets to this [VariantInputs] */
- fun VariantInputs.addSourceSets() {
- // Each variant has a source provider for the variant (such as debug) and the 'main'
- // variant. The actual files that Lint will run on is both of these providers
- // combined - so we can just add the dependencies to the first we see.
- val sourceProvider = sourceProviders.get().firstOrNull() ?: return
- dependencies.forEach { sourceSet ->
- sourceProvider.javaDirectories.withChangesAllowed {
- from(sourceSet.kotlin.sourceDirectories)
- }
- }
- }
-
- // Lint for libraries is split into two tasks - analysis, and reporting. We need to
- // add the new sources to both, so all parts of the pipeline are aware.
- project.tasks.withType<AndroidLintAnalysisTask>().configureEach {
- it.variantInputs.addSourceSets()
- }
-
- project.tasks.withType<AndroidLintTask>().configureEach { it.variantInputs.addSourceSets() }
-
- // Also configure the model writing task, so that we don't run into mismatches between
- // analyzed sources in one module and a downstream module
- project.tasks.withType<LintModelWriterTask>().configureEach {
- it.variantInputs.addSourceSets()
- }
- }
-}
-
-/**
- * Lint uses [ConfigurableFileCollection.disallowChanges] during initialization, which prevents
- * modifying the file collection separately (there is no time to configure it before AGP has
- * initialized and disallowed changes). This uses reflection to temporarily allow changes, and apply
- * [block].
- */
-private fun ConfigurableFileCollection.withChangesAllowed(
- block: ConfigurableFileCollection.() -> Unit
-) {
- val disallowChanges = this::class.java.getDeclaredField("disallowChanges")
- disallowChanges.isAccessible = true
- disallowChanges.set(this, false)
- block()
- disallowChanges.set(this, true)
-}
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXExtension.kt b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXExtension.kt
index cdd9add..038ad3c 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXExtension.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXExtension.kt
@@ -410,6 +410,12 @@
override val kotlinBomVersion: Provider<String>
get() = kotlinTarget.map { project.getVersionByName(it.catalogVersion) }
+ /**
+ * Whether to validate the androidx configuration block using validateProjectParser. This
+ * should always be set to true unless we are temporarily working around a bug.
+ */
+ var runProjectParser: Boolean = true
+
companion object {
const val DEFAULT_UNSPECIFIED_VERSION = "unspecified"
}
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt
index 2b8e4e7..8fdbf41 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt
@@ -35,6 +35,7 @@
import androidx.build.testConfiguration.addAppApkToTestConfigGeneration
import androidx.build.testConfiguration.configureTestConfigGeneration
import com.android.build.api.artifact.SingleArtifact
+import com.android.build.api.variant.AndroidComponentsExtension
import com.android.build.api.variant.ApplicationAndroidComponentsExtension
import com.android.build.api.variant.HasAndroidTest
import com.android.build.api.variant.LibraryAndroidComponentsExtension
@@ -124,6 +125,7 @@
}
}
+ project.configureLint()
project.configureKtlint()
project.configureKotlinVersion()
@@ -162,6 +164,7 @@
project.validatePublishedMultiplatformHasDefault()
}
}
+ project.disallowAccidentalAndroidDependenciesInKmpProject(kmpExtension)
}
private fun Project.registerProjectOrArtifact() {
@@ -400,7 +403,6 @@
}
project.configureKmpTests()
project.configureSourceJarForMultiplatform()
- project.configureLintForMultiplatform(extension)
// Disable any source JAR task(s) added by KotlinMultiplatformPlugin.
// https://youtrack.jetbrains.com/issue/KT-55881
@@ -428,13 +430,6 @@
it.configureTests()
it.artRewritingWorkaround()
}
- finalizeDsl {
- project.configureAndroidProjectForLint(
- it.lint,
- androidXExtension,
- isLibrary = false
- )
- }
}
}
@@ -536,9 +531,6 @@
it.configureTests()
it.artRewritingWorkaround()
}
- finalizeDsl {
- project.configureAndroidProjectForLint(it.lint, androidXExtension, isLibrary = true)
- }
}
project.configurePublicResourcesStub(libraryExtension)
@@ -613,10 +605,6 @@
}
}
- // Standard lint, docs, and Metalava configuration for AndroidX projects.
- if (project.multiplatformExtension == null) {
- project.configureNonAndroidProjectForLint(extension)
- }
val apiTaskConfig =
if (project.multiplatformExtension != null) {
KmpApiTaskConfig
@@ -931,13 +919,6 @@
return
}
project.afterEvaluate {
- if (project.hasKotlinNativeTarget().get()) {
- // KMP plugin cannot handle constraints properly for native targets
- // b/274786186, YT: KT-57531
- // It is expected to be fixed in Kotlin 1.9 after which, we should remove this check
- return@afterEvaluate
- }
-
// make sure that the project has a group
val projectGroup = extension.mavenGroup ?: return@afterEvaluate
// make sure that this group is configured to use a single version
@@ -1105,6 +1086,11 @@
}
}
+val Project.androidExtension: AndroidComponentsExtension<*, *, *>
+ get() = extensions.findByType<LibraryAndroidComponentsExtension>()
+ ?: extensions.findByType<ApplicationAndroidComponentsExtension>()
+ ?: throw IllegalArgumentException("Failed to find any registered Android extension")
+
val Project.multiplatformExtension
get() = extensions.findByType(KotlinMultiplatformExtension::class.java)
@@ -1221,11 +1207,38 @@
}
}
+/**
+ * Verifies we don't accidentially write "implementation" instead of "commonMainImplementation"
+ */
+fun Project.disallowAccidentalAndroidDependenciesInKmpProject(
+ kmpExtension: AndroidXMultiplatformExtension
+) {
+ project.afterEvaluate {
+ if (kmpExtension.supportedPlatforms.isNotEmpty()) {
+ val androidConfiguration = project.configurations.findByName("implementation")
+ if (androidConfiguration != null) {
+ if (
+ androidConfiguration.dependencies.isNotEmpty() ||
+ androidConfiguration.dependencyConstraints.isNotEmpty()
+ ) {
+ throw GradleException(
+ "The 'implementation' Configuration should not be used in a " +
+ "multiplatform project: this Configuration is declared by the " +
+ "Android plugin rather than the kmp plugin. Did you mean " +
+ "'commonMainImplementation'?")
+ }
+ }
+ }
+ }
+}
+
/** Verifies that ProjectParser computes the correct values for this project */
fun Project.validateProjectParser(extension: AndroidXExtension) {
// If configuration fails, we don't want to validate the ProjectParser
// (otherwise it could report a confusing, unnecessary error)
project.gradle.taskGraph.whenReady {
+ if (!extension.runProjectParser) return@whenReady
+
val parsed = project.parse()
check(extension.type == parsed.libraryType) {
"ProjectParser incorrectly computed libraryType = ${parsed.libraryType} " +
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXMultiplatformExtension.kt b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXMultiplatformExtension.kt
index 86d3217..a3fd34e 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXMultiplatformExtension.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXMultiplatformExtension.kt
@@ -50,16 +50,14 @@
private val kotlinExtension: KotlinMultiplatformExtension by kotlinExtensionDelegate
/**
- * The list of platforms that have been requested in the build configuration.
+ * The list of platforms that have been declared as supported in the build configuration.
*
- * The list of enabled platforms in [targetPlatforms] will vary based on the build environment.
- * For example, a project's build configuration may have requested `mac()` but this is not
- * available when building on Linux.
+ * This may be a superset of the currently enabled platforms in [targetPlatforms].
*/
- val requestedPlatforms: MutableSet<PlatformIdentifier> = mutableSetOf()
+ val supportedPlatforms: MutableSet<PlatformIdentifier> = mutableSetOf()
/**
- * The list of platforms that are enabled.
+ * The list of platforms that are currently enabled.
*
* This will vary across build environments. For example, a project's build configuration may
* have requested `mac()` but this is not available when building on Linux.
@@ -91,14 +89,14 @@
* identifier for that platform.
*/
var defaultPlatform: String? = null
- get() = field ?: requestedPlatforms.singleOrNull()?.id
+ get() = field ?: supportedPlatforms.singleOrNull()?.id
set(value) {
if (value != null) {
- if (requestedPlatforms.none { it.id == value }) {
+ if (supportedPlatforms.none { it.id == value }) {
throw GradleException(
"Platform $value has not been requested as a target. " +
"Available platforms are: " +
- requestedPlatforms.joinToString(", ") { it.id }
+ supportedPlatforms.joinToString(", ") { it.id }
)
}
if (targetPlatforms.none { it == value }) {
@@ -145,7 +143,7 @@
@JvmOverloads
fun jvm(block: Action<KotlinJvmTarget>? = null): KotlinJvmTarget? {
- requestedPlatforms.add(PlatformIdentifier.JVM)
+ supportedPlatforms.add(PlatformIdentifier.JVM)
return if (project.enableJvm()) {
kotlinExtension.jvm {
block?.execute(this)
@@ -163,7 +161,7 @@
@JvmOverloads
fun android(block: Action<KotlinAndroidTarget>? = null): KotlinAndroidTarget? {
- requestedPlatforms.add(PlatformIdentifier.ANDROID)
+ supportedPlatforms.add(PlatformIdentifier.ANDROID)
return if (project.enableJvm()) {
kotlinExtension.androidTarget { block?.execute(this) }
} else {
@@ -173,7 +171,7 @@
@JvmOverloads
fun desktop(block: Action<KotlinJvmTarget>? = null): KotlinJvmTarget? {
- requestedPlatforms.add(PlatformIdentifier.DESKTOP)
+ supportedPlatforms.add(PlatformIdentifier.DESKTOP)
return if (project.enableDesktop()) {
kotlinExtension.jvm("desktop") { block?.execute(this) }
} else {
@@ -189,7 +187,7 @@
@JvmOverloads
fun macosX64(block: Action<KotlinNativeTarget>? = null): KotlinNativeTargetWithHostTests? {
- requestedPlatforms.add(PlatformIdentifier.MAC_OSX_64)
+ supportedPlatforms.add(PlatformIdentifier.MAC_OSX_64)
return if (project.enableMac()) {
kotlinExtension.macosX64().also { block?.execute(it) }
} else {
@@ -199,7 +197,7 @@
@JvmOverloads
fun macosArm64(block: Action<KotlinNativeTarget>? = null): KotlinNativeTargetWithHostTests? {
- requestedPlatforms.add(PlatformIdentifier.MAC_ARM_64)
+ supportedPlatforms.add(PlatformIdentifier.MAC_ARM_64)
return if (project.enableMac()) {
kotlinExtension.macosArm64().also { block?.execute(it) }
} else {
@@ -209,7 +207,7 @@
@JvmOverloads
fun iosArm64(block: Action<KotlinNativeTarget>? = null): KotlinNativeTarget? {
- requestedPlatforms.add(PlatformIdentifier.IOS_ARM_64)
+ supportedPlatforms.add(PlatformIdentifier.IOS_ARM_64)
return if (project.enableMac()) {
kotlinExtension.iosArm64().also { block?.execute(it) }
} else {
@@ -225,7 +223,7 @@
@JvmOverloads
fun iosX64(block: Action<KotlinNativeTarget>? = null): KotlinNativeTarget? {
- requestedPlatforms.add(PlatformIdentifier.IOS_X_64)
+ supportedPlatforms.add(PlatformIdentifier.IOS_X_64)
return if (project.enableMac()) {
kotlinExtension.iosX64().also { block?.execute(it) }
} else {
@@ -235,7 +233,7 @@
@JvmOverloads
fun iosSimulatorArm64(block: Action<KotlinNativeTarget>? = null): KotlinNativeTarget? {
- requestedPlatforms.add(PlatformIdentifier.IOS_SIMULATOR_ARM_64)
+ supportedPlatforms.add(PlatformIdentifier.IOS_SIMULATOR_ARM_64)
return if (project.enableMac()) {
kotlinExtension.iosSimulatorArm64().also { block?.execute(it) }
} else {
@@ -252,7 +250,7 @@
@JvmOverloads
fun linuxX64(block: Action<KotlinNativeTarget>? = null): KotlinNativeTargetWithHostTests? {
- requestedPlatforms.add(PlatformIdentifier.LINUX_64)
+ supportedPlatforms.add(PlatformIdentifier.LINUX_64)
return if (project.enableLinux()) {
kotlinExtension.linuxX64().also { block?.execute(it) }
} else {
@@ -262,7 +260,7 @@
@JvmOverloads
fun js(block: Action<KotlinJsTargetDsl>? = null): KotlinJsTargetDsl? {
- requestedPlatforms.add(PlatformIdentifier.JS)
+ supportedPlatforms.add(PlatformIdentifier.JS)
return if (project.enableJs()) {
kotlinExtension.js().also { block?.execute(it) }
} else {
@@ -286,7 +284,7 @@
fun Project.validatePublishedMultiplatformHasDefault() {
val extension = project.extensions.getByType(AndroidXMultiplatformExtension::class.java)
- if (extension.defaultPlatform == null && extension.requestedPlatforms.isNotEmpty()) {
+ if (extension.defaultPlatform == null && extension.supportedPlatforms.isNotEmpty()) {
throw GradleException(
"Project is published and multiple platforms are requested. You " +
"must explicitly specify androidXMultiplatform.defaultPlatform as one of: " +
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/LintConfiguration.kt b/buildSrc/private/src/main/kotlin/androidx/build/LintConfiguration.kt
index 8d1ccc2..2552af0 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/LintConfiguration.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/LintConfiguration.kt
@@ -17,123 +17,278 @@
package androidx.build
import com.android.build.api.dsl.Lint
+import com.android.build.gradle.AppPlugin
import com.android.build.gradle.BaseExtension
+import com.android.build.gradle.LibraryPlugin
import com.android.build.gradle.internal.lint.AndroidLintAnalysisTask
import com.android.build.gradle.internal.lint.AndroidLintTask
import com.android.build.gradle.internal.lint.LintModelWriterTask
import com.android.build.gradle.internal.lint.VariantInputs
import java.io.File
-import java.util.Locale
import org.gradle.api.GradleException
import org.gradle.api.Project
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.RegularFileProperty
+import org.gradle.api.plugins.JavaPlugin
import org.gradle.api.plugins.JavaPluginExtension
import org.gradle.kotlin.dsl.findByType
import org.gradle.kotlin.dsl.getByType
import org.gradle.kotlin.dsl.withType
+import org.jetbrains.kotlin.gradle.plugin.KotlinBasePlugin
+import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
+import org.jetbrains.kotlin.tooling.core.withClosure
-fun Project.configureNonAndroidProjectForLint(extension: AndroidXExtension) {
- apply(mapOf("plugin" to "com.android.lint"))
-
- // Create fake variant tasks since that is what is invoked by developers.
- val lintTask = tasks.named("lint")
- tasks.register("lintDebug") {
- it.dependsOn(lintTask)
- it.enabled = false
- }
- tasks.register("lintAnalyzeDebug") { it.enabled = false }
- tasks.register("lintRelease") {
- it.dependsOn(lintTask)
- it.enabled = false
- }
- addToBuildOnServer(lintTask)
-
- val lint = extensions.getByType<Lint>()
- // Support the lint standalone plugin case which, as yet, lacks AndroidComponents finalizeDsl
- afterEvaluate { configureLint(lint, extension, true) }
-}
-
-fun Project.configureAndroidProjectForLint(
- lint: Lint,
- extension: AndroidXExtension,
- isLibrary: Boolean
-) {
- project.afterEvaluate {
- // makes sure that the lintDebug task will exist, so we can find it by name
- setUpLintDebugIfNeeded()
- }
- tasks.register("lintAnalyze") { it.enabled = false }
- configureLint(lint, extension, isLibrary)
- tasks.named("lint").configure { task ->
- // We already run lintDebug, we don't need to run lint which lints the release variant
- task.enabled = false
- }
-}
-
-private fun Project.setUpLintDebugIfNeeded() {
- val variants = project.agpVariants
- val variantNames = variants.map { v -> v.name }
- if (!variantNames.contains("debug")) {
- tasks.register("lintDebug") {
- for (variantName in variantNames) {
- if (variantName.lowercase(Locale.US).contains("debug")) {
- it.dependsOn(
- tasks.named(
- "lint${variantName.replaceFirstChar {
- if (it.isLowerCase()) it.titlecase(Locale.US) else it.toString()
- }}"
- )
- )
- }
+/**
+ * Single entry point to Android Lint configuration.
+ */
+fun Project.configureLint() {
+ project.plugins.all { plugin ->
+ when (plugin) {
+ is AppPlugin -> configureAndroidProjectForLint(isLibrary = false)
+ is LibraryPlugin -> configureAndroidProjectForLint(isLibrary = true)
+ // Only configure non-multiplatform Java projects via JavaPlugin. Multiplatform
+ // projects targeting Java (e.g. `jvm { withJava() }`) are configured via
+ // KotlinBasePlugin.
+ is JavaPlugin -> if (project.multiplatformExtension == null) {
+ configureNonAndroidProjectForLint()
+ }
+ // Only configure non-Android multiplatform projects via KotlinBasePlugin.
+ // Multiplatform projects targeting Android (e.g. `id("com.android.library")`) are
+ // configured via AppPlugin or LibraryPlugin.
+ is KotlinBasePlugin -> if (
+ project.multiplatformExtension != null &&
+ !project.plugins.hasPlugin(AppPlugin::class.java) &&
+ !project.plugins.hasPlugin(LibraryPlugin::class.java)
+ ) {
+ configureNonAndroidProjectForLint()
}
}
}
}
/**
- * Installs AIDL source directories on lint tasks. Adapted from AndroidXComposeImplPlugin's
- * `configureLintForMultiplatformLibrary` extension function. See b/189250111 for feature request.
+ * Android Lint configuration entry point for Android projects.
+ */
+private fun Project.configureAndroidProjectForLint(
+ isLibrary: Boolean
+) = androidExtension.finalizeDsl { extension ->
+ // The lintAnalyze task is used by `androidx-studio-integration-lint.sh`.
+ tasks.register("lintAnalyze") { task -> task.enabled = false }
+
+ configureLint(extension.lint, isLibrary)
+
+ // We already run lintDebug, we don't need to run lint on the release variant.
+ tasks.named("lint").configure { task -> task.enabled = false }
+
+ afterEvaluate {
+ registerLintDebugIfNeededAfterEvaluate()
+
+ if (extension.buildFeatures.aidl == true) {
+ configureLintForAidlAfterEvaluate()
+ }
+ }
+}
+
+/**
+ * Android Lint configuration entry point for non-Android projects.
+ */
+private fun Project.configureNonAndroidProjectForLint() = afterEvaluate {
+ // The lint plugin expects certain configurations and source sets which are only added by
+ // the Java and Android plugins. If this is a multiplatform project targeting JVM, we'll
+ // need to manually create these configurations and source sets based on their multiplatform
+ // JVM equivalents.
+ addSourceSetsForMultiplatformAfterEvaluate()
+
+ // For Android projects, the Android Gradle Plugin is responsible for applying the lint plugin;
+ // however, we need to apply it ourselves for non-Android projects.
+ apply(mapOf("plugin" to "com.android.lint"))
+
+ // Create task aliases matching those creates by AGP for Android projects, since those are what
+ // developers expect to invoke. Redirect them to the "real" lint task.
+ val lintTask = tasks.named("lint")
+ tasks.register("lintDebug") {
+ it.dependsOn(lintTask)
+ it.enabled = false
+ }
+ tasks.register("lintRelease") {
+ it.dependsOn(lintTask)
+ it.enabled = false
+ }
+
+ // The lintAnalyzeDebug task is used by `androidx-studio-integration-lint.sh`.
+ tasks.register("lintAnalyzeDebug") { it.enabled = false }
+
+ addToBuildOnServer(lintTask)
+
+ // For Android projects, we can run lint configuration last using `DslLifecycle.finalizeDsl`;
+ // however, we need to run it using `Project.afterEvaluate` for non-Android projects.
+ configureLint(project.extensions.getByType(), isLibrary = true)
+}
+
+/**
+ * Registers the `lintDebug` task if there are debug variants present.
+ *
+ * This method *must* run after evaluation.
+ */
+private fun Project.registerLintDebugIfNeededAfterEvaluate() {
+ val variantNames = project.agpVariants.map { it.name }
+ if (!variantNames.contains("debug")) {
+ tasks.register("lintDebug") { task ->
+ // The lintDebug tasks depends on lint tasks for all debug variants.
+ variantNames
+ .filter { it.contains("debug", ignoreCase = true) }
+ .map { tasks.named("lint${it.camelCase()}") }
+ .forEach { task.dependsOn(it) }
+ }
+ }
+}
+
+private fun String.camelCase() =
+ replaceFirstChar { if (it.isLowerCase()) it.titlecase() else it.toString() }
+
+/**
+ * If the project is targeting Android and using the AIDL build feature, installs AIDL source
+ * directories on lint tasks.
+ *
+ * Adapted from AndroidXComposeImplPlugin's `configureLintForMultiplatformLibrary` extension
+ * function. See b/189250111 for AGP feature request.
*
* The `UnstableAidlAnnotationDetector` check from `lint-checks` requires that _only_ unstable AIDL
* files are passed to Lint, e.g. files in the AGP-defined `aidl` source set but not files in the
* Stable AIDL plugin-defined `stableAidl` source set. If we decide to lint Stable AIDL files, we'll
* need some other way to distinguish stable from unstable AIDL.
+ *
+ * This method *must* run after evaluation.
*/
-fun Project.configureLintForAidl() {
- afterEvaluate {
- val extension = project.extensions.findByType<BaseExtension>() ?: return@afterEvaluate
- if (extension.buildFeatures.aidl != true) return@afterEvaluate
+private fun Project.configureLintForAidlAfterEvaluate() {
+ // BaseExtension needed to access resolved source files on `aidl`.
+ val extension = project.extensions.findByType<BaseExtension>() ?: return
+ val mainAidl = extension.sourceSets.getByName("main").aidl.getSourceFiles()
- val mainAidl = extension.sourceSets.getByName("main").aidl.getSourceFiles()
+ /** Helper function to add the missing sourcesets to this [VariantInputs] */
+ fun VariantInputs.addSourceSets() {
+ // Each variant has a source provider for the variant (such as debug) and the 'main'
+ // variant. The actual files that Lint will run on is both of these providers
+ // combined - so we can just add the dependencies to the first we see.
+ val variantAidl = extension.sourceSets.getByName(name.get()).aidl.getSourceFiles()
+ val sourceProvider = sourceProviders.get().firstOrNull() ?: return
+ sourceProvider.javaDirectories.withChangesAllowed { from(mainAidl, variantAidl) }
+ }
- /** Helper function to add the missing sourcesets to this [VariantInputs] */
- fun VariantInputs.addSourceSets() {
- // Each variant has a source provider for the variant (such as debug) and the 'main'
- // variant. The actual files that Lint will run on is both of these providers
- // combined - so we can just add the dependencies to the first we see.
- val variantAidl = extension.sourceSets.getByName(name.get()).aidl.getSourceFiles()
- val sourceProvider = sourceProviders.get().firstOrNull() ?: return
- sourceProvider.javaDirectories.withChangesAllowed { from(mainAidl, variantAidl) }
+ // Lint for libraries is split into two tasks - analysis, and reporting. We need to
+ // add the new sources to both, so all parts of the pipeline are aware.
+ project.tasks.withType<AndroidLintAnalysisTask>().configureEach {
+ it.variantInputs.addSourceSets()
+ }
+
+ project.tasks.withType<AndroidLintTask>().configureEach {
+ it.variantInputs.addSourceSets()
+ }
+
+ // Also configure the model writing task, so that we don't run into mismatches between
+ // analyzed sources in one module and a downstream module
+ project.tasks.withType<LintModelWriterTask>().configureEach {
+ it.variantInputs.addSourceSets()
+ }
+}
+
+/**
+ * If the project is using multiplatform, adds configurations and source sets expected by the lint
+ * plugin, which allows it to configure itself when running against a non-Android multiplatform
+ * project.
+ *
+ * The version of lint that we're using does not directly support Kotlin multiplatform, but we can
+ * synthesize the necessary configurations and source sets from existing `jvm` configurations and
+ * `kotlinSourceSets`, respectively.
+ *
+ * This method *must* run after evaluation.
+ */
+private fun Project.addSourceSetsForMultiplatformAfterEvaluate() {
+ val kmpTargets = project.multiplatformExtension?.targets ?: return
+
+ // Synthesize target configurations based on multiplatform configurations.
+ val kmpApiElements = kmpTargets.map { it.apiElementsConfigurationName }
+ val kmpRuntimeElements = kmpTargets.map { it.runtimeElementsConfigurationName }
+ listOf(
+ kmpRuntimeElements to "runtimeElements",
+ kmpApiElements to "apiElements"
+ ).forEach { (kmpConfigNames, targetConfigName) ->
+ project.configurations.maybeCreate(targetConfigName).apply {
+ kmpConfigNames
+ .mapNotNull { configName -> project.configurations.findByName(configName) }
+ .forEach { config -> extendsFrom(config) }
}
+ }
- // Lint for libraries is split into two tasks - analysis, and reporting. We need to
- // add the new sources to both, so all parts of the pipeline are aware.
- project.tasks.withType<AndroidLintAnalysisTask>().configureEach {
- it.variantInputs.addSourceSets()
- }
-
- project.tasks.withType<AndroidLintTask>().configureEach { it.variantInputs.addSourceSets() }
-
- // Also configure the model writing task, so that we don't run into mismatches between
- // analyzed sources in one module and a downstream module
- project.tasks.withType<LintModelWriterTask>().configureEach {
- it.variantInputs.addSourceSets()
+ // Synthesize source sets based on multiplatform source sets.
+ val javaExtension = project.extensions.findByType(JavaPluginExtension::class.java)
+ ?: throw GradleException("Failed to find extension of type 'JavaPluginExtension'")
+ listOf(
+ "main" to "main",
+ "test" to "test"
+ ).forEach { (kmpCompilationName, targetSourceSetName) ->
+ javaExtension.sourceSets.maybeCreate(targetSourceSetName).apply {
+ kmpTargets
+ .mapNotNull { target -> target.compilations.findByName(kmpCompilationName) }
+ .flatMap { compilation -> compilation.kotlinSourceSets }
+ .flatMap { sourceSet -> sourceSet.kotlin.srcDirs }
+ .forEach { srcDirs -> java.srcDirs += srcDirs }
}
}
}
-fun Project.configureLint(lint: Lint, extension: AndroidXExtension, isLibrary: Boolean) {
+/**
+ * If the project is using multiplatform targeted to Android, adds source sets directly to lint
+ * tasks, which allows it to run against Android multiplatform projects.
+ *
+ * Lint is not aware of MPP, and MPP doesn't configure Lint. There is no built-in API to adjust the
+ * default Lint task's sources, so we use this hack to manually add sources for MPP source sets. In
+ * the future, with the new Kotlin Project Model (https://youtrack.jetbrains.com/issue/KT-42572) and
+ * an AGP / MPP integration plugin, this will no longer be needed. See also b/195329463.
+ */
+private fun Project.addSourceSetsForAndroidMultiplatformAfterEvaluate() {
+ val multiplatformExtension = project.multiplatformExtension ?: return
+ multiplatformExtension.targets.findByName("android") ?: return
+
+ val androidMain = multiplatformExtension.sourceSets.findByName("androidMain")
+ ?: throw GradleException("Failed to find source set with name 'androidMain'")
+
+ // Get all the source sets androidMain transitively / directly depends on.
+ val dependencySourceSets = androidMain.withClosure(KotlinSourceSet::dependsOn)
+
+ /**
+ * Helper function to add the missing sourcesets to this [VariantInputs]
+ */
+ fun VariantInputs.addSourceSets() {
+ // Each variant has a source provider for the variant (such as debug) and the 'main'
+ // variant. The actual files that Lint will run on is both of these providers
+ // combined - so we can just add the dependencies to the first we see.
+ val sourceProvider = sourceProviders.get().firstOrNull() ?: return
+ dependencySourceSets.forEach { sourceSet ->
+ sourceProvider.javaDirectories.withChangesAllowed {
+ from(sourceSet.kotlin.sourceDirectories)
+ }
+ }
+ }
+
+ // Lint for libraries is split into two tasks - analysis, and reporting. We need to
+ // add the new sources to both, so all parts of the pipeline are aware.
+ project.tasks.withType<AndroidLintAnalysisTask>().configureEach {
+ it.variantInputs.addSourceSets()
+ }
+
+ project.tasks.withType<AndroidLintTask>().configureEach { it.variantInputs.addSourceSets() }
+
+ // Also configure the model writing task, so that we don't run into mismatches between
+ // analyzed sources in one module and a downstream module
+ project.tasks.withType<LintModelWriterTask>().configureEach {
+ it.variantInputs.addSourceSets()
+ }
+}
+
+private fun Project.configureLint(lint: Lint, isLibrary: Boolean) {
+ val extension = project.androidXExtension
+ val isMultiplatform = project.multiplatformExtension != null
val lintChecksProject =
project.rootProject.findProject(":lint-checks")
?: if (allowMissingLintProject()) {
@@ -144,7 +299,9 @@
project.dependencies.add("lintChecks", lintChecksProject)
- project.configureLintForAidl()
+ afterEvaluate {
+ addSourceSetsForAndroidMultiplatformAfterEvaluate()
+ }
// The purpose of this specific project is to test that lint is running, so
// it contains expected violations that we do not want to trigger a build failure
@@ -198,6 +355,13 @@
fatal.add("VisibleForTests")
}
+ if (isMultiplatform) {
+ // Disable classfile-based checks because lint cannot find the class files for
+ // multiplatform projects and `SourceSet.java.classesDirectory` is not configurable.
+ // This is not ideal, but it's better than having no lint checks at all.
+ disable.add("LintError")
+ }
+
// Reenable after b/238892319 is resolved
disable.add("NotificationPermission")
@@ -291,46 +455,6 @@
}
/**
- * Lint on multiplatform projects is only applied to Java code and android source sets. To force it
- * to run on JVM code, we add the java source sets that lint looks for, but use the sources
- * directories of the JVM source sets if they exist.
- */
-fun Project.configureLintForMultiplatform(extension: AndroidXExtension) = afterEvaluate {
- // if lint has been applied through some other mechanism, this step is unnecessary
- runCatching { project.tasks.named("lint") }
- .onSuccess {
- return@afterEvaluate
- }
- val jvmTarget =
- project.multiplatformExtension?.targets?.findByName("jvm") ?: return@afterEvaluate
- val runtimeConfiguration =
- project.configurations.findByName("jvmRuntimeElements") ?: return@afterEvaluate
- val apiConfiguration =
- project.configurations.findByName("jvmApiElements") ?: return@afterEvaluate
- val javaExtension =
- project.extensions.findByType(JavaPluginExtension::class.java) ?: return@afterEvaluate
- project.configurations.maybeCreate("runtimeElements").apply {
- extendsFrom(runtimeConfiguration)
- }
- project.configurations.maybeCreate("apiElements").apply { extendsFrom(apiConfiguration) }
- val mainSourceSets = jvmTarget.compilations.getByName("main").kotlinSourceSets
- val testSourceSets = jvmTarget.compilations.getByName("test").kotlinSourceSets
- javaExtension.sourceSets.maybeCreate("main").apply {
- java.setSrcDirs(mainSourceSets.flatMap { it.kotlin.srcDirs })
- java.classesDirectory
- }
- javaExtension.sourceSets.maybeCreate("test").apply {
- java.srcDirs.addAll(testSourceSets.flatMap { it.kotlin.srcDirs })
- }
- project.configureNonAndroidProjectForLint(extension)
-
- // Disable classfile based checks because lint cannot find the classfiles for multiplatform
- // projects, and SourceSet.java.classesDirectory is not configurable. This is not ideal, but
- // better than having no lint checks at all.
- extensions.getByType<Lint>().disable.add("LintError")
-}
-
-/**
* Lint uses [ConfigurableFileCollection.disallowChanges] during initialization, which prevents
* modifying the file collection separately (there is no time to configure it before AGP has
* initialized and disallowed changes). This uses reflection to temporarily allow changes, and apply
@@ -346,5 +470,5 @@
disallowChanges.set(this, true)
}
-val Project.lintBaseline: RegularFileProperty
- get() = project.objects.fileProperty().fileValue(File(projectDir, "/lint-baseline.xml"))
+private val Project.lintBaseline: RegularFileProperty
+ get() = project.objects.fileProperty().fileValue(File(projectDir, "lint-baseline.xml"))
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/Release.kt b/buildSrc/private/src/main/kotlin/androidx/build/Release.kt
index 8aab95b..76a41ec 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/Release.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/Release.kt
@@ -259,7 +259,9 @@
onConfigure = { archiveTask: VerifyVersionFilesTask ->
archiveTask.group = "Distribution"
archiveTask.description = "Builds all archives for publishing"
- archiveTask.repositoryDirectory = project.rootProject.getRepositoryDirectory()
+ archiveTask.repositoryDirectory.set(
+ project.rootProject.getRepositoryDirectory()
+ )
},
onRegister = {}
)
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/VerifyVersionFilesTask.kt b/buildSrc/private/src/main/kotlin/androidx/build/VerifyVersionFilesTask.kt
index f2f6a46..56fd8f0 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/VerifyVersionFilesTask.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/VerifyVersionFilesTask.kt
@@ -16,11 +16,11 @@
package androidx.build
-import java.io.File
import java.io.FileInputStream
import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream
import org.gradle.api.DefaultTask
+import org.gradle.api.file.DirectoryProperty
import org.gradle.api.tasks.CacheableTask
import org.gradle.api.tasks.InputDirectory
import org.gradle.api.tasks.PathSensitive
@@ -29,13 +29,13 @@
/** Task for verifying version files in Androidx artifacts */
@CacheableTask
-open class VerifyVersionFilesTask : DefaultTask() {
-
- @InputDirectory @PathSensitive(PathSensitivity.RELATIVE) lateinit var repositoryDirectory: File
+abstract class VerifyVersionFilesTask : DefaultTask() {
+ @get:[InputDirectory PathSensitive(PathSensitivity.RELATIVE)]
+ abstract val repositoryDirectory: DirectoryProperty
@TaskAction
fun verifyVersionFilesPresent() {
- repositoryDirectory.walk().forEach { file ->
+ repositoryDirectory.asFile.get().walk().forEach { file ->
var expectedPrefix = "androidx"
if (file.path.contains("/libyuv/"))
expectedPrefix = "libyuv_libyuv" // external library that we don't publish
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/dependencyTracker/AffectedModuleDetector.kt b/buildSrc/private/src/main/kotlin/androidx/build/dependencyTracker/AffectedModuleDetector.kt
index 9265e5c..724504f 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/dependencyTracker/AffectedModuleDetector.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/dependencyTracker/AffectedModuleDetector.kt
@@ -566,10 +566,6 @@
":media2:media2-session:version-compat-tests:service",
":media2:media2-session:version-compat-tests:client-previous",
":media2:media2-session:version-compat-tests:service-previous"
- ), // Link graphics and material to always run @Large in presubmit per b/160624022
- setOf(
- ":compose:ui:ui-graphics",
- ":compose:material:material"
), // Link material and material-ripple
setOf(":compose:material:material-ripple", ":compose:material:material"),
setOf(
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/resources/CheckResourceApiReleaseTask.kt b/buildSrc/private/src/main/kotlin/androidx/build/resources/CheckResourceApiReleaseTask.kt
index 99897c9..683e597 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/resources/CheckResourceApiReleaseTask.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/resources/CheckResourceApiReleaseTask.kt
@@ -20,9 +20,9 @@
import java.io.File
import org.gradle.api.DefaultTask
import org.gradle.api.GradleException
+import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.CacheableTask
-import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.PathSensitive
@@ -33,9 +33,9 @@
@CacheableTask
abstract class CheckResourceApiReleaseTask : DefaultTask() {
/** Reference resource API file (in source control). */
- @get:InputFile
+ @get:InputFiles // InputFiles allows non-existent files, whereas InputFile does not.
@get:PathSensitive(PathSensitivity.RELATIVE)
- abstract val referenceApiFile: Property<File>
+ abstract val referenceApiFile: RegularFileProperty
/** Generated resource API file (in build output). */
@get:Internal abstract val apiLocation: Property<ApiLocation>
@@ -48,7 +48,7 @@
@TaskAction
fun checkResourceApiRelease() {
- val referenceApiFile = referenceApiFile.get()
+ val referenceApiFile = referenceApiFile.get().asFile
val apiFile = apiLocation.get().resourceFile
// Read the current API surface, if any, into memory.
@@ -60,7 +60,12 @@
}
// Read the reference API surface into memory.
- val referenceApiSet = referenceApiFile.readLines().toSet()
+ val referenceApiSet =
+ if (referenceApiFile.exists()) {
+ referenceApiFile.readLines().toSet()
+ } else {
+ emptySet()
+ }
// POLICY: Ensure that no resources are removed from the last released version.
val removedApiSet = referenceApiSet - newApiSet
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/testConfiguration/AndroidTestConfigBuilder.kt b/buildSrc/private/src/main/kotlin/androidx/build/testConfiguration/AndroidTestConfigBuilder.kt
index e33eaf4..657f702 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/testConfiguration/AndroidTestConfigBuilder.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/testConfiguration/AndroidTestConfigBuilder.kt
@@ -23,7 +23,7 @@
var appApkName: String? = null
var appApkSha256: String? = null
lateinit var applicationId: String
- var isBenchmark: Boolean = false
+ var isMicrobenchmark: Boolean = false
var isPostsubmit: Boolean = true
lateinit var minSdk: String
val tags = mutableListOf<String>()
@@ -40,7 +40,8 @@
fun applicationId(applicationId: String) = apply { this.applicationId = applicationId }
- fun isBenchmark(isBenchmark: Boolean) = apply { this.isBenchmark = isBenchmark }
+ fun isMicrobenchmark(isMicrobenchmark: Boolean) =
+ apply { this.isMicrobenchmark = isMicrobenchmark }
fun isPostsubmit(isPostsubmit: Boolean) = apply { this.isPostsubmit = isPostsubmit }
@@ -59,7 +60,7 @@
fun buildJson(): String {
val gson = GsonBuilder().setPrettyPrinting().create()
val instrumentationArgs =
- if (isBenchmark && !isPostsubmit) {
+ if (isMicrobenchmark && !isPostsubmit) {
listOf(
InstrumentationArg("notAnnotation", "androidx.test.filters.FlakyTest"),
InstrumentationArg("androidx.benchmark.dryRunMode.enable", "true"),
@@ -91,12 +92,11 @@
sb.append(MODULE_METADATA_TAG_OPTION.replace("APPLICATION_ID", applicationId))
.append(WIFI_DISABLE_OPTION)
.append(FLAKY_TEST_OPTION)
- if (isBenchmark) {
- sb.append(benchmarkPostInstallCommandOption(applicationId))
+ if (isMicrobenchmark) {
if (isPostsubmit) {
- sb.append(BENCHMARK_POSTSUBMIT_OPTIONS)
+ sb.append(MICROBENCHMARK_POSTSUBMIT_OPTIONS)
} else {
- sb.append(BENCHMARK_PRESUBMIT_OPTION)
+ sb.append(MICROBENCHMARK_PRESUBMIT_OPTION)
}
}
sb.append(SETUP_INCLUDE)
@@ -105,7 +105,11 @@
if (!appApkName.isNullOrEmpty())
sb.append(APK_INSTALL_OPTION.replace("APK_NAME", appApkName!!))
sb.append(TARGET_PREPARER_CLOSE)
- .append(TEST_BLOCK_OPEN)
+ // Post install commands after SuiteApkInstaller is declared
+ if (isMicrobenchmark) {
+ sb.append(benchmarkPostInstallCommandOption(applicationId))
+ }
+ sb.append(TEST_BLOCK_OPEN)
.append(RUNNER_OPTION.replace("TEST_RUNNER", testRunner))
.append(PACKAGE_OPTION.replace("APPLICATION_ID", applicationId))
.append(TEST_BLOCK_CLOSE)
@@ -309,16 +313,15 @@
"""
.trimIndent()
-private val BENCHMARK_PRESUBMIT_OPTION =
+private val MICROBENCHMARK_PRESUBMIT_OPTION =
"""
<option name="instrumentation-arg" key="androidx.benchmark.dryRunMode.enable" value="true" />
"""
.trimIndent()
-private val BENCHMARK_POSTSUBMIT_OPTIONS =
+private val MICROBENCHMARK_POSTSUBMIT_OPTIONS =
"""
- <option name="instrumentation-arg" key="androidx.benchmark.output.enable" value="true" />
<option name="instrumentation-arg" key="listener" value="androidx.benchmark.junit4.InstrumentationResultsRunListener" />
"""
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/testConfiguration/GenerateTestConfigurationTask.kt b/buildSrc/private/src/main/kotlin/androidx/build/testConfiguration/GenerateTestConfigurationTask.kt
index 87b7114..d428c0f 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/testConfiguration/GenerateTestConfigurationTask.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/testConfiguration/GenerateTestConfigurationTask.kt
@@ -134,10 +134,14 @@
configBuilder.isPostsubmit(!isPresubmit)
// This section adds metadata tags that will help filter runners to specific modules.
if (hasBenchmarkPlugin.get()) {
- configBuilder.isBenchmark(true)
- if (configBuilder.isPostsubmit) {
- configBuilder.tag("microbenchmarks")
- } else {
+ configBuilder.isMicrobenchmark(true)
+
+ // tag microbenchmarks as "microbenchmarks" in either build config, so that benchmark
+ // test configs will always have something to run, regardless of build (though presubmit
+ // builds will still set dry run, and not output metrics)
+ configBuilder.tag("microbenchmarks")
+
+ if (isPresubmit) {
// in presubmit, we treat micro benchmarks as regular correctness tests as
// they run with dryRunMode to check crashes don't happen, without measurement
configBuilder.tag("androidx_unit_tests")
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/testConfiguration/OwnersService.kt b/buildSrc/private/src/main/kotlin/androidx/build/testConfiguration/OwnersService.kt
index 56384dd..10a9cc1 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/testConfiguration/OwnersService.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/testConfiguration/OwnersService.kt
@@ -38,7 +38,13 @@
@Input
fun getSerialized(): String {
val gson = GsonBuilder().setPrettyPrinting().create()
- return gson.toJson(testModules.associateBy { it.name })
+ // media service/client tests are created from multiple projects, so we get multiple
+ // entries with the same TestModule.name. This code merges all the TestModule.path entries
+ // across the test modules with the same name.
+ val data = testModules.groupBy { it.name }.map {
+ TestModule(name = it.key, path = it.value.flatMap { module -> module.path })
+ }.associateBy { it.name }
+ return gson.toJson(data)
}
@TaskAction
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/testConfiguration/TestSuiteConfiguration.kt b/buildSrc/private/src/main/kotlin/androidx/build/testConfiguration/TestSuiteConfiguration.kt
index abc2099..ce67a56 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/testConfiguration/TestSuiteConfiguration.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/testConfiguration/TestSuiteConfiguration.kt
@@ -60,8 +60,7 @@
) {
val xmlName = "${path.asFilenamePrefix()}$variantName.xml"
val jsonName = "_${path.asFilenamePrefix()}$variantName.json"
- rootProject.tasks.named("createModuleInfo").configure {
- it as ModuleInfoGenerator
+ rootProject.tasks.named<ModuleInfoGenerator>("createModuleInfo").configure {
it.testModules.add(
TestModule(
name = xmlName,
@@ -284,10 +283,57 @@
) {
val mediaPrefix = getMediaConfigTaskPrefix(isMedia2)
val mediaTask = getOrCreateMediaTestConfigTask(this, isMedia2)
+
+ fun getJsonName(clientToT: Boolean, serviceToT: Boolean, clientTests: Boolean): String {
+ return "_${mediaPrefix}Client${
+ if (clientToT) "ToT" else "Previous"
+ }Service${
+ if (serviceToT) "ToT" else "Previous"
+ }${
+ if (clientTests) "Client" else "Service"
+ }Tests$variantName.json"
+ }
+
+ fun ModuleInfoGenerator.addTestModule(clientToT: Boolean, serviceToT: Boolean) {
+ // We don't test the combination of previous versions of service and client as that is not
+ // useful data. We always want at least one tip of tree project.
+ if (!clientToT && !serviceToT) return
+ testModules.add(
+ TestModule(
+ name = getJsonName(
+ clientToT = clientToT,
+ serviceToT = serviceToT,
+ clientTests = true
+ ),
+ path = listOf(projectDir.toRelativeString(getSupportRootFolder()))
+ )
+ )
+ testModules.add(
+ TestModule(
+ name = getJsonName(
+ clientToT = clientToT,
+ serviceToT = serviceToT,
+ clientTests = false
+ ),
+ path = listOf(projectDir.toRelativeString(getSupportRootFolder()))
+ )
+ )
+ }
+ val isClient = this.name.contains("client")
+ val isPrevious = this.name.contains("previous")
+
+ rootProject.tasks.named<ModuleInfoGenerator>("createModuleInfo").configure {
+ if (isClient) {
+ it.addTestModule(clientToT = !isPrevious, serviceToT = false)
+ it.addTestModule(clientToT = !isPrevious, serviceToT = true)
+ } else {
+ it.addTestModule(clientToT = true, serviceToT = !isPrevious)
+ it.addTestModule(clientToT = false, serviceToT = !isPrevious)
+ }
+ }
mediaTask.configure {
- it as GenerateMediaTestConfigurationTask
- if (this.name.contains("client")) {
- if (this.name.contains("previous")) {
+ if (isClient) {
+ if (isPrevious) {
it.clientPreviousFolder.set(artifacts.get(SingleArtifact.APK))
it.clientPreviousLoader.set(artifacts.getBuiltArtifactsLoader())
} else {
@@ -295,7 +341,7 @@
it.clientToTLoader.set(artifacts.getBuiltArtifactsLoader())
}
} else {
- if (this.name.contains("previous")) {
+ if (isPrevious) {
it.servicePreviousFolder.set(artifacts.get(SingleArtifact.APK))
it.servicePreviousLoader.set(artifacts.getBuiltArtifactsLoader())
} else {
@@ -305,32 +351,32 @@
}
it.jsonClientPreviousServiceToTClientTests.set(
getFileInTestConfigDirectory(
- "_${mediaPrefix}ClientPreviousServiceToTClientTests$variantName.json"
+ getJsonName(clientToT = false, serviceToT = true, clientTests = true)
)
)
it.jsonClientPreviousServiceToTServiceTests.set(
getFileInTestConfigDirectory(
- "_${mediaPrefix}ClientPreviousServiceToTServiceTests$variantName.json"
+ getJsonName(clientToT = false, serviceToT = true, clientTests = false)
)
)
it.jsonClientToTServicePreviousClientTests.set(
getFileInTestConfigDirectory(
- "_${mediaPrefix}ClientToTServicePreviousClientTests$variantName.json"
+ getJsonName(clientToT = true, serviceToT = false, clientTests = true)
)
)
it.jsonClientToTServicePreviousServiceTests.set(
getFileInTestConfigDirectory(
- "_${mediaPrefix}ClientToTServicePreviousServiceTests$variantName.json"
+ getJsonName(clientToT = true, serviceToT = false, clientTests = false)
)
)
it.jsonClientToTServiceToTClientTests.set(
getFileInTestConfigDirectory(
- "_${mediaPrefix}ClientToTServiceToTClientTests$variantName.json"
+ getJsonName(clientToT = true, serviceToT = true, clientTests = true)
)
)
it.jsonClientToTServiceToTServiceTests.set(
getFileInTestConfigDirectory(
- "_${mediaPrefix}ClientToTServiceToTServiceTests$variantName.json"
+ getJsonName(clientToT = true, serviceToT = true, clientTests = false)
)
)
it.totClientApk.set(getFileInTestConfigDirectory("${mediaPrefix}ClientToT$variantName.apk"))
diff --git a/buildSrc/repos.gradle b/buildSrc/repos.gradle
index 4ae93fa..9b5077d 100644
--- a/buildSrc/repos.gradle
+++ b/buildSrc/repos.gradle
@@ -65,13 +65,6 @@
url("https://maven.pkg.jetbrains.space/public/p/compose/dev")
}
handler.mavenLocal()
- // TODO(b/280646217): Remove after official release to gmaven.
- handler.maven {
- url("https://storage.googleapis.com/r8-releases/raw")
- content {
- includeModule("com.android.tools", "r8")
- }
- }
}
// Ordering appears to be important: b/229733266
def androidPluginRepoOverride = System.getenv("GRADLE_PLUGIN_REPO")
diff --git a/busytown/androidx_incremental.sh b/busytown/androidx_incremental.sh
index f83a1a9..481f9d4 100755
--- a/busytown/androidx_incremental.sh
+++ b/busytown/androidx_incremental.sh
@@ -46,7 +46,13 @@
# reproducible
DIAGNOSE_ARG=""
if [ "$PRESUBMIT" == "false" ]; then
- DIAGNOSE_ARG="--diagnose"
+ if [ "$BUILD_NUMBER" == "" ]; then
+ # This is a local build so we can diagnose without a timeout. The user can cancel it when they're satisfied.
+ DIAGNOSE_ARG="--diagnose"
+ else
+ # This is running on the build server so we should not spend long trying to diagnose it
+ DIAGNOSE_ARG="--diagnose --diagnose-timeout 600"
+ fi
fi
EXIT_VALUE=0
diff --git a/busytown/impl/build.sh b/busytown/impl/build.sh
index 3bb3317..3b10921 100755
--- a/busytown/impl/build.sh
+++ b/busytown/impl/build.sh
@@ -32,6 +32,13 @@
else
DIAGNOSE=false
fi
+if [ "$1" == "--diagnose-timeout" ]; then
+ shift
+ DIAGNOSE_TIMEOUT_ARG="--timeout $1"
+ shift
+else
+ DIAGNOSE_TIMEOUT_ARG=""
+fi
# record the build start time
BUILD_START_MARKER="$OUT_DIR/build.sh.start"
@@ -119,11 +126,7 @@
# We probably won't have enough time to fully diagnose the problem given this timeout, but
# we might be able to determine whether this problem is reproducible enough for a developer to
# more easily investigate further
- ./development/diagnose-build-failure/diagnose-build-failure.sh --timeout 600 "--ci $*" || true
- # Temporary workaround for b/291623531
- # If the failure was interesting enough to try to diagnose, we also try to fix it
- # Delete a specific cache file if it is older than this script (build.sh)
- rm -f $OUT_DIR/gradle-project-cache/8.0/executionHistory/executionHistory.bin
+ ./development/diagnose-build-failure/diagnose-build-failure.sh $DIAGNOSE_TIMEOUT_ARG "--ci $*"
fi
fi
BUILD_STATUS=1 # failure
diff --git a/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/testing/TestUseCaseCamera.kt b/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/testing/TestUseCaseCamera.kt
index 8097e90..3a304c6 100644
--- a/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/testing/TestUseCaseCamera.kt
+++ b/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/testing/TestUseCaseCamera.kt
@@ -104,9 +104,7 @@
sessionConfigAdapter,
CameraStateAdapter(),
cameraGraph,
- streamConfigMap,
- callbackMap,
- requestListener,
+ streamConfigMap
).provideUseCaseGraphConfig(
useCaseSurfaceManager = useCaseSurfaceManager,
cameraInteropStateCallbackRepository = CameraInteropStateCallbackRepository()
@@ -154,6 +152,7 @@
}
override var runningUseCases = useCases.toSet()
+
override fun <T> setParameterAsync(
key: CaptureRequest.Key<T>,
value: T,
diff --git a/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/testing/VerifyResultListener.kt b/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/testing/VerifyResultListener.kt
index 46a90fc..87327a7 100644
--- a/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/testing/VerifyResultListener.kt
+++ b/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/testing/VerifyResultListener.kt
@@ -16,11 +16,11 @@
package androidx.camera.camera2.pipe.testing
-import android.hardware.camera2.CaptureFailure
import androidx.annotation.RequiresApi
import androidx.camera.camera2.pipe.FrameInfo
import androidx.camera.camera2.pipe.FrameNumber
import androidx.camera.camera2.pipe.Request
+import androidx.camera.camera2.pipe.RequestFailure
import androidx.camera.camera2.pipe.RequestMetadata
import java.util.concurrent.TimeUnit
import java.util.concurrent.TimeoutException
@@ -36,8 +36,10 @@
private val waitingCount = atomic(capturesCount)
private val failureException =
TimeoutException("Test doesn't complete after waiting for $capturesCount frames.")
+
@Volatile
private var startReceiving = false
+
@Volatile
private var _verifyBlock: (
captureRequest: RequestMetadata,
@@ -79,14 +81,10 @@
}
}
- @Deprecated(
- message = "Migrating to using RequestFailureWrapper instead of CaptureFailure",
- level = DeprecationLevel.WARNING
- )
override fun onFailed(
requestMetadata: RequestMetadata,
frameNumber: FrameNumber,
- captureFailure: CaptureFailure
+ requestFailure: RequestFailure
) {
if (!startReceiving) {
return
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraCoordinatorAdapter.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraCoordinatorAdapter.kt
index 273d30d..c8276d6 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraCoordinatorAdapter.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraCoordinatorAdapter.kt
@@ -17,6 +17,7 @@
package androidx.camera.camera2.pipe.integration.adapter
import androidx.annotation.RequiresApi
+import androidx.annotation.VisibleForTesting
import androidx.camera.camera2.pipe.CameraDevices
import androidx.camera.camera2.pipe.CameraId
import androidx.camera.camera2.pipe.integration.internal.CameraGraphCreator
@@ -25,6 +26,7 @@
import androidx.camera.core.CameraInfo
import androidx.camera.core.CameraSelector
import androidx.camera.core.concurrent.CameraCoordinator
+import androidx.camera.core.concurrent.CameraCoordinator.CAMERA_OPERATING_MODE_UNSPECIFIED
import androidx.camera.core.concurrent.CameraCoordinator.CameraOperatingMode
import androidx.camera.core.impl.CameraInternal
@@ -33,17 +35,15 @@
cameraDevices: CameraDevices,
private val cameraGraphCreator: CameraGraphCreator
) : CameraCoordinator {
- private val cameraInternalMap = mutableMapOf<CameraId, CameraInternalAdapter>()
-
- private var concurrentCameraIdsSet: Set<Set<CameraId>> = mutableSetOf()
- private var concurrentCameraIdMap: MutableMap<String, MutableList<String>> = mutableMapOf()
- private var activeConcurrentCameraInfosList: MutableList<CameraInfo> = mutableListOf()
-
- private var concurrentMode: Int = CameraCoordinator.CAMERA_OPERATING_MODE_UNSPECIFIED
- private var concurrentModeOn = false
+ @VisibleForTesting val cameraInternalMap = mutableMapOf<CameraId, CameraInternalAdapter>()
+ @VisibleForTesting var concurrentCameraIdsSet = mutableSetOf<Set<CameraId>>()
+ @VisibleForTesting var concurrentCameraIdMap = mutableMapOf<String, MutableList<String>>()
+ @VisibleForTesting var activeConcurrentCameraInfosList = mutableListOf<CameraInfo>()
+ @VisibleForTesting var concurrentMode: Int = CAMERA_OPERATING_MODE_UNSPECIFIED
+ @VisibleForTesting var concurrentModeOn = false
init {
- concurrentCameraIdsSet = cameraDevices.awaitConcurrentCameraIds()!!
+ concurrentCameraIdsSet = cameraDevices.awaitConcurrentCameraIds()!!.toMutableSet()
for (cameraIdSet in concurrentCameraIdsSet) {
val cameraIdsList = cameraIdSet.toList()
if (cameraIdsList.size >= 2) {
@@ -129,4 +129,13 @@
override fun removeListener(listener: CameraCoordinator.ConcurrentCameraModeListener) {
}
+
+ override fun shutdown() {
+ cameraInternalMap.clear()
+ concurrentCameraIdsSet.clear()
+ concurrentCameraIdMap.clear()
+ activeConcurrentCameraInfosList.clear()
+ concurrentMode = CAMERA_OPERATING_MODE_UNSPECIFIED
+ concurrentModeOn = false
+ }
}
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/config/UseCaseCameraConfig.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/config/UseCaseCameraConfig.kt
index d4a50cf..b60e5a3c 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/config/UseCaseCameraConfig.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/config/UseCaseCameraConfig.kt
@@ -26,11 +26,9 @@
import androidx.camera.camera2.pipe.integration.adapter.CameraStateAdapter
import androidx.camera.camera2.pipe.integration.adapter.SessionConfigAdapter
import androidx.camera.camera2.pipe.integration.compat.workaround.CapturePipelineTorchCorrection
-import androidx.camera.camera2.pipe.integration.impl.CameraCallbackMap
import androidx.camera.camera2.pipe.integration.impl.CameraInteropStateCallbackRepository
import androidx.camera.camera2.pipe.integration.impl.CapturePipeline
import androidx.camera.camera2.pipe.integration.impl.CapturePipelineImpl
-import androidx.camera.camera2.pipe.integration.impl.ComboRequestListener
import androidx.camera.camera2.pipe.integration.impl.UseCaseCamera
import androidx.camera.camera2.pipe.integration.impl.UseCaseCameraImpl
import androidx.camera.camera2.pipe.integration.impl.UseCaseCameraRequestControlImpl
@@ -79,9 +77,7 @@
private val sessionConfigAdapter: SessionConfigAdapter,
private val cameraStateAdapter: CameraStateAdapter,
private val cameraGraph: CameraGraph,
- private val streamConfigMap: Map<CameraStream.Config, DeferrableSurface>,
- private val callbackMap: CameraCallbackMap,
- private val requestListener: ComboRequestListener,
+ private val streamConfigMap: Map<CameraStream.Config, DeferrableSurface>
) {
@UseCaseCameraScope
@Provides
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/AndroidCaptureFailure.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/AndroidCaptureFailure.kt
new file mode 100644
index 0000000..083412f
--- /dev/null
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/AndroidCaptureFailure.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.camera2.pipe.integration.impl
+
+import android.hardware.camera2.CaptureFailure
+import android.os.Build
+import androidx.annotation.RequiresApi
+import androidx.camera.camera2.pipe.FrameNumber
+import androidx.camera.camera2.pipe.RequestFailure
+import androidx.camera.camera2.pipe.RequestMetadata
+import androidx.camera.camera2.pipe.UnsafeWrapper
+import kotlin.reflect.KClass
+
+/**
+ * This class implements the [RequestFailure] interface by passing the package-private
+ * [CaptureFailure] object.
+ */
+@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
+internal class AndroidCaptureFailure(
+ override val requestMetadata: RequestMetadata,
+ override val captureFailure: CaptureFailure
+) : RequestFailure, UnsafeWrapper {
+ override val frameNumber: FrameNumber = FrameNumber(captureFailure.frameNumber)
+ override val reason: Int = captureFailure.reason
+ override val wasImageCaptured: Boolean = captureFailure.wasImageCaptured()
+
+ @Suppress("UNCHECKED_CAST")
+ override fun <T : Any> unwrapAs(type: KClass<T>): T? =
+ when (type) {
+ CaptureFailure::class -> captureFailure as T?
+ else -> null
+ }
+}
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/CameraCallbackMap.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/CameraCallbackMap.kt
index 315e653..228ca50 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/CameraCallbackMap.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/CameraCallbackMap.kt
@@ -17,7 +17,6 @@
package androidx.camera.camera2.pipe.integration.impl
import android.hardware.camera2.CameraCaptureSession
-import android.hardware.camera2.CaptureFailure
import android.hardware.camera2.CaptureRequest
import android.hardware.camera2.CaptureResult
import android.hardware.camera2.TotalCaptureResult
@@ -30,6 +29,7 @@
import androidx.camera.camera2.pipe.FrameMetadata
import androidx.camera.camera2.pipe.FrameNumber
import androidx.camera.camera2.pipe.Request
+import androidx.camera.camera2.pipe.RequestFailure
import androidx.camera.camera2.pipe.RequestMetadata
import androidx.camera.camera2.pipe.StreamId
import androidx.camera.camera2.pipe.integration.adapter.CameraUseCaseAdapter
@@ -118,21 +118,18 @@
}
}
- @Deprecated(
- message = "Migrating to using RequestFailureWrapper instead of CaptureFailure",
- level = DeprecationLevel.WARNING
- )
override fun onFailed(
requestMetadata: RequestMetadata,
frameNumber: FrameNumber,
- captureFailure: CaptureFailure
+ requestFailure: RequestFailure
) {
for ((callback, executor) in callbacks) {
if (callback is CameraUseCaseAdapter.CaptureCallbackContainer) {
val session: CameraCaptureSession? =
requestMetadata.unwrapAs(CameraCaptureSession::class)
val request: CaptureRequest? = requestMetadata.unwrapAs(CaptureRequest::class)
- if (session != null && request != null) {
+ val captureFailure = requestFailure.captureFailure
+ if (session != null && request != null && captureFailure != null) {
executor.execute {
callback.captureCallback.onCaptureFailed(
session, request,
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/CapturePipeline.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/CapturePipeline.kt
index d696fb1..5469642 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/CapturePipeline.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/CapturePipeline.kt
@@ -37,7 +37,6 @@
import android.annotation.SuppressLint
import android.hardware.camera2.CameraCharacteristics.CONTROL_AE_STATE_FLASH_REQUIRED
import android.hardware.camera2.CameraDevice
-import android.hardware.camera2.CaptureFailure
import android.hardware.camera2.CaptureResult
import androidx.annotation.RequiresApi
import androidx.camera.camera2.pipe.CameraGraph
@@ -45,6 +44,7 @@
import androidx.camera.camera2.pipe.FrameNumber
import androidx.camera.camera2.pipe.Lock3ABehavior
import androidx.camera.camera2.pipe.Request
+import androidx.camera.camera2.pipe.RequestFailure
import androidx.camera.camera2.pipe.RequestMetadata
import androidx.camera.camera2.pipe.Result3A
import androidx.camera.camera2.pipe.core.Log.debug
@@ -296,22 +296,17 @@
completeSignal.complete(null)
}
- @Deprecated(
- message = "Migrating to using RequestFailureWrapper instead of " +
- "CaptureFailure",
- level = DeprecationLevel.WARNING,
- replaceWith = ReplaceWith("onFailed")
- )
@SuppressLint("ClassVerificationFailure")
override fun onFailed(
requestMetadata: RequestMetadata,
frameNumber: FrameNumber,
- captureFailure: CaptureFailure
+ requestFailure: RequestFailure
) {
completeSignal.completeExceptionally(
ImageCaptureException(
ERROR_CAPTURE_FAILED,
- "Capture request failed with reason " + captureFailure.reason,
+ "Capture request failed with reason " +
+ requestFailure.reason,
null
)
)
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/ComboRequestListener.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/ComboRequestListener.kt
index 6f98ef1..c29b7d6 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/ComboRequestListener.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/ComboRequestListener.kt
@@ -16,13 +16,13 @@
package androidx.camera.camera2.pipe.integration.impl
-import android.hardware.camera2.CaptureFailure
import androidx.annotation.RequiresApi
import androidx.camera.camera2.pipe.CameraTimestamp
import androidx.camera.camera2.pipe.FrameInfo
import androidx.camera.camera2.pipe.FrameMetadata
import androidx.camera.camera2.pipe.FrameNumber
import androidx.camera.camera2.pipe.Request
+import androidx.camera.camera2.pipe.RequestFailure
import androidx.camera.camera2.pipe.RequestMetadata
import androidx.camera.camera2.pipe.StreamId
import androidx.camera.camera2.pipe.integration.config.CameraScope
@@ -84,18 +84,19 @@
}
}
- @Deprecated(
- message = "Migrating to using RequestFailureWrapper instead of CaptureFailure",
- level = DeprecationLevel.WARNING
- )
override fun onFailed(
requestMetadata: RequestMetadata,
frameNumber: FrameNumber,
- captureFailure: CaptureFailure
+ requestFailure: RequestFailure
) {
- @Suppress("DEPRECATION")
listeners.forEach { (listener, executor) ->
- executor.execute { listener.onFailed(requestMetadata, frameNumber, captureFailure) }
+ executor.execute {
+ listener.onFailed(
+ requestMetadata,
+ frameNumber,
+ requestFailure
+ )
+ }
}
}
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/FakeCaptureFailure.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/FakeCaptureFailure.kt
new file mode 100644
index 0000000..d7d309b6
--- /dev/null
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/FakeCaptureFailure.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.camera2.pipe.integration.impl
+
+import android.hardware.camera2.CaptureFailure
+import android.os.Build
+import androidx.annotation.RequiresApi
+import androidx.camera.camera2.pipe.FrameNumber
+import androidx.camera.camera2.pipe.RequestFailure
+import androidx.camera.camera2.pipe.RequestMetadata
+
+/**
+ * This class implements the [RequestFailure] interface by extracting the fields of
+ * the package-private [CaptureFailure] object.
+ */
+@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
+internal data class FakeCaptureFailure(
+ override val requestMetadata: RequestMetadata,
+ override val wasImageCaptured: Boolean,
+ override val frameNumber: FrameNumber,
+ override val reason: Int,
+ override val captureFailure: CaptureFailure?
+) : RequestFailure
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraState.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraState.kt
index b67a339..5199d9f 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraState.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraState.kt
@@ -18,7 +18,6 @@
package androidx.camera.camera2.pipe.integration.impl
-import android.hardware.camera2.CaptureFailure
import android.hardware.camera2.CaptureRequest
import androidx.annotation.GuardedBy
import androidx.annotation.RequiresApi
@@ -30,6 +29,7 @@
import androidx.camera.camera2.pipe.FrameNumber
import androidx.camera.camera2.pipe.Metadata
import androidx.camera.camera2.pipe.Request
+import androidx.camera.camera2.pipe.RequestFailure
import androidx.camera.camera2.pipe.RequestMetadata
import androidx.camera.camera2.pipe.RequestTemplate
import androidx.camera.camera2.pipe.StreamId
@@ -333,23 +333,19 @@
}
}
- @Deprecated(
- message = "Migrating to using RequestFailureWrapper instead of CaptureFailure",
- level = DeprecationLevel.WARNING
- )
override fun onFailed(
requestMetadata: RequestMetadata,
frameNumber: FrameNumber,
- captureFailure: CaptureFailure,
+ requestFailure: RequestFailure,
) {
@Suppress("DEPRECATION")
- super.onFailed(requestMetadata, frameNumber, captureFailure)
- completeExceptionally(requestMetadata, captureFailure)
+ super.onFailed(requestMetadata, frameNumber, requestFailure)
+ completeExceptionally(requestMetadata, requestFailure)
}
private fun completeExceptionally(
requestMetadata: RequestMetadata,
- captureFailure: CaptureFailure? = null
+ requestFailure: RequestFailure? = null
) {
threads.scope.launch(start = CoroutineStart.UNDISPATCHED) {
requestMetadata[USE_CASE_CAMERA_STATE_CUSTOM_TAG]?.let { requestNo ->
@@ -357,7 +353,7 @@
updateSignals.completeExceptionally(
requestNo,
Throwable(
- "Failed in framework level" + (captureFailure?.reason?.let {
+ "Failed in framework level" + (requestFailure?.reason?.let {
" with CaptureFailure.reason = $it"
} ?: "")
)
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManager.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManager.kt
index 863a679..aa17277 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManager.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManager.kt
@@ -88,6 +88,8 @@
class UseCaseManager @Inject constructor(
private val cameraPipe: CameraPipe,
private val cameraGraphCreator: CameraGraphCreator,
+ private val callbackMap: CameraCallbackMap,
+ private val requestListener: ComboRequestListener,
private val cameraConfig: CameraConfig,
private val builder: UseCaseCameraComponent.Builder,
@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN") // Java version required for Dagger
@@ -310,8 +312,6 @@
val sessionConfigAdapter = SessionConfigAdapter(useCases)
val streamConfigMap = mutableMapOf<CameraStream.Config, DeferrableSurface>()
- val callbackMap = CameraCallbackMap()
- val requestListener = ComboRequestListener()
val graphConfig = createCameraGraphConfig(
sessionConfigAdapter, streamConfigMap, callbackMap,
@@ -327,9 +327,7 @@
sessionConfigAdapter,
cameraStateAdapter,
cameraGraph,
- streamConfigMap,
- callbackMap,
- requestListener,
+ streamConfigMap
)
)
.build()
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/adapter/CameraCoordinatorAdapterTest.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/adapter/CameraCoordinatorAdapterTest.kt
index 1222685..12c69f8 100644
--- a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/adapter/CameraCoordinatorAdapterTest.kt
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/adapter/CameraCoordinatorAdapterTest.kt
@@ -25,6 +25,7 @@
import androidx.camera.camera2.pipe.testing.FakeCameraMetadata
import androidx.camera.core.concurrent.CameraCoordinator.CAMERA_OPERATING_MODE_CONCURRENT
import androidx.camera.core.concurrent.CameraCoordinator.CAMERA_OPERATING_MODE_SINGLE
+import androidx.camera.core.concurrent.CameraCoordinator.CAMERA_OPERATING_MODE_UNSPECIFIED
import androidx.camera.core.impl.CameraInfoInternal
import com.google.common.truth.Truth.assertThat
import org.junit.Before
@@ -123,4 +124,22 @@
assertThat(cameraCoordinatorAdapter.cameraOperatingMode)
.isEqualTo(CAMERA_OPERATING_MODE_SINGLE)
}
+
+ @Test
+ fun shutdown() {
+ cameraCoordinatorAdapter.cameraOperatingMode = CAMERA_OPERATING_MODE_CONCURRENT
+ cameraCoordinatorAdapter.activeConcurrentCameraInfos = mutableListOf(
+ FakeCameraInfoAdapterCreator.createCameraInfoAdapter(cameraId = CameraId("0")),
+ FakeCameraInfoAdapterCreator.createCameraInfoAdapter(cameraId = CameraId("1")))
+
+ cameraCoordinatorAdapter.shutdown()
+
+ assertThat(cameraCoordinatorAdapter.cameraInternalMap).isEmpty()
+ assertThat(cameraCoordinatorAdapter.activeConcurrentCameraInfos).isEmpty()
+ assertThat(cameraCoordinatorAdapter.concurrentCameraIdMap).isEmpty()
+ assertThat(cameraCoordinatorAdapter.concurrentCameraIdsSet).isEmpty()
+ assertThat(cameraCoordinatorAdapter.cameraOperatingMode).isEqualTo(
+ CAMERA_OPERATING_MODE_UNSPECIFIED)
+ assertThat(cameraCoordinatorAdapter.concurrentModeOn).isFalse()
+ }
}
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/CapturePipelineTest.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/CapturePipelineTest.kt
index a98c7d5..500139f 100644
--- a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/CapturePipelineTest.kt
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/CapturePipelineTest.kt
@@ -612,12 +612,15 @@
fakeCameraGraphSession.requestHandler = { requests ->
requests.forEach { request ->
// Callback capture fail immediately.
- @Suppress("DEPRECATION")
request.listeners.forEach {
+ val requestMetadata = FakeRequestMetadata()
it.onFailed(
- requestMetadata = FakeRequestMetadata(),
+ requestMetadata = requestMetadata,
frameNumber = FrameNumber(100L),
- captureFailure = mock(CaptureFailure::class.java),
+ requestFailure = AndroidCaptureFailure(
+ requestMetadata,
+ mock(CaptureFailure::class.java)
+ )
)
}
}
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/StillCaptureRequestTest.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/StillCaptureRequestTest.kt
index 945507d..dd51c0d 100644
--- a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/StillCaptureRequestTest.kt
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/StillCaptureRequestTest.kt
@@ -201,6 +201,8 @@
@Test
fun captureRequestsFailWithCaptureFailedError_onFailed(): Unit = runTest(testDispatcher) {
val requestFuture = stillCaptureRequestControl.issueCaptureRequests()
+ val fakeRequestMetadata = FakeRequestMetadata()
+ val frameNumber = FrameNumber(0)
advanceUntilIdle()
assumeTrue(fakeCameraGraphSession.submittedRequests.size == captureConfigList.size)
@@ -209,9 +211,15 @@
request.listeners.forEach { listener ->
@Suppress("DEPRECATION")
listener.onFailed(
- FakeRequestMetadata(),
- FrameNumber(0),
- createCaptureFailure()
+ fakeRequestMetadata,
+ frameNumber,
+ FakeCaptureFailure(
+ fakeRequestMetadata,
+ false,
+ frameNumber,
+ CaptureFailure.REASON_ERROR,
+ null
+ )
)
}
}
@@ -414,13 +422,6 @@
}
}
- private fun createCaptureFailure(): CaptureFailure {
- val c = Class.forName("android.hardware.camera2.CaptureFailure")
- val constructor = c.getDeclaredConstructor()
- constructor.isAccessible = true
- return constructor.newInstance() as CaptureFailure
- }
-
private fun initUseCaseCameraScopeObjects() {
fakeCameraGraphSession = FakeCameraGraphSession()
fakeCameraGraph = FakeCameraGraph(
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManagerTest.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManagerTest.kt
index c57a4e6..e59f361 100644
--- a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManagerTest.kt
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManagerTest.kt
@@ -347,6 +347,8 @@
cameraPipe = CameraPipe(CameraPipe.Config(ApplicationProvider.getApplicationContext())),
cameraGraphCreator = CameraGraphCreator(),
cameraConfig = CameraConfig(cameraId),
+ callbackMap = CameraCallbackMap(),
+ requestListener = ComboRequestListener(),
builder = useCaseCameraComponentBuilder,
controls = controls as java.util.Set<UseCaseCameraControl>,
cameraProperties = FakeCameraProperties(
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/testing/FakeCameraGraphSession.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/testing/FakeCameraGraphSession.kt
index c3d481e..cd6027d 100644
--- a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/testing/FakeCameraGraphSession.kt
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/testing/FakeCameraGraphSession.kt
@@ -29,6 +29,7 @@
import androidx.camera.camera2.pipe.Request
import androidx.camera.camera2.pipe.Result3A
import androidx.camera.camera2.pipe.TorchState
+import androidx.camera.camera2.pipe.integration.impl.FakeCaptureFailure
import androidx.camera.camera2.pipe.integration.testing.FakeCameraGraphSession.RequestStatus.ABORTED
import androidx.camera.camera2.pipe.integration.testing.FakeCameraGraphSession.RequestStatus.FAILED
import androidx.camera.camera2.pipe.integration.testing.FakeCameraGraphSession.RequestStatus.TOTAL_CAPTURE_DONE
@@ -169,19 +170,26 @@
request: Request,
status: RequestStatus
) {
+ val requestMetadata = FakeRequestMetadata(request = request)
last().listeners.forEach { listener ->
when (status) {
TOTAL_CAPTURE_DONE -> listener.onTotalCaptureResult(
- FakeRequestMetadata(request = request), FrameNumber(0), FakeFrameInfo()
+ requestMetadata, FrameNumber(0), FakeFrameInfo()
)
- FAILED -> @Suppress("DEPRECATION") listener.onFailed(
- FakeRequestMetadata(request = request), FrameNumber(0), getFakeCaptureFailure()
+ FAILED -> listener.onFailed(
+ requestMetadata,
+ FrameNumber(0),
+ FakeCaptureFailure(
+ requestMetadata,
+ false,
+ FrameNumber(0),
+ CaptureFailure.REASON_ERROR,
+ null
+ )
)
- ABORTED -> listener.onRequestSequenceAborted(
- FakeRequestMetadata(request = request)
- )
+ ABORTED -> listener.onRequestSequenceAborted(requestMetadata)
}
}
}
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/testing/FakeUseCaseCamera.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/testing/FakeUseCaseCamera.kt
index c8d8cb2..0af1e6c 100644
--- a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/testing/FakeUseCaseCamera.kt
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/testing/FakeUseCaseCamera.kt
@@ -33,8 +33,6 @@
import androidx.camera.camera2.pipe.integration.adapter.SessionConfigAdapter
import androidx.camera.camera2.pipe.integration.config.UseCaseCameraComponent
import androidx.camera.camera2.pipe.integration.config.UseCaseCameraConfig
-import androidx.camera.camera2.pipe.integration.impl.CameraCallbackMap
-import androidx.camera.camera2.pipe.integration.impl.ComboRequestListener
import androidx.camera.camera2.pipe.integration.impl.UseCaseCamera
import androidx.camera.camera2.pipe.integration.impl.UseCaseCameraRequestControl
import androidx.camera.core.UseCase
@@ -52,12 +50,10 @@
private var sessionConfigAdapter = SessionConfigAdapter(emptyList())
private var cameraGraph = FakeCameraGraph()
private var streamConfigMap = mutableMapOf<CameraStream.Config, DeferrableSurface>()
- private var callbackMap = CameraCallbackMap()
- private var requestListener = ComboRequestListener()
private var config: UseCaseCameraConfig =
UseCaseCameraConfig(emptyList(), sessionConfigAdapter, CameraStateAdapter(), cameraGraph,
- streamConfigMap, callbackMap, requestListener)
+ streamConfigMap)
override fun config(config: UseCaseCameraConfig): UseCaseCameraComponent.Builder {
this.config = config
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 ba087f3..2f68f45 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,7 @@
import androidx.camera.camera2.pipe.GraphState.GraphStateError
import androidx.camera.camera2.pipe.Metadata
import androidx.camera.camera2.pipe.Request
-import androidx.camera.camera2.pipe.RequestFailureWrapper
+import androidx.camera.camera2.pipe.RequestFailure
import androidx.camera.camera2.pipe.StreamId
import kotlinx.atomicfu.atomic
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -283,9 +283,9 @@
}
}
- fun simulateFailure(requestFailureWrapper: RequestFailureWrapper) {
+ fun simulateFailure(requestFailure: RequestFailure) {
requestSequence.invokeOnRequest(requestMetadata) {
- it.onFailed(requestMetadata, frameNumber, requestFailureWrapper)
+ it.onFailed(requestMetadata, frameNumber, requestFailure)
}
}
diff --git a/camera/camera-camera2-pipe-testing/src/main/java/androidx/camera/camera2/pipe/testing/FakeRequestListener.kt b/camera/camera-camera2-pipe-testing/src/main/java/androidx/camera/camera2/pipe/testing/FakeRequestListener.kt
index e097dc3..41b1c0b 100644
--- a/camera/camera-camera2-pipe-testing/src/main/java/androidx/camera/camera2/pipe/testing/FakeRequestListener.kt
+++ b/camera/camera-camera2-pipe-testing/src/main/java/androidx/camera/camera2/pipe/testing/FakeRequestListener.kt
@@ -18,13 +18,13 @@
package androidx.camera.camera2.pipe.testing
-import android.hardware.camera2.CaptureFailure
import androidx.annotation.RequiresApi
import androidx.camera.camera2.pipe.CameraTimestamp
import androidx.camera.camera2.pipe.FrameInfo
import androidx.camera.camera2.pipe.FrameMetadata
import androidx.camera.camera2.pipe.FrameNumber
import androidx.camera.camera2.pipe.Request
+import androidx.camera.camera2.pipe.RequestFailure
import androidx.camera.camera2.pipe.RequestMetadata
import androidx.camera.camera2.pipe.StreamId
import kotlinx.coroutines.flow.MutableSharedFlow
@@ -148,18 +148,13 @@
"($replayBuffer) may need to be increased."
}
- @Deprecated(
- message = "Migrating to using RequestFailureWrapper instead of CaptureFailure",
- level = DeprecationLevel.WARNING
- )
override fun onFailed(
requestMetadata: RequestMetadata,
frameNumber: FrameNumber,
- captureFailure: CaptureFailure
+ requestFailure: RequestFailure
) = check(
_onFailedFlow.tryEmit(
- @Suppress("SyntheticAccessor")
- OnFailed(requestMetadata, frameNumber, captureFailure)
+ OnFailed(requestMetadata, frameNumber, requestFailure)
)
) {
"Failed to emit OnFailed event! The size of the replay buffer" +
@@ -205,5 +200,5 @@
class OnFailed(
val requestMetadata: RequestMetadata,
val frameNumber: FrameNumber,
- val captureFailure: CaptureFailure
+ val requestFailure: RequestFailure
) : RequestListenerEvent()
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraGraph.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraGraph.kt
index 581aeac..3c81b44 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraGraph.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraGraph.kt
@@ -22,6 +22,7 @@
import android.hardware.camera2.CaptureRequest
import android.hardware.camera2.params.MeteringRectangle
import android.hardware.camera2.params.SessionConfiguration
+import android.os.Build
import android.view.Surface
import androidx.annotation.RequiresApi
import androidx.annotation.RestrictTo
@@ -151,7 +152,29 @@
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
data class Flags(
val configureBlankSessionOnStop: Boolean = false,
- val abortCapturesOnStop: Boolean = false,
+
+ /**
+ * When creating a new capture session, the camera framework waits for all the inflight
+ * capture requests from the prior session before creating the new session. Calling
+ * abortCaptures() triggers an explicit flush on the camera HAL side. Therefore, aborting
+ * the captures allows us to switch to a new capture session sooner (see the referenced bug
+ * for more info).
+ *
+ * However, there might be cases where we might not want to trigger the flush. For example,
+ * if we're recording a video, we may not want the video recording to be disrupted too
+ * early. Hence, this flag is provided so that we can override this behavior.
+ *
+ * Ideally we should be able to invoke abortCaptures() every time during close. However,
+ * improper flush implementations, which seem to occur largely on older devices, have shown
+ * to cause irregular behaviors, such as NPEs (b/139448807), capture session entering
+ * abnormal states (b/162314023), and (potentially) camera device close stalling based on
+ * testing, etc. Hence, we're enabling this behavior by default on API level >= R (30) for
+ * now.
+ *
+ * - Bug(s): b/287020251
+ * - API levels: R (30) and above
+ */
+ val abortCapturesOnStop: Boolean = Build.VERSION.SDK_INT >= Build.VERSION_CODES.R,
/**
* A quirk that waits for the last repeating capture request to start before stopping the
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/Requests.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/Requests.kt
index 1c98dad..4ba4278 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/Requests.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/Requests.kt
@@ -142,7 +142,7 @@
}
@Deprecated(
- message = "Migrating to using RequestFailureWrapper instead of CaptureFailure",
+ message = "Migrating to using RequestFailure instead of CaptureFailure",
level = DeprecationLevel.WARNING
)
fun onFailed(
@@ -160,13 +160,13 @@
*
* @param requestMetadata the data about the camera2 request that was sent to the camera.
* @param frameNumber the android frame number for this exposure
- * @param requestFailureWrapper the android [RequestFailureWrapper] data wrapper
+ * @param requestFailure the android [RequestFailure] data wrapper
* @see android.hardware.camera2.CameraCaptureSession.CaptureCallback.onCaptureFailed
*/
fun onFailed(
requestMetadata: RequestMetadata,
frameNumber: FrameNumber,
- requestFailureWrapper: RequestFailureWrapper
+ requestFailure: RequestFailure
) {
}
@@ -256,7 +256,7 @@
* constructor prevents directly creating an instance of it.
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-interface RequestFailureWrapper {
+interface RequestFailure {
val requestMetadata: RequestMetadata
val frameNumber: FrameNumber
@@ -264,6 +264,8 @@
val reason: Int
val wasImageCaptured: Boolean
+
+ val captureFailure: CaptureFailure?
}
/**
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/AndroidCaptureFailure.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/AndroidCaptureFailure.kt
index e0c1a2c..c2d8842 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/AndroidCaptureFailure.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/AndroidCaptureFailure.kt
@@ -22,17 +22,28 @@
import android.os.Build
import androidx.annotation.RequiresApi
import androidx.camera.camera2.pipe.FrameNumber
-import androidx.camera.camera2.pipe.RequestFailureWrapper
+import androidx.camera.camera2.pipe.RequestFailure
import androidx.camera.camera2.pipe.RequestMetadata
+import androidx.camera.camera2.pipe.UnsafeWrapper
+import kotlin.reflect.KClass
/**
- * This class implements the [RequestFailureWrapper] interface to create a
- * CaptureFailure object that can be used instead of the package-private [CaptureFailure]
+ * This class implements the [RequestFailure] interface by passing the package-private
+ * [CaptureFailure] object.
*/
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
internal class AndroidCaptureFailure(
override val requestMetadata: RequestMetadata,
- override val wasImageCaptured: Boolean,
- override val frameNumber: FrameNumber,
- override val reason: Int
-) : RequestFailureWrapper
+ override val captureFailure: CaptureFailure
+) : RequestFailure, UnsafeWrapper {
+ override val frameNumber: FrameNumber = FrameNumber(captureFailure.frameNumber)
+ override val reason: Int = captureFailure.reason
+ override val wasImageCaptured: Boolean = captureFailure.wasImageCaptured()
+
+ @Suppress("UNCHECKED_CAST")
+ override fun <T : Any> unwrapAs(type: KClass<T>): T? =
+ when (type) {
+ CaptureFailure::class -> captureFailure as T?
+ else -> null
+ }
+}
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2CaptureSequence.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2CaptureSequence.kt
index f0ad932a..1efc937 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2CaptureSequence.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2CaptureSequence.kt
@@ -30,6 +30,7 @@
import androidx.camera.camera2.pipe.CaptureSequences.invokeOnRequests
import androidx.camera.camera2.pipe.FrameNumber
import androidx.camera.camera2.pipe.Request
+import androidx.camera.camera2.pipe.RequestFailure
import androidx.camera.camera2.pipe.RequestMetadata
import androidx.camera.camera2.pipe.RequestNumber
import androidx.camera.camera2.pipe.StreamId
@@ -154,42 +155,56 @@
invokeOnRequest(request) { it.onComplete(request, frameNumber, frameInfo) }
}
- @Deprecated(
- message = "Migrating to using RequestFailureWrapper instead of CaptureFailure",
- level = DeprecationLevel.WARNING,
- replaceWith = ReplaceWith("onFailed")
- )
override fun onCaptureFailed(
captureSession: CameraCaptureSession,
captureRequest: CaptureRequest,
captureFailure: CaptureFailure
- ) = onCaptureFailed(
- captureRequest,
- FrameNumber(captureFailure.frameNumber)
- )
-
- override fun onCaptureFailed(
- captureRequest: CaptureRequest,
- frameNumber: FrameNumber
) {
- sequenceListener.onCaptureSequenceComplete(this)
-
val requestNumber = readRequestNumber(captureRequest)
// Load the request and throw if we are not able to find an associated request. Under
// normal circumstances this should never happen.
val request = readRequestMetadata(requestNumber)
- val androidCaptureFailure = AndroidCaptureFailure(
+ val androidCaptureFailure = AndroidCaptureFailure(request, captureFailure)
+
+ invokeCaptureFailure(
+ request,
+ FrameNumber(captureFailure.frameNumber),
+ androidCaptureFailure
+ )
+ }
+
+ private fun invokeCaptureFailure(
+ request: RequestMetadata,
+ frameNumber: FrameNumber,
+ requestFailure: RequestFailure
+ ) {
+ sequenceListener.onCaptureSequenceComplete(this)
+ invokeOnRequest(request) {
+ it.onFailed(request, frameNumber, requestFailure)
+ }
+ }
+
+ override fun onCaptureFailed(
+ captureRequest: CaptureRequest,
+ frameNumber: FrameNumber
+ ) {
+ val requestNumber = readRequestNumber(captureRequest)
+
+ // Load the request and throw if we are not able to find an associated request. Under
+ // normal circumstances this should never happen.
+ val request = readRequestMetadata(requestNumber)
+
+ val simpleCaptureFailure = SimpleCaptureFailure(
request,
false,
frameNumber,
- CaptureFailure.REASON_ERROR
+ CaptureFailure.REASON_ERROR,
+ null
)
- invokeOnRequest(request) {
- it.onFailed(request, frameNumber, androidCaptureFailure)
- }
+ invokeCaptureFailure(request, frameNumber, simpleCaptureFailure)
}
override fun onCaptureBufferLost(
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2CaptureSequenceProcessor.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2CaptureSequenceProcessor.kt
index 212b808..f94eb82 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2CaptureSequenceProcessor.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2CaptureSequenceProcessor.kt
@@ -291,12 +291,12 @@
}
override fun abortCaptures(): Unit = synchronized(lock) {
- if (closed) return
+ Log.debug { "$this#abortCaptures" }
session.abortCaptures()
}
override fun stopRepeating(): Unit = synchronized(lock) {
- if (closed) return
+ Log.debug { "$this#stopRepeating" }
session.stopRepeating()
}
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2Quirks.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2Quirks.kt
index c9ad6e2..bb36212 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2Quirks.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2Quirks.kt
@@ -85,4 +85,26 @@
CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL]
return level == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY
}
+
+ companion object {
+ private val SHOULD_WAIT_FOR_REPEATING_DEVICE_MAP =
+ mapOf(
+ "Google" to setOf("oriole", "raven", "bluejay", "panther", "cheetah", "lynx"),
+ )
+
+ /**
+ * A quirk that waits for a certain number of repeating requests to complete before allowing
+ * (single) capture requests to be issued. This is needed on some devices where issuing a
+ * capture request too early might cause it to fail prematurely.
+ *
+ * - Bug(s): b/287020251, b/289284907
+ * - Device(s): See [SHOULD_WAIT_FOR_REPEATING_DEVICE_MAP]
+ * - API levels: Before 34 (U)
+ */
+ internal fun shouldWaitForRepeatingBeforeCapture(): Boolean {
+ return SHOULD_WAIT_FOR_REPEATING_DEVICE_MAP[
+ Build.MANUFACTURER]?.contains(Build.DEVICE) == true &&
+ Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE
+ }
+ }
}
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/CaptureSessionState.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/CaptureSessionState.kt
index 7a07920..9d21ba8 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/CaptureSessionState.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/CaptureSessionState.kt
@@ -248,7 +248,7 @@
* a closed state. This will not cancel repeating requests or abort captures.
*/
fun disconnect() {
- shutdown(abortAndStopRepeating = false)
+ shutdown(abortAndStopRepeating = cameraGraphFlags.abortCapturesOnStop)
}
/**
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Exceptions.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Exceptions.kt
index 3cf8857..8a0adf7 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Exceptions.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Exceptions.kt
@@ -22,6 +22,7 @@
import androidx.annotation.RequiresApi
import androidx.camera.camera2.pipe.CameraError
import androidx.camera.camera2.pipe.CameraId
+import androidx.camera.camera2.pipe.core.Log
import androidx.camera.camera2.pipe.internal.CameraErrorListener
/**
@@ -55,6 +56,7 @@
try {
return block()
} catch (e: Exception) {
+ Log.warn { "Unexpected error: " + e.message }
when (e) {
is IllegalArgumentException,
is IllegalStateException,
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/SimpleCaptureFailure.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/SimpleCaptureFailure.kt
new file mode 100644
index 0000000..2d43ff8
--- /dev/null
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/SimpleCaptureFailure.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
+
+package androidx.camera.camera2.pipe.compat
+
+import android.hardware.camera2.CaptureFailure
+import android.os.Build
+import androidx.annotation.RequiresApi
+import androidx.camera.camera2.pipe.FrameNumber
+import androidx.camera.camera2.pipe.RequestFailure
+import androidx.camera.camera2.pipe.RequestMetadata
+
+/**
+ * This class implements the [RequestFailure] interface by extracting the fields of
+ * the package-private [CaptureFailure] object.
+ */
+@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
+internal data class SimpleCaptureFailure(
+ override val requestMetadata: RequestMetadata,
+ override val wasImageCaptured: Boolean,
+ override val frameNumber: FrameNumber,
+ override val reason: Int,
+ override val captureFailure: CaptureFailure?
+) : RequestFailure
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/GraphProcessor.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/GraphProcessor.kt
index f000f4f..fdb24f2 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/GraphProcessor.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/GraphProcessor.kt
@@ -22,6 +22,8 @@
import androidx.annotation.RequiresApi
import androidx.camera.camera2.pipe.CameraGraph
import androidx.camera.camera2.pipe.CaptureSequenceProcessor
+import androidx.camera.camera2.pipe.FrameInfo
+import androidx.camera.camera2.pipe.FrameNumber
import androidx.camera.camera2.pipe.GraphState
import androidx.camera.camera2.pipe.GraphState.GraphStateError
import androidx.camera.camera2.pipe.GraphState.GraphStateStarted
@@ -29,6 +31,8 @@
import androidx.camera.camera2.pipe.GraphState.GraphStateStopped
import androidx.camera.camera2.pipe.GraphState.GraphStateStopping
import androidx.camera.camera2.pipe.Request
+import androidx.camera.camera2.pipe.RequestMetadata
+import androidx.camera.camera2.pipe.compat.Camera2Quirks
import androidx.camera.camera2.pipe.config.CameraGraphScope
import androidx.camera.camera2.pipe.config.ForCameraGraph
import androidx.camera.camera2.pipe.core.Debug
@@ -37,6 +41,8 @@
import androidx.camera.camera2.pipe.core.Threads
import androidx.camera.camera2.pipe.formatForLogs
import androidx.camera.camera2.pipe.putAllMetadata
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
import javax.inject.Inject
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineScope
@@ -155,6 +161,27 @@
@GuardedBy("lock")
private var pendingParametersDeferred: CompletableDeferred<Boolean>? = null
+ // On some devices, we need to wait for 10 frames to complete before we can guarantee the
+ // success of single capture requests. This is a quirk identified as part of b/287020251 and
+ // reported in b/289284907.
+ private var repeatingRequestsCompleted = CountDownLatch(10)
+
+ // Graph listener added to repeating requests in order to handle the aforementioned quirk.
+ private val graphProcessorRepeatingListeners =
+ if (!Camera2Quirks.shouldWaitForRepeatingBeforeCapture()) {
+ graphListeners
+ } else {
+ graphListeners + object : Request.Listener {
+ override fun onComplete(
+ requestMetadata: RequestMetadata,
+ frameNumber: FrameNumber,
+ result: FrameInfo
+ ) {
+ repeatingRequestsCompleted.countDown()
+ }
+ }
+ }
+
private val _graphState = MutableStateFlow<GraphState>(GraphStateStopped)
override val graphState: StateFlow<GraphState>
@@ -452,7 +479,7 @@
requests = listOf(request),
defaultParameters = cameraGraphConfig.defaultParameters,
requiredParameters = requiredParameters,
- listeners = graphListeners
+ listeners = graphProcessorRepeatingListeners,
)
) {
// ONLY update the current repeating request if the update succeeds
@@ -502,6 +529,15 @@
}
private fun submitLoop() {
+ if (Camera2Quirks.shouldWaitForRepeatingBeforeCapture() && hasRepeatingRequest()) {
+ debug {
+ "Quirk: Waiting for 10 repeating requests to complete before submitting requests"
+ }
+ if (!repeatingRequestsCompleted.await(2, TimeUnit.SECONDS)) {
+ warn { "Failed to wait for 10 repeating requests to complete after 5 seconds" }
+ }
+ }
+
var burst: List<Request>
var processor: GraphRequestProcessor
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/concurrent/Camera2CameraCoordinator.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/concurrent/Camera2CameraCoordinator.java
index 93e24c7..e6680bf 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/concurrent/Camera2CameraCoordinator.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/concurrent/Camera2CameraCoordinator.java
@@ -140,6 +140,15 @@
mConcurrentCameraModeListeners.remove(listener);
}
+ @Override
+ public void shutdown() {
+ mConcurrentCameraModeListeners.clear();
+ mConcurrentCameraIdMap.clear();
+ mActiveConcurrentCameraInfos.clear();
+ mConcurrentCameraIds.clear();
+ mCameraOperatingMode = CAMERA_OPERATING_MODE_UNSPECIFIED;
+ }
+
private void retrieveConcurrentCameraIds() {
try {
mConcurrentCameraIds = mCameraManager.getConcurrentCameraIds();
diff --git a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/concurrent/Camera2CameraCoordinatorTest.kt b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/concurrent/Camera2CameraCoordinatorTest.kt
index 6b7de32..fa3f220 100644
--- a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/concurrent/Camera2CameraCoordinatorTest.kt
+++ b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/concurrent/Camera2CameraCoordinatorTest.kt
@@ -57,8 +57,6 @@
)
class Camera2CameraCoordinatorTest {
- private val mContext = ApplicationProvider.getApplicationContext<Context>()
-
private lateinit var cameraCoordinator: CameraCoordinator
@Before
@@ -109,26 +107,7 @@
@Test
fun getPairedCameraId() {
- val characteristics0 = ShadowCameraCharacteristics.newCameraCharacteristics()
- (Shadow.extract<Any>(
- ApplicationProvider.getApplicationContext<Context>()
- .getSystemService(Context.CAMERA_SERVICE)
- ) as ShadowCameraManager)
- .addCamera("0", characteristics0)
- val characteristics1 = ShadowCameraCharacteristics.newCameraCharacteristics()
- (Shadow.extract<Any>(
- ApplicationProvider.getApplicationContext<Context>()
- .getSystemService(Context.CAMERA_SERVICE)
- ) as ShadowCameraManager)
- .addCamera("1", characteristics1)
-
- val mCameraManagerCompat =
- CameraManagerCompat.from((ApplicationProvider.getApplicationContext() as Context))
-
- cameraCoordinator.activeConcurrentCameraInfos = listOf(
- Camera2CameraInfoImpl("0", mCameraManagerCompat),
- Camera2CameraInfoImpl("1", mCameraManagerCompat)
- )
+ cameraCoordinator.activeConcurrentCameraInfos = createConcurrentCameraInfos()
assertThat(cameraCoordinator.getPairedConcurrentCameraId("0")).isEqualTo("1")
assertThat(cameraCoordinator.getPairedConcurrentCameraId("1")).isEqualTo("0")
@@ -164,6 +143,41 @@
anyInt(), anyInt())
}
+ @Test
+ fun shutdown() {
+ cameraCoordinator.cameraOperatingMode = CAMERA_OPERATING_MODE_CONCURRENT
+ cameraCoordinator.activeConcurrentCameraInfos = createConcurrentCameraInfos()
+
+ cameraCoordinator.shutdown()
+
+ assertThat(cameraCoordinator.concurrentCameraSelectors).isEmpty()
+ assertThat(cameraCoordinator.activeConcurrentCameraInfos).isEmpty()
+ assertThat(cameraCoordinator.cameraOperatingMode).isEqualTo(
+ CAMERA_OPERATING_MODE_UNSPECIFIED)
+ }
+
+ private fun createConcurrentCameraInfos(): List<Camera2CameraInfoImpl> {
+ val characteristics0 = ShadowCameraCharacteristics.newCameraCharacteristics()
+ (Shadow.extract<Any>(
+ ApplicationProvider.getApplicationContext<Context>()
+ .getSystemService(Context.CAMERA_SERVICE)
+ ) as ShadowCameraManager)
+ .addCamera("0", characteristics0)
+ val characteristics1 = ShadowCameraCharacteristics.newCameraCharacteristics()
+ (Shadow.extract<Any>(
+ ApplicationProvider.getApplicationContext<Context>()
+ .getSystemService(Context.CAMERA_SERVICE)
+ ) as ShadowCameraManager)
+ .addCamera("1", characteristics1)
+ val cameraManagerCompat =
+ CameraManagerCompat.from((ApplicationProvider.getApplicationContext() as Context))
+
+ return listOf(
+ Camera2CameraInfoImpl("0", cameraManagerCompat),
+ Camera2CameraInfoImpl("1", cameraManagerCompat)
+ )
+ }
+
private class FakeCameraManagerImpl : CameraManagerCompat.CameraManagerCompatImpl {
private val mCameraManagerImpl = CameraManagerCompat.CameraManagerCompatImpl.from(
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/Preview.java b/camera/camera-core/src/main/java/androidx/camera/core/Preview.java
index e36507c..a7686c7 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/Preview.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/Preview.java
@@ -454,11 +454,14 @@
}
private void sendSurfaceRequest() {
+ // App receives TransformationInfo when 1) the listener is set or 2) the info is sent. We
+ // should send the info before the listen is set so the app only receives once.
+ sendTransformationInfoIfReady();
+
+ // Send the SurfaceRequest.
final SurfaceProvider surfaceProvider = checkNotNull(mSurfaceProvider);
final SurfaceRequest surfaceRequest = checkNotNull(mCurrentSurfaceRequest);
-
mSurfaceProviderExecutor.execute(() -> surfaceProvider.onSurfaceRequested(surfaceRequest));
- sendTransformationInfoIfReady();
}
/**
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/concurrent/CameraCoordinator.java b/camera/camera-core/src/main/java/androidx/camera/core/concurrent/CameraCoordinator.java
index 60d6be3..b98d11e 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/concurrent/CameraCoordinator.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/concurrent/CameraCoordinator.java
@@ -130,6 +130,11 @@
void removeListener(@NonNull ConcurrentCameraModeListener listener);
/**
+ * Clean up all the resources when CameraX shutdown.
+ */
+ void shutdown();
+
+ /**
* Interface for concurrent camera mode update.
*
* <p>Everytime user changes {@link CameraOperatingMode}, the observer will be notified and
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/PreviewTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/PreviewTest.kt
index 768b8bb..7f43051 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/PreviewTest.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/PreviewTest.kt
@@ -28,12 +28,14 @@
import android.util.Rational
import android.util.Size
import android.view.Surface
+import android.view.Surface.ROTATION_90
import androidx.annotation.RequiresApi
import androidx.camera.core.CameraEffect.IMAGE_CAPTURE
import androidx.camera.core.CameraEffect.PREVIEW
import androidx.camera.core.CameraEffect.VIDEO_CAPTURE
import androidx.camera.core.CameraSelector.LENS_FACING_FRONT
import androidx.camera.core.MirrorMode.MIRROR_MODE_ON_FRONT_ONLY
+import androidx.camera.core.Preview.SurfaceProvider
import androidx.camera.core.SurfaceRequest.TransformationInfo
import androidx.camera.core.impl.CameraFactory
import androidx.camera.core.impl.CameraThreadConfig
@@ -45,6 +47,7 @@
import androidx.camera.core.impl.UseCaseConfig
import androidx.camera.core.impl.UseCaseConfigFactory
import androidx.camera.core.impl.utils.executor.CameraXExecutors
+import androidx.camera.core.impl.utils.executor.CameraXExecutors.directExecutor
import androidx.camera.core.impl.utils.executor.CameraXExecutors.mainThreadExecutor
import androidx.camera.core.internal.CameraUseCaseAdapter
import androidx.camera.core.internal.utils.SizeUtil
@@ -63,6 +66,8 @@
import com.google.common.truth.Truth.assertThat
import java.util.Collections
import java.util.concurrent.ExecutionException
+import java.util.concurrent.Semaphore
+import java.util.concurrent.TimeUnit
import org.junit.After
import org.junit.Assert
import org.junit.Before
@@ -165,6 +170,23 @@
}
@Test
+ fun attachPreview_receiveTransformationInfoOnlyOnce() {
+ // Arrange.
+ val semaphore = Semaphore(0)
+
+ // Act: create preview and listen to transformation info.
+ createPreview(surfaceProvider = {
+ it.setTransformationInfoListener(directExecutor()) {
+ semaphore.release()
+ }
+ })
+
+ // Assert: only receive transformation info once.
+ assertThat(semaphore.tryAcquire(1, 1, TimeUnit.SECONDS)).isTrue()
+ assertThat(semaphore.tryAcquire(2, 1, TimeUnit.SECONDS)).isFalse()
+ }
+
+ @Test
fun createPreview_sessionConfigMatchesStreamSpec() {
// Act: Create a preview use case.
val preview = createPreview()
@@ -477,7 +499,7 @@
val preview = createPreview(
effect,
frontCamera,
- targetRotation = Surface.ROTATION_90
+ targetRotation = ROTATION_90
)
assertThat(preview.cameraEdge.hasCameraTransform()).isTrue()
// Assert: rotationDegrees is not flipped.
@@ -491,7 +513,7 @@
val preview = createPreview(
effect,
frontCamera,
- targetRotation = Surface.ROTATION_90
+ targetRotation = ROTATION_90
)
// Assert: rotationDegrees is 0.
assertThat(preview.cameraEdge.rotationDegrees).isEqualTo(0)
@@ -504,7 +526,7 @@
val preview = createPreview(
effect,
frontCamera,
- targetRotation = Surface.ROTATION_90
+ targetRotation = ROTATION_90
)
// Assert
assertThat(preview.cameraEdge.hasCameraTransform()).isFalse()
@@ -518,7 +540,7 @@
val preview = createPreview(
effect,
frontCamera,
- targetRotation = Surface.ROTATION_90
+ targetRotation = ROTATION_90
)
// Assert
assertThat(preview.cameraEdge.mirroring).isFalse()
@@ -802,13 +824,15 @@
private fun createPreview(
effect: CameraEffect? = null,
camera: FakeCamera = backCamera,
- targetRotation: Int = Surface.ROTATION_0
+ targetRotation: Int = ROTATION_90,
+ surfaceProvider: SurfaceProvider = SurfaceProvider {
+ }
): Preview {
previewToDetach = Preview.Builder()
.setTargetRotation(targetRotation)
.build()
previewToDetach.effect = effect
- previewToDetach.setSurfaceProvider(CameraXExecutors.directExecutor()) {}
+ previewToDetach.setSurfaceProvider(directExecutor(), surfaceProvider)
val previewConfig = PreviewConfig(
cameraXConfig.getUseCaseConfigFactoryProvider(null)!!.newInstance(context).getConfig(
UseCaseConfigFactory.CaptureType.PREVIEW,
diff --git a/camera/camera-lifecycle/src/androidTest/java/androidx/camera/lifecycle/ProcessCameraProviderTest.kt b/camera/camera-lifecycle/src/androidTest/java/androidx/camera/lifecycle/ProcessCameraProviderTest.kt
index f585001..7dbc5c3 100644
--- a/camera/camera-lifecycle/src/androidTest/java/androidx/camera/lifecycle/ProcessCameraProviderTest.kt
+++ b/camera/camera-lifecycle/src/androidTest/java/androidx/camera/lifecycle/ProcessCameraProviderTest.kt
@@ -30,6 +30,7 @@
import androidx.camera.core.ConcurrentCamera.SingleCameraConfig
import androidx.camera.core.Preview
import androidx.camera.core.UseCaseGroup
+import androidx.camera.core.concurrent.CameraCoordinator.CAMERA_OPERATING_MODE_UNSPECIFIED
import androidx.camera.core.impl.CameraFactory
import androidx.camera.core.impl.utils.executor.CameraXExecutors.mainThreadExecutor
import androidx.camera.testing.fakes.FakeAppConfig
@@ -63,6 +64,7 @@
private val context = ApplicationProvider.getApplicationContext() as Context
private val lifecycleOwner0 = FakeLifecycleOwner()
private val lifecycleOwner1 = FakeLifecycleOwner()
+ private val cameraCoordinator = FakeCameraCoordinator()
private lateinit var provider: ProcessCameraProvider
@@ -665,6 +667,10 @@
// Should not throw exception
ProcessCameraProvider.configureInstance(FakeAppConfig.create())
+ assertThat(cameraCoordinator.cameraOperatingMode).isEqualTo(
+ CAMERA_OPERATING_MODE_UNSPECIFIED)
+ assertThat(cameraCoordinator.concurrentCameraSelectors).isEmpty()
+ assertThat(cameraCoordinator.activeConcurrentCameraInfos).isEmpty()
}
@Test
@@ -841,7 +847,6 @@
}
private fun createConcurrentCameraAppConfig(): CameraXConfig {
- val cameraCoordinator = FakeCameraCoordinator()
val combination0 = mapOf(
"0" to CameraSelector.Builder().requireLensFacing(LENS_FACING_BACK).build(),
"1" to CameraSelector.Builder().requireLensFacing(LENS_FACING_FRONT).build())
diff --git a/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/ProcessCameraProvider.java b/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/ProcessCameraProvider.java
index d675fb858..f6e56cd 100644
--- a/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/ProcessCameraProvider.java
+++ b/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/ProcessCameraProvider.java
@@ -291,6 +291,10 @@
mLifecycleCameraRepository.clear();
});
+ if (mCameraX != null) {
+ mCameraX.getCameraFactory().getCameraCoordinator().shutdown();
+ }
+
ListenableFuture<Void> shutdownFuture = mCameraX != null ? mCameraX.shutdown() :
Futures.immediateFuture(null);
diff --git a/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeCameraCoordinator.java b/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeCameraCoordinator.java
index 3281ec1..1dc5b00 100644
--- a/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeCameraCoordinator.java
+++ b/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeCameraCoordinator.java
@@ -125,4 +125,13 @@
public void removeListener(@NonNull ConcurrentCameraModeListener listener) {
mConcurrentCameraModeListeners.remove(listener);
}
+
+ @Override
+ public void shutdown() {
+ mConcurrentCameraIdMap.clear();
+ mConcurrentCameraIds.clear();
+ mConcurrentCameraSelectors.clear();
+ mActiveConcurrentCameraInfos.clear();
+ mConcurrentCameraModeListeners.clear();
+ }
}
diff --git a/camera/camera-video/api/1.3.0-beta02.txt b/camera/camera-video/api/1.3.0-beta02.txt
index fb0f4c7..b9cc77a 100644
--- a/camera/camera-video/api/1.3.0-beta02.txt
+++ b/camera/camera-video/api/1.3.0-beta02.txt
@@ -14,6 +14,9 @@
field public static final int AUDIO_STATE_SOURCE_SILENCED = 2; // 0x2
}
+ @SuppressCompatibility @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalPersistentRecording {
+ }
+
@RequiresApi(21) public class FallbackStrategy {
method public static androidx.camera.video.FallbackStrategy higherQualityOrLowerThan(androidx.camera.video.Quality);
method public static androidx.camera.video.FallbackStrategy higherQualityThan(androidx.camera.video.Quality);
@@ -75,6 +78,7 @@
}
@RequiresApi(21) public final class PendingRecording {
+ method @SuppressCompatibility @androidx.camera.video.ExperimentalPersistentRecording public androidx.camera.video.PendingRecording asPersistentRecording();
method @CheckResult public androidx.camera.video.Recording start(java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.camera.video.VideoRecordEvent!>);
method @RequiresPermission(android.Manifest.permission.RECORD_AUDIO) public androidx.camera.video.PendingRecording withAudioEnabled();
}
@@ -122,6 +126,7 @@
@RequiresApi(21) public final class Recording implements java.lang.AutoCloseable {
method public void close();
+ method @SuppressCompatibility @androidx.camera.video.ExperimentalPersistentRecording public boolean isPersistent();
method public void mute(boolean);
method public void pause();
method public void resume();
diff --git a/camera/camera-video/api/current.txt b/camera/camera-video/api/current.txt
index fb0f4c7..b9cc77a 100644
--- a/camera/camera-video/api/current.txt
+++ b/camera/camera-video/api/current.txt
@@ -14,6 +14,9 @@
field public static final int AUDIO_STATE_SOURCE_SILENCED = 2; // 0x2
}
+ @SuppressCompatibility @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalPersistentRecording {
+ }
+
@RequiresApi(21) public class FallbackStrategy {
method public static androidx.camera.video.FallbackStrategy higherQualityOrLowerThan(androidx.camera.video.Quality);
method public static androidx.camera.video.FallbackStrategy higherQualityThan(androidx.camera.video.Quality);
@@ -75,6 +78,7 @@
}
@RequiresApi(21) public final class PendingRecording {
+ method @SuppressCompatibility @androidx.camera.video.ExperimentalPersistentRecording public androidx.camera.video.PendingRecording asPersistentRecording();
method @CheckResult public androidx.camera.video.Recording start(java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.camera.video.VideoRecordEvent!>);
method @RequiresPermission(android.Manifest.permission.RECORD_AUDIO) public androidx.camera.video.PendingRecording withAudioEnabled();
}
@@ -122,6 +126,7 @@
@RequiresApi(21) public final class Recording implements java.lang.AutoCloseable {
method public void close();
+ method @SuppressCompatibility @androidx.camera.video.ExperimentalPersistentRecording public boolean isPersistent();
method public void mute(boolean);
method public void pause();
method public void resume();
diff --git a/camera/camera-video/api/restricted_1.3.0-beta02.txt b/camera/camera-video/api/restricted_1.3.0-beta02.txt
index fb0f4c7..b9cc77a 100644
--- a/camera/camera-video/api/restricted_1.3.0-beta02.txt
+++ b/camera/camera-video/api/restricted_1.3.0-beta02.txt
@@ -14,6 +14,9 @@
field public static final int AUDIO_STATE_SOURCE_SILENCED = 2; // 0x2
}
+ @SuppressCompatibility @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalPersistentRecording {
+ }
+
@RequiresApi(21) public class FallbackStrategy {
method public static androidx.camera.video.FallbackStrategy higherQualityOrLowerThan(androidx.camera.video.Quality);
method public static androidx.camera.video.FallbackStrategy higherQualityThan(androidx.camera.video.Quality);
@@ -75,6 +78,7 @@
}
@RequiresApi(21) public final class PendingRecording {
+ method @SuppressCompatibility @androidx.camera.video.ExperimentalPersistentRecording public androidx.camera.video.PendingRecording asPersistentRecording();
method @CheckResult public androidx.camera.video.Recording start(java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.camera.video.VideoRecordEvent!>);
method @RequiresPermission(android.Manifest.permission.RECORD_AUDIO) public androidx.camera.video.PendingRecording withAudioEnabled();
}
@@ -122,6 +126,7 @@
@RequiresApi(21) public final class Recording implements java.lang.AutoCloseable {
method public void close();
+ method @SuppressCompatibility @androidx.camera.video.ExperimentalPersistentRecording public boolean isPersistent();
method public void mute(boolean);
method public void pause();
method public void resume();
diff --git a/camera/camera-video/api/restricted_current.txt b/camera/camera-video/api/restricted_current.txt
index fb0f4c7..b9cc77a 100644
--- a/camera/camera-video/api/restricted_current.txt
+++ b/camera/camera-video/api/restricted_current.txt
@@ -14,6 +14,9 @@
field public static final int AUDIO_STATE_SOURCE_SILENCED = 2; // 0x2
}
+ @SuppressCompatibility @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalPersistentRecording {
+ }
+
@RequiresApi(21) public class FallbackStrategy {
method public static androidx.camera.video.FallbackStrategy higherQualityOrLowerThan(androidx.camera.video.Quality);
method public static androidx.camera.video.FallbackStrategy higherQualityThan(androidx.camera.video.Quality);
@@ -75,6 +78,7 @@
}
@RequiresApi(21) public final class PendingRecording {
+ method @SuppressCompatibility @androidx.camera.video.ExperimentalPersistentRecording public androidx.camera.video.PendingRecording asPersistentRecording();
method @CheckResult public androidx.camera.video.Recording start(java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.camera.video.VideoRecordEvent!>);
method @RequiresPermission(android.Manifest.permission.RECORD_AUDIO) public androidx.camera.video.PendingRecording withAudioEnabled();
}
@@ -122,6 +126,7 @@
@RequiresApi(21) public final class Recording implements java.lang.AutoCloseable {
method public void close();
+ method @SuppressCompatibility @androidx.camera.video.ExperimentalPersistentRecording public boolean isPersistent();
method public void mute(boolean);
method public void pause();
method public void resume();
diff --git a/camera/camera-video/src/androidTest/java/androidx/camera/video/VideoCaptureDeviceTest.kt b/camera/camera-video/src/androidTest/java/androidx/camera/video/VideoCaptureDeviceTest.kt
index 43507b1..ecbde2c 100644
--- a/camera/camera-video/src/androidTest/java/androidx/camera/video/VideoCaptureDeviceTest.kt
+++ b/camera/camera-video/src/androidTest/java/androidx/camera/video/VideoCaptureDeviceTest.kt
@@ -28,8 +28,10 @@
import androidx.camera.core.CameraXConfig
import androidx.camera.core.DynamicRange
import androidx.camera.core.DynamicRange.BIT_DEPTH_10_BIT
+import androidx.camera.core.DynamicRange.HDR10_10_BIT
import androidx.camera.core.DynamicRange.HDR_UNSPECIFIED_10_BIT
import androidx.camera.core.DynamicRange.HLG_10_BIT
+import androidx.camera.core.DynamicRange.SDR
import androidx.camera.core.Preview
import androidx.camera.core.SurfaceRequest
import androidx.camera.core.impl.CameraInfoInternal
@@ -39,7 +41,9 @@
import androidx.camera.testing.CameraPipeConfigTestRule
import androidx.camera.testing.CameraUtil
import androidx.camera.testing.CameraXUtil
+import androidx.camera.testing.EncoderProfilesUtil.RESOLUTION_1080P
import androidx.camera.testing.EncoderProfilesUtil.RESOLUTION_2160P
+import androidx.camera.testing.EncoderProfilesUtil.RESOLUTION_480P
import androidx.camera.testing.EncoderProfilesUtil.RESOLUTION_720P
import androidx.camera.testing.EncoderProfilesUtil.createFakeEncoderProfilesProxy
import androidx.camera.testing.GLUtil
@@ -120,14 +124,18 @@
private val context: Context = ApplicationProvider.getApplicationContext()
private val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
- // TODO(b/278168212): Only SDR is checked by now. Need to extend to HDR dynamic ranges.
- private val dynamicRange = DynamicRange.SDR
private val supportedResolutionMap = mapOf(
- DynamicRange.SDR to linkedMapOf(
+ SDR to mapOf(
Quality.HIGHEST to RESOLUTION_2160P,
Quality.UHD to RESOLUTION_2160P,
Quality.HD to RESOLUTION_720P,
Quality.LOWEST to RESOLUTION_720P
+ ),
+ HDR10_10_BIT to mapOf(
+ Quality.HIGHEST to RESOLUTION_1080P,
+ Quality.FHD to RESOLUTION_1080P,
+ Quality.SD to RESOLUTION_480P,
+ Quality.LOWEST to RESOLUTION_480P
)
)
@@ -230,43 +238,46 @@
assumeExtraCroppingQuirk(implName)
val videoCapabilities = createFakeVideoCapabilities(supportedResolutionMap)
- assumeTrue(videoCapabilities.getSupportedQualities(dynamicRange).isNotEmpty())
- // Cuttlefish API 29 has inconsistent resolution issue. See b/184015059.
- assumeFalse(Build.MODEL.contains("Cuttlefish") && Build.VERSION.SDK_INT == 29)
+ videoCapabilities.supportedDynamicRanges.forEach { dynamicRange ->
+ assumeTrue(videoCapabilities.getSupportedQualities(dynamicRange).isNotEmpty())
+ // Cuttlefish API 29 has inconsistent resolution issue. See b/184015059.
+ assumeFalse(Build.MODEL.contains("Cuttlefish") && Build.VERSION.SDK_INT == 29)
- // Arrange.
- val qualityList = videoCapabilities.getSupportedQualities(dynamicRange)
- qualityList.forEach loop@{ quality ->
- val profile = videoCapabilities.getProfiles(quality, dynamicRange)!!.defaultVideoProfile
- val targetResolution = Size(profile.width, profile.height)
- val videoOutput = createTestVideoOutput(
- mediaSpec = MediaSpec.builder().configureVideo {
- it.setQualitySelector(QualitySelector.from(quality))
- }.build(),
- videoCapabilities = videoCapabilities
- )
+ // Arrange.
+ val qualityList = videoCapabilities.getSupportedQualities(dynamicRange)
+ qualityList.forEach loop@{ quality ->
+ val profile =
+ videoCapabilities.getProfiles(quality, dynamicRange)!!.defaultVideoProfile
+ val targetResolution = Size(profile.width, profile.height)
+ val videoOutput = createTestVideoOutput(
+ mediaSpec = MediaSpec.builder().configureVideo {
+ it.setQualitySelector(QualitySelector.from(quality))
+ }.build(),
+ videoCapabilities = videoCapabilities
+ )
- // Use custom VideoEncoderInfoFinder which always returns default FakeVideoEncoderInfo,
- // which tolerance typical resolutions.
- val videoCapture = VideoCapture.Builder(videoOutput)
- .setVideoEncoderInfoFinder { FakeVideoEncoderInfo() }.build()
+ // Use custom VideoEncoderInfoFinder which always returns default
+ // FakeVideoEncoderInfo, which tolerance typical resolutions.
+ val videoCapture = VideoCapture.Builder(videoOutput)
+ .setVideoEncoderInfoFinder { FakeVideoEncoderInfo() }.build()
- // Act.
- if (!cameraUseCaseAdapter.isUseCasesCombinationSupported(videoCapture)) {
- return@loop
- }
- withContext(Dispatchers.Main) {
- cameraUseCaseAdapter.addUseCases(listOf(videoCapture))
- }
+ // Act.
+ if (!cameraUseCaseAdapter.isUseCasesCombinationSupported(videoCapture)) {
+ return@loop
+ }
+ withContext(Dispatchers.Main) {
+ cameraUseCaseAdapter.addUseCases(listOf(videoCapture))
+ }
- // Assert.
- assertWithMessage("Set quality value by $quality")
- .that(videoCapture.attachedSurfaceResolution).isEqualTo(targetResolution)
+ // Assert.
+ assertWithMessage("Set quality value by $quality")
+ .that(videoCapture.attachedSurfaceResolution).isEqualTo(targetResolution)
- // Cleanup.
- withContext(Dispatchers.Main) {
- cameraUseCaseAdapter.apply {
- removeUseCases(listOf(videoCapture))
+ // Cleanup.
+ withContext(Dispatchers.Main) {
+ cameraUseCaseAdapter.apply {
+ removeUseCases(listOf(videoCapture))
+ }
}
}
}
@@ -384,7 +395,7 @@
@Test
fun defaultDynamicRange_isSdr(): Unit = runBlocking {
testDynamicRangeSelection { selectedDynamicRange ->
- assertThat(selectedDynamicRange).isEqualTo(DynamicRange.SDR)
+ assertThat(selectedDynamicRange).isEqualTo(SDR)
}
}
@@ -527,7 +538,7 @@
* Create a fake VideoCapabilities.
*/
private fun createFakeVideoCapabilities(
- resolutionMap: Map<DynamicRange, LinkedHashMap<Quality, Size>>
+ resolutionMap: Map<DynamicRange, Map<Quality, Size>>
): VideoCapabilities {
return object : VideoCapabilities {
diff --git a/camera/camera-video/src/main/java/androidx/camera/video/ExperimentalPersistentRecording.java b/camera/camera-video/src/main/java/androidx/camera/video/ExperimentalPersistentRecording.java
new file mode 100644
index 0000000..08a702c
--- /dev/null
+++ b/camera/camera-video/src/main/java/androidx/camera/video/ExperimentalPersistentRecording.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.video;
+
+import static java.lang.annotation.RetentionPolicy.CLASS;
+
+import androidx.annotation.RequiresOptIn;
+
+import java.lang.annotation.Retention;
+
+/**
+ * Denotes that the annotated method uses the experimental methods which allows a recording to be
+ * persistent.
+ *
+ * <p>A persistent recording will only be stopped by explicitly calling {@link Recording#stop()}
+ * or {@link Recording#close()} and will ignore events that would normally cause recording to
+ * stop, such as lifecycle events or explicit unbinding of a {@link VideoCapture} use case that
+ * the recording's {@link Recorder} is attached to.
+ */
+@Retention(CLASS)
+@RequiresOptIn
+public @interface ExperimentalPersistentRecording {
+}
diff --git a/camera/camera-video/src/main/java/androidx/camera/video/PendingRecording.java b/camera/camera-video/src/main/java/androidx/camera/video/PendingRecording.java
index a242264..2307d71 100644
--- a/camera/camera-video/src/main/java/androidx/camera/video/PendingRecording.java
+++ b/camera/camera-video/src/main/java/androidx/camera/video/PendingRecording.java
@@ -24,8 +24,6 @@
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.annotation.RequiresPermission;
-import androidx.annotation.RestrictTo;
-import androidx.annotation.RestrictTo.Scope;
import androidx.camera.core.impl.utils.ContextUtil;
import androidx.core.content.PermissionChecker;
import androidx.core.util.Consumer;
@@ -151,13 +149,55 @@
* normally cause recording to stop, such as lifecycle events or explicit unbinding of a
* {@link VideoCapture} use case that the recording's {@link Recorder} is attached to.
*
+ * <p>Even though lifecycle events or explicit unbinding use cases won't stop a persistent
+ * recording, it will still stop the camera from producing data, resulting in the in-progress
+ * persistent recording stopping getting data until the camera stream is activated again. For
+ * example, when the activity goes into background, the recording will keep waiting for new
+ * data to be recorded until the activity is back to foreground.
+ *
* <p>A {@link Recorder} instance is recommended to be associated with a single
* {@link VideoCapture} instance, especially when using persistent recording. Otherwise, there
* might be unexpected behavior. Any in-progress persistent recording created from the same
* {@link Recorder} should be stopped before starting a new recording, even if the
* {@link Recorder} is associated with a different {@link VideoCapture}.
+ *
+ * <p>To switch to a different camera stream while a recording is in progress, first create
+ * the recording as persistent recording, then rebind the {@link VideoCapture} it's
+ * associated with to a different camera. The implementation may be like:
+ * <pre>{@code
+ * // Prepare the Recorder and VideoCapture, then bind the VideoCapture to the back camera.
+ * Recorder recorder = Recorder.Builder().build();
+ * VideoCapture videoCapture = VideoCapture.withOutput(recorder);
+ * cameraProvider.bindToLifecycle(
+ * lifecycleOwner, CameraSelector.DEFAULT_BACK_CAMERA, videoCapture);
+ *
+ * // Prepare the persistent recording and start it.
+ * Recording recording = recorder
+ * .prepareRecording(context, outputOptions)
+ * .asPersistentRecording()
+ * .start(eventExecutor, eventListener);
+ *
+ * // Record from the back camera for a period of time.
+ *
+ * // Rebind the VideoCapture to the front camera.
+ * cameraProvider.unbindAll();
+ * cameraProvider.bindToLifecycle(
+ * lifecycleOwner, CameraSelector.DEFAULT_FRONT_CAMERA, videoCapture);
+ *
+ * // Record from the front camera for a period of time.
+ *
+ * // Stop the recording explicitly.
+ * recording.stop();
+ * }</pre>
+ *
+ * <p>The audio data will still be recorded after the {@link VideoCapture} is unbound.
+ * {@link Recording#pause() Pause} the recording first and {@link Recording#resume() resume} it
+ * later to stop recording audio while rebinding use cases.
+ *
+ * <p>If the recording is unable to receive data from the new camera, possibly because of
+ * incompatible surface combination, an exception will be thrown when binding to lifecycle.
*/
- @RestrictTo(Scope.LIBRARY_GROUP)
+ @ExperimentalPersistentRecording
@NonNull
public PendingRecording asPersistentRecording() {
mIsPersistent = true;
@@ -188,6 +228,10 @@
* {@link VideoRecordEvent.Finalize} event will contain error
* {@link VideoRecordEvent.Finalize#ERROR_RECORDING_GARBAGE_COLLECTED}.
*
+ * <p>The {@link Recording} will be stopped automatically if the {@link VideoCapture} its
+ * {@link Recorder} is attached to is unbound unless it's created
+ * {@link #asPersistentRecording() as a persistent recording}.
+ *
* @throws IllegalStateException if the associated Recorder currently has an unfinished
* active recording.
* @param listenerExecutor the executor that the event listener will be run on.
diff --git a/camera/camera-video/src/main/java/androidx/camera/video/Recording.java b/camera/camera-video/src/main/java/androidx/camera/video/Recording.java
index 297935b..5a603a0 100644
--- a/camera/camera-video/src/main/java/androidx/camera/video/Recording.java
+++ b/camera/camera-video/src/main/java/androidx/camera/video/Recording.java
@@ -122,7 +122,7 @@
*
* @return {@code true} if the recording is a persistent recording, otherwise {@code false}.
*/
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ @ExperimentalPersistentRecording
public boolean isPersistent() {
return mIsPersistent;
}
diff --git a/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraXActivity.java b/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraXActivity.java
index af3c564..b3e6e80 100644
--- a/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraXActivity.java
+++ b/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraXActivity.java
@@ -113,6 +113,7 @@
import androidx.camera.core.impl.utils.AspectRatioUtil;
import androidx.camera.core.impl.utils.executor.CameraXExecutors;
import androidx.camera.lifecycle.ProcessCameraProvider;
+import androidx.camera.video.ExperimentalPersistentRecording;
import androidx.camera.video.FileOutputOptions;
import androidx.camera.video.MediaStoreOutputOptions;
import androidx.camera.video.OutputOptions;
@@ -581,7 +582,8 @@
});
}
- @SuppressLint("MissingPermission")
+ @SuppressLint({"MissingPermission", "NullAnnotationGroup"})
+ @OptIn(markerClass = ExperimentalPersistentRecording.class)
private void setUpRecordButton() {
mRecordUi.getButtonRecord().setOnClickListener((view) -> {
RecordUi.State state = mRecordUi.getState();
diff --git a/car/app/app-automotive/build.gradle b/car/app/app-automotive/build.gradle
index 7361f0e..7c764d5 100644
--- a/car/app/app-automotive/build.gradle
+++ b/car/app/app-automotive/build.gradle
@@ -24,18 +24,14 @@
dependencies {
api(project(":car:app:app"))
api(libs.guavaListenableFuture)
+ api("androidx.annotation:annotation-experimental:1.3.1")
implementation(libs.guavaAndroid)
- implementation "androidx.concurrent:concurrent-futures:1.1.0"
+ implementation("androidx.concurrent:concurrent-futures:1.1.0")
implementation("androidx.fragment:fragment:1.3.0")
implementation("androidx.lifecycle:lifecycle-common-java8:2.2.0")
implementation("androidx.annotation:annotation:1.1.0")
implementation("androidx.core:core:1.7.0")
implementation(libs.autoValueAnnotations)
- api("androidx.annotation:annotation-experimental:1.1.0")
- // There is an implicit compile-only dep due to :annotation-experimental
- // Build will complain without this manual declaration.
- api(libs.kotlinStdlib)
- implementation "androidx.annotation:annotation-experimental:1.3.0"
annotationProcessor(libs.nullaway)
annotationProcessor(libs.autoValue)
diff --git a/car/app/app/api/current.txt b/car/app/app/api/current.txt
index 5c2d5cb..261afe98 100644
--- a/car/app/app/api/current.txt
+++ b/car/app/app/api/current.txt
@@ -874,7 +874,9 @@
package androidx.car.app.messaging.model {
@SuppressCompatibility @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.ExperimentalCarApi @androidx.car.app.annotations.RequiresCarApi(7) public class CarMessage {
- method public androidx.car.app.model.CarText getBody();
+ method public androidx.car.app.model.CarText? getBody();
+ method public String? getMultimediaMimeType();
+ method public android.net.Uri? getMultimediaUri();
method public long getReceivedTimeEpochMillis();
method public androidx.core.app.Person? getSender();
method public boolean isRead();
@@ -883,7 +885,9 @@
public static final class CarMessage.Builder {
ctor public CarMessage.Builder();
method public androidx.car.app.messaging.model.CarMessage build();
- method public androidx.car.app.messaging.model.CarMessage.Builder setBody(androidx.car.app.model.CarText);
+ method public androidx.car.app.messaging.model.CarMessage.Builder setBody(androidx.car.app.model.CarText?);
+ method public androidx.car.app.messaging.model.CarMessage.Builder setMultimediaMimeType(String?);
+ method public androidx.car.app.messaging.model.CarMessage.Builder setMultimediaUri(android.net.Uri?);
method public androidx.car.app.messaging.model.CarMessage.Builder setRead(boolean);
method public androidx.car.app.messaging.model.CarMessage.Builder setReceivedTimeEpochMillis(long);
method public androidx.car.app.messaging.model.CarMessage.Builder setSender(androidx.core.app.Person?);
@@ -900,6 +904,7 @@
}
@SuppressCompatibility @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.ExperimentalCarApi @androidx.car.app.annotations.RequiresCarApi(7) public class ConversationItem implements androidx.car.app.model.Item {
+ method public java.util.List<androidx.car.app.model.Action!> getActions();
method public androidx.car.app.messaging.model.ConversationCallbackDelegate getConversationCallbackDelegate();
method public androidx.car.app.model.CarIcon? getIcon();
method public String getId();
@@ -912,6 +917,7 @@
public static final class ConversationItem.Builder {
ctor public ConversationItem.Builder();
ctor public ConversationItem.Builder(androidx.car.app.messaging.model.ConversationItem);
+ method public androidx.car.app.messaging.model.ConversationItem.Builder addAction(androidx.car.app.model.Action);
method public androidx.car.app.messaging.model.ConversationItem build();
method public androidx.car.app.messaging.model.ConversationItem.Builder setConversationCallback(androidx.car.app.messaging.model.ConversationCallback);
method public androidx.car.app.messaging.model.ConversationItem.Builder setGroupConversation(boolean);
diff --git a/car/app/app/api/restricted_current.txt b/car/app/app/api/restricted_current.txt
index 5c2d5cb..261afe98 100644
--- a/car/app/app/api/restricted_current.txt
+++ b/car/app/app/api/restricted_current.txt
@@ -874,7 +874,9 @@
package androidx.car.app.messaging.model {
@SuppressCompatibility @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.ExperimentalCarApi @androidx.car.app.annotations.RequiresCarApi(7) public class CarMessage {
- method public androidx.car.app.model.CarText getBody();
+ method public androidx.car.app.model.CarText? getBody();
+ method public String? getMultimediaMimeType();
+ method public android.net.Uri? getMultimediaUri();
method public long getReceivedTimeEpochMillis();
method public androidx.core.app.Person? getSender();
method public boolean isRead();
@@ -883,7 +885,9 @@
public static final class CarMessage.Builder {
ctor public CarMessage.Builder();
method public androidx.car.app.messaging.model.CarMessage build();
- method public androidx.car.app.messaging.model.CarMessage.Builder setBody(androidx.car.app.model.CarText);
+ method public androidx.car.app.messaging.model.CarMessage.Builder setBody(androidx.car.app.model.CarText?);
+ method public androidx.car.app.messaging.model.CarMessage.Builder setMultimediaMimeType(String?);
+ method public androidx.car.app.messaging.model.CarMessage.Builder setMultimediaUri(android.net.Uri?);
method public androidx.car.app.messaging.model.CarMessage.Builder setRead(boolean);
method public androidx.car.app.messaging.model.CarMessage.Builder setReceivedTimeEpochMillis(long);
method public androidx.car.app.messaging.model.CarMessage.Builder setSender(androidx.core.app.Person?);
@@ -900,6 +904,7 @@
}
@SuppressCompatibility @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.ExperimentalCarApi @androidx.car.app.annotations.RequiresCarApi(7) public class ConversationItem implements androidx.car.app.model.Item {
+ method public java.util.List<androidx.car.app.model.Action!> getActions();
method public androidx.car.app.messaging.model.ConversationCallbackDelegate getConversationCallbackDelegate();
method public androidx.car.app.model.CarIcon? getIcon();
method public String getId();
@@ -912,6 +917,7 @@
public static final class ConversationItem.Builder {
ctor public ConversationItem.Builder();
ctor public ConversationItem.Builder(androidx.car.app.messaging.model.ConversationItem);
+ method public androidx.car.app.messaging.model.ConversationItem.Builder addAction(androidx.car.app.model.Action);
method public androidx.car.app.messaging.model.ConversationItem build();
method public androidx.car.app.messaging.model.ConversationItem.Builder setConversationCallback(androidx.car.app.messaging.model.ConversationCallback);
method public androidx.car.app.messaging.model.ConversationItem.Builder setGroupConversation(boolean);
diff --git a/car/app/app/src/main/java/androidx/car/app/messaging/model/CarMessage.java b/car/app/app/src/main/java/androidx/car/app/messaging/model/CarMessage.java
index e81089d..303ce3d 100644
--- a/car/app/app/src/main/java/androidx/car/app/messaging/model/CarMessage.java
+++ b/car/app/app/src/main/java/androidx/car/app/messaging/model/CarMessage.java
@@ -18,8 +18,7 @@
import static androidx.car.app.messaging.model.ConversationItem.validateSender;
-import static java.util.Objects.requireNonNull;
-
+import android.net.Uri;
import android.os.Bundle;
import androidx.annotation.NonNull;
@@ -41,8 +40,12 @@
public class CarMessage {
@Nullable
private final Bundle mSender;
- @NonNull
+ @Nullable
private final CarText mBody;
+ @Nullable
+ private final String mMultimediaMimeType;
+ @Nullable
+ private final Uri mMultimediaUri;
private final long mReceivedTimeEpochMillis;
private final boolean mIsRead;
@@ -75,7 +78,9 @@
CarMessage(@NonNull Builder builder) {
this.mSender = builder.mSender == null ? null : validateSender(builder.mSender).toBundle();
- this.mBody = requireNonNull(builder.mBody);
+ this.mBody = builder.mBody;
+ this.mMultimediaMimeType = builder.mMultimediaMimeType;
+ this.mMultimediaUri = builder.mMultimediaUri;
this.mReceivedTimeEpochMillis = builder.mReceivedTimeEpochMillis;
this.mIsRead = builder.mIsRead;
}
@@ -83,7 +88,9 @@
/** Default constructor for serialization. */
private CarMessage() {
this.mSender = null;
- this.mBody = new CarText.Builder("").build();
+ this.mBody = null;
+ this.mMultimediaMimeType = null;
+ this.mMultimediaUri = null;
this.mReceivedTimeEpochMillis = 0;
this.mIsRead = false;
}
@@ -100,12 +107,57 @@
return mSender == null ? null : Person.fromBundle(mSender);
}
- /** Returns a {@link CarText} representing the message body */
- @NonNull
+ /**
+ * Returns a {@link CarText} representing the message body
+ *
+ * <p> Messages must have one or both of the following:
+ * <ul>
+ * <li> A message body (text)
+ * <li> A MIME type + URI (image, audio, etc.)
+ * </ul>
+ *
+ * @see #getMultimediaMimeType()
+ * @see #getMultimediaUri()
+ */
+ @Nullable
public CarText getBody() {
return mBody;
}
+ /**
+ * Returns a {@link String} representing the MIME type of a multimedia message
+ *
+ * <p> Messages must have one or both of the following:
+ * <ul>
+ * <li> A message body (text)
+ * <li> A MIME type + URI (image, audio, etc.)
+ * </ul>
+ *
+ * @see #getBody()
+ * @see #getMultimediaUri()
+ */
+ @Nullable
+ public String getMultimediaMimeType() {
+ return mMultimediaMimeType;
+ }
+
+ /**
+ * Returns a {@link Uri} pointing to the contents of a multimedia message.
+ *
+ * <p> Messages must have one or both of the following:
+ * <ul>
+ * <li> A message body (text)
+ * <li> A MIME type + URI (image, audio, etc.)
+ * </ul>
+ *
+ * @see #getBody()
+ * @see #getMultimediaMimeType()
+ */
+ @Nullable
+ public Uri getMultimediaUri() {
+ return mMultimediaUri;
+ }
+
/** Returns a {@code long} representing the message timestamp (in epoch millis) */
public long getReceivedTimeEpochMillis() {
return mReceivedTimeEpochMillis;
@@ -122,6 +174,10 @@
Person mSender;
@Nullable
CarText mBody;
+ @Nullable
+ String mMultimediaMimeType;
+ @Nullable
+ Uri mMultimediaUri;
long mReceivedTimeEpochMillis;
boolean mIsRead;
@@ -137,12 +193,57 @@
return this;
}
- /** Sets a {@link CarText} representing the message body */
- public @NonNull Builder setBody(@NonNull CarText body) {
+ /**
+ * Sets a {@link CarText} representing the message body
+ *
+ * <p> Messages must have one or both of the following:
+ * <ul>
+ * <li> A message body (text)
+ * <li> A MIME type + URI (image, audio, etc.)
+ * </ul>
+ *
+ * @see #setMultimediaMimeType(String)
+ * @see #setMultimediaUri(Uri)
+ */
+ public @NonNull Builder setBody(@Nullable CarText body) {
mBody = body;
return this;
}
+ /**
+ * Sets a {@link String} representing the MIME type of a multimedia message
+ *
+ * <p> Messages must have one or both of the following:
+ * <ul>
+ * <li> A message body (text)
+ * <li> A MIME type + URI (image, audio, etc.)
+ * </ul>
+ *
+ * @see #setBody(CarText)
+ * @see #setMultimediaUri(Uri)
+ */
+ public @NonNull Builder setMultimediaMimeType(@Nullable String multimediaMimeType) {
+ this.mMultimediaMimeType = multimediaMimeType;
+ return this;
+ }
+
+ /**
+ * Sets a {@link Uri} pointing to the contents of a multimedia message.
+ *
+ * <p> Messages must have one or both of the following:
+ * <ul>
+ * <li> A message body (text)
+ * <li> A MIME type + URI (image, audio, etc.)
+ * </ul>
+ *
+ * @see #setBody(CarText)
+ * @see #setMultimediaMimeType(String)
+ */
+ public @NonNull Builder setMultimediaUri(@Nullable Uri multimediaUri) {
+ this.mMultimediaUri = multimediaUri;
+ return this;
+ }
+
/** Sets a {@code long} representing the message timestamp (in epoch millis) */
public @NonNull Builder setReceivedTimeEpochMillis(long receivedTimeEpochMillis) {
mReceivedTimeEpochMillis = receivedTimeEpochMillis;
@@ -157,6 +258,20 @@
/** Returns a new {@link CarMessage} instance defined by this builder */
public @NonNull CarMessage build() {
+ if (mMultimediaMimeType == null ^ mMultimediaUri == null) {
+ throw new IllegalStateException("Incomplete multimedia data detected in "
+ + "CarMessage. Please be sure to provide both MIME type and URI for "
+ + "multimedia messages.");
+ }
+
+ // Conceptually, we're checking that body text and multimedia data (mime type or URI)
+ // are null.
+ // The compiler complains if I check both mime type and URI, due to previous validation.
+ if (mBody == null && mMultimediaMimeType == null) {
+ throw new IllegalStateException("Message must have content. Please provide body "
+ + "text, multimedia data (URI + MIME type), or both.");
+ }
+
return new CarMessage(this);
}
}
diff --git a/car/app/app/src/main/java/androidx/car/app/messaging/model/ConversationItem.java b/car/app/app/src/main/java/androidx/car/app/messaging/model/ConversationItem.java
index d83d5b0..1acc0f4 100644
--- a/car/app/app/src/main/java/androidx/car/app/messaging/model/ConversationItem.java
+++ b/car/app/app/src/main/java/androidx/car/app/messaging/model/ConversationItem.java
@@ -29,13 +29,16 @@
import androidx.car.app.annotations.ExperimentalCarApi;
import androidx.car.app.annotations.KeepFields;
import androidx.car.app.annotations.RequiresCarApi;
+import androidx.car.app.model.Action;
import androidx.car.app.model.CarIcon;
import androidx.car.app.model.CarText;
import androidx.car.app.model.Item;
+import androidx.car.app.model.constraints.ActionsConstraints;
import androidx.car.app.utils.CollectionUtils;
import androidx.core.app.Person;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import java.util.Objects;
@@ -58,6 +61,8 @@
private final List<CarMessage> mMessages;
@NonNull
private final ConversationCallbackDelegate mConversationCallbackDelegate;
+ @NonNull
+ private final List<Action> mActions;
@Override
public int hashCode() {
@@ -67,7 +72,8 @@
mTitle,
mIcon,
mIsGroupConversation,
- mMessages
+ mMessages,
+ mActions
);
}
@@ -89,6 +95,7 @@
.arePersonsEqual(getSelf(), otherConversationItem.getSelf())
&& mIsGroupConversation == otherConversationItem.mIsGroupConversation
&& Objects.equals(mMessages, otherConversationItem.mMessages)
+ && Objects.equals(mActions, otherConversationItem.mActions)
;
}
@@ -101,6 +108,7 @@
this.mMessages = requireNonNull(CollectionUtils.unmodifiableCopy(builder.mMessages));
checkState(!mMessages.isEmpty(), "Message list cannot be empty.");
this.mConversationCallbackDelegate = requireNonNull(builder.mConversationCallbackDelegate);
+ this.mActions = CollectionUtils.unmodifiableCopy(builder.mActions);
}
/** Default constructor for serialization. */
@@ -123,6 +131,7 @@
// Do nothing
}
});
+ mActions = Collections.emptyList();
}
/**
@@ -175,6 +184,16 @@
}
/**
+ * Returns the list of additional actions.
+ *
+ * @see ConversationItem.Builder#addAction(Action)
+ */
+ @NonNull
+ public List<Action> getActions() {
+ return mActions;
+ }
+
+ /**
* Verifies that a given {@link Person} has the required fields to be a message sender. Returns
* the input {@link Person} if valid, or throws an exception if invalid.
*
@@ -202,6 +221,7 @@
List<CarMessage> mMessages;
@Nullable
ConversationCallbackDelegate mConversationCallbackDelegate;
+ final List<Action> mActions;
/**
* Specifies a unique identifier for the conversation
@@ -278,6 +298,23 @@
return this;
}
+ /**
+ * Adds an additional action for the conversation.
+ *
+ * @throws NullPointerException if {@code action} is {@code null}
+ * @throws IllegalArgumentException if {@code action} contains unsupported Action types,
+ * exceeds the maximum number of allowed actions (1) or
+ * does not contain a valid {@link CarIcon}.
+ */
+ @NonNull
+ public Builder addAction(@NonNull Action action) {
+ List<Action> mActionsCopy = new ArrayList<>(mActions);
+ mActionsCopy.add(requireNonNull(action));
+ ActionsConstraints.ACTIONS_CONSTRAINTS_CONVERSATION_ITEM.validateOrThrow(mActionsCopy);
+ mActions.add(action);
+ return this;
+ }
+
/** Returns a new {@link ConversationItem} instance defined by this builder */
@NonNull
public ConversationItem build() {
@@ -286,6 +323,7 @@
/** Returns an empty {@link Builder} instance. */
public Builder() {
+ mActions = new ArrayList<>();
}
/** Returns a builder from the given {@link ConversationItem}. */
@@ -297,6 +335,7 @@
this.mIsGroupConversation = other.isGroupConversation();
this.mConversationCallbackDelegate = other.getConversationCallbackDelegate();
this.mMessages = other.getMessages();
+ this.mActions = new ArrayList<>(other.getActions());
}
}
}
diff --git a/car/app/app/src/main/java/androidx/car/app/model/constraints/ActionsConstraints.java b/car/app/app/src/main/java/androidx/car/app/model/constraints/ActionsConstraints.java
index b91efc0f..e61e54b 100644
--- a/car/app/app/src/main/java/androidx/car/app/model/constraints/ActionsConstraints.java
+++ b/car/app/app/src/main/java/androidx/car/app/model/constraints/ActionsConstraints.java
@@ -145,6 +145,19 @@
.build();
/**
+ * Constraints for additional ConversationItem actions. Only allows custom actions.
+ */
+ @NonNull
+ public static final ActionsConstraints ACTIONS_CONSTRAINTS_CONVERSATION_ITEM =
+ new ActionsConstraints.Builder()
+ .setMaxActions(1)
+ .setMaxCustomTitles(1)
+ .addAllowedActionType(Action.TYPE_CUSTOM)
+ .setRequireActionIcons(true)
+ .setOnClickListenerAllowed(true)
+ .build();
+
+ /**
* Constraints for floating action buttons.
*
* <p>Only buttons with icons and background color are allowed.
diff --git a/car/app/app/src/test/java/androidx/car/app/messaging/model/CarMessageTest.java b/car/app/app/src/test/java/androidx/car/app/messaging/model/CarMessageTest.java
index 06caf6b..c574bc6 100644
--- a/car/app/app/src/test/java/androidx/car/app/messaging/model/CarMessageTest.java
+++ b/car/app/app/src/test/java/androidx/car/app/messaging/model/CarMessageTest.java
@@ -39,6 +39,14 @@
// assert no crash
}
+ /** Ensure the builder does not fail for the minimum set of required fields. */
+ @Test
+ public void build_withRequiredFieldsOnly_multimedia() {
+ TestConversationFactory.createMinimalMultimediaMessage();
+
+ // assert no crash
+ }
+
/** Ensure the builder does not fail when all fields are assigned. */
@Test
public void build_withAllFields() {
@@ -47,18 +55,52 @@
// assert no crash
}
- // Ignore nullability, so we can null out a builder field
- @SuppressWarnings("ConstantConditions")
@Test
- public void build_throwsException_ifMessageBodyMissing() {
+ public void build_throwsException_ifMultimediaDataIncomplete() {
assertThrows(
- NullPointerException.class,
- () -> TestConversationFactory.createMinimalMessageBuilder()
- .setBody(null)
+ IllegalStateException.class,
+ () -> TestConversationFactory.createMinimalMultimediaMessageBuilder()
+ .setMultimediaMimeType(null)
+ .build()
+ );
+
+ assertThrows(
+ IllegalStateException.class,
+ () -> TestConversationFactory.createMinimalMultimediaMessageBuilder()
+ .setMultimediaUri(null)
+ .build()
+ );
+
+ // Even if the message has valid body text, incomplete multimedia metadata is still
+ // considered invalid
+ assertThrows(
+ IllegalStateException.class,
+ () -> TestConversationFactory.createFullyPopulatedMessageBuilder()
+ .setMultimediaMimeType(null)
+ .build()
+ );
+
+ assertThrows(
+ IllegalStateException.class,
+ () -> TestConversationFactory.createFullyPopulatedMessageBuilder()
+ .setMultimediaUri(null)
.build()
);
}
+ @Test
+ public void build_throwsException_ifNoContent() {
+ assertThrows(
+ IllegalStateException.class,
+ () -> TestConversationFactory.createFullyPopulatedMessageBuilder()
+ .setBody(null)
+ .setMultimediaMimeType(null)
+ .setMultimediaUri(null)
+ .build()
+ );
+ }
+
+ @Test
public void build_throwsException_ifSenderNameMissing() {
assertThrows(
NullPointerException.class,
@@ -70,6 +112,7 @@
);
}
+ @Test
public void build_throwsException_ifSenderKeyMissing() {
assertThrows(
NullPointerException.class,
diff --git a/car/app/app/src/test/java/androidx/car/app/messaging/model/ConversationItemTest.java b/car/app/app/src/test/java/androidx/car/app/messaging/model/ConversationItemTest.java
index ef2956c..e6b6e49 100644
--- a/car/app/app/src/test/java/androidx/car/app/messaging/model/ConversationItemTest.java
+++ b/car/app/app/src/test/java/androidx/car/app/messaging/model/ConversationItemTest.java
@@ -21,8 +21,11 @@
import static org.junit.Assert.assertThrows;
import androidx.annotation.NonNull;
+import androidx.car.app.TestUtils;
+import androidx.car.app.model.Action;
import androidx.car.app.model.CarIcon;
import androidx.car.app.model.CarText;
+import androidx.test.core.app.ApplicationProvider;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -191,6 +194,77 @@
assertEqual(fullyPopulatedItem, modifiedConversationCallback);
}
+ @Test
+ public void addAction() {
+ CarIcon icon = TestUtils.getTestCarIcon(ApplicationProvider.getApplicationContext(),
+ "ic_test_1");
+ Action customAction = new Action.Builder().setIcon(icon).build();
+ ConversationItem item =
+ TestConversationFactory
+ .createFullyPopulatedConversationItemBuilder()
+ .addAction(customAction)
+ .build();
+
+ assertThat(item.getActions()).containsExactly(customAction);
+ }
+
+ @Test
+ public void addAction_appIconInvalid_throws() {
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> TestConversationFactory
+ .createFullyPopulatedConversationItemBuilder()
+ .addAction(Action.APP_ICON)
+ .build());
+ }
+
+ @Test
+ public void addAction_backInvalid_throws() {
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> TestConversationFactory
+ .createFullyPopulatedConversationItemBuilder()
+ .addAction(Action.BACK)
+ .build());
+ }
+
+ @Test
+ public void addAction_panInvalid_throws() {
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> TestConversationFactory
+ .createFullyPopulatedConversationItemBuilder()
+ .addAction(Action.PAN)
+ .build());
+ }
+
+ @Test
+ public void addAction_manyActions_throws() {
+ CarIcon icon = TestUtils.getTestCarIcon(ApplicationProvider.getApplicationContext(),
+ "ic_test_1");
+ Action customAction = new Action.Builder().setIcon(icon).build();
+
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> TestConversationFactory
+ .createFullyPopulatedConversationItemBuilder()
+ .addAction(customAction)
+ .addAction(customAction)
+ .build());
+ }
+
+ @Test
+ public void addAction_invalidActionNullIcon_throws() {
+ Action customAction = TestUtils.createAction("Title", /* icon= */ null);
+
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> TestConversationFactory
+ .createFullyPopulatedConversationItemBuilder()
+ .addAction(customAction)
+ .build());
+ }
+
private void assertEqual(ConversationItem item1, ConversationItem item2) {
assertThat(item1).isEqualTo(item2);
assertThat(item1.hashCode()).isEqualTo(item2.hashCode());
diff --git a/car/app/app/src/test/java/androidx/car/app/messaging/model/TestConversationFactory.java b/car/app/app/src/test/java/androidx/car/app/messaging/model/TestConversationFactory.java
index bd1274a..72dcbb0 100644
--- a/car/app/app/src/test/java/androidx/car/app/messaging/model/TestConversationFactory.java
+++ b/car/app/app/src/test/java/androidx/car/app/messaging/model/TestConversationFactory.java
@@ -23,6 +23,7 @@
import androidx.car.app.model.CarIcon;
import androidx.car.app.model.CarText;
import androidx.car.app.model.ItemList;
+import androidx.car.app.model.ListTemplate;
import androidx.core.app.Person;
import androidx.core.graphics.drawable.IconCompat;
@@ -47,6 +48,9 @@
}
};
+ public static String MULTIMEDIA_MESSAGE_MIME_TYPE = "coolmimetypes/mycooldataformat";
+ public static Uri MULTIMEDIA_MESSAGE_URI = new Uri.Builder().path("foo/bar/test/uri").build();
+
// region Person
/**
* Creates a {@link Person.Builder} instance for testing
@@ -113,10 +117,32 @@
/**
* Creates a {@link CarMessage.Builder} instance for testing
*
+ * <p>This method fills in the minimum required data to create a valid {@link CarMessage}.
+ */
+ public static CarMessage.Builder createMinimalMultimediaMessageBuilder() {
+ return new CarMessage.Builder()
+ .setMultimediaMimeType(MULTIMEDIA_MESSAGE_MIME_TYPE)
+ .setMultimediaUri(MULTIMEDIA_MESSAGE_URI);
+ }
+
+ /**
+ * Creates a {@link CarMessage} instance for testing
+ *
+ * <p>This method fills in the minimum required data to create a valid {@link CarMessage}.
+ */
+ public static CarMessage createMinimalMultimediaMessage() {
+ return createMinimalMultimediaMessageBuilder().build();
+ }
+
+ /**
+ * Creates a {@link CarMessage.Builder} instance for testing
+ *
* <p>This method populates every field in {@link CarMessage.Builder}.
*/
public static CarMessage.Builder createFullyPopulatedMessageBuilder() {
return createMinimalMessageBuilder()
+ .setMultimediaMimeType(MULTIMEDIA_MESSAGE_MIME_TYPE)
+ .setMultimediaUri(MULTIMEDIA_MESSAGE_URI)
.setSender(createFullyPopulatedPerson())
.setRead(true)
.setReceivedTimeEpochMillis(12345);
@@ -187,6 +213,12 @@
return new ItemList.Builder().addItem(createMinimalConversationItem()).build();
}
+ public static ListTemplate createListTemplateWithConversationItem() {
+ return new ListTemplate.Builder()
+ .setSingleList(createItemListWithConversationItem())
+ .build();
+ }
+
private TestConversationFactory() {
// Do not instantiate
}
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/AbstractCompilerTest.kt b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/AbstractCompilerTest.kt
index 69c3faf..af87416 100644
--- a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/AbstractCompilerTest.kt
+++ b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/AbstractCompilerTest.kt
@@ -29,9 +29,12 @@
import org.jetbrains.kotlin.codegen.GeneratedClassLoader
import org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi
import org.jetbrains.kotlin.compiler.plugin.registerExtensionsForTest
-import org.jetbrains.kotlin.config.CommonConfigurationKeys
+import org.jetbrains.kotlin.config.ApiVersion
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.config.JVMConfigurationKeys
+import org.jetbrains.kotlin.config.LanguageVersion
+import org.jetbrains.kotlin.config.LanguageVersionSettingsImpl
+import org.jetbrains.kotlin.config.languageVersionSettings
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
import org.junit.After
import org.junit.BeforeClass
@@ -112,8 +115,13 @@
) = KotlinCompilerFacade.create(
testRootDisposable,
updateConfiguration = {
+ val languageVersion =
+ if (useFir) LanguageVersion.KOTLIN_2_0 else LanguageVersion.KOTLIN_1_9
+ languageVersionSettings = LanguageVersionSettingsImpl(
+ languageVersion,
+ ApiVersion.createByLanguageVersion(languageVersion),
+ )
updateConfiguration()
- put(CommonConfigurationKeys.USE_FIR, useFir)
addJvmClasspathRoots(additionalPaths)
addJvmClasspathRoots(defaultClassPathRoots)
if (!getBoolean(JVMConfigurationKeys.NO_JDK) &&
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ComposerParamSignatureTests.kt b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ComposerParamSignatureTests.kt
index 5d649af..f476366 100644
--- a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ComposerParamSignatureTests.kt
+++ b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ComposerParamSignatureTests.kt
@@ -1471,15 +1471,7 @@
public final class TestKt {
public final static Example(LA;)V
public final static Usage()V
- final static INNERCLASS TestKt%Usage%1 null null
- }
- final class TestKt%Usage%1 implements A {
- <init>()V
- public final compute(I)V
- static <clinit>()V
- public final static LTestKt%Usage%1; INSTANCE
- OUTERCLASS TestKt Usage ()V
- final static INNERCLASS TestKt%Usage%1 null null
+ private final static Usage%lambda%0(I)V
}
"""
)
@@ -1524,6 +1516,93 @@
"""
)
+ @Test
+ fun testFunInterfacesInComposableCall() = checkApi(
+ """
+ fun interface MeasurePolicy {
+ fun compute(value: Int): Unit
+ }
+
+ @NonRestartableComposable
+ @Composable fun Text() {
+ Layout { value ->
+ println(value)
+ }
+ }
+
+ @Composable inline fun Layout(policy: MeasurePolicy) {
+ policy.compute(0)
+ }
+ """,
+ """
+ public abstract interface MeasurePolicy {
+ public abstract compute(I)V
+ }
+ public final class TestKt {
+ public final static Text(Landroidx/compose/runtime/Composer;I)V
+ public final static Layout(LMeasurePolicy;Landroidx/compose/runtime/Composer;I)V
+ private final static Text%lambda%0(I)V
+ }
+ """,
+ )
+
+ @Test
+ fun testComposableFunInterfacesInVariance() = checkApi(
+ """
+ import androidx.compose.runtime.*
+
+ fun interface Consumer<T> {
+ @Composable fun consume(t: T)
+ }
+
+ class Repro<T : Any>() {
+ fun test(consumer: Consumer<in T>) {}
+ }
+
+ fun test() {
+ Repro<String>().test { string ->
+ println(string)
+ }
+ }
+ """,
+ """
+ public abstract interface Consumer {
+ public abstract consume(Ljava/lang/Object;Landroidx/compose/runtime/Composer;I)V
+ }
+ public final class Repro {
+ public <init>()V
+ public final test(LConsumer;)V
+ static <clinit>()V
+ public final static I %stable
+ }
+ public final class TestKt {
+ public final static test()V
+ final static INNERCLASS TestKt%test%1 null null
+ }
+ final class TestKt%test%1 implements Consumer {
+ <init>()V
+ public final consume(Ljava/lang/String;Landroidx/compose/runtime/Composer;I)V
+ public synthetic bridge consume(Ljava/lang/Object;Landroidx/compose/runtime/Composer;I)V
+ static <clinit>()V
+ public final static LTestKt%test%1; INSTANCE
+ OUTERCLASS TestKt test ()V
+ final static INNERCLASS TestKt%test%1 null null
+ final static INNERCLASS TestKt%test%1%consume%1 null null
+ }
+ final class TestKt%test%1%consume%1 extends kotlin/jvm/internal/Lambda implements kotlin/jvm/functions/Function2 {
+ <init>(LTestKt%test%1;Ljava/lang/String;I)V
+ public final invoke(Landroidx/compose/runtime/Composer;I)V
+ public synthetic bridge invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+ final synthetic LTestKt%test%1; %tmp0_rcvr
+ final synthetic Ljava/lang/String; %string
+ final synthetic I %%changed
+ OUTERCLASS TestKt%test%1 consume (Ljava/lang/String;Landroidx/compose/runtime/Composer;I)V
+ final static INNERCLASS TestKt%test%1 null null
+ final static INNERCLASS TestKt%test%1%consume%1 null null
+ }
+ """
+ )
+
val hashCodeEqualsAndToString = if (useFir) {
"""
public static equals-impl(ILjava/lang/Object;)Z
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ComposerParamTransformTests.kt b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ComposerParamTransformTests.kt
index 3dc46b0..115f7b9 100644
--- a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ComposerParamTransformTests.kt
+++ b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ComposerParamTransformTests.kt
@@ -278,6 +278,69 @@
"""
)
+ // Regression test for b/286132194
+ @Test
+ fun testStableVarargParams(): Unit = composerParam(
+ """
+ @Composable
+ fun B(vararg values: Int) {
+ print(values)
+ }
+
+ @NonRestartableComposable
+ @Composable
+ fun Test() {
+ B(0, 1, 2, 3)
+ }
+ """,
+ """
+ @Composable
+ fun B(values: IntArray, %composer: Composer?, %changed: Int) {
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(B):Test.kt#2487m")
+ val %dirty = %changed
+ %composer.startMovableGroup(<>, values.size)
+ val <iterator> = values.iterator()
+ while (<iterator>.hasNext()) {
+ val value = <iterator>.next()
+ %dirty = %dirty or if (%composer.changed(value)) 0b0100 else 0
+ }
+ %composer.endMovableGroup()
+ if (%dirty and 0b1110 === 0) {
+ %dirty = %dirty or 0b0010
+ }
+ if (%dirty and 0b1011 !== 0b0010 || !%composer.skipping) {
+ if (isTraceInProgress()) {
+ traceEventStart(<>, %changed, -1, <>)
+ }
+ print(values)
+ if (isTraceInProgress()) {
+ traceEventEnd()
+ }
+ } else {
+ %composer.skipToGroupEnd()
+ }
+ %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
+ B(*values, %composer, updateChangedFlags(%changed or 0b0001))
+ }
+ }
+ @NonRestartableComposable
+ @Composable
+ fun Test(%composer: Composer?, %changed: Int) {
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C(Test)<B(0,>:Test.kt#2487m")
+ if (isTraceInProgress()) {
+ traceEventStart(<>, %changed, -1, <>)
+ }
+ B(0, 1, 2, 3, %composer, 0)
+ if (isTraceInProgress()) {
+ traceEventEnd()
+ }
+ %composer.endReplaceableGroup()
+ }
+ """
+ )
+
@Test
fun testNonComposableCode(): Unit = composerParam(
"""
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ControlFlowTransformTests.kt b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ControlFlowTransformTests.kt
index d66814e..d5dca9d 100644
--- a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ControlFlowTransformTests.kt
+++ b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ControlFlowTransformTests.kt
@@ -1032,16 +1032,25 @@
}
if (condition) {
val tmp0_return = false
+ if (isTraceInProgress()) {
+ traceEventEnd()
+ }
sourceInformationMarkerEnd(%composer)
return tmp0_return
}
with(obj) {
if (condition) {
val tmp0_return = false
+ if (isTraceInProgress()) {
+ traceEventEnd()
+ }
sourceInformationMarkerEnd(%composer)
return tmp0_return
}
val tmp1_return = %composer.inserting
+ if (isTraceInProgress()) {
+ traceEventEnd()
+ }
sourceInformationMarkerEnd(%composer)
return tmp1_return
}
@@ -5661,6 +5670,9 @@
}
if (a < 100) {
val tmp1_return = 0
+ if (isTraceInProgress()) {
+ traceEventEnd()
+ }
sourceInformationMarkerEnd(%composer)
return tmp1_return
}
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/FunctionalInterfaceExtensionReceiverTransformTests.kt b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/FunctionalInterfaceExtensionReceiverTransformTests.kt
deleted file mode 100644
index 29e745d..0000000
--- a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/FunctionalInterfaceExtensionReceiverTransformTests.kt
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
- * Copyright 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.compose.compiler.plugins.kotlin
-
-import org.junit.Test
-
-class FunctionalInterfaceExtensionReceiverTransformTests(
- useFir: Boolean
-) : AbstractControlFlowTransformTests(useFir) {
- @Test
- fun testFunctionalInterfaceWithExtensionReceiverTransformation() {
- verifyComposeIrTransform(
- source = """
- import androidx.compose.runtime.*
- fun interface TestContent {
- @Composable
- fun String.Content()
- }
- @Composable
- fun Test(content: TestContent) {
- with(content) {
- "".Content()
- }
- }
-
- @Composable
- fun CallTest() {
- Test { this.length }
- }
- """.trimIndent(),
- expectedTransformed = """
- interface TestContent {
- @Composable
- abstract fun String.Content(%composer: Composer?, %changed: Int)
- }
- @Composable
- @ComposableInferredTarget(scheme = "[0[0]]")
- fun Test(content: TestContent, %composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>)
- sourceInformation(%composer, "C(Test)*<Conten...>:Test.kt")
- val %dirty = %changed
- if (%changed and 0b1110 === 0) {
- %dirty = %dirty or if (%composer.changed(content)) 0b0100 else 0b0010
- }
- if (%dirty and 0b1011 !== 0b0010 || !%composer.skipping) {
- if (isTraceInProgress()) {
- traceEventStart(<>, %changed, -1, <>)
- }
- with(content) {
- %this%with.Content(%composer, 0b0110)
- }
- if (isTraceInProgress()) {
- traceEventEnd()
- }
- } else {
- %composer.skipToGroupEnd()
- }
- %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
- Test(content, %composer, updateChangedFlags(%changed or 0b0001))
- }
- }
- @Composable
- fun CallTest(%composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>)
- sourceInformation(%composer, "C(CallTest)<Test>:Test.kt")
- if (%changed !== 0 || !%composer.skipping) {
- if (isTraceInProgress()) {
- traceEventStart(<>, %changed, -1, <>)
- }
- Test(class <no name provided> : TestContent {
- @Composable
- override fun Content(%this%Test: String, %composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>)
- sourceInformation(%composer, "C(Content):Test.kt")
- val %dirty = %changed
- if (%changed and 0b1110 === 0) {
- %dirty = %dirty or if (%composer.changed(%this%Test)) 0b0100 else 0b0010
- }
- if (%dirty and 0b1011 !== 0b0010 || !%composer.skipping) {
- if (isTraceInProgress()) {
- traceEventStart(<>, %changed, -1, <>)
- }
- %this%Test.length
- if (isTraceInProgress()) {
- traceEventEnd()
- }
- } else {
- %composer.skipToGroupEnd()
- }
- val tmp0_rcvr = <this>
- %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
- tmp0_rcvr.Content(%this%Test, %composer, updateChangedFlags(%changed or 0b0001))
- }
- }
- }
- <no name provided>(), %composer, 0)
- if (isTraceInProgress()) {
- traceEventEnd()
- }
- } else {
- %composer.skipToGroupEnd()
- }
- %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
- CallTest(%composer, updateChangedFlags(%changed or 0b0001))
- }
- }
- """.trimIndent()
- )
- }
-}
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/FunctionalInterfaceTransformTests.kt b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/FunctionalInterfaceTransformTests.kt
new file mode 100644
index 0000000..606cc70
--- /dev/null
+++ b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/FunctionalInterfaceTransformTests.kt
@@ -0,0 +1,390 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.compiler.plugins.kotlin
+
+import org.junit.Test
+
+class FunctionalInterfaceTransformTests(
+ useFir: Boolean
+) : AbstractControlFlowTransformTests(useFir) {
+ @Test
+ fun testFunctionalInterfaceWithExtensionReceiverTransformation() {
+ verifyComposeIrTransform(
+ source = """
+ import androidx.compose.runtime.*
+ fun interface TestContent {
+ @Composable
+ fun String.Content()
+ }
+ @Composable
+ fun Test(content: TestContent) {
+ with(content) {
+ "".Content()
+ }
+ }
+
+ @Composable
+ fun CallTest() {
+ Test { this.length }
+ }
+ """.trimIndent(),
+ expectedTransformed = """
+ interface TestContent {
+ @Composable
+ abstract fun String.Content(%composer: Composer?, %changed: Int)
+ }
+ @Composable
+ @ComposableInferredTarget(scheme = "[0[0]]")
+ fun Test(content: TestContent, %composer: Composer?, %changed: Int) {
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Test)*<Conten...>:Test.kt")
+ val %dirty = %changed
+ if (%changed and 0b1110 === 0) {
+ %dirty = %dirty or if (%composer.changed(content)) 0b0100 else 0b0010
+ }
+ if (%dirty and 0b1011 !== 0b0010 || !%composer.skipping) {
+ if (isTraceInProgress()) {
+ traceEventStart(<>, %changed, -1, <>)
+ }
+ with(content) {
+ %this%with.Content(%composer, 0b0110)
+ }
+ if (isTraceInProgress()) {
+ traceEventEnd()
+ }
+ } else {
+ %composer.skipToGroupEnd()
+ }
+ %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
+ Test(content, %composer, updateChangedFlags(%changed or 0b0001))
+ }
+ }
+ @Composable
+ fun CallTest(%composer: Composer?, %changed: Int) {
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(CallTest)<Test>:Test.kt")
+ if (%changed !== 0 || !%composer.skipping) {
+ if (isTraceInProgress()) {
+ traceEventStart(<>, %changed, -1, <>)
+ }
+ Test(class <no name provided> : TestContent {
+ @Composable
+ override fun Content(%this%Test: String, %composer: Composer?, %changed: Int) {
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Content):Test.kt")
+ val %dirty = %changed
+ if (%changed and 0b1110 === 0) {
+ %dirty = %dirty or if (%composer.changed(%this%Test)) 0b0100 else 0b0010
+ }
+ if (%dirty and 0b1011 !== 0b0010 || !%composer.skipping) {
+ if (isTraceInProgress()) {
+ traceEventStart(<>, %changed, -1, <>)
+ }
+ %this%Test.length
+ if (isTraceInProgress()) {
+ traceEventEnd()
+ }
+ } else {
+ %composer.skipToGroupEnd()
+ }
+ val tmp0_rcvr = <this>
+ %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
+ tmp0_rcvr.Content(%this%Test, %composer, updateChangedFlags(%changed or 0b0001))
+ }
+ }
+ }
+ <no name provided>(), %composer, 0)
+ if (isTraceInProgress()) {
+ traceEventEnd()
+ }
+ } else {
+ %composer.skipToGroupEnd()
+ }
+ %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
+ CallTest(%composer, updateChangedFlags(%changed or 0b0001))
+ }
+ }
+ """.trimIndent()
+ )
+ }
+
+ @Test
+ fun testFunInterfaces() = verifyComposeIrTransform(
+ """
+ import androidx.compose.runtime.*
+
+ fun interface A {
+ fun compute(value: Int): Unit
+ }
+
+ @Composable
+ fun Example(a: A) {
+ Example { it -> a.compute(it) }
+ }
+ """,
+ """
+ interface A {
+ abstract fun compute(value: Int)
+ }
+ @Composable
+ fun Example(a: A, %composer: Composer?, %changed: Int) {
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Example)<Exampl...>:Test.kt")
+ val %dirty = %changed
+ if (%changed and 0b1110 === 0) {
+ %dirty = %dirty or if (%composer.changed(a)) 0b0100 else 0b0010
+ }
+ if (%dirty and 0b1011 !== 0b0010 || !%composer.skipping) {
+ if (isTraceInProgress()) {
+ traceEventStart(<>, %changed, -1, <>)
+ }
+ Example(A { it: Int ->
+ a.compute(it)
+ }, %composer, 0)
+ if (isTraceInProgress()) {
+ traceEventEnd()
+ }
+ } else {
+ %composer.skipToGroupEnd()
+ }
+ %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
+ Example(a, %composer, updateChangedFlags(%changed or 0b0001))
+ }
+ }
+ """
+ )
+
+ @Test
+ fun testComposableFunInterfaces() = verifyComposeIrTransform(
+ """
+ import androidx.compose.runtime.*
+
+ fun interface A {
+ @Composable fun compute(value: Int): Unit
+ }
+ fun Example(a: A) {
+ Example { it -> a.compute(it) }
+ }
+ """,
+ """
+ interface A {
+ @Composable
+ abstract fun compute(value: Int, %composer: Composer?, %changed: Int)
+ }
+ fun Example(a: A) {
+ Example(class <no name provided> : A {
+ @Composable
+ override fun compute(it: Int, %composer: Composer?, %changed: Int) {
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(compute)<comput...>:Test.kt")
+ val %dirty = %changed
+ if (%changed and 0b1110 === 0) {
+ %dirty = %dirty or if (%composer.changed(it)) 0b0100 else 0b0010
+ }
+ if (%dirty and 0b1011 !== 0b0010 || !%composer.skipping) {
+ if (isTraceInProgress()) {
+ traceEventStart(<>, %dirty, -1, <>)
+ }
+ a.compute(it, %composer, 0b1110 and %dirty)
+ if (isTraceInProgress()) {
+ traceEventEnd()
+ }
+ } else {
+ %composer.skipToGroupEnd()
+ }
+ val tmp0_rcvr = <this>
+ %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
+ tmp0_rcvr.compute(it, %composer, updateChangedFlags(%changed or 0b0001))
+ }
+ }
+ }
+ <no name provided>())
+ }
+ """
+ )
+
+ @Test
+ fun testComposableFunInterfacesInVariance() = verifyComposeIrTransform(
+ """
+ import androidx.compose.runtime.*
+
+ fun interface Consumer<T> {
+ @Composable fun consume(t: T)
+ }
+
+ class Repro<T : Any> {
+ fun test(consumer: Consumer<in T>) {}
+ }
+
+ fun test() {
+ Repro<String>().test { string ->
+ println(string)
+ }
+ }
+ """,
+ """
+ interface Consumer<T> {
+ @Composable
+ abstract fun consume(t: T, %composer: Composer?, %changed: Int)
+ }
+ @StabilityInferred(parameters = 0)
+ class Repro<T: Any> {
+ fun test(consumer: Consumer<in T>) { }
+ static val %stable: Int = 0
+ }
+ fun test() {
+ Repro().test(class <no name provided> : Consumer<Any?> {
+ @Composable
+ override fun consume(string: String, %composer: Composer?, %changed: Int) {
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(consume):Test.kt")
+ val %dirty = %changed
+ if (%changed and 0b1110 === 0) {
+ %dirty = %dirty or if (%composer.changed(string)) 0b0100 else 0b0010
+ }
+ if (%dirty and 0b1011 !== 0b0010 || !%composer.skipping) {
+ if (isTraceInProgress()) {
+ traceEventStart(<>, %changed, -1, <>)
+ }
+ println(string)
+ if (isTraceInProgress()) {
+ traceEventEnd()
+ }
+ } else {
+ %composer.skipToGroupEnd()
+ }
+ val tmp0_rcvr = <this>
+ %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
+ tmp0_rcvr.consume(string, %composer, updateChangedFlags(%changed or 0b0001))
+ }
+ }
+ }
+ <no name provided>())
+ }
+ """
+ )
+
+ @Test
+ fun testCaptureStableFunInterface() = verifyComposeIrTransform(
+ """
+ import androidx.compose.runtime.*
+
+ fun interface Consumer {
+ fun consume(t: Int)
+ }
+
+ @Composable fun Test(int: Int) {
+ Example {
+ println(int)
+ }
+ }
+
+ @Composable inline fun Example(consumer: Consumer) {
+ }
+ """,
+ """
+ interface Consumer {
+ abstract fun consume(t: Int)
+ }
+ @Composable
+ fun Test(int: Int, %composer: Composer?, %changed: Int) {
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Test)<{>,<Exampl...>:Test.kt")
+ val %dirty = %changed
+ if (%changed and 0b1110 === 0) {
+ %dirty = %dirty or if (%composer.changed(int)) 0b0100 else 0b0010
+ }
+ if (%dirty and 0b1011 !== 0b0010 || !%composer.skipping) {
+ if (isTraceInProgress()) {
+ traceEventStart(<>, %dirty, -1, <>)
+ }
+ Example(remember(int, {
+ Consumer { it: Int ->
+ println(int)
+ }
+ }, %composer, 0b1110 and %dirty), %composer, 0)
+ if (isTraceInProgress()) {
+ traceEventEnd()
+ }
+ } else {
+ %composer.skipToGroupEnd()
+ }
+ %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
+ Test(int, %composer, updateChangedFlags(%changed or 0b0001))
+ }
+ }
+ @Composable
+ fun Example(consumer: Consumer, %composer: Composer?, %changed: Int) {
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "CC(Example):Test.kt")
+ %composer.endReplaceableGroup()
+ }
+ """
+ )
+
+ @Test
+ fun testNoCaptureFunInterface() = verifyComposeIrTransform(
+ """
+ import androidx.compose.runtime.*
+
+ fun interface Consumer {
+ fun consume(t: Int)
+ }
+
+ @Composable fun Test(int: Int) {
+ Example {
+ println(it)
+ }
+ }
+
+ @Composable inline fun Example(consumer: Consumer) {
+ }
+ """,
+ """
+ interface Consumer {
+ abstract fun consume(t: Int)
+ }
+ @Composable
+ fun Test(int: Int, %composer: Composer?, %changed: Int) {
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Test)<Exampl...>:Test.kt")
+ if (%changed and 0b0001 !== 0 || !%composer.skipping) {
+ if (isTraceInProgress()) {
+ traceEventStart(<>, %changed, -1, <>)
+ }
+ Example(Consumer { it: Int ->
+ println(it)
+ }, %composer, 0b0110)
+ if (isTraceInProgress()) {
+ traceEventEnd()
+ }
+ } else {
+ %composer.skipToGroupEnd()
+ }
+ %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
+ Test(int, %composer, updateChangedFlags(%changed or 0b0001))
+ }
+ }
+ @Composable
+ fun Example(consumer: Consumer, %composer: Composer?, %changed: Int) {
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "CC(Example):Test.kt")
+ %composer.endReplaceableGroup()
+ }
+ """
+ )
+}
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/TargetAnnotationsTransformTests.kt b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/TargetAnnotationsTransformTests.kt
index d3f96e3..3817942 100644
--- a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/TargetAnnotationsTransformTests.kt
+++ b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/TargetAnnotationsTransformTests.kt
@@ -890,16 +890,13 @@
}
static val %stable: Int = 0
}
- val localBoxMeasurePolicy: MeasurePolicy = class <no name provided> : MeasurePolicy {
- override fun measure(%this%MeasurePolicy: MeasureScope, <unused var>: List<Measurable>, constraints: Constraints): MeasureResult {
- return %this%MeasurePolicy.layout(
- width = constraints.minWidth,
- height = constraints.minHeight
- ) {
- }
+ val localBoxMeasurePolicy: MeasurePolicy = MeasurePolicy { <unused var>: List<Measurable>, constraints: Constraints ->
+ %this%MeasurePolicy.layout(
+ width = constraints.minWidth,
+ height = constraints.minHeight
+ ) {
}
}
- <no name provided>()
@Composable
@ComposableInferredTarget(scheme = "[androidx.compose.ui.UiComposable[androidx.compose.ui.UiComposable]]")
fun LocalBox(modifier: Modifier?, content: @[ExtensionFunctionType] Function3<LocalBoxScope, Composer, Int, Unit>, %composer: Composer?, %changed: Int, %default: Int) {
@@ -983,12 +980,9 @@
sourceInformationMarkerStart(%composer, <>, "C:Test.kt")
Unit
sourceInformationMarkerEnd(%composer)
- }, null, class <no name provided> : MeasurePolicy {
- override fun measure(%this%Layout: MeasureScope, <unused var>: List<Measurable>, <unused var>: Constraints): MeasureResult {
- ${if (!useFir) "return " else ""}error("")
- }
- }
- <no name provided>(), %composer, 0, 0b0010)
+ }, null, MeasurePolicy { <unused var>: List<Measurable>, <unused var>: Constraints ->
+ error("")
+ }, %composer, 0b000110000000, 0b0010)
if (isTraceInProgress()) {
traceEventEnd()
}
@@ -1012,12 +1006,9 @@
if (isTraceInProgress()) {
traceEventStart(<>, %dirty, -1, <>)
}
- Layout(content, null, class <no name provided> : MeasurePolicy {
- override fun measure(%this%Layout: MeasureScope, <unused var>: List<Measurable>, <unused var>: Constraints): MeasureResult {
- ${if (!useFir) "return " else ""}error("")
- }
- }
- <no name provided>(), %composer, 0b1110 and %dirty, 0b0010)
+ Layout(content, null, MeasurePolicy { <unused var>: List<Measurable>, <unused var>: Constraints ->
+ error("")
+ }, %composer, 0b000110000000 or 0b1110 and %dirty, 0b0010)
if (isTraceInProgress()) {
traceEventEnd()
}
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/TraceInformationTest.kt b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/TraceInformationTest.kt
index 74c8e95..2321f8c 100644
--- a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/TraceInformationTest.kt
+++ b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/TraceInformationTest.kt
@@ -87,6 +87,52 @@
)
@Test
+ fun testReadOnlyComposable() = verifyComposeIrTransform(
+ """
+ import androidx.compose.runtime.*
+
+ @Composable
+ @ReadOnlyComposable
+ internal fun someFun(a: Boolean): Boolean {
+ if (a) {
+ return a
+ } else {
+ return a
+ }
+ }
+ """,
+ """
+ @Composable
+ @ReadOnlyComposable
+ internal fun someFun(a: Boolean, %composer: Composer?, %changed: Int): Boolean {
+ sourceInformationMarkerStart(%composer, <>, "C(someFun):Test.kt")
+ if (isTraceInProgress()) {
+ traceEventStart(<>, %changed, -1, <>)
+ }
+ if (a) {
+ val tmp0_return = a
+ if (isTraceInProgress()) {
+ traceEventEnd()
+ }
+ sourceInformationMarkerEnd(%composer)
+ return tmp0_return
+ } else {
+ val tmp1_return = a
+ if (isTraceInProgress()) {
+ traceEventEnd()
+ }
+ sourceInformationMarkerEnd(%composer)
+ return tmp1_return
+ }
+ if (isTraceInProgress()) {
+ traceEventEnd()
+ }
+ sourceInformationMarkerEnd(%composer)
+ }
+ """
+ )
+
+ @Test
fun testInlineFunctionsDonotGenerateTraceMarkers() = verifyComposeIrTransform(
"""
import androidx.compose.runtime.*
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/analysis/ComposeMultiplatformCheckerTests.kt b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/analysis/ComposeMultiplatformCheckerTests.kt
index 8b1083e..b8fae4d 100644
--- a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/analysis/ComposeMultiplatformCheckerTests.kt
+++ b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/analysis/ComposeMultiplatformCheckerTests.kt
@@ -17,17 +17,21 @@
package androidx.compose.compiler.plugins.kotlin.analysis
import androidx.compose.compiler.plugins.kotlin.AbstractComposeDiagnosticsTest
-import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments
-import org.jetbrains.kotlin.cli.common.setupLanguageVersionSettings
import org.jetbrains.kotlin.config.CompilerConfiguration
+import org.jetbrains.kotlin.config.LanguageFeature
+import org.jetbrains.kotlin.config.LanguageVersionSettingsImpl
+import org.jetbrains.kotlin.config.languageVersionSettings
import org.junit.Test
class ComposeMultiplatformCheckerTests(useFir: Boolean) : AbstractComposeDiagnosticsTest(useFir) {
override fun CompilerConfiguration.updateConfiguration() {
- setupLanguageVersionSettings(K2JVMCompilerArguments().apply {
- // enabling multiPlatform to use expect/actual declarations
- multiPlatform = true
- })
+ languageVersionSettings = LanguageVersionSettingsImpl(
+ languageVersionSettings.languageVersion,
+ languageVersionSettings.apiVersion,
+ specificFeatures = hashMapOf(
+ LanguageFeature.MultiPlatformProjects to LanguageFeature.State.ENABLED
+ )
+ )
}
@Test
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/facade/KotlinCompilerFacade.kt b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/facade/KotlinCompilerFacade.kt
index 9d52ad0..a09b001 100644
--- a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/facade/KotlinCompilerFacade.kt
+++ b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/facade/KotlinCompilerFacade.kt
@@ -38,6 +38,7 @@
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.config.JVMConfigurationKeys
import org.jetbrains.kotlin.config.JvmTarget
+import org.jetbrains.kotlin.config.languageVersionSettings
import org.jetbrains.kotlin.idea.KotlinLanguage
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
import org.jetbrains.kotlin.ir.util.IrMessageLogger
@@ -126,7 +127,7 @@
environment.project.registerExtensions(configuration)
- return if (configuration.getBoolean(CommonConfigurationKeys.USE_FIR)) {
+ return if (configuration.languageVersionSettings.languageVersion.usesK2) {
K2CompilerFacade(environment)
} else {
K1CompilerFacade(environment)
diff --git a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposePlugin.kt b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposePlugin.kt
index 00be1dc..07ea0ae 100644
--- a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposePlugin.kt
+++ b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposePlugin.kt
@@ -32,11 +32,11 @@
import org.jetbrains.kotlin.compiler.plugin.CommandLineProcessor
import org.jetbrains.kotlin.compiler.plugin.CompilerPluginRegistrar
import org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi
-import org.jetbrains.kotlin.config.CommonConfigurationKeys
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.config.CompilerConfigurationKey
import org.jetbrains.kotlin.config.JVMConfigurationKeys
import org.jetbrains.kotlin.config.KotlinCompilerVersion
+import org.jetbrains.kotlin.config.languageVersionSettings
import org.jetbrains.kotlin.extensions.StorageComponentContainerContributor
import org.jetbrains.kotlin.extensions.internal.TypeResolutionInterceptor
import org.jetbrains.kotlin.fir.extensions.FirExtensionRegistrarAdapter
@@ -337,9 +337,7 @@
val validateIr = configuration.getBoolean(
JVMConfigurationKeys.VALIDATE_IR
)
- val useK2 = configuration.getBoolean(
- CommonConfigurationKeys.USE_FIR
- )
+ val useK2 = configuration.languageVersionSettings.languageVersion.usesK2
return ComposeIrGenerationExtension(
liveLiteralsEnabled = liveLiteralsEnabled,
diff --git a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/VersionChecker.kt b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/VersionChecker.kt
index da5550a..c20d2da 100644
--- a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/VersionChecker.kt
+++ b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/VersionChecker.kt
@@ -127,7 +127,7 @@
* The maven version string of this compiler. This string should be updated before/after every
* release.
*/
- const val compilerVersion: String = "1.5.0"
+ const val compilerVersion: String = "1.5.1"
private val minimumRuntimeVersion: String
get() = runtimeVersionToMavenVersionTable[minimumRuntimeVersionInt] ?: "unknown"
}
diff --git a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/AbstractComposeLowering.kt b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/AbstractComposeLowering.kt
index e1cd194b..066e0d3 100644
--- a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/AbstractComposeLowering.kt
+++ b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/AbstractComposeLowering.kt
@@ -70,6 +70,7 @@
import org.jetbrains.kotlin.ir.expressions.IrMemberAccessExpression
import org.jetbrains.kotlin.ir.expressions.IrReturn
import org.jetbrains.kotlin.ir.expressions.IrStatementOrigin
+import org.jetbrains.kotlin.ir.expressions.IrTypeOperatorCall
import org.jetbrains.kotlin.ir.expressions.IrVararg
import org.jetbrains.kotlin.ir.expressions.impl.IrBlockImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrBranchImpl
@@ -886,7 +887,8 @@
else -> false
}
}
- is IrFunctionExpression ->
+ is IrFunctionExpression,
+ is IrTypeOperatorCall ->
context.irTrace[ComposeWritableSlices.IS_STATIC_FUNCTION_EXPRESSION, this] ?: false
is IrGetField ->
// K2 sometimes produces `IrGetField` for reads from constant properties
diff --git a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposableFunInterfaceLowering.kt b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposableFunInterfaceLowering.kt
index cb8f689..9bacca8 100644
--- a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposableFunInterfaceLowering.kt
+++ b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposableFunInterfaceLowering.kt
@@ -16,15 +16,20 @@
package androidx.compose.compiler.plugins.kotlin.lower
+import androidx.compose.compiler.plugins.kotlin.hasComposableAnnotation
import org.jetbrains.kotlin.backend.common.IrElementTransformerVoidWithContext
import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext
+import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.expressions.IrFunctionExpression
-import org.jetbrains.kotlin.ir.expressions.IrTypeOperator
+import org.jetbrains.kotlin.ir.expressions.IrTypeOperator.IMPLICIT_CAST
+import org.jetbrains.kotlin.ir.expressions.IrTypeOperator.SAM_CONVERSION
import org.jetbrains.kotlin.ir.expressions.IrTypeOperatorCall
+import org.jetbrains.kotlin.ir.types.IrType
import org.jetbrains.kotlin.ir.types.IrTypeSystemContextImpl
import org.jetbrains.kotlin.ir.types.classOrNull
+import org.jetbrains.kotlin.ir.util.functions
import org.jetbrains.kotlin.ir.util.isLambda
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
import org.jetbrains.kotlin.platform.jvm.isJvm
@@ -40,32 +45,10 @@
}
}
- private fun isFunInterfaceConversion(expression: IrTypeOperatorCall): Boolean {
- val argument = expression.argument
- val operator = expression.operator
- val type = expression.typeOperand
- val functionClass = type.classOrNull
- return operator == IrTypeOperator.SAM_CONVERSION &&
- argument is IrFunctionExpression &&
- argument.origin.isLambda &&
- functionClass != null &&
- functionClass.owner.isFun
- // IMPORTANT(b/178663739):
- // We are transforming not just SAM conversions for composable fun interfaces, but ALL
- // fun interfaces temporarily until KT-44622 gets fixed in the version of kotlin we
- // are using, which should be in 1.4.30.
- // Once it does, we should either add the below additional condition to this predicate,
- // or, if possible, remove this lowering all together if kotlin's lowering works for
- // composable fun interfaces as well.
- //
- // functionClass.functions.single {
- // it.owner.modality == Modality.ABSTRACT
- // }.owner.annotations.hasAnnotation(ComposeFqNames.Composable)
- }
-
override fun visitTypeOperator(expression: IrTypeOperatorCall): IrExpression {
- if (isFunInterfaceConversion(expression)) {
- val argument = expression.argument.transform(this, null) as IrFunctionExpression
+ val functionExpr = expression.findSamFunctionExpr()
+ if (functionExpr != null && expression.typeOperand.isComposableFunInterface()) {
+ val argument = functionExpr.transform(this, null) as IrFunctionExpression
val superType = expression.typeOperand
val superClass = superType.classOrNull ?: error("Expected non-null class")
return FunctionReferenceBuilder(
@@ -81,3 +64,38 @@
return super.visitTypeOperator(expression)
}
}
+
+private fun IrType.isComposableFunInterface(): Boolean =
+ classOrNull
+ ?.functions
+ ?.single { it.owner.modality == Modality.ABSTRACT }
+ ?.owner
+ ?.hasComposableAnnotation() == true
+
+internal fun IrTypeOperatorCall.findSamFunctionExpr(): IrFunctionExpression? {
+ val argument = argument
+ val operator = operator
+ val type = typeOperand
+ val functionClass = type.classOrNull
+
+ val isFunInterfaceConversion = operator == SAM_CONVERSION &&
+ functionClass != null &&
+ functionClass.owner.isFun
+
+ return if (isFunInterfaceConversion) {
+ // if you modify this logic, make sure to update wrapping of type operators
+ // in ComposerLambdaMemoization.kt
+ when {
+ argument is IrFunctionExpression && argument.origin.isLambda -> argument
+ // some expressions are wrapped with additional implicit cast operator
+ // unwrapping that allows to avoid SAM conversion that capture FunctionN and box params.
+ argument is IrTypeOperatorCall && argument.operator == IMPLICIT_CAST -> {
+ val functionExpr = argument.argument
+ functionExpr as? IrFunctionExpression
+ }
+ else -> null
+ }
+ } else {
+ null
+ }
+}
diff --git a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposableFunctionBodyTransformer.kt b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposableFunctionBodyTransformer.kt
index 736a93c..e334859 100644
--- a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposableFunctionBodyTransformer.kt
+++ b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposableFunctionBodyTransformer.kt
@@ -929,9 +929,16 @@
returnVar?.let { irReturn(declaration.symbol, irGet(it)) }
)
)
- if (elideGroups && !hasExplicitGroups && collectSourceInformation) {
+ if (elideGroups && !hasExplicitGroups) {
scope.realizeEndCalls {
- irSourceInformationMarkerEnd(body, scope)
+ irComposite(
+ statements = listOfNotNull(
+ if (emitTraceMarkers) irTraceEventEnd() else null,
+ if (collectSourceInformation)
+ irSourceInformationMarkerEnd(body, scope)
+ else null
+ )
+ )
}
}
@@ -2683,6 +2690,9 @@
}
}
}
+ arg is IrVararg -> {
+ meta.stability = stabilityOf(arg.varargElementType)
+ }
}
}
diff --git a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposerLambdaMemoization.kt b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposerLambdaMemoization.kt
index 3a8ba5a..166307c 100644
--- a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposerLambdaMemoization.kt
+++ b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposerLambdaMemoization.kt
@@ -69,11 +69,14 @@
import org.jetbrains.kotlin.ir.expressions.IrFunctionExpression
import org.jetbrains.kotlin.ir.expressions.IrFunctionReference
import org.jetbrains.kotlin.ir.expressions.IrStatementOrigin
+import org.jetbrains.kotlin.ir.expressions.IrTypeOperator
+import org.jetbrains.kotlin.ir.expressions.IrTypeOperatorCall
import org.jetbrains.kotlin.ir.expressions.IrValueAccessExpression
import org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrFunctionReferenceImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrGetObjectValueImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrInstanceInitializerCallImpl
+import org.jetbrains.kotlin.ir.expressions.impl.IrTypeOperatorCallImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrVarargImpl
import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol
import org.jetbrains.kotlin.ir.symbols.IrSymbol
@@ -509,8 +512,70 @@
return result
}
+ override fun visitTypeOperator(expression: IrTypeOperatorCall): IrExpression {
+ // SAM conversions are handled by Kotlin compiler
+ // We only need to make sure that remember is handled correctly around type operator
+ if (
+ expression.operator != IrTypeOperator.SAM_CONVERSION ||
+ currentFunctionContext?.canRemember != true
+ ) {
+ return super.visitTypeOperator(expression)
+ }
+
+ // Unwrap function from type operator
+ val originalFunctionExpression =
+ expression.findSamFunctionExpr() ?: return super.visitTypeOperator(expression)
+
+ // Record capture variables for this scope
+ val collector = CaptureCollector()
+ startCollector(collector)
+ // Handle inside of the function expression
+ val result = super.visitFunctionExpression(originalFunctionExpression)
+ stopCollector(collector)
+
+ // If the ancestor converted this then return
+ val newFunctionExpression = result as? IrFunctionExpression ?: return result
+
+ // Construct new type operator call to wrap remember around.
+ val newArgument = when (val argument = expression.argument) {
+ is IrFunctionExpression -> newFunctionExpression
+ is IrTypeOperatorCall -> {
+ require(
+ argument.operator == IrTypeOperator.IMPLICIT_CAST &&
+ argument.argument == originalFunctionExpression
+ ) {
+ "Only implicit cast is supported inside SAM conversion"
+ }
+ IrTypeOperatorCallImpl(
+ argument.startOffset,
+ argument.endOffset,
+ argument.type,
+ argument.operator,
+ argument.typeOperand,
+ newFunctionExpression
+ )
+ }
+ else -> error("Unknown ")
+ }
+
+ val expressionToRemember =
+ IrTypeOperatorCallImpl(
+ expression.startOffset,
+ expression.endOffset,
+ expression.type,
+ IrTypeOperator.SAM_CONVERSION,
+ expression.typeOperand,
+ newArgument
+ )
+ return rememberExpression(
+ currentFunctionContext!!,
+ expressionToRemember,
+ collector.captures.toList()
+ )
+ }
+
private fun visitNonComposableFunctionExpression(
- expression: IrFunctionExpression
+ expression: IrFunctionExpression,
): IrExpression {
val functionContext = currentFunctionContext
?: return super.visitFunctionExpression(expression)
diff --git a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/IrSourcePrinter.kt b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/IrSourcePrinter.kt
index 6103318..04c7f2c 100644
--- a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/IrSourcePrinter.kt
+++ b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/IrSourcePrinter.kt
@@ -616,10 +616,12 @@
IrTypeOperator.NOT_INSTANCEOF -> {
expression.argument.print()
}
- IrTypeOperator.CAST, IrTypeOperator.IMPLICIT_CAST, IrTypeOperator.SAFE_CAST -> {
+ IrTypeOperator.CAST, IrTypeOperator.SAFE_CAST, IrTypeOperator.IMPLICIT_CAST -> {
expression.argument.print()
}
IrTypeOperator.SAM_CONVERSION -> {
+ print(expression.type.renderSrc())
+ print(" ")
expression.argument.print()
}
IrTypeOperator.IMPLICIT_NOTNULL -> {
diff --git a/compose/desktop/desktop/build.gradle b/compose/desktop/desktop/build.gradle
index 3d2c88c..ed903b5 100644
--- a/compose/desktop/desktop/build.gradle
+++ b/compose/desktop/desktop/build.gradle
@@ -18,30 +18,24 @@
import androidx.build.BuildOnServerKt
import androidx.build.KmpPlatformsKt
import androidx.build.LibraryType
+import androidx.build.PlatformIdentifier
+import androidx.build.Publish
import androidx.build.RunApiTasks
plugins {
id("AndroidXPlugin")
- id("java") // https://github.com/gradle/gradle/issues/18622
id("AndroidXComposePlugin")
- id("kotlin-multiplatform")
}
+// Compose Desktop is qualified on "desktop" but actually builds as "jvm".
def desktopEnabled = KmpPlatformsKt.enableDesktop(project)
-dependencies {
-}
+androidXMultiplatform {
+ if (desktopEnabled) jvm()
-kotlin {
- if (desktopEnabled) {
- jvm() {
- withJava()
- }
- }
-
- if (desktopEnabled) {
- sourceSets {
- commonMain.dependencies {
+ sourceSets {
+ commonMain {
+ dependencies {
implementation(libs.kotlinStdlibCommon)
implementation(project(":compose:ui:ui-util"))
api(project(":compose:foundation:foundation"))
@@ -50,17 +44,30 @@
api(project(":compose:ui:ui"))
api(project(":compose:ui:ui-tooling-preview"))
}
+ }
- jvmMain.dependencies {
- implementation(libs.kotlinStdlib)
- implementation(libs.kotlinStdlibJdk8)
- implementation(libs.kotlinCoroutinesCore)
+ commonTest {
+ dependencies {
+ }
+ }
+
+ if (desktopEnabled) {
+ jvmMain {
+ dependsOn(commonMain)
+ dependencies {
+ implementation(libs.kotlinStdlib)
+ implementation(libs.kotlinStdlibJdk8)
+ implementation(libs.kotlinCoroutinesCore)
+ }
}
jvmTest {
+ dependsOn(commonTest)
+
resources.srcDirs += new File(AndroidXConfig.getExternalProjectPath(project),
"noto-fonts/other/")
resources.srcDirs += "src/jvmTest/res"
+
dependencies {
implementation(libs.kotlinCoroutinesTest)
implementation(libs.skikoCurrentOs)
@@ -73,14 +80,6 @@
}
}
-if (!desktopEnabled) {
- kotlin {
- jvm("disabled") {
- withJava()
- }
- }
-}
-
File getGoldenPath(Project project) {
if (System.getenv("COMPOSE_DESKTOP_GITHUB_BUILD") != null) {
def externalPath = SupportConfigKt.getExternalProjectPath(project)
@@ -91,6 +90,7 @@
}
if (desktopEnabled) {
+ // Set golden images path based on whether this is the GitHub or AndroidX repo.
tasks.findByName("jvmTest").configure {
systemProperties["GOLDEN_PATH"] = getGoldenPath(project).toString()
}
@@ -99,11 +99,13 @@
androidx {
name = "Compose Desktop"
type = LibraryType.PUBLISHED_LIBRARY
+ publish = desktopEnabled ? Publish.SNAPSHOT_AND_RELEASE : Publish.NONE
inceptionYear = "2020"
legacyDisableKotlinStrictApiMode = true
- runApiTasks = new RunApiTasks.No(
- "API tracking for desktop isn't supported at the moment, see b/163110180"
- )
+ runApiTasks = new RunApiTasks.No("Not supported yet, see b/163110180")
+
+ // Project parser does not support dynamic configuration of `publish`.
+ runProjectParser = false
}
def jvmOs(container, name, skikoDep) {
@@ -133,18 +135,22 @@
}
}
-afterEvaluate {
- publishing {
- publications {
- jvmOs(it, "linux-x64", libs.skikoLinuxX64.get())
- jvmOs(it, "linux-arm64", libs.skikoLinuxArm64.get())
- jvmOs(it, "macos-x64", libs.skikoMacOsX64.get())
- jvmOs(it, "macos-arm64", libs.skikoMacOsArm64.get())
- jvmOs(it, "windows-x64", libs.skikoWindowsX64.get())
+if (desktopEnabled) {
+ // Set up additional publications containing native Skiko dependencies.
+ afterEvaluate {
+ publishing {
+ publications {
+ jvmOs(it, "linux-x64", libs.skikoLinuxX64.get())
+ jvmOs(it, "linux-arm64", libs.skikoLinuxArm64.get())
+ jvmOs(it, "macos-x64", libs.skikoMacOsX64.get())
+ jvmOs(it, "macos-arm64", libs.skikoMacOsArm64.get())
+ jvmOs(it, "windows-x64", libs.skikoWindowsX64.get())
+ }
}
}
}
if (desktopEnabled) {
+ // Validate desktop build by explicitly building the jvmJar task as part of bOS.
BuildOnServerKt.addToBuildOnServer(project, "${project.path}:jvmJar")
}
diff --git a/compose/desktop/desktop/samples/build.gradle b/compose/desktop/desktop/samples/build.gradle
index 8ce657b..5b95cd0 100644
--- a/compose/desktop/desktop/samples/build.gradle
+++ b/compose/desktop/desktop/samples/build.gradle
@@ -20,31 +20,50 @@
plugins {
id("AndroidXPlugin")
- id("java")
id("AndroidXComposePlugin")
- id("kotlin-multiplatform")
}
+// Compose Desktop is qualified on "desktop" but actually builds as "jvm".
def desktopEnabled = KmpPlatformsKt.enableDesktop(project)
-if (desktopEnabled) {
- kotlin {
- jvm()
+androidXMultiplatform {
+ if (desktopEnabled) jvm()
- sourceSets {
+ sourceSets {
+ commonMain {
+ dependencies {
+ }
+ }
+
+ commonTest {
+ dependencies {
+ }
+ }
+
+ if (desktopEnabled) {
jvmMain {
+ dependsOn(commonMain)
+
resources.srcDirs += new File(AndroidXConfig.getExternalProjectPath(project),
"noto-fonts/other/")
resources.srcDirs += "src/jvmMain/res"
+
+ dependencies {
+ implementation(libs.skikoCurrentOs)
+ implementation(project(":compose:desktop:desktop"))
+ }
}
- jvmMain.dependencies {
- implementation(libs.skikoCurrentOs)
- implementation(project(":compose:desktop:desktop"))
+ jvmTest {
+ dependsOn(commonTest)
+ dependencies {
+ }
}
}
}
+}
+if (desktopEnabled) {
task run1(type: JavaExec) {
dependsOn(":compose:desktop:desktop:jar")
main = "androidx.compose.desktop.examples.example1.Main_jvmKt"
@@ -106,15 +125,9 @@
task run {
dependsOn("run1")
}
-
-
- BuildOnServerKt.addToBuildOnServer(project, "${project.path}:jvmJar")
}
-if (!desktopEnabled) {
- kotlin {
- jvm("disabled") {
- withJava()
- }
- }
+if (desktopEnabled) {
+ // Validate desktop build by explicitly building the jvmJar task as part of bOS.
+ BuildOnServerKt.addToBuildOnServer(project, "${project.path}:jvmJar")
}
diff --git a/compose/docs/compose-component-api-guidelines.md b/compose/docs/compose-component-api-guidelines.md
index 499595d..e337d3a 100644
--- a/compose/docs/compose-component-api-guidelines.md
+++ b/compose/docs/compose-component-api-guidelines.md
@@ -1,6 +1,6 @@
# API Guidelines for `@Composable` components in Jetpack Compose
-## Last updated: July 14, 2023
+## Last updated: July 19, 2023
Set of guidelines and recommendations for building scalable and user-friendly @Composable components.
@@ -18,7 +18,48 @@
App development is often subject to strong organizational priorities and norms and requirements to integrate with existing app architecture. This may call for not only stylistic deviation from these guidelines but structural deviation as well. Where possible, alternative approaches for app development will be listed in this document that may be more appropriate in these situations.
-### Note on vocabulary in this doc
+## Table of content
+- [Note on vocabulary in this doc](#note-on-vocabulary-in-this-doc)
+- [Before you create a component](#before-you-create-a-component)
+ - [Component’s purpose](#components-purpose)
+ - [Component layering](#component-layering)
+ - [Do you need a component?](#do-you-need-a-component)
+ - [Component or Modifier](#component-or-modifier)
+- [Name of a Component](#name-of-a-component)
+ - [BasicComponent vs Component](#basiccomponent-vs-component)
+ - [Design, Usecase or Company/Project specific prefixes](#design-usecase-or-companyproject-specific-prefixes)
+- [Component dependencies](#component-dependencies)
+ - [Prefer multiple components over style classes](#prefer-multiple-components-over-style-classes)
+ - [Explicit vs implicit dependencies](#explicit-vs-implicit-dependencies)
+- [Component parameters](#component-parameters)
+ - [Parameters vs. Modifier on the component](#parameters-vs-modifier-on-the-component)
+ - [`modifier` parameter](#modifier-parameter)
+ - [Parameters order](#parameters-order)
+ - [Nullable parameter](#nullable-parameter)
+ - [Default expressions](#default-expressions)
+ - [MutableState\<T\> as a parameter](#mutablestatet-as-a-parameter)
+ - [State\<T\> as a parameter](#statet-as-a-parameter)
+ - [Slot parameters](#slot-parameters)
+ - [What are slots](#what-are-slots)
+ - [Why slots](#why-slots)
+ - [Single “content” slot overloads](#single-content-slot-overloads)
+ - [Layout strategy scope for slot APIs](#layout-strategy-scope-for-slot-apis)
+ - [Lifecycle expectations for slot parameters](#lifecycle-expectations-for-slot-parameters)
+ - [DSL based slots](#dsl-based-slots)
+- [Component-related classes and functions](#component-related-classes-and-functions)
+ - [State](#state)
+ - [ComponentDefault object](#componentdefault-object)
+ - [ComponentColor/ComponentElevation objects](#componentcolorcomponentelevation-objects)
+- [Documentation for the component](#documentation-for-the-component)
+ - [Documentation structure and ordering](#documentation-structure-and-ordering)
+ - [Documentation example](#documentation-example)
+- [Accessibility of the component](#accessibility-of-the-component)
+ - [Semantics merging](#semantics-merging)
+ - [Accessibility related parameters](#accessibility-related-parameters)
+ - [Accessibility tuning](#accessibility-tuning)
+- [Evolution of the Component APIs](#evolution-of-the-component-apis)
+
+## Note on vocabulary in this doc
**@Composable component** - A @Composable function that returns `Unit` and emits the UI when it is composed in a hierarchy (later: component).
@@ -501,19 +542,19 @@
**DON’T:**
```
@Composable
-fun Image(
+fun Icon(
bitmap: ImageBitmap,
// no modifier parameter
- clipToCircle: Boolean = false
+ tint: Color = Color.Black
)
```
**DON’T:**
```
@Composable
-fun Image(
+fun Icon(
bitmap: ImageBitmap,
- clipToCircle: Boolean = false,
+ tint: Color = Color.Black,
// 1: modifier is not the first optional parameter
// 2: padding will be lost as soon as the user sets its own modifier
modifier: Modifier = Modifier.padding(8.dp)
@@ -539,14 +580,15 @@
fun IconButton(
buttonBitmap: ImageBitmap,
modifier: Modifier = Modifier,
- clipToCircle: Boolean = false
+ tint: Color = Color.Black
) {
Box(Modifier.padding(16.dp)) {
Icon(
buttonBitmap,
// modifier should be applied to the outer-most layout
// and be the first one in the chain
- modifier = Modifier.background().then(modifier)
+ modifier = Modifier.aspectRatio(1f).then(modifier),
+ tint = tint
)
}
}
@@ -559,11 +601,11 @@
buttonBitmap: ImageBitmap,
// good: first optional parameter, single of its kind
modifier: Modifier = Modifier,
- clipToCircle: Boolean = false
+ tint: Color = Color.Black
) {
// good: applied before other modifiers to the outer layout
Box(modifier.padding(16.dp)) {
- Icon(buttonBitmap, modifier = Modifier.background())
+ Icon(buttonBitmap, modifier = Modifier.aspectRatio(1f), tint = tint)
}
}
```
@@ -571,14 +613,14 @@
**Also Do:**
```
@Composable
-fun RedCanvas(
+fun ColoredCanvas(
// ok: canvas has no intrinsic size, asking for size modifiers
modifier: Modifier,
- clipToCircle: Boolean = false,
+ color: Color = Color.White,
...
) {
// good: applied before other modifiers to the outer layout
- Box(modifier.background(Color.Red)) {
+ Box(modifier.background(color)) {
...
}
}
@@ -734,7 +776,7 @@
**Note:** If your component has a limited number of parameters that have short and predictable defaults (``elevation = 0.dp``), `ComponentDefaults` object might be omitted in favor of simple inline constants.
-### MutableState<T> as a parameter
+### MutableState\<T\> as a parameter
Parameters of type `MutableState<T>` are discouraged since it promotes joint ownership over a state between a component and its user. If possible, consider making the component stateless and concede the state change to the caller. If mutation of the parent’s owned property is required in the component, consider creating a `ComponentState` class with the domain specific meaningful field that is backed by `mutableStateOf()`.
@@ -769,7 +811,7 @@
) {}
```
-### State<T> as a parameter
+### State\<T\> as a parameter
Parameters of type `State<T> `are discouraged since it unnecessarily narrows the type of objects that can be passed in the function. Given `param: State<Float>`, there are two better alternatives available, depending on the use case:
@@ -1123,7 +1165,7 @@
fun Button(
onClick: () -> Unit,
enabled: Boolean = true,
- buttonColors: ButtonColors = ButtonDefaults.buttonColors(),
+ colors: ButtonColors = ButtonDefaults.buttonColors(),
content: @Composable RowScope.() -> Unit
) {
val resolvedBackgroundColor = colors.backgroundColor(enabled)
@@ -1252,4 +1294,4 @@
// default should be provided
secondaryColor: Color = Color.Blue
) {}
-```
\ No newline at end of file
+```
diff --git a/compose/foundation/foundation-layout/src/androidAndroidTest/kotlin/androidx/compose/foundation/layout/IntrinsicTest.kt b/compose/foundation/foundation-layout/src/androidAndroidTest/kotlin/androidx/compose/foundation/layout/IntrinsicTest.kt
index dcc13ab..09bf890 100644
--- a/compose/foundation/foundation-layout/src/androidAndroidTest/kotlin/androidx/compose/foundation/layout/IntrinsicTest.kt
+++ b/compose/foundation/foundation-layout/src/androidAndroidTest/kotlin/androidx/compose/foundation/layout/IntrinsicTest.kt
@@ -49,6 +49,76 @@
@RunWith(AndroidJUnit4::class)
class IntrinsicTest : LayoutTest() {
@Test
+ fun testMaxIntrinsic_HandleNegative() = with(density) {
+ val positionedLatch = CountDownLatch(2)
+ val size = Ref<IntSize>()
+ val position = Ref<Offset>()
+ val sizeTwo = Ref<IntSize>()
+ val positionTwo = Ref<Offset>()
+ val measurePolicy = object : MeasurePolicy {
+ override fun MeasureScope.measure(
+ measurables: List<Measurable>,
+ constraints: Constraints
+ ): MeasureResult {
+ return layout(0, 0) {}
+ }
+
+ override fun IntrinsicMeasureScope.minIntrinsicHeight(
+ measurables: List<IntrinsicMeasurable>,
+ width: Int
+ ): Int {
+ return -1
+ }
+
+ override fun IntrinsicMeasureScope.minIntrinsicWidth(
+ measurables: List<IntrinsicMeasurable>,
+ height: Int
+ ): Int {
+ return -1
+ }
+
+ override fun IntrinsicMeasureScope.maxIntrinsicHeight(
+ measurables: List<IntrinsicMeasurable>,
+ width: Int
+ ): Int {
+ return -1
+ }
+
+ override fun IntrinsicMeasureScope.maxIntrinsicWidth(
+ measurables: List<IntrinsicMeasurable>,
+ height: Int
+ ): Int {
+ return -1
+ }
+ }
+ show {
+ Column {
+ Layout(modifier = Modifier
+ .width(IntrinsicSize.Min)
+ .height(IntrinsicSize.Min)
+ .saveLayoutInfo(
+ size = size,
+ position = position,
+ positionedLatch = positionedLatch
+ ), measurePolicy = measurePolicy)
+ Layout(modifier = Modifier
+ .width(IntrinsicSize.Max)
+ .height(IntrinsicSize.Max)
+ .saveLayoutInfo(
+ size = sizeTwo,
+ position = positionTwo,
+ positionedLatch = positionedLatch
+ ), measurePolicy = measurePolicy)
+ }
+ }
+ assertTrue(positionedLatch.await(1, TimeUnit.SECONDS))
+ assertEquals(IntSize(0.dp.roundToPx(), 0.dp.roundToPx()), size.value)
+ assertEquals(IntSize(0.dp.roundToPx(), 0.dp.roundToPx()), sizeTwo.value)
+ assertEquals(Offset(0f, 0f), position.value)
+ assertEquals(Offset(0f, 0f), positionTwo.value)
+ }
+
+ @Test
fun testMinIntrinsicWidth() = with(density) {
val positionedLatch = CountDownLatch(2)
val minIntrinsicWidthSize = Ref<IntSize>()
diff --git a/compose/foundation/foundation-layout/src/androidAndroidTest/kotlin/androidx/compose/foundation/layout/RowColumnTest.kt b/compose/foundation/foundation-layout/src/androidAndroidTest/kotlin/androidx/compose/foundation/layout/RowColumnTest.kt
index fd13029..91c442b 100644
--- a/compose/foundation/foundation-layout/src/androidAndroidTest/kotlin/androidx/compose/foundation/layout/RowColumnTest.kt
+++ b/compose/foundation/foundation-layout/src/androidAndroidTest/kotlin/androidx/compose/foundation/layout/RowColumnTest.kt
@@ -3838,6 +3838,53 @@
}
@Test
+ fun testRow_withNoItems_hasCorrectIntrinsicMeasurements() = with(density) {
+ testIntrinsics(
+ @Composable {
+ Row(
+ Modifier.width(IntrinsicSize.Max).height(IntrinsicSize.Max),
+ horizontalArrangement = Arrangement.spacedBy(
+ 48.dp
+ ),
+ ) { }
+ },
+ @Composable {
+ Row(
+ Modifier.width(IntrinsicSize.Min).height(IntrinsicSize.Min),
+ horizontalArrangement = Arrangement.spacedBy(
+ 48.dp
+ ),
+ ) { }
+ },
+ @Composable {
+ Column(
+ Modifier.width(IntrinsicSize.Max).height(IntrinsicSize.Max),
+ verticalArrangement = Arrangement.spacedBy(
+ 48.dp
+ ),
+ ) { }
+ },
+ @Composable {
+ Column(
+ Modifier.width(IntrinsicSize.Min).height(IntrinsicSize.Min),
+ verticalArrangement = Arrangement.spacedBy(
+ 48.dp
+ ),
+ ) { }
+ },
+ ) { minIntrinsicWidth, minIntrinsicHeight, maxIntrinsicWidth, maxIntrinsicHeight ->
+ // Min width.
+ assertEquals(0.toDp().roundToPx(), minIntrinsicWidth(0.toDp().roundToPx()))
+ // Min height.
+ assertEquals(0.toDp().roundToPx(), minIntrinsicHeight(0.toDp().roundToPx()))
+ // Max width.
+ assertEquals(0.toDp().roundToPx(), maxIntrinsicWidth(0.toDp().roundToPx()))
+ // Max height.
+ assertEquals(0.toDp().roundToPx(), maxIntrinsicHeight(0.toDp().roundToPx()))
+ }
+ }
+
+ @Test
fun testRow_withWeightChildren_hasCorrectIntrinsicMeasurements() = with(density) {
testIntrinsics(
@Composable {
diff --git a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Intrinsic.kt b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Intrinsic.kt
index b957ba4..37fe411 100644
--- a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Intrinsic.kt
+++ b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Intrinsic.kt
@@ -162,11 +162,12 @@
measurable: Measurable,
constraints: Constraints
): Constraints {
- val measuredWidth = if (width == IntrinsicSize.Min) {
+ var measuredWidth = if (width == IntrinsicSize.Min) {
measurable.minIntrinsicWidth(constraints.maxHeight)
} else {
measurable.maxIntrinsicWidth(constraints.maxHeight)
}
+ if (measuredWidth < 0) { measuredWidth = 0 }
return Constraints.fixedWidth(measuredWidth)
}
@@ -217,11 +218,12 @@
measurable: Measurable,
constraints: Constraints
): Constraints {
- val measuredHeight = if (height == IntrinsicSize.Min) {
+ var measuredHeight = if (height == IntrinsicSize.Min) {
measurable.minIntrinsicHeight(constraints.maxWidth)
} else {
measurable.maxIntrinsicHeight(constraints.maxWidth)
}
+ if (measuredHeight < 0) { measuredHeight = 0 }
return Constraints.fixedHeight(measuredHeight)
}
diff --git a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/RowColumnImpl.kt b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/RowColumnImpl.kt
index ce9028a..39edd26 100644
--- a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/RowColumnImpl.kt
+++ b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/RowColumnImpl.kt
@@ -557,6 +557,7 @@
crossAxisAvailable: Int,
mainAxisSpacing: Int
): Int {
+ if (children.isEmpty()) return 0
var weightUnitSpace = 0
var fixedSpace = 0
var totalWeight = 0f
@@ -581,6 +582,7 @@
mainAxisAvailable: Int,
mainAxisSpacing: Int
): Int {
+ if (children.isEmpty()) return 0
var fixedSpace = min((children.size - 1) * mainAxisSpacing, mainAxisAvailable)
var crossAxisMax = 0
var totalWeight = 0f
diff --git a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Size.kt b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Size.kt
index febc9bc..6e8b669 100644
--- a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Size.kt
+++ b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Size.kt
@@ -917,7 +917,8 @@
override fun equals(other: Any?): Boolean {
if (this === other) return true
- if (javaClass != other?.javaClass) return false
+ if (other === null) return false
+ if (this::class != other::class) return false
other as WrapContentElement
diff --git a/compose/foundation/foundation/api/current.txt b/compose/foundation/foundation/api/current.txt
index 605ad44..3b0d6b1 100644
--- a/compose/foundation/foundation/api/current.txt
+++ b/compose/foundation/foundation/api/current.txt
@@ -1055,6 +1055,13 @@
package androidx.compose.foundation.pager {
+ @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public sealed interface PageInfo {
+ method public int getIndex();
+ method public int getOffset();
+ property public abstract int index;
+ property public abstract int offset;
+ }
+
@SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Stable public interface PageSize {
method public int calculateMainAxisPageSize(androidx.compose.ui.unit.Density, int availableSpace, int pageSpacing);
}
@@ -1084,6 +1091,29 @@
method @Deprecated @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Composable public static void VerticalPager(int pageCount, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.pager.PagerState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.foundation.pager.PageSize pageSize, optional int beyondBoundsPageCount, optional float pageSpacing, optional androidx.compose.ui.Alignment.Horizontal horizontalAlignment, optional androidx.compose.foundation.gestures.snapping.SnapFlingBehavior flingBehavior, optional boolean userScrollEnabled, optional boolean reverseLayout, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,?>? key, optional androidx.compose.ui.input.nestedscroll.NestedScrollConnection pageNestedScrollConnection, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.pager.PagerScope,? super java.lang.Integer,kotlin.Unit> pageContent);
}
+ @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public sealed interface PagerLayoutInfo {
+ method public int getAfterContentPadding();
+ method public int getBeforeContentPadding();
+ method public androidx.compose.foundation.gestures.Orientation getOrientation();
+ method public int getPageSize();
+ method public int getPageSpacing();
+ method public boolean getReverseLayout();
+ method public int getViewportEndOffset();
+ method public long getViewportSize();
+ method public int getViewportStartOffset();
+ method public java.util.List<androidx.compose.foundation.pager.PageInfo> getVisiblePagesInfo();
+ property public abstract int afterContentPadding;
+ property public abstract int beforeContentPadding;
+ property public abstract androidx.compose.foundation.gestures.Orientation orientation;
+ property public abstract int pageSize;
+ property public abstract int pageSpacing;
+ property public abstract boolean reverseLayout;
+ property public abstract int viewportEndOffset;
+ property public abstract long viewportSize;
+ property public abstract int viewportStartOffset;
+ property public abstract java.util.List<androidx.compose.foundation.pager.PageInfo> visiblePagesInfo;
+ }
+
@SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public sealed interface PagerScope {
}
@@ -1107,6 +1137,7 @@
method public final int getInitialPage();
method public final float getInitialPageOffsetFraction();
method public final androidx.compose.foundation.interaction.InteractionSource getInteractionSource();
+ method public final androidx.compose.foundation.pager.PagerLayoutInfo getLayoutInfo();
method public final float getOffsetFractionForPage(int page);
method public abstract int getPageCount();
method public final int getSettledPage();
@@ -1122,6 +1153,7 @@
property public final float initialPageOffsetFraction;
property public final androidx.compose.foundation.interaction.InteractionSource interactionSource;
property public boolean isScrollInProgress;
+ property public final androidx.compose.foundation.pager.PagerLayoutInfo layoutInfo;
property public abstract int pageCount;
property public final int settledPage;
property public final int targetPage;
diff --git a/compose/foundation/foundation/api/restricted_current.txt b/compose/foundation/foundation/api/restricted_current.txt
index 01842ad..1e5f27a 100644
--- a/compose/foundation/foundation/api/restricted_current.txt
+++ b/compose/foundation/foundation/api/restricted_current.txt
@@ -1057,6 +1057,13 @@
package androidx.compose.foundation.pager {
+ @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public sealed interface PageInfo {
+ method public int getIndex();
+ method public int getOffset();
+ property public abstract int index;
+ property public abstract int offset;
+ }
+
@SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Stable public interface PageSize {
method public int calculateMainAxisPageSize(androidx.compose.ui.unit.Density, int availableSpace, int pageSpacing);
}
@@ -1086,6 +1093,29 @@
method @Deprecated @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Composable public static void VerticalPager(int pageCount, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.pager.PagerState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.foundation.pager.PageSize pageSize, optional int beyondBoundsPageCount, optional float pageSpacing, optional androidx.compose.ui.Alignment.Horizontal horizontalAlignment, optional androidx.compose.foundation.gestures.snapping.SnapFlingBehavior flingBehavior, optional boolean userScrollEnabled, optional boolean reverseLayout, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,?>? key, optional androidx.compose.ui.input.nestedscroll.NestedScrollConnection pageNestedScrollConnection, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.pager.PagerScope,? super java.lang.Integer,kotlin.Unit> pageContent);
}
+ @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public sealed interface PagerLayoutInfo {
+ method public int getAfterContentPadding();
+ method public int getBeforeContentPadding();
+ method public androidx.compose.foundation.gestures.Orientation getOrientation();
+ method public int getPageSize();
+ method public int getPageSpacing();
+ method public boolean getReverseLayout();
+ method public int getViewportEndOffset();
+ method public long getViewportSize();
+ method public int getViewportStartOffset();
+ method public java.util.List<androidx.compose.foundation.pager.PageInfo> getVisiblePagesInfo();
+ property public abstract int afterContentPadding;
+ property public abstract int beforeContentPadding;
+ property public abstract androidx.compose.foundation.gestures.Orientation orientation;
+ property public abstract int pageSize;
+ property public abstract int pageSpacing;
+ property public abstract boolean reverseLayout;
+ property public abstract int viewportEndOffset;
+ property public abstract long viewportSize;
+ property public abstract int viewportStartOffset;
+ property public abstract java.util.List<androidx.compose.foundation.pager.PageInfo> visiblePagesInfo;
+ }
+
@SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public sealed interface PagerScope {
}
@@ -1109,6 +1139,7 @@
method public final int getInitialPage();
method public final float getInitialPageOffsetFraction();
method public final androidx.compose.foundation.interaction.InteractionSource getInteractionSource();
+ method public final androidx.compose.foundation.pager.PagerLayoutInfo getLayoutInfo();
method public final float getOffsetFractionForPage(int page);
method public abstract int getPageCount();
method public final int getSettledPage();
@@ -1124,6 +1155,7 @@
property public final float initialPageOffsetFraction;
property public final androidx.compose.foundation.interaction.InteractionSource interactionSource;
property public boolean isScrollInProgress;
+ property public final androidx.compose.foundation.pager.PagerLayoutInfo layoutInfo;
property public abstract int pageCount;
property public final int settledPage;
property public final int targetPage;
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 5d26bb0..979c731 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
@@ -38,9 +38,11 @@
import androidx.compose.material.Text
import androidx.compose.material.TopAppBar
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
@@ -338,3 +340,16 @@
}
}
}
+
+@OptIn(ExperimentalFoundationApi::class)
+@Sampled
+@Composable
+fun UsingPagerLayoutInfoForSideEffectSample() {
+ val pagerState = rememberPagerState() { 10 }
+ LaunchedEffect(pagerState) {
+ snapshotFlow { pagerState.layoutInfo.visiblePagesInfo.firstOrNull() }
+ .collect {
+ // use the new first visible page info
+ }
+ }
+}
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/MagnifierTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/MagnifierTest.kt
index cf58f35..89438cc 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/MagnifierTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/MagnifierTest.kt
@@ -719,6 +719,36 @@
@SdkSuppress(minSdkVersion = 28)
@Test
+ fun platformMagnifierModifier_firesOnSizeChanged_initially_whenSourceCenterUnspecified() {
+ val magnifierSize = IntSize(10, 11)
+ val sizeEvents = mutableListOf<DpSize>()
+ val platformMagnifier = CountingPlatformMagnifier().apply {
+ size = magnifierSize
+ }
+ rule.setContent {
+ Box(
+ Modifier.magnifier(
+ sourceCenter = { Offset.Unspecified },
+ magnifierCenter = { Offset.Unspecified },
+ zoom = Float.NaN,
+ style = MagnifierStyle.Default,
+ onSizeChanged = { sizeEvents += it },
+ platformMagnifierFactory = PlatformMagnifierFactory(platformMagnifier)
+ )
+ )
+ }
+
+ rule.runOnIdle {
+ assertThat(sizeEvents).containsExactly(
+ with(rule.density) {
+ magnifierSize.toSize().toDpSize()
+ }
+ )
+ }
+ }
+
+ @SdkSuppress(minSdkVersion = 28)
+ @Test
fun platformMagnifierModifier_firesOnSizeChanged_whenNewSizeRequested() {
val size1 = IntSize(10, 11)
val size2 = size1 * 2
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/OverscrollTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/OverscrollTest.kt
index f0f7f96..826dfbbc 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/OverscrollTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/OverscrollTest.kt
@@ -24,6 +24,7 @@
import androidx.compose.foundation.gestures.ScrollableState
import androidx.compose.foundation.gestures.scrollable
import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
@@ -61,6 +62,7 @@
import com.google.common.truth.Truth.assertWithMessage
import kotlin.math.abs
import kotlinx.coroutines.runBlocking
+import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@@ -786,6 +788,25 @@
}
}
+ @MediumTest
+ @Test
+ fun testOverscrollModifierDrawsOnce() {
+ var drawCount = 0
+ rule.setContent {
+ Spacer(
+ modifier = Modifier.testTag(boxTag)
+ .size(100.dp)
+ .overscroll(ScrollableDefaults.overscrollEffect())
+ .drawBehind {
+ drawCount++
+ }
+ )
+ }
+ rule.runOnIdle {
+ assertEquals(1, drawCount)
+ }
+ }
+
@OptIn(ExperimentalTestApi::class)
@ExperimentalFoundationApi
@MediumTest
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/pager/PagerAccessibilityTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/pager/PagerAccessibilityTest.kt
index 752614c..ac34782 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/pager/PagerAccessibilityTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/pager/PagerAccessibilityTest.kt
@@ -31,6 +31,7 @@
import androidx.core.view.ViewCompat
import androidx.test.filters.LargeTest
import com.google.common.truth.Truth.assertThat
+import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
@@ -63,6 +64,7 @@
rule.runOnIdle { assertThat(pagerState.currentPageOffsetFraction).isEqualTo(0.0f) }
}
+ @Ignore
@Test
fun accessibilityPaging_animateScrollToPage() {
createPager(initialPage = 5, pageCount = { DefaultPageCount })
@@ -106,6 +108,7 @@
rule.runOnIdle { assertThat(pagerState.currentPageOffsetFraction).isEqualTo(0.0f) }
}
+ @Ignore
@Test
fun userScrollEnabledIsOff_shouldNotAllowPageAccessibilityActions() {
// Arrange
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/pager/PagerLayoutInfoTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/pager/PagerLayoutInfoTest.kt
index b9975e5..acbcc3f 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/pager/PagerLayoutInfoTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/pager/PagerLayoutInfoTest.kt
@@ -175,26 +175,6 @@
}
@Test
- fun totalCountIsCorrect() {
- var count by mutableStateOf(10)
- createPager(
- pageCount = { count },
- pageSize = { PageSize.Fixed(10.dp) }
- ) {
- Box(Modifier.requiredSize(10.dp))
- }
-
- rule.runOnIdle {
- assertThat(pagerState.layoutInfo.pagesCount).isEqualTo(10)
- count = 20
- }
-
- rule.runOnIdle {
- assertThat(pagerState.layoutInfo.pagesCount).isEqualTo(20)
- }
- }
-
- @Test
fun viewportOffsetsAndSizeAreCorrect() {
val sizePx = 45
val sizeDp = with(rule.density) { sizePx.toDp() }
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/selection/AbstractSelectionMagnifierTests.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/selection/AbstractSelectionMagnifierTests.kt
index 3160b86..7caa305 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/selection/AbstractSelectionMagnifierTests.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/selection/AbstractSelectionMagnifierTests.kt
@@ -16,7 +16,6 @@
package androidx.compose.foundation.text.selection
-import androidx.compose.foundation.MagnifierPositionInRoot
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.text.BasicText
@@ -25,12 +24,10 @@
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.geometry.isUnspecified
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.semantics.getOrNull
import androidx.compose.ui.test.SemanticsMatcher
-import androidx.compose.ui.test.SemanticsMatcher.Companion.keyIsDefined
import androidx.compose.ui.test.TouchInjectionScope
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.longClick
@@ -100,7 +97,7 @@
rule.onNodeWithTag(tag).performTouchInput { longClick() }
// No magnifier yet.
- assertNoMagnifierExists()
+ assertNoMagnifierExists(rule)
}
@Test
@@ -112,7 +109,7 @@
// TODO(b/209698586) Select programmatically once that's fixed.
rule.onNodeWithTag(tag).performTouchInput { longClick() }
// No magnifier yet.
- assertNoMagnifierExists()
+ assertNoMagnifierExists(rule)
}
@Test
@@ -288,13 +285,39 @@
rule.onNode(isSelectionHandle(handle))
.performTouchInput { down(center) }
- assertThat(getMagnifierCenterOffset()).isNotEqualTo(Offset.Zero)
+ assertMagnifierExists(rule)
// Stop touching the handle to hide the magnifier.
rule.onNode(isSelectionHandle(handle))
.performTouchInput { up() }
- assertNoMagnifierExists()
+ assertNoMagnifierExists(rule)
+ }
+
+ protected fun checkMagnifierAppears_whenCursorHandleDragged() {
+ rule.setContent {
+ Content("aaaa aaaa aaaa", Modifier.testTag(tag))
+ }
+
+ showHandle(Handle.Cursor)
+
+ // Touch the handle
+ rule.onNode(isSelectionHandle(Handle.Cursor))
+ .performTouchInput { down(center) }
+
+ assertNoMagnifierExists(rule)
+
+ // move the handle to show the magnifier
+ rule.onNode(isSelectionHandle(Handle.Cursor))
+ .performTouchInput { movePastSlopBy(Offset(x = 1f, y = 0f)) }
+
+ assertMagnifierExists(rule)
+
+ // Stop touching the handle to hide the magnifier.
+ rule.onNode(isSelectionHandle(Handle.Cursor))
+ .performTouchInput { up() }
+
+ assertNoMagnifierExists(rule)
}
protected fun checkMagnifierShowsDuringInitialLongPressDrag(
@@ -326,8 +349,7 @@
}
// Magnifier should show after long-press starts.
- val magnifierInitialPosition = getMagnifierCenterOffset()
- assertThat(magnifierInitialPosition).isNotEqualTo(Offset.Zero)
+ val magnifierInitialPosition = getMagnifierCenterOffset(rule, requireSpecified = true)
// Drag horizontally - the magnifier should follow.
rule.onNodeWithTag(tag)
@@ -337,7 +359,7 @@
moveBy(dragDistance * dragDirection)
}
- assertThat(getMagnifierCenterOffset())
+ assertThat(getMagnifierCenterOffset(rule))
.isEqualTo(magnifierInitialPosition + (dragDistance * dragDirection))
}
@@ -368,13 +390,13 @@
down(center)
movePastSlopBy(dragDistance)
}
- val magnifierInitialPosition = getMagnifierCenterOffset()
+ val magnifierInitialPosition = getMagnifierCenterOffset(rule, requireSpecified = true)
// Drag the handle horizontally - the magnifier should follow.
rule.onNode(isSelectionHandle(handle))
.performTouchInput { moveBy(dragDistance) }
- assertThat(getMagnifierCenterOffset())
+ assertThat(getMagnifierCenterOffset(rule))
.isEqualTo(magnifierInitialPosition + dragDistance)
}
@@ -414,7 +436,8 @@
movePastSlopBy(moveOffset)
}
}
- val magnifierInitialPosition = getMagnifierCenterOffset()
+
+ val magnifierInitialPosition = getMagnifierCenterOffset(rule, requireSpecified = true)
// Drag just a little past the end of the line.
rule.onNode(isSelectionHandle(handle))
@@ -428,7 +451,7 @@
}
// The magnifier shouldn't have moved.
- assertThat(getMagnifierCenterOffset()).isEqualTo(magnifierInitialPosition)
+ assertThat(getMagnifierCenterOffset(rule)).isEqualTo(magnifierInitialPosition)
}
protected fun checkMagnifierHiddenWhenDraggedTooFar(
@@ -468,7 +491,7 @@
}
// The magnifier should be gone.
- assertNoMagnifierExists()
+ assertNoMagnifierExists(rule)
}
protected fun checkMagnifierFollowsHandleVerticallyBetweenLines(handle: Handle) {
@@ -491,13 +514,13 @@
// Touch the handle to show the magnifier.
rule.onNode(isSelectionHandle(handle))
.performTouchInput { down(center) }
- val magnifierInitialPosition = getMagnifierCenterOffset()
+ val magnifierInitialPosition = getMagnifierCenterOffset(rule, requireSpecified = true)
// Drag the handle down - the magnifier should follow.
rule.onNode(isSelectionHandle(handle))
.performTouchInput { movePastSlopBy(dragDistance) }
- val (x, y) = getMagnifierCenterOffset()
+ val (x, y) = getMagnifierCenterOffset(rule)
assertThat(x).isEqualTo(magnifierInitialPosition.x)
assertThat(y)
.isWithin(1f)
@@ -530,7 +553,7 @@
rule.onNode(isSelectionHandle(handle))
.performTouchInput { movePastSlopBy(dragDistance) }
- assertNoMagnifierExists()
+ assertNoMagnifierExists(rule)
}
protected fun checkMagnifierDoesNotFollowHandleVerticallyWithinLine(handle: Handle) {
@@ -551,7 +574,8 @@
// Touch the handle to show the magnifier.
rule.onNode(isSelectionHandle(handle))
.performTouchInput { down(center) }
- val magnifierInitialPosition = getMagnifierCenterOffset()
+
+ val magnifierInitialPosition = getMagnifierCenterOffset(rule, requireSpecified = true)
// Drag the handle up - the magnifier should not follow.
// Note that dragging it down *should* cause it to move to the line below, so only drag up.
@@ -560,7 +584,7 @@
movePastSlopBy(-dragDistance)
}
- assertThat(getMagnifierCenterOffset())
+ assertThat(getMagnifierCenterOffset(rule))
.isEqualTo(magnifierInitialPosition)
}
@@ -590,26 +614,4 @@
)
moveBy(delta + slop)
}
-
- private fun getMagnifierCenterOffset(): Offset =
- rule.onNode(keyIsDefined(MagnifierPositionInRoot))
- .fetchSemanticsNode()
- .config[MagnifierPositionInRoot]
- .let(rule::runOnIdle)
-
- /**
- * Asserts that there is no magnifier being displayed. This may be because no
- * `Modifier.magnifier` modifiers are currently set on any nodes, or because all the magnifiers
- * that exist have an unspecified position.
- */
- private fun assertNoMagnifierExists() {
- // The magnifier semantics will be present whenever the modifier is, even if the modifier
- // isn't actually showing a magnifier because the position is unspecified. So instead of
- // just checking that no semantics property exists, we need to check that the value of each
- // property won't show a magnifier.
- val magnifierNodes = rule.onAllNodes(keyIsDefined(MagnifierPositionInRoot))
- .fetchSemanticsNodes(atLeastOneRootRequired = false)
- .map { it.config[MagnifierPositionInRoot].invoke() }
- assertThat(magnifierNodes.all { it.isUnspecified }).isTrue()
- }
}
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/selection/MagnifierTestUtils.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/selection/MagnifierTestUtils.kt
new file mode 100644
index 0000000..e926727
--- /dev/null
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/selection/MagnifierTestUtils.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.text.selection
+
+import androidx.compose.foundation.MagnifierPositionInRoot
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.isSpecified
+import androidx.compose.ui.geometry.isUnspecified
+import androidx.compose.ui.test.SemanticsMatcher
+import androidx.compose.ui.test.junit4.ComposeTestRule
+import com.google.common.truth.Truth.assertWithMessage
+import kotlin.test.fail
+
+internal fun getMagnifierCenterOffset(
+ rule: ComposeTestRule,
+ requireSpecified: Boolean = false
+): Offset {
+ val positions = getMagnifierPositions(rule)
+ return if (requireSpecified) {
+ val specifiedPositions = positions.filter { it.isSpecified }
+ if (specifiedPositions.size != 1) {
+ fail(
+ "Expected one specified magnifier position, but found ${specifiedPositions.size}${
+ if (specifiedPositions.isEmpty()) "." else ": $specifiedPositions"
+ }"
+ )
+ }
+ specifiedPositions.single()
+ } else {
+ positions.firstOrNull() ?: fail("No magnifier position found")
+ }
+}
+
+internal fun assertMagnifierExists(rule: ComposeTestRule) {
+ assertWithMessage("Expected magnifier to exist and have specified coordinates.")
+ .that(getMagnifierPositions(rule).any { it.isSpecified })
+ .isTrue()
+}
+
+/**
+ * Asserts that there is no magnifier being displayed. This may be because no
+ * `Modifier.magnifier` modifiers are currently set on any nodes, or because all the magnifiers
+ * that exist have an unspecified position.
+ */
+internal fun assertNoMagnifierExists(rule: ComposeTestRule) {
+ // The magnifier semantics will be present whenever the modifier is, even if the modifier
+ // isn't actually showing a magnifier because the position is unspecified. So instead of
+ // just checking that no semantics property exists, we need to check that the value of each
+ // property won't show a magnifier.
+ assertWithMessage("Expected magnifier to not exist or exist with unspecified coordinates.")
+ .that(getMagnifierPositions(rule).all { it.isUnspecified })
+ .isTrue()
+}
+
+internal fun getMagnifierPositions(rule: ComposeTestRule) =
+ rule.onAllNodes(SemanticsMatcher.keyIsDefined(MagnifierPositionInRoot))
+ .fetchSemanticsNodes(atLeastOneRootRequired = false)
+ .map { it.config[MagnifierPositionInRoot] }
+ .let { positionFunctions ->
+ rule.runOnIdle {
+ positionFunctions.map { it.invoke() }
+ }
+ }
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/selection/TextFieldMagnifierTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/selection/TextFieldMagnifierTest.kt
index 0cc0ff8..ee1c079 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/selection/TextFieldMagnifierTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/selection/TextFieldMagnifierTest.kt
@@ -70,8 +70,8 @@
}
@Test
- fun magnifier_appears_whileStartCursorTouched() {
- checkMagnifierAppears_whileHandleTouched(Handle.Cursor)
+ fun magnifier_appears_whenCursorStartDrag() {
+ checkMagnifierAppears_whenCursorHandleDragged()
}
@Test
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/selection/TextFieldVisualTransformationMagnifierTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/selection/TextFieldVisualTransformationMagnifierTest.kt
index 0b9362c..c0105175 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/selection/TextFieldVisualTransformationMagnifierTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/selection/TextFieldVisualTransformationMagnifierTest.kt
@@ -16,7 +16,6 @@
package androidx.compose.foundation.text.selection
-import androidx.compose.foundation.MagnifierPositionInRoot
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.text.BasicTextField
@@ -90,11 +89,16 @@
val handle = config.handle
showHandle(handle)
+ assertNoMagnifierExists(rule)
+
// Touch the handle to show the magnifier.
rule.onNode(isSelectionHandle(handle))
- .performTouchInput { down(center) }
+ .performTouchInput {
+ down(center)
+ movePastSlopBy(Offset(1f, 0f))
+ }
- assertThat(getMagnifierCenterOffset()).isNotEqualTo(Offset.Zero)
+ assertMagnifierExists(rule)
}
@Ignore("b/266233836")
@@ -124,13 +128,13 @@
// Touch the handle to show the magnifier.
rule.onNode(isSelectionHandle(handle))
.performTouchInput { down(center) }
- val magnifierInitialPosition = getMagnifierCenterOffset()
+ val magnifierInitialPosition = getMagnifierCenterOffset(rule, requireSpecified = true)
// Drag the handle horizontally - the magnifier should follow.
rule.onNode(isSelectionHandle(handle))
.performTouchInput { movePastSlopBy(dragDistance) }
- assertThat(getMagnifierCenterOffset())
+ assertThat(getMagnifierCenterOffset(rule))
.isEqualTo(magnifierInitialPosition + dragDistance)
}
@@ -162,12 +166,6 @@
moveBy(delta + slop)
}
- private fun getMagnifierCenterOffset(): Offset =
- rule.onNode(SemanticsMatcher.keyIsDefined(MagnifierPositionInRoot))
- .fetchSemanticsNode()
- .config[MagnifierPositionInRoot]
- .let(rule::runOnIdle)
-
companion object {
@JvmStatic
@Parameterized.Parameters(name = "{0}")
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/selection/gestures/TextFieldSelectionHandlesGesturesTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/selection/gestures/TextFieldSelectionHandlesGesturesTest.kt
new file mode 100644
index 0000000..a61a929
--- /dev/null
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/selection/gestures/TextFieldSelectionHandlesGesturesTest.kt
@@ -0,0 +1,136 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.text.selection.gestures
+
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.text.BasicTextField
+import androidx.compose.foundation.text.Handle
+import androidx.compose.foundation.text.selection.fetchTextLayoutResult
+import androidx.compose.foundation.text.selection.gestures.util.TextFieldSelectionAsserter
+import androidx.compose.foundation.text.selection.gestures.util.applyAndAssert
+import androidx.compose.foundation.text.selection.gestures.util.to
+import androidx.compose.foundation.text.selection.isSelectionHandle
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.SemanticsNodeInteraction
+import androidx.compose.ui.test.longClick
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.performTouchInput
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.input.TextFieldValue
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+internal class TextFieldSelectionHandlesGesturesTest : AbstractSelectionGesturesTest() {
+
+ override val pointerAreaTag = "testTag"
+ private val textContent = "line1\nline2 text1 text2\nline3"
+
+ private val textFieldValue = mutableStateOf(TextFieldValue(textContent))
+
+ private lateinit var asserter: TextFieldSelectionAsserter
+
+ @Composable
+ override fun Content() {
+ BasicTextField(
+ value = textFieldValue.value,
+ onValueChange = { textFieldValue.value = it },
+ textStyle = TextStyle(fontFamily = fontFamily, fontSize = fontSize),
+ modifier = Modifier
+ .fillMaxWidth()
+ .testTag(pointerAreaTag),
+ )
+ }
+
+ @Before
+ fun setupAsserter() {
+ performTouchGesture {
+ longClick(characterPosition(13))
+ }
+
+ asserter = TextFieldSelectionAsserter(
+ textContent = textContent,
+ rule = rule,
+ textToolbar = textToolbar,
+ hapticFeedback = hapticFeedback,
+ getActual = { textFieldValue.value }
+ ).apply {
+ selection = 12 to 17
+ selectionHandlesShown = true
+ textToolbarShown = true
+ hapticsCount++
+ }
+ }
+
+ @Test
+ fun whenOnlySetup_middleWordIsSelected() {
+ asserter.assert()
+ }
+
+ @Test
+ fun whenTouchHandle_magnifierReplacesToolbar() {
+ withHandlePressed(Handle.SelectionEnd) {
+ asserter.applyAndAssert {
+ magnifierShown = true
+ textToolbarShown = false
+ }
+ }
+
+ asserter.applyAndAssert {
+ magnifierShown = false
+ textToolbarShown = true
+ }
+
+ withHandlePressed(Handle.SelectionStart) {
+ asserter.applyAndAssert {
+ magnifierShown = true
+ textToolbarShown = false
+ }
+ }
+
+ asserter.applyAndAssert {
+ magnifierShown = false
+ textToolbarShown = true
+ }
+ }
+
+ private fun withHandlePressed(
+ handle: Handle,
+ block: SemanticsNodeInteraction.() -> Unit
+ ) = rule.onNode(isSelectionHandle(handle)).run {
+ performTouchInput { down(center) }
+ block()
+ performTouchInput { up() }
+ }
+
+ private fun characterPosition(offset: Int): Offset = characterBox(offset).center
+
+ private fun characterBox(offset: Int): Rect {
+ val nodePosition = rule.onNodeWithTag(pointerAreaTag).fetchSemanticsNode().positionInRoot
+ val textLayoutResult = rule.onNodeWithTag(pointerAreaTag).fetchTextLayoutResult()
+ return textLayoutResult.getBoundingBox(offset).translate(nodePosition)
+ }
+}
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text2/BasicSecureTextFieldTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text2/BasicSecureTextFieldTest.kt
index c2d6ba6..8943967 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text2/BasicSecureTextFieldTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text2/BasicSecureTextFieldTest.kt
@@ -21,6 +21,7 @@
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.text.selection.FakeTextToolbar
import androidx.compose.foundation.text.selection.fetchTextLayoutResult
import androidx.compose.foundation.text2.input.TextFieldState
import androidx.compose.foundation.text2.input.TextObfuscationMode
@@ -33,6 +34,7 @@
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalClipboardManager
+import androidx.compose.ui.platform.LocalTextToolbar
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.semantics.SemanticsActions
import androidx.compose.ui.semantics.SemanticsProperties
@@ -330,4 +332,38 @@
assertThat(state.text.toString()).isEqualTo("Hello World!")
}
}
+
+ @OptIn(ExperimentalTestApi::class)
+ @Test
+ fun toolbarDoesNotShowCopyOrCut() {
+ var copyOptionAvailable = false
+ var cutOptionAvailable = false
+ var showMenuRequested = false
+ val textToolbar = FakeTextToolbar(
+ onShowMenu = { _, onCopyRequested, _, onCutRequested, _ ->
+ showMenuRequested = true
+ copyOptionAvailable = onCopyRequested != null
+ cutOptionAvailable = onCutRequested != null
+ },
+ onHideMenu = {}
+ )
+ val state = TextFieldState("Hello")
+ rule.setContent {
+ CompositionLocalProvider(LocalTextToolbar provides textToolbar) {
+ BasicSecureTextField(
+ state = state,
+ modifier = Modifier.testTag(Tag)
+ )
+ }
+ }
+
+ rule.onNodeWithTag(Tag).requestFocus()
+ rule.onNodeWithTag(Tag).performTextInputSelection(TextRange(0, 5))
+
+ rule.runOnIdle {
+ assertThat(showMenuRequested).isTrue()
+ assertThat(copyOptionAvailable).isFalse()
+ assertThat(cutOptionAvailable).isFalse()
+ }
+ }
}
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text2/selection/TextFieldLongClickTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text2/selection/TextFieldLongClickTest.kt
index 64cb81d..767804ea 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text2/selection/TextFieldLongClickTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text2/selection/TextFieldLongClickTest.kt
@@ -42,7 +42,6 @@
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.test.filters.LargeTest
-import androidx.test.filters.SdkSuppress
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
@@ -123,9 +122,8 @@
assertThat(state.text.selectionInChars).isEqualTo(TextRange(4, 7))
}
- @SdkSuppress(minSdkVersion = 23)
@Test
- fun longClickOnWhitespace_selectsNextWord() {
+ fun longClickOnWhitespace_doesNotSelectWhitespace() {
val state = TextFieldState("abc def ghi")
rule.setContent {
BasicTextField2(
@@ -141,7 +139,8 @@
rule.onNode(isSelectionHandle(Handle.SelectionStart)).assertIsDisplayed()
rule.onNode(isSelectionHandle(Handle.SelectionEnd)).assertIsDisplayed()
- assertThat(state.text.selectionInChars).isEqualTo(TextRange(8, 11))
+ assertThat(state.text.selectionInChars).isNotEqualTo(TextRange(7, 8))
+ assertThat(state.text.selectionInChars.collapsed).isFalse()
}
@Test
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text2/selection/TextFieldTextToolbarTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text2/selection/TextFieldTextToolbarTest.kt
index 31bcb9c..93e8de8 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text2/selection/TextFieldTextToolbarTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text2/selection/TextFieldTextToolbarTest.kt
@@ -27,6 +27,7 @@
import androidx.compose.foundation.text.selection.FakeTextToolbar
import androidx.compose.foundation.text.selection.isSelectionHandle
import androidx.compose.foundation.text2.BasicTextField2
+import androidx.compose.foundation.text2.input.TextEditFilter
import androidx.compose.foundation.text2.input.TextFieldLineLimits
import androidx.compose.foundation.text2.input.TextFieldState
import androidx.compose.foundation.text2.input.placeCursorAtEnd
@@ -52,8 +53,10 @@
import androidx.compose.ui.test.performKeyInput
import androidx.compose.ui.test.performMouseInput
import androidx.compose.ui.test.performTextInput
+import androidx.compose.ui.test.performTextInputSelection
import androidx.compose.ui.test.performTouchInput
import androidx.compose.ui.test.pressKey
+import androidx.compose.ui.test.requestFocus
import androidx.compose.ui.test.swipeLeft
import androidx.compose.ui.test.swipeRight
import androidx.compose.ui.text.AnnotatedString
@@ -389,6 +392,142 @@
}
}
+ @OptIn(ExperimentalTestApi::class)
+ @Test
+ fun toolbarDoesNotShowCopyOrCut_whenSelectionIsCollapsed() {
+ var cutOptionAvailable = false
+ var copyOptionAvailable = false
+ val textToolbar = FakeTextToolbar(
+ onShowMenu = { _, onCopyRequested, _, onCutRequested, _ ->
+ copyOptionAvailable = onCopyRequested != null
+ cutOptionAvailable = onCutRequested != null
+ },
+ onHideMenu = {}
+ )
+ val state = TextFieldState("Hello")
+ setupContent(state, textToolbar, true)
+
+ rule.onNodeWithTag(TAG).requestFocus()
+ rule.onNodeWithTag(TAG).performTextInputSelection(TextRange(2, 2))
+
+ rule.runOnIdle {
+ assertThat(copyOptionAvailable).isFalse()
+ assertThat(cutOptionAvailable).isFalse()
+ }
+ }
+
+ @OptIn(ExperimentalTestApi::class)
+ @Test
+ fun toolbarShowsCopyAndCut_whenSelectionIsExpanded() {
+ var cutOptionAvailable = false
+ var copyOptionAvailable = false
+ val textToolbar = FakeTextToolbar(
+ onShowMenu = { _, onCopyRequested, _, onCutRequested, _ ->
+ copyOptionAvailable = onCopyRequested != null
+ cutOptionAvailable = onCutRequested != null
+ },
+ onHideMenu = {}
+ )
+ val state = TextFieldState("Hello")
+ setupContent(state, textToolbar, true)
+
+ rule.onNodeWithTag(TAG).requestFocus()
+ rule.onNodeWithTag(TAG).performTextInputSelection(TextRange(2, 4))
+
+ rule.runOnIdle {
+ assertThat(copyOptionAvailable).isTrue()
+ assertThat(cutOptionAvailable).isTrue()
+ }
+ }
+
+ @OptIn(ExperimentalTestApi::class)
+ @Test
+ fun copyUpdatesClipboardManager_placesCursorAtTheEndOfSelectedRegion() {
+ var copyOption: (() -> Unit)? = null
+ val textToolbar = FakeTextToolbar(
+ onShowMenu = { _, onCopyRequested, _, _, _ ->
+ copyOption = onCopyRequested
+ },
+ onHideMenu = {}
+ )
+ val clipboardManager = FakeClipboardManager()
+ val state = TextFieldState("Hello")
+ setupContent(state, textToolbar, true, clipboardManager)
+
+ rule.onNodeWithTag(TAG).requestFocus()
+ rule.onNodeWithTag(TAG).performTextInputSelection(TextRange(0, 5))
+
+ rule.runOnIdle {
+ copyOption!!.invoke()
+ }
+
+ rule.runOnIdle {
+ assertThat(clipboardManager.getText()?.toString()).isEqualTo("Hello")
+ assertThat(state.text.selectionInChars).isEqualTo(TextRange(5))
+ }
+ }
+
+ @OptIn(ExperimentalTestApi::class)
+ @Test
+ fun cutUpdatesClipboardManager_placesCursorAtTheEndOfSelectedRegion_removesTheCutContent() {
+ var cutOption: (() -> Unit)? = null
+ val textToolbar = FakeTextToolbar(
+ onShowMenu = { _, _, _, onCutRequested, _ ->
+ cutOption = onCutRequested
+ },
+ onHideMenu = {}
+ )
+ val clipboardManager = FakeClipboardManager()
+ val state = TextFieldState("Hello World!")
+ setupContent(state, textToolbar, true, clipboardManager)
+
+ rule.onNodeWithTag(TAG).requestFocus()
+ rule.onNodeWithTag(TAG).performTextInputSelection(TextRange(1, 5))
+
+ rule.runOnIdle {
+ cutOption!!.invoke()
+ }
+
+ rule.runOnIdle {
+ assertThat(clipboardManager.getText()?.toString()).isEqualTo("ello")
+ assertThat(state.text.toString()).isEqualTo("H World!")
+ assertThat(state.text.selectionInChars).isEqualTo(TextRange(1))
+ }
+ }
+
+ @OptIn(ExperimentalTestApi::class)
+ @Test
+ fun cutAppliesFilter() {
+ var cutOption: (() -> Unit)? = null
+ val textToolbar = FakeTextToolbar(
+ onShowMenu = { _, _, _, onCutRequested, _ ->
+ cutOption = onCutRequested
+ },
+ onHideMenu = {}
+ )
+ val clipboardManager = FakeClipboardManager()
+ val state = TextFieldState("Hello World!")
+ setupContent(state, textToolbar, true, clipboardManager) { original, changes ->
+ // only reject text changes, accept selection
+ val selection = changes.selectionInChars
+ changes.replace(0, changes.length, original.toString())
+ changes.selectCharsIn(selection)
+ }
+
+ rule.onNodeWithTag(TAG).requestFocus()
+ rule.onNodeWithTag(TAG).performTextInputSelection(TextRange(1, 5))
+
+ rule.runOnIdle {
+ cutOption!!.invoke()
+ }
+
+ rule.runOnIdle {
+ assertThat(clipboardManager.getText()?.toString()).isEqualTo("ello")
+ assertThat(state.text.toString()).isEqualTo("Hello World!")
+ assertThat(state.text.selectionInChars).isEqualTo(TextRange(1))
+ }
+ }
+
@Test
fun tappingTextField_hidesTheToolbar() {
val textToolbar = FakeTextToolbar()
@@ -507,6 +646,7 @@
toolbar: TextToolbar = FakeTextToolbar(),
singleLine: Boolean = false,
clipboardManager: ClipboardManager = FakeClipboardManager(),
+ filter: TextEditFilter? = null
) {
rule.setContent {
CompositionLocalProvider(
@@ -526,7 +666,8 @@
TextFieldLineLimits.SingleLine
} else {
TextFieldLineLimits.Default
- }
+ },
+ filter = filter
)
}
}
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/AndroidOverscroll.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/AndroidOverscroll.kt
index 48fccdc..2da447e 100644
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/AndroidOverscroll.kt
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/AndroidOverscroll.kt
@@ -27,9 +27,10 @@
import androidx.compose.foundation.gestures.awaitEachGesture
import androidx.compose.foundation.gestures.awaitFirstDown
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.neverEqualPolicy
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.DrawModifier
import androidx.compose.ui.geometry.Offset
@@ -123,7 +124,9 @@
allEffects.fastForEach { it.color = overscrollConfig.glowColor.toArgb() }
}
- private val redrawSignal = mutableStateOf(Unit, neverEqualPolicy())
+ // TODO replace with mutableStateOf(Unit, neverEqualPolicy()) after b/291647821 is addressed
+ private var consumeCount = -1
+ private var invalidateCount by mutableIntStateOf(0)
@VisibleForTesting
internal var invalidationEnabled = true
@@ -349,7 +352,7 @@
return
}
this.drawIntoCanvas {
- redrawSignal.value // <-- value read to redraw if needed
+ consumeCount = invalidateCount // <-- value read to redraw if needed
val canvas = it.nativeCanvas
var needsInvalidate = false
// each side workflow:
@@ -435,7 +438,9 @@
private fun invalidateOverscroll() {
if (invalidationEnabled) {
- redrawSignal.value = Unit
+ if (consumeCount == invalidateCount) {
+ invalidateCount++
+ }
}
}
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/Magnifier.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/Magnifier.kt
index 229b44c..662a7b3 100644
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/Magnifier.kt
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/Magnifier.kt
@@ -462,6 +462,7 @@
val view = view ?: return
val density = density ?: return
magnifier = platformMagnifierFactory.create(style, view, density, zoom)
+ updateSizeIfNecessary()
}
private fun updateMagnifier() {
@@ -490,17 +491,23 @@
},
zoom = zoom
)
-
- if (magnifier.size != previousSize) {
- onSizeChanged?.invoke(with(density) { magnifier.size.toSize().toDpSize() })
- previousSize = magnifier.size
- }
+ updateSizeIfNecessary()
} else {
// Can't place the magnifier at an unspecified location, so just hide it.
magnifier.dismiss()
}
}
+ private fun updateSizeIfNecessary() {
+ val magnifier = magnifier ?: return
+ val density = density ?: return
+
+ if (magnifier.size != previousSize) {
+ onSizeChanged?.invoke(with(density) { magnifier.size.toSize().toDpSize() })
+ previousSize = magnifier.size
+ }
+ }
+
override fun ContentDrawScope.draw() {
drawContent()
// don't update the magnifier immediately, actual frame draw happens right after all draw
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Clickable.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Clickable.kt
index 87e9610..f117895 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Clickable.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Clickable.kt
@@ -432,7 +432,8 @@
override fun equals(other: Any?): Boolean {
if (this === other) return true
- if (javaClass != other?.javaClass) return false
+ if (other === null) return false
+ if (this::class != other::class) return false
other as ClickableElement
@@ -494,7 +495,8 @@
override fun equals(other: Any?): Boolean {
if (this === other) return true
- if (javaClass != other?.javaClass) return false
+ if (other === null) return false
+ if (this::class != other::class) return false
other as CombinedClickableElement
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Draggable.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Draggable.kt
index 05da99d..7a08add 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Draggable.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Draggable.kt
@@ -239,7 +239,8 @@
override fun equals(other: Any?): Boolean {
if (this === other) return true
- if (javaClass != other?.javaClass) return false
+ if (other === null) return false
+ if (this::class != other::class) return false
other as DraggableElement
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Transformable.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Transformable.kt
index 5c4f420..1e4abd9 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Transformable.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Transformable.kt
@@ -110,7 +110,8 @@
override fun equals(other: Any?): Boolean {
if (this === other) return true
- if (javaClass != other?.javaClass) return false
+ if (other === null) return false
+ if (this::class != other::class) return false
other as TransformableElement
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/LazyLayoutPager.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/LazyLayoutPager.kt
index 2730111..2bae034 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/LazyLayoutPager.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/LazyLayoutPager.kt
@@ -123,12 +123,6 @@
PagerWrapperFlingBehavior(flingBehavior, state)
}
- val pagerSemantics = if (userScrollEnabled) {
- Modifier.pagerSemantics(state, orientation == Orientation.Vertical)
- } else {
- Modifier
- }
-
val semanticState = rememberPagerSemanticState(
state,
reverseLayout,
@@ -141,7 +135,6 @@
modifier = modifier
.then(state.remeasurementModifier)
.then(state.awaitLayoutModifier)
- .then(pagerSemantics)
.lazyLayoutSemantics(
itemProviderLambda = pagerItemProvider,
state = semanticState,
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PageInfo.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PageInfo.kt
index a4fd6b3..0e2f4b1 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PageInfo.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PageInfo.kt
@@ -18,8 +18,20 @@
import androidx.compose.foundation.ExperimentalFoundationApi
+/**
+ * This represents a single measured page in a [Pager] layout.
+ */
@ExperimentalFoundationApi
-internal interface PageInfo {
+sealed interface PageInfo {
+
+ /**
+ * The index of this page.
+ */
val index: Int
+
+ /**
+ * The main axis offset of the item in pixels. It is relative to the start of the [Pager]
+ * container.
+ */
val offset: Int
}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerBeyondBoundsModifier.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerBeyondBoundsModifier.kt
index 323ad15..10164bb 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerBeyondBoundsModifier.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerBeyondBoundsModifier.kt
@@ -65,7 +65,7 @@
}
override val itemCount: Int
- get() = state.layoutInfo.pagesCount
+ get() = state.pageCount
override val hasVisibleItems: Boolean
get() = state.layoutInfo.visiblePagesInfo.isNotEmpty()
override val firstPlacedIndex: Int
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerLayoutInfo.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerLayoutInfo.kt
index f3e2dbd..74f1f87 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerLayoutInfo.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerLayoutInfo.kt
@@ -20,19 +20,76 @@
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.ui.unit.IntSize
+/**
+ * Contains useful information about the currently displayed layout state of a [Pager]. This
+ * information is available after the first measure pass.
+ *
+ * Use [PagerState.layoutInfo] to retrieve this
+ */
@ExperimentalFoundationApi
-internal interface PagerLayoutInfo {
+sealed interface PagerLayoutInfo {
+ /**
+ * A list of all pages that are currently visible in the [Pager]
+ */
val visiblePagesInfo: List<PageInfo>
- val closestPageToSnapPosition: PageInfo?
- val pagesCount: Int
+
+ /**
+ * The size of the Pages in this [Pager] provided by the [PageSize] API in the Pager definition.
+ */
val pageSize: Int
+
+ /**
+ * The spacing provided in the [Pager] creation.
+ */
val pageSpacing: Int
+
+ /**
+ * The start offset of the layout's viewport in pixels. You can think of it as a minimum offset
+ * which would be visible. Usually it is 0, but it can be negative if non-zero
+ * beforeContentPadding was applied as the content displayed in the content padding area is
+ * still visible.
+ *
+ * You can use it to understand what items from [visiblePagesInfo] are fully visible.
+ */
val viewportStartOffset: Int
+
+ /**
+ * The end offset of the layout's viewport in pixels. You can think of it as a maximum offset
+ * which would be visible. It is the size of the lazy list layout minus [beforeContentPadding].
+ *
+ * You can use it to understand what items from [visiblePagesInfo] are fully visible.
+ */
val viewportEndOffset: Int
+
+ /**
+ * The content padding in pixels applied before the first page in the direction of scrolling.
+ * For example it is a top content padding for [VerticalPager] with reverseLayout set to false.
+ */
val beforeContentPadding: Int
+
+ /**
+ * The content padding in pixels applied after the last page in the direction of scrolling.
+ * For example it is a bottom content padding for [VerticalPager] with reverseLayout set to
+ * false.
+ */
val afterContentPadding: Int
+
+ /**
+ * The size of the viewport in pixels. It is the [Pager] layout size including all the
+ * content paddings.
+ */
val viewportSize: IntSize
+
+ /**
+ * The [Pager] orientation.
+ */
val orientation: Orientation
+
+ /**
+ * True if the direction of scrolling and layout is reversed.
+ */
+ @Suppress("GetterSetterNames")
+ @get:Suppress("GetterSetterNames")
val reverseLayout: Boolean
}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerMeasure.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerMeasure.kt
index 9142421..9aac2c0 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerMeasure.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerMeasure.kt
@@ -19,7 +19,6 @@
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.fastFilter
import androidx.compose.foundation.gestures.Orientation
-import androidx.compose.foundation.gestures.snapping.calculateDistanceToDesiredSnapPosition
import androidx.compose.foundation.layout.Arrangement.Absolute.spacedBy
import androidx.compose.foundation.lazy.layout.LazyLayoutMeasureScope
import androidx.compose.ui.Alignment
@@ -32,7 +31,6 @@
import androidx.compose.ui.unit.constrainHeight
import androidx.compose.ui.unit.constrainWidth
import androidx.compose.ui.util.fastForEach
-import androidx.compose.ui.util.fastMaxBy
import kotlin.math.abs
import kotlin.math.roundToInt
import kotlin.math.sign
@@ -66,7 +64,6 @@
return if (pageCount <= 0) {
PagerMeasureResult(
visiblePagesInfo = emptyList(),
- pagesCount = 0,
pageSize = pageAvailableSize,
pageSpacing = spaceBetweenPages,
afterContentPadding = afterContentPadding,
@@ -75,7 +72,6 @@
viewportEndOffset = mainAxisAvailableSize + afterContentPadding,
measureResult = layout(constraints.minWidth, constraints.minHeight) {},
consumedScroll = 0f,
- closestPageToSnapPosition = null,
firstVisiblePage = null,
firstVisiblePageOffset = 0,
reverseLayout = false,
@@ -374,26 +370,10 @@
val visiblePagesInfo = if (noExtraPages) positionedPages else positionedPages.fastFilter {
(it.index >= visiblePages.first().index && it.index <= visiblePages.last().index)
}
- val viewPortSize = if (orientation == Orientation.Vertical) layoutHeight else layoutWidth
-
- val closestPageToSnapPosition = visiblePagesInfo.fastMaxBy {
- -abs(
- calculateDistanceToDesiredSnapPosition(
- mainAxisViewPortSize = viewPortSize,
- beforeContentPadding = beforeContentPadding,
- afterContentPadding = afterContentPadding,
- itemSize = pageAvailableSize,
- itemOffset = it.offset,
- itemIndex = it.index,
- snapPositionInLayout = SnapAlignmentStartToStart
- )
- )
- }
return PagerMeasureResult(
firstVisiblePage = firstPage,
firstVisiblePageOffset = currentFirstPageScrollOffset,
- closestPageToSnapPosition = closestPageToSnapPosition,
consumedScroll = consumedScroll,
measureResult = layout(layoutWidth, layoutHeight) {
positionedPages.fastForEach {
@@ -403,7 +383,6 @@
viewportStartOffset = -beforeContentPadding,
viewportEndOffset = maxOffset + afterContentPadding,
visiblePagesInfo = visiblePagesInfo,
- pagesCount = pageCount,
reverseLayout = reverseLayout,
orientation = orientation,
pageSize = pageAvailableSize,
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerMeasureResult.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerMeasureResult.kt
index ac69349..381c691 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerMeasureResult.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerMeasureResult.kt
@@ -24,7 +24,6 @@
@OptIn(ExperimentalFoundationApi::class)
internal class PagerMeasureResult(
override val visiblePagesInfo: List<PageInfo>,
- override val pagesCount: Int,
override val pageSize: Int,
override val pageSpacing: Int,
override val afterContentPadding: Int,
@@ -34,7 +33,6 @@
override val reverseLayout: Boolean,
val consumedScroll: Float,
val firstVisiblePage: MeasuredPage?,
- override val closestPageToSnapPosition: PageInfo?,
val firstVisiblePageOffset: Int,
val canScrollForward: Boolean,
measureResult: MeasureResult,
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerScrollPosition.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerScrollPosition.kt
index 5597496..03395ab 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerScrollPosition.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerScrollPosition.kt
@@ -17,10 +17,15 @@
package androidx.compose.foundation.pager
import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.gestures.snapping.calculateDistanceToDesiredSnapPosition
import androidx.compose.foundation.lazy.layout.LazyLayoutNearestRangeState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.setValue
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.util.fastMaxBy
+import kotlin.math.abs
/**
* Contains the current scroll position represented by the first visible page and the first
@@ -29,7 +34,8 @@
@OptIn(ExperimentalFoundationApi::class)
internal class PagerScrollPosition(
initialPage: Int = 0,
- initialScrollOffset: Int = 0
+ initialScrollOffset: Int = 0,
+ val state: PagerState
) {
var firstVisiblePage by mutableIntStateOf(initialPage)
var currentPage by mutableIntStateOf(initialPage)
@@ -56,7 +62,7 @@
// we ignore the index and offset from measureResult until we get at least one
// measurement with real pages. otherwise the initial index and scroll passed to the
// state would be lost and overridden with zeros.
- if (hadFirstNotEmptyLayout || measureResult.pagesCount > 0) {
+ if (hadFirstNotEmptyLayout || measureResult.visiblePagesInfo.isNotEmpty()) {
hadFirstNotEmptyLayout = true
val scrollOffset = measureResult.firstVisiblePageOffset
check(scrollOffset >= 0f) { "scrollOffset should be non-negative ($scrollOffset)" }
@@ -65,12 +71,32 @@
measureResult.firstVisiblePage?.index ?: 0,
scrollOffset
)
- measureResult.closestPageToSnapPosition?.index?.let {
+ measureResult.closestPageToSnapPosition(state.density)?.index?.let {
this.currentPage = it
}
}
}
+ private fun PagerMeasureResult.closestPageToSnapPosition(density: Density): PageInfo? {
+ val viewPortSize =
+ if (orientation == Orientation.Vertical) viewportSize.height else viewportSize.width
+ return with(density) {
+ visiblePagesInfo.fastMaxBy {
+ -abs(
+ calculateDistanceToDesiredSnapPosition(
+ mainAxisViewPortSize = viewPortSize,
+ beforeContentPadding = beforeContentPadding,
+ afterContentPadding = afterContentPadding,
+ itemSize = (pageSize + pageSpacing),
+ itemOffset = it.offset,
+ itemIndex = it.index,
+ snapPositionInLayout = SnapAlignmentStartToStart
+ )
+ )
+ }
+ }
+ }
+
/**
* Updates the scroll position - the passed values will be used as a start position for
* composing the pages during the next measure pass and will be updated by the real
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerState.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerState.kt
index d7d2187..27d7a6f 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerState.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerState.kt
@@ -26,7 +26,6 @@
import androidx.compose.foundation.gestures.ScrollableState
import androidx.compose.foundation.gestures.animateScrollBy
import androidx.compose.foundation.gestures.snapping.SnapPositionInLayout
-import androidx.compose.foundation.gestures.snapping.calculateDistanceToDesiredSnapPosition
import androidx.compose.foundation.interaction.InteractionSource
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.lazy.layout.AwaitFirstLayoutModifier
@@ -188,7 +187,7 @@
internal var upDownDifference: Offset by mutableStateOf(Offset.Zero)
internal var snapRemainingScrollOffset by mutableFloatStateOf(0f)
- private val scrollPosition = PagerScrollPosition(initialPage, 0)
+ private val scrollPosition = PagerScrollPosition(initialPage, 0, this)
internal val firstVisiblePage: Int get() = scrollPosition.firstVisiblePage
@@ -231,9 +230,22 @@
private var wasScrollingForward = false
/** Backing state for PagerLayoutInfo */
- private var pagerLayoutInfoState = mutableStateOf(EmptyLayoutInfo)
+ private var pagerLayoutInfoState = mutableStateOf<PagerLayoutInfo>(EmptyLayoutInfo)
- internal val layoutInfo: PagerLayoutInfo get() = pagerLayoutInfoState.value
+ /**
+ * A [PagerLayoutInfo] that contains useful information about the Pager's last layout pass.
+ * For instance, you can query which pages are currently visible in the layout.
+ *
+ * This property is observable and is updated after every scroll or remeasure.
+ * If you use it in the composable function it will be recomposed on every change causing
+ * potential performance issues including infinity recomposition loop.
+ * Therefore, avoid using it in the composition.
+ *
+ * If you want to run some side effects like sending an analytics event or updating a state
+ * based on this value consider using "snapshotFlow":
+ * @sample androidx.compose.foundation.samples.UsingPagerLayoutInfoForSideEffectSample
+ */
+ val layoutInfo: PagerLayoutInfo get() = pagerLayoutInfoState.value
internal val pageSpacing: Int
get() = pagerLayoutInfoState.value.pageSpacing
@@ -259,19 +271,6 @@
minThreshold / pageSize.toFloat()
}
- private val distanceToSnapPosition: Float
- get() = layoutInfo.closestPageToSnapPosition?.let {
- density.calculateDistanceToDesiredSnapPosition(
- mainAxisViewPortSize = layoutInfo.mainAxisViewportSize,
- beforeContentPadding = layoutInfo.beforeContentPadding,
- afterContentPadding = layoutInfo.afterContentPadding,
- itemSize = layoutInfo.pageSize,
- itemOffset = it.offset,
- itemIndex = it.index,
- snapPositionInLayout = SnapAlignmentStartToStart
- )
- } ?: 0f
-
internal val internalInteractionSource: MutableInteractionSource = MutableInteractionSource()
/**
@@ -459,6 +458,7 @@
}
var currentPosition = currentPage
val targetPage = page.coerceInPageRange()
+ var currentPositionOffsetFraction = currentPageOffsetFraction
animationTargetPage = targetPage
// If our future page is too far off, that is, outside of the current viewport
val firstVisiblePageIndex = visiblePages.first().index
@@ -480,14 +480,21 @@
// Pre-jump to 1 viewport away from destination page, if possible
scrollToPage(preJumpPosition)
currentPosition = preJumpPosition
+ currentPositionOffsetFraction = 0.0f
}
val targetOffset = targetPage * pageAvailableSpace
val currentOffset = currentPosition * pageAvailableSpace
- val pageOffsetToSnappedPosition =
- distanceToSnapPosition + pageOffsetFraction * pageAvailableSpace
- val displacement = targetOffset - currentOffset + pageOffsetToSnappedPosition
+ val targetPageOffsetToSnappedPosition = pageOffsetFraction * pageAvailableSpace
+
+ val offsetFromFraction = currentPositionOffsetFraction * pageAvailableSpace
+
+ // The final delta displacement will be the difference between the pages offsets
+ // discounting whatever offset the original page had scrolled plus the offset
+ // fraction requested by the user.
+ val displacement =
+ targetOffset - currentOffset - offsetFromFraction + targetPageOffsetToSnappedPosition
debugLog { "animateScrollToPage $displacement pixels" }
animateScrollBy(displacement, animationSpec)
@@ -588,7 +595,7 @@
info.visiblePagesInfo.first().index - 1
}
if (indexToPrefetch != this.indexToPrefetch &&
- indexToPrefetch in 0 until info.pagesCount
+ indexToPrefetch in 0 until pageCount
) {
if (wasScrollingForward != scrollingForward) {
// the scrolling direction has been changed which means the last prefetched
@@ -656,10 +663,8 @@
private const val MaxPagesForAnimateScroll = 3
@OptIn(ExperimentalFoundationApi::class)
-internal val EmptyLayoutInfo = object : PagerLayoutInfo {
+internal object EmptyLayoutInfo : PagerLayoutInfo {
override val visiblePagesInfo: List<PageInfo> = emptyList()
- override val closestPageToSnapPosition: PageInfo? = null
- override val pagesCount: Int = 0
override val pageSize: Int = 0
override val pageSpacing: Int = 0
override val beforeContentPadding: Int = 0
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/CoreTextField.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/CoreTextField.kt
index 154e98e..91f3980 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/CoreTextField.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/CoreTextField.kt
@@ -852,7 +852,7 @@
/**
* A flag to check if the floating toolbar should show.
*/
- var showFloatingToolbar = false
+ var showFloatingToolbar by mutableStateOf(false)
/**
* True if the position of the selection start handle is within a visible part of the window
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/TextFieldSelectionManager.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/TextFieldSelectionManager.kt
index 6d55c9c..0093bd8 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/TextFieldSelectionManager.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/TextFieldSelectionManager.kt
@@ -416,11 +416,13 @@
draggingHandle = if (isStartHandle) Handle.SelectionStart else Handle.SelectionEnd
currentDragPosition = getAdjustedCoordinates(getHandlePosition(isStartHandle))
state?.isInTouchMode = true
+ state?.showFloatingToolbar = false
}
override fun onUp() {
draggingHandle = null
currentDragPosition = null
+ state?.showFloatingToolbar = true
}
override fun onStart(startPoint: Offset) {
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/BasicSecureTextField.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/BasicSecureTextField.kt
index 84b3208..a22cb59 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/BasicSecureTextField.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/BasicSecureTextField.kt
@@ -33,6 +33,7 @@
import androidx.compose.foundation.text2.input.mask
import androidx.compose.foundation.text2.input.then
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.remember
@@ -40,9 +41,13 @@
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.onFocusChanged
+import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor
+import androidx.compose.ui.platform.LocalTextToolbar
+import androidx.compose.ui.platform.TextToolbar
+import androidx.compose.ui.platform.TextToolbarStatus
import androidx.compose.ui.semantics.copyText
import androidx.compose.ui.semantics.cutText
import androidx.compose.ui.semantics.password
@@ -177,31 +182,33 @@
}
)
- BasicTextField2(
- state = state,
- modifier = secureTextFieldModifier,
- enabled = enabled,
- readOnly = false,
- filter = if (revealLastTypedEnabled) {
- filter?.then(secureTextFieldController.passwordRevealFilter)
- ?: secureTextFieldController.passwordRevealFilter
- } else filter,
- textStyle = textStyle,
- interactionSource = interactionSource,
- cursorBrush = cursorBrush,
- lineLimits = TextFieldLineLimits.SingleLine,
- scrollState = scrollState,
- keyboardOptions = KeyboardOptions(
- autoCorrect = false,
- keyboardType = keyboardType,
- imeAction = imeAction
- ),
- keyboardActions = onSubmit?.let { KeyboardActions(onSubmit = it) }
- ?: KeyboardActions.Default,
- onTextLayout = onTextLayout,
- codepointTransformation = codepointTransformation,
- decorationBox = decorationBox,
- )
+ DisableCopyTextToolbar {
+ BasicTextField2(
+ state = state,
+ modifier = secureTextFieldModifier,
+ enabled = enabled,
+ readOnly = false,
+ filter = if (revealLastTypedEnabled) {
+ filter?.then(secureTextFieldController.passwordRevealFilter)
+ ?: secureTextFieldController.passwordRevealFilter
+ } else filter,
+ textStyle = textStyle,
+ interactionSource = interactionSource,
+ cursorBrush = cursorBrush,
+ lineLimits = TextFieldLineLimits.SingleLine,
+ scrollState = scrollState,
+ keyboardOptions = KeyboardOptions(
+ autoCorrect = false,
+ keyboardType = keyboardType,
+ imeAction = imeAction
+ ),
+ keyboardActions = onSubmit?.let { KeyboardActions(onSubmit = it) }
+ ?: KeyboardActions.Default,
+ onTextLayout = onTextLayout,
+ codepointTransformation = codepointTransformation,
+ decorationBox = decorationBox,
+ )
+ }
}
@OptIn(ExperimentalFoundationApi::class)
@@ -310,3 +317,41 @@
onSearch = { if (!onSubmit(ImeAction.Search)) defaultKeyboardAction(ImeAction.Search) },
onSend = { if (!onSubmit(ImeAction.Send)) defaultKeyboardAction(ImeAction.Send) },
)
+
+/**
+ * Overrides the TextToolbar provided by LocalTextToolbar to never show copy or cut options by the
+ * children composables.
+ */
+@Composable
+private fun DisableCopyTextToolbar(
+ content: @Composable () -> Unit
+) {
+ val currentToolbar = LocalTextToolbar.current
+ val copyDisabledToolbar = remember(currentToolbar) {
+ object : TextToolbar {
+ override fun showMenu(
+ rect: Rect,
+ onCopyRequested: (() -> Unit)?,
+ onPasteRequested: (() -> Unit)?,
+ onCutRequested: (() -> Unit)?,
+ onSelectAllRequested: (() -> Unit)?
+ ) {
+ currentToolbar.showMenu(
+ rect = rect,
+ onPasteRequested = onPasteRequested,
+ onSelectAllRequested = onSelectAllRequested,
+ onCopyRequested = null,
+ onCutRequested = null
+ )
+ }
+
+ override fun hide() {
+ currentToolbar.hide()
+ }
+
+ override val status: TextToolbarStatus
+ get() = currentToolbar.status
+ }
+ }
+ CompositionLocalProvider(LocalTextToolbar provides copyDisabledToolbar, content = content)
+}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/TextEditFilter.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/TextEditFilter.kt
index eb1532b..c9f0889 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/TextEditFilter.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/TextEditFilter.kt
@@ -99,7 +99,8 @@
override fun equals(other: Any?): Boolean {
if (this === other) return true
- if (javaClass != other?.javaClass) return false
+ if (other === null) return false
+ if (this::class != other::class) return false
other as FilterChain
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/TextFieldCharSequence.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/TextFieldCharSequence.kt
index 4a4f8c0..d04e4fb 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/TextFieldCharSequence.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/TextFieldCharSequence.kt
@@ -132,7 +132,8 @@
*/
override fun equals(other: Any?): Boolean {
if (this === other) return true
- if (javaClass != other?.javaClass) return false
+ if (other === null) return false
+ if (this::class != other::class) return false
other as TextFieldCharSequenceWrapper
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/TextFieldLineLimits.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/TextFieldLineLimits.kt
index fcb8edc..062d09a 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/TextFieldLineLimits.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/TextFieldLineLimits.kt
@@ -63,7 +63,8 @@
override fun equals(other: Any?): Boolean {
if (this === other) return true
- if (javaClass != other?.javaClass) return false
+ if (other === null) return false
+ if (this::class != other::class) return false
other as MultiLine
if (minHeightInLines != other.minHeightInLines) return false
if (maxHeightInLines != other.maxHeightInLines) return false
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/GapBuffer.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/GapBuffer.kt
index dd64e57f..01c13aa 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/GapBuffer.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/GapBuffer.kt
@@ -243,7 +243,7 @@
val rightCopyCount = minOf(this.text.length - end, SURROUNDING_SIZE)
// Copy left surrounding
- this.text.toString().toCharArray(charArray, 0, start - leftCopyCount, start)
+ this.text.toCharArray(charArray, 0, start - leftCopyCount, start)
// Copy right surrounding
this.text.toCharArray(
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/ToCharArray.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/ToCharArray.kt
index 3b3f0b2..d817c12 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/ToCharArray.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/ToCharArray.kt
@@ -26,7 +26,7 @@
) = toCharArray(destination, destinationOffset, startIndex = 0, endIndex = this.length)
/**
- * Copies characters from this [String] into [destination].
+ * Copies characters from this [CharSequence] into [destination].
*
* Platform-specific implementations should use native functions for performing this operation if
* they exist, since they will likely be more efficient than copying each character individually.
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/selection/TextFieldSelectionState.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/selection/TextFieldSelectionState.kt
index e065062..f6111aa 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/selection/TextFieldSelectionState.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/selection/TextFieldSelectionState.kt
@@ -590,16 +590,17 @@
// coordinates. Convert visibleBounds to root before checking the overlap.
val visibleBounds = innerCoordinates?.visibleBounds()
if (visibleBounds != null) {
- val visibleBoundsTopLeftInRoot = textLayoutState
- .innerTextFieldCoordinates
- ?.localToRoot(visibleBounds.topLeft)
- val visibleBoundsInRoot = Rect(visibleBoundsTopLeftInRoot!!, visibleBounds.size)
- val contentRect = getContentRect().takeIf { visibleBoundsInRoot.overlaps(it) }
- ?: Rect.Zero
+ val visibleBoundsTopLeftInRoot =
+ innerCoordinates?.localToRoot(visibleBounds.topLeft)
+ val visibleBoundsInRoot =
+ Rect(visibleBoundsTopLeftInRoot!!, visibleBounds.size)
- // contentRect can be very wide if a huge text content is selected. Our toolbar
+ // contentRect can be very wide if a big part of text is selected. Our toolbar
// should be aligned only to visible region.
- contentRect.intersect(visibleBoundsInRoot)
+ getContentRect()
+ .takeIf { visibleBoundsInRoot.overlaps(it) }
+ ?.intersect(visibleBoundsInRoot)
+ ?: Rect.Zero
} else {
Rect.Zero
}
@@ -791,15 +792,32 @@
}
} else null
+ val copy: (() -> Unit)? = if (!selection.collapsed) {
+ {
+ copy()
+ cursorHandleShowToolbar = false
+ }
+ } else null
+
+ val cut: (() -> Unit)? = if (!selection.collapsed && editable) {
+ {
+ cut()
+ cursorHandleShowToolbar = false
+ }
+ } else null
+
val selectAll: (() -> Unit)? = if (selection.length != textFieldState.text.length) {
{
editWithFilter { selectAll() }
+ cursorHandleShowToolbar = false
}
} else null
textToolbar?.showMenu(
rect = contentRect,
+ onCopyRequested = copy,
onPasteRequested = paste,
+ onCutRequested = cut,
onSelectAllRequested = selectAll
)
}
diff --git a/compose/lint/common/src/main/java/androidx/compose/lint/KotlinUtils.kt b/compose/lint/common/src/main/java/androidx/compose/lint/KotlinUtils.kt
index 5a39288..9ec00a4 100644
--- a/compose/lint/common/src/main/java/androidx/compose/lint/KotlinUtils.kt
+++ b/compose/lint/common/src/main/java/androidx/compose/lint/KotlinUtils.kt
@@ -16,17 +16,12 @@
package androidx.compose.lint
-import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.psi.KtLambdaExpression
import org.jetbrains.kotlin.psi.KtParameter
import org.jetbrains.kotlin.psi.KtSimpleNameExpression
import org.jetbrains.kotlin.psi.psiUtil.collectDescendantsOfType
import org.jetbrains.kotlin.psi.psiUtil.isAncestor
-import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
import org.jetbrains.uast.ULambdaExpression
-import org.jetbrains.uast.kotlin.KotlinUFunctionCallExpression
-import org.jetbrains.uast.kotlin.KotlinUastResolveProviderService
-import org.jetbrains.uast.sourcePsiElement
import org.jetbrains.uast.toUElement
/**
@@ -148,10 +143,3 @@
else -> true
}
}
-
-fun KotlinUFunctionCallExpression.resolveCall() =
- (sourcePsiElement as KtElement).getResolvedCall(
- context = sourcePsi.project
- .getService(KotlinUastResolveProviderService::class.java)
- .getBindingContext(sourcePsi)
- )
diff --git a/compose/lint/common/src/main/java/androidx/compose/lint/Names.kt b/compose/lint/common/src/main/java/androidx/compose/lint/Names.kt
index f51de29..0c192ef 100644
--- a/compose/lint/common/src/main/java/androidx/compose/lint/Names.kt
+++ b/compose/lint/common/src/main/java/androidx/compose/lint/Names.kt
@@ -134,6 +134,11 @@
val kmClassName: ClassName
get() = pkg.segments.joinToString("/", postfix = "/") +
nameSegments.joinToString(".")
+
+ /**
+ * The [PackageName] of this element.
+ */
+ val packageName: PackageName get() = pkg
}
/**
diff --git a/compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/ComposeIssueRegistry.kt b/compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/ComposeIssueRegistry.kt
index 223ceaf..7e43f93 100644
--- a/compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/ComposeIssueRegistry.kt
+++ b/compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/ComposeIssueRegistry.kt
@@ -32,7 +32,8 @@
ListIteratorDetector.ISSUE,
SteppedForLoopDetector.ISSUE,
UnnecessaryLambdaCreationDetector.ISSUE,
- PlatformImportInCommonModuleDetector.ISSUE,
+ PlatformReferenceInCommonModuleDetector.IMPORT_ISSUE,
+ PlatformReferenceInCommonModuleDetector.REFERENCE_ISSUE,
PrimitiveInLambdaDetector.ISSUE,
)
}
diff --git a/compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/PlatformImportInCommonModuleDetector.kt b/compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/PlatformImportInCommonModuleDetector.kt
deleted file mode 100644
index 856bc57..0000000
--- a/compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/PlatformImportInCommonModuleDetector.kt
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.compose.lint
-
-import com.android.tools.lint.client.api.UElementHandler
-import com.android.tools.lint.detector.api.Category
-import com.android.tools.lint.detector.api.Detector
-import com.android.tools.lint.detector.api.Implementation
-import com.android.tools.lint.detector.api.Issue
-import com.android.tools.lint.detector.api.JavaContext
-import com.android.tools.lint.detector.api.Scope
-import com.android.tools.lint.detector.api.Severity
-import com.android.tools.lint.detector.api.SourceCodeScanner
-import org.jetbrains.uast.UImportStatement
-import org.jetbrains.uast.getContainingUFile
-import org.jetbrains.uast.getIoFile
-
-/**
- * Lint [Detector] that catches platform-dependent imports in a common module.
- */
-class PlatformImportInCommonModuleDetector : Detector(), SourceCodeScanner {
-
- override fun getApplicableUastTypes() =
- listOf(UImportStatement::class.java)
-
- override fun createUastHandler(context: JavaContext): UElementHandler =
- object : UElementHandler() {
- // this only verifies imports and not fqname references
- // potentially we want to make sure to search for those too
-
- override fun visitImportStatement(node: UImportStatement) {
- val reference = node.importReference?.asRenderString() ?: return
- val isPlatformImport = PLATFORM_PACKAGES.any { platformPackage ->
- (platformPackage == reference && node.isOnDemand) ||
- reference.startsWith("$platformPackage.")
- }
- if (!isPlatformImport) return
-
- val file = node.getContainingUFile()?.getIoFile() ?: return
- val isInCommonModule = file.absolutePath.contains(COMMON_MAIN_PATH_PREFIX)
- if (!isInCommonModule) return
-
- val target = node.importReference!!
- context.report(
- ISSUE,
- target,
- context.getLocation(target),
- "Platform-dependent import in a common module"
- )
- }
- }
-
- companion object {
- val ISSUE = Issue.create(
- id = "PlatformImportInCommonModule",
- briefDescription = "Platform-dependent import in a common module",
- explanation = "Common Kotlin module cannot contain references to JVM or Android " +
- "classes, as it reduces future portability to other Kotlin targets. Instead of " +
- "referencing them directly, use expect/actual declarations.",
- category = Category.CORRECTNESS,
- priority = 5,
- severity = Severity.ERROR,
- implementation = Implementation(
- PlatformImportInCommonModuleDetector::class.java,
- Scope.JAVA_FILE_SCOPE
- )
- )
-
- private const val COMMON_MAIN_PATH_PREFIX = "src/commonMain"
- private val PLATFORM_PACKAGES = listOf(
- "java",
- "javax",
- "android"
- )
- }
-}
diff --git a/compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/PlatformReferenceInCommonModuleDetector.kt b/compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/PlatformReferenceInCommonModuleDetector.kt
new file mode 100644
index 0000000..77982f8
--- /dev/null
+++ b/compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/PlatformReferenceInCommonModuleDetector.kt
@@ -0,0 +1,129 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.lint
+
+import com.android.tools.lint.client.api.UElementHandler
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.Scope
+import com.android.tools.lint.detector.api.Severity
+import com.android.tools.lint.detector.api.SourceCodeScanner
+import com.intellij.psi.PsiMethod
+import org.jetbrains.uast.UElement
+import org.jetbrains.uast.UImportStatement
+import org.jetbrains.uast.USimpleNameReferenceExpression
+import org.jetbrains.uast.tryResolve
+
+/**
+ * Lint [Detector] that catches platform-dependent references in a common module.
+ */
+class PlatformReferenceInCommonModuleDetector : Detector(), SourceCodeScanner {
+
+ override fun getApplicableUastTypes(): List<Class<out UElement>> =
+ listOf(UImportStatement::class.java, USimpleNameReferenceExpression::class.java)
+
+ override fun createUastHandler(context: JavaContext): UElementHandler {
+ if (!context.file.absolutePath.contains(COMMON_MAIN_PATH_PREFIX)) {
+ return UElementHandler.NONE
+ }
+
+ return object : UElementHandler() {
+ override fun visitImportStatement(node: UImportStatement) {
+ val reference = node.importReference?.asRenderString() ?: return
+ val isPlatformImport = PLATFORM_PACKAGES.any { platformPackage ->
+ (platformPackage == reference && node.isOnDemand) ||
+ reference.startsWith("$platformPackage.")
+ }
+ if (!isPlatformImport) return
+
+ val target = node.importReference!!
+ context.report(
+ IMPORT_ISSUE,
+ target,
+ context.getLocation(target),
+ "Platform-dependent import in a common module"
+ )
+ }
+
+ override fun visitSimpleNameReferenceExpression(node: USimpleNameReferenceExpression) {
+ if (node.identifier in RESTRICTED_PROPERTIES) {
+ val method = node.tryResolve()
+ if (method !is PsiMethod) return
+
+ val fqName = RESTRICTED_PROPERTIES[node.identifier]!!
+ if (method.name != fqName.shortName) return
+ if (!method.isInPackageName(fqName.packageName)) return
+
+ context.report(
+ REFERENCE_ISSUE,
+ node,
+ context.getLocation(node),
+ "Platform reference in a common module"
+ )
+ }
+ }
+ }
+ }
+
+ companion object {
+ val IMPORT_ISSUE = Issue.create(
+ id = "PlatformImportInCommonModule",
+ briefDescription = "Platform-dependent import in a common module",
+ explanation = "Common Kotlin module cannot contain references to JVM or Android " +
+ "classes, as it reduces future portability to other Kotlin targets. Consider " +
+ "alternative methods allowed in common Kotlin code, or use expect/actual " +
+ "to reference the platform code instead.",
+ category = Category.CORRECTNESS,
+ priority = 5,
+ severity = Severity.ERROR,
+ implementation = Implementation(
+ PlatformReferenceInCommonModuleDetector::class.java,
+ Scope.JAVA_FILE_SCOPE
+ )
+ )
+
+ val REFERENCE_ISSUE = Issue.create(
+ id = "PlatformReferenceInCommonModule",
+ briefDescription = "Platform-dependent reference in a common module",
+ explanation = "Common Kotlin module cannot contain references to JVM or Android " +
+ "classes, as it reduces future portability to other Kotlin targets. Consider " +
+ "alternative methods allowed in common Kotlin code, or use expect/actual " +
+ "to reference the platform code instead.",
+ category = Category.CORRECTNESS,
+ priority = 5,
+ severity = Severity.ERROR,
+ implementation = Implementation(
+ PlatformReferenceInCommonModuleDetector::class.java,
+ Scope.JAVA_FILE_SCOPE
+ )
+ )
+
+ private const val COMMON_MAIN_PATH_PREFIX = "src/commonMain"
+ private val PLATFORM_PACKAGES = listOf(
+ "java",
+ "javax",
+ "android"
+ )
+ private val RESTRICTED_PROPERTIES = mapOf(
+ "javaClass" to Name(Package("kotlin.jvm"), "getJavaClass"),
+ "java" to Name(Package("kotlin.jvm"), "getJavaClass"),
+ )
+ }
+}
diff --git a/compose/lint/internal-lint-checks/src/test/java/androidx/compose/lint/PlatformImportInCommonModuleDetectorTest.kt b/compose/lint/internal-lint-checks/src/test/java/androidx/compose/lint/PlatformImportInCommonModuleDetectorTest.kt
deleted file mode 100644
index e716456..0000000
--- a/compose/lint/internal-lint-checks/src/test/java/androidx/compose/lint/PlatformImportInCommonModuleDetectorTest.kt
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * Copyright 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-@file:Suppress("UnstableApiUsage")
-
-package androidx.compose.lint
-
-import com.android.tools.lint.checks.infrastructure.LintDetectorTest
-import com.android.tools.lint.detector.api.Detector
-import com.android.tools.lint.detector.api.Issue
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-
-/* ktlint-disable max-line-length */
-@RunWith(JUnit4::class)
-class PlatformImportInCommonModuleDetectorTest : LintDetectorTest() {
- override fun getDetector(): Detector = PlatformImportInCommonModuleDetector()
-
- override fun getIssues(): MutableList<Issue> = mutableListOf(
- PlatformImportInCommonModuleDetector.ISSUE
- )
-
- @Test
- fun detectsImportInCommonMain() {
- val file = kotlin(
- "commonMain/test/TestFile.kt",
- """
- package test
-
- import java.util.ArrayList as MyList
- import java.util.*
- import java.*
- import android.os.Bundle
- import android.*
- """
- ).within("src")
-
- lint().files(
- file
- )
- .run()
- .expect(
- """
-src/commonMain/test/TestFile.kt:4: Error: Platform-dependent import in a common module [PlatformImportInCommonModule]
- import java.util.ArrayList as MyList
- ~~~~~~~~~~~~~~~~~~~
-src/commonMain/test/TestFile.kt:5: Error: Platform-dependent import in a common module [PlatformImportInCommonModule]
- import java.util.*
- ~~~~~~~~~
-src/commonMain/test/TestFile.kt:6: Error: Platform-dependent import in a common module [PlatformImportInCommonModule]
- import java.*
- ~~~~
-src/commonMain/test/TestFile.kt:7: Error: Platform-dependent import in a common module [PlatformImportInCommonModule]
- import android.os.Bundle
- ~~~~~~~~~~~~~~~~~
-src/commonMain/test/TestFile.kt:8: Error: Platform-dependent import in a common module [PlatformImportInCommonModule]
- import android.*
- ~~~~~~~
-5 errors, 0 warnings
- """.trimIndent()
- )
- }
-
- @Test
- fun ignoresImportInOtherModules() {
- val jvmFile = kotlin(
- "jvmMain/test/TestFile.kt",
- """
- package test
-
- import java.util.ArrayList as MyList
- import java.util.*
- import java.*
- import android.os.Bundle
- import android.*
- """
- ).within("src")
-
- val androidFile = kotlin(
- "androidMain/test/TestFile.kt",
- """
- package test
-
- import java.util.*
- import java.*
- import android.os.Bundle
- import android.*
- """
- ).within("src")
-
- val file = kotlin(
- "main/test/TestFile.kt",
- """
- package test
-
- import java.util.*
- import java.*
- import android.os.Bundle
- import android.*
- """
- ).within("src")
-
- lint().files(
- file,
- androidFile,
- jvmFile
- )
- .run()
- .expectClean()
- }
-}
-/* ktlint-enable max-line-length */
diff --git a/compose/lint/internal-lint-checks/src/test/java/androidx/compose/lint/PlatformReferenceInCommonModuleDetectorTest.kt b/compose/lint/internal-lint-checks/src/test/java/androidx/compose/lint/PlatformReferenceInCommonModuleDetectorTest.kt
new file mode 100644
index 0000000..811a9a1
--- /dev/null
+++ b/compose/lint/internal-lint-checks/src/test/java/androidx/compose/lint/PlatformReferenceInCommonModuleDetectorTest.kt
@@ -0,0 +1,243 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:Suppress("UnstableApiUsage")
+
+package androidx.compose.lint
+
+import com.android.tools.lint.checks.infrastructure.LintDetectorTest
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Issue
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+/* ktlint-disable max-line-length */
+@RunWith(JUnit4::class)
+class PlatformReferenceInCommonModuleDetectorTest : LintDetectorTest() {
+ override fun getDetector(): Detector = PlatformReferenceInCommonModuleDetector()
+
+ override fun getIssues(): MutableList<Issue> = mutableListOf(
+ PlatformReferenceInCommonModuleDetector.IMPORT_ISSUE,
+ PlatformReferenceInCommonModuleDetector.REFERENCE_ISSUE
+ )
+
+ @Test
+ fun detectsImportInCommonMain() {
+ val file = kotlin(
+ "commonMain/test/TestFile.kt",
+ """
+ package test
+
+ import java.util.ArrayList as MyList
+ import java.util.*
+ import java.*
+ import android.os.Bundle
+ import android.*
+ """
+ ).within("src")
+
+ lint().files(
+ file
+ )
+ .run()
+ .expect(
+ """
+src/commonMain/test/TestFile.kt:4: Error: Platform-dependent import in a common module [PlatformImportInCommonModule]
+ import java.util.ArrayList as MyList
+ ~~~~~~~~~~~~~~~~~~~
+src/commonMain/test/TestFile.kt:5: Error: Platform-dependent import in a common module [PlatformImportInCommonModule]
+ import java.util.*
+ ~~~~~~~~~
+src/commonMain/test/TestFile.kt:6: Error: Platform-dependent import in a common module [PlatformImportInCommonModule]
+ import java.*
+ ~~~~
+src/commonMain/test/TestFile.kt:7: Error: Platform-dependent import in a common module [PlatformImportInCommonModule]
+ import android.os.Bundle
+ ~~~~~~~~~~~~~~~~~
+src/commonMain/test/TestFile.kt:8: Error: Platform-dependent import in a common module [PlatformImportInCommonModule]
+ import android.*
+ ~~~~~~~
+5 errors, 0 warnings
+ """.trimIndent()
+ )
+ }
+
+ @Test
+ fun detectsJavaClassCallsInCommonMain() {
+ val file = kotlin(
+ "commonMain/test/TestFile.kt",
+ """
+ package test
+
+ fun test(test: String) {
+ test.javaClass
+ test::class.java
+ }
+
+ class AttributeSnapshot {
+ // since data classes can't be subclassed
+ override fun equals(other: Any?): Boolean {
+ if (javaClass != other?.javaClass) return false
+ return true
+ }
+ }
+ """
+ ).within("src")
+
+ lint().files(
+ file
+ )
+ .run()
+ .expect(
+ """
+src/commonMain/test/TestFile.kt:5: Error: Platform reference in a common module [PlatformReferenceInCommonModule]
+ test.javaClass
+ ~~~~~~~~~
+src/commonMain/test/TestFile.kt:6: Error: Platform reference in a common module [PlatformReferenceInCommonModule]
+ test::class.java
+ ~~~~
+src/commonMain/test/TestFile.kt:12: Error: Platform reference in a common module [PlatformReferenceInCommonModule]
+ if (javaClass != other?.javaClass) return false
+ ~~~~~~~~~
+src/commonMain/test/TestFile.kt:12: Error: Platform reference in a common module [PlatformReferenceInCommonModule]
+ if (javaClass != other?.javaClass) return false
+ ~~~~~~~~~
+4 errors, 0 warnings
+ """.trimIndent()
+ )
+ }
+
+ @Test
+ fun ignoresImportInOtherModules() {
+ val jvmFile = kotlin(
+ "jvmMain/test/TestFile.kt",
+ """
+ package test
+
+ import java.util.ArrayList as MyList
+ import java.util.*
+ import java.*
+ import android.os.Bundle
+ import android.*
+ """
+ ).within("src")
+
+ val androidFile = kotlin(
+ "androidMain/test/TestFile.kt",
+ """
+ package test
+
+ import java.util.*
+ import java.*
+ import android.os.Bundle
+ import android.*
+ """
+ ).within("src")
+
+ val file = kotlin(
+ "main/test/TestFile.kt",
+ """
+ package test
+
+ import java.util.*
+ import java.*
+ import android.os.Bundle
+ import android.*
+ """
+ ).within("src")
+
+ lint().files(
+ file,
+ androidFile,
+ jvmFile
+ )
+ .run()
+ .expectClean()
+ }
+
+ @Test
+ fun ignoresJavaClassCallsInOtherSourceSets() {
+ val jvmFile = kotlin(
+ "jvmMain/test/TestFile.kt",
+ """
+ package test
+
+ fun test(test: String) {
+ test.javaClass
+ test::class.java
+ }
+
+ class AttributeSnapshot {
+ // since data classes can't be subclassed
+ override fun equals(other: Any?): Boolean {
+ if (javaClass != other?.javaClass) return false
+ return true
+ }
+ }
+ """
+ ).within("src")
+
+ val androidFile = kotlin(
+ "androidMain/test/TestFile.kt",
+ """
+ package test
+
+ fun test(test: String) {
+ test.javaClass
+ test::class.java
+ }
+
+ class AttributeSnapshot {
+ // since data classes can't be subclassed
+ override fun equals(other: Any?): Boolean {
+ if (javaClass != other?.javaClass) return false
+ return true
+ }
+ }
+ """
+ ).within("src")
+
+ val file = kotlin(
+ "main/test/TestFile.kt",
+ """
+ package test
+
+ fun test(test: String) {
+ test.javaClass
+ test::class.java
+ }
+
+ class AttributeSnapshot {
+ // since data classes can't be subclassed
+ override fun equals(other: Any?): Boolean {
+ if (javaClass != other?.javaClass) return false
+ return true
+ }
+ }
+ """
+ ).within("src")
+
+ lint().files(
+ file,
+ androidFile,
+ jvmFile
+ )
+ .run()
+ .expectClean()
+ }
+}
+/* ktlint-enable max-line-length */
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/BottomSheetScaffold.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/BottomSheetScaffold.kt
index 013adfe..b4bbc9c 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/BottomSheetScaffold.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/BottomSheetScaffold.kt
@@ -437,11 +437,9 @@
content: @Composable (PaddingValues) -> Unit
) {
// b/278692145 Remove this once deprecated methods without density are removed
- if (scaffoldState.bottomSheetState.density == null) {
- val density = LocalDensity.current
- SideEffect {
- scaffoldState.bottomSheetState.density = density
- }
+ val density = LocalDensity.current
+ SideEffect {
+ scaffoldState.bottomSheetState.density = density
}
val peekHeightPx = with(LocalDensity.current) { sheetPeekHeight.toPx() }
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Drawer.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Drawer.kt
index 6fbc761..bd51ba9 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Drawer.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Drawer.kt
@@ -648,11 +648,9 @@
content: @Composable () -> Unit
) {
// b/278692145 Remove this once deprecated methods without density are removed
- if (drawerState.density == null) {
- val density = LocalDensity.current
- SideEffect {
- drawerState.density = density
- }
+ val density = LocalDensity.current
+ SideEffect {
+ drawerState.density = density
}
val scope = rememberCoroutineScope()
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/ModalBottomSheet.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/ModalBottomSheet.kt
index dfe23a1..a07039a 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/ModalBottomSheet.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/ModalBottomSheet.kt
@@ -564,11 +564,9 @@
content: @Composable () -> Unit
) {
// b/278692145 Remove this once deprecated methods without density are removed
- if (sheetState.density == null) {
- val density = LocalDensity.current
- SideEffect {
- sheetState.density = density
- }
+ val density = LocalDensity.current
+ SideEffect {
+ sheetState.density = density
}
val scope = rememberCoroutineScope()
val orientation = Orientation.Vertical
diff --git a/compose/material3/benchmark/src/androidTest/java/androidx/compose/material3/benchmark/DatePickerBenchmark.kt b/compose/material3/benchmark/src/androidTest/java/androidx/compose/material3/benchmark/DatePickerBenchmark.kt
index d047879..e5e5664 100644
--- a/compose/material3/benchmark/src/androidTest/java/androidx/compose/material3/benchmark/DatePickerBenchmark.kt
+++ b/compose/material3/benchmark/src/androidTest/java/androidx/compose/material3/benchmark/DatePickerBenchmark.kt
@@ -53,50 +53,32 @@
@Test
fun datePicker_measure() {
- benchmarkRule.benchmarkFirstMeasure(
- caseFactory = datePickerTestCaseFactory,
- allowPendingChanges = true
- )
+ benchmarkRule.benchmarkMeasureUntilStable(datePickerTestCaseFactory)
}
@Test
fun dateInput_measure() {
- benchmarkRule.benchmarkFirstMeasure(
- caseFactory = dateInputTestCaseFactory,
- allowPendingChanges = true
- )
+ benchmarkRule.benchmarkMeasureUntilStable(dateInputTestCaseFactory)
}
@Test
fun datePicker_layout() {
- benchmarkRule.benchmarkFirstLayout(
- caseFactory = datePickerTestCaseFactory,
- allowPendingChanges = true
- )
+ benchmarkRule.benchmarkLayoutUntilStable(datePickerTestCaseFactory)
}
@Test
fun dateInput_layout() {
- benchmarkRule.benchmarkFirstLayout(
- caseFactory = dateInputTestCaseFactory,
- allowPendingChanges = true
- )
+ benchmarkRule.benchmarkLayoutUntilStable(dateInputTestCaseFactory)
}
@Test
fun datePicker_draw() {
- benchmarkRule.benchmarkFirstDraw(
- caseFactory = datePickerTestCaseFactory,
- allowPendingChanges = true
- )
+ benchmarkRule.benchmarkDrawUntilStable(datePickerTestCaseFactory)
}
@Test
fun dateInput_draw() {
- benchmarkRule.benchmarkFirstDraw(
- caseFactory = dateInputTestCaseFactory,
- allowPendingChanges = true
- )
+ benchmarkRule.benchmarkDrawUntilStable(dateInputTestCaseFactory)
}
}
diff --git a/compose/material3/benchmark/src/androidTest/java/androidx/compose/material3/benchmark/DateRangePickerBenchmark.kt b/compose/material3/benchmark/src/androidTest/java/androidx/compose/material3/benchmark/DateRangePickerBenchmark.kt
index d8a46d8..b9f97a3 100644
--- a/compose/material3/benchmark/src/androidTest/java/androidx/compose/material3/benchmark/DateRangePickerBenchmark.kt
+++ b/compose/material3/benchmark/src/androidTest/java/androidx/compose/material3/benchmark/DateRangePickerBenchmark.kt
@@ -61,10 +61,7 @@
@Test
fun dateRangeInput_measure() {
- benchmarkRule.benchmarkFirstMeasure(
- caseFactory = dateRangeInputTestCaseFactory,
- allowPendingChanges = true
- )
+ benchmarkRule.benchmarkMeasureUntilStable(dateRangeInputTestCaseFactory)
}
@Test
@@ -74,10 +71,7 @@
@Test
fun dateRangeInput_layout() {
- benchmarkRule.benchmarkFirstLayout(
- caseFactory = dateRangeInputTestCaseFactory,
- allowPendingChanges = true
- )
+ benchmarkRule.benchmarkLayoutUntilStable(dateRangeInputTestCaseFactory)
}
@Test
@@ -87,10 +81,7 @@
@Test
fun dateRangeInput_draw() {
- benchmarkRule.benchmarkFirstDraw(
- caseFactory = dateRangeInputTestCaseFactory,
- allowPendingChanges = true
- )
+ benchmarkRule.benchmarkDrawUntilStable(dateRangeInputTestCaseFactory)
}
}
diff --git a/compose/material3/benchmark/src/androidTest/java/androidx/compose/material3/benchmark/ExposedDropdownMenuBenchmark.kt b/compose/material3/benchmark/src/androidTest/java/androidx/compose/material3/benchmark/ExposedDropdownMenuBenchmark.kt
new file mode 100644
index 0000000..fc6d8bd
--- /dev/null
+++ b/compose/material3/benchmark/src/androidTest/java/androidx/compose/material3/benchmark/ExposedDropdownMenuBenchmark.kt
@@ -0,0 +1,130 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.material3.benchmark
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.size
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.ExposedDropdownMenuBox
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.testutils.LayeredComposeTestCase
+import androidx.compose.testutils.ToggleableTestCase
+import androidx.compose.testutils.benchmark.ComposeBenchmarkRule
+import androidx.compose.testutils.benchmark.benchmarkFirstCompose
+import androidx.compose.testutils.benchmark.toggleStateBenchmarkComposeMeasureLayout
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import androidx.test.filters.LargeTest
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+@LargeTest
+@RunWith(Parameterized::class)
+class ExposedDropdownMenuBenchmark(private val expanded: Boolean) {
+ @get:Rule
+ val benchmarkRule = ComposeBenchmarkRule()
+
+ @Test
+ fun edm_first_compose() {
+ benchmarkRule.benchmarkFirstCompose { ExposedDropdownMenuTestCase(expanded) }
+ }
+
+ @Test
+ fun edm_measure() {
+ benchmarkRule.benchmarkMeasureUntilStable({
+ ExposedDropdownMenuTestCase(expanded)
+ })
+ }
+
+ @Test
+ fun edm_layout() {
+ benchmarkRule.benchmarkLayoutUntilStable({
+ ExposedDropdownMenuTestCase(expanded)
+ })
+ }
+
+ @Test
+ fun edm_draw() {
+ benchmarkRule.benchmarkDrawUntilStable({
+ ExposedDropdownMenuTestCase(expanded)
+ })
+ }
+
+ @Test
+ fun edm_textFieldAnchor_repositioned() {
+ benchmarkRule.toggleStateBenchmarkComposeMeasureLayout({
+ ExposedDropdownMenuTestCase(expanded)
+ })
+ }
+
+ companion object {
+ @Parameterized.Parameters(name = "expanded = {0}")
+ @JvmStatic
+ fun parameters() = arrayOf(true, false)
+ }
+}
+
+internal class ExposedDropdownMenuTestCase(
+ private val expanded: Boolean
+) : LayeredComposeTestCase(), ToggleableTestCase {
+ private lateinit var state: MutableState<Dp>
+
+ @OptIn(ExperimentalMaterial3Api::class)
+ @Composable
+ override fun MeasuredContent() {
+ val spacerHeight = remember { mutableStateOf(100.dp) }
+ state = spacerHeight
+
+ Column(Modifier.fillMaxSize()) {
+ Spacer(Modifier.height(state.value))
+
+ ExposedDropdownMenuBox(
+ expanded = expanded,
+ onExpandedChange = {},
+ ) {
+ Spacer(modifier = Modifier.size(100.dp).menuAnchor())
+ ExposedDropdownMenu(
+ expanded = expanded,
+ onDismissRequest = {},
+ content = { Spacer(modifier = Modifier.height(50.dp).fillMaxWidth()) },
+ )
+ }
+ }
+ }
+
+ @Composable
+ override fun ContentWrappers(content: @Composable () -> Unit) {
+ MaterialTheme {
+ content()
+ }
+ }
+
+ override fun toggleState() {
+ state.value = if (state.value == 100.dp) 200.dp else 100.dp
+ }
+}
diff --git a/compose/material3/benchmark/src/androidTest/java/androidx/compose/material3/benchmark/MaterialBenchmarkExtensions.kt b/compose/material3/benchmark/src/androidTest/java/androidx/compose/material3/benchmark/MaterialBenchmarkExtensions.kt
index 5eaa7df..5a50c15 100644
--- a/compose/material3/benchmark/src/androidTest/java/androidx/compose/material3/benchmark/MaterialBenchmarkExtensions.kt
+++ b/compose/material3/benchmark/src/androidTest/java/androidx/compose/material3/benchmark/MaterialBenchmarkExtensions.kt
@@ -28,101 +28,126 @@
import androidx.compose.testutils.doFramesUntilNoChangesPending
import org.junit.Assert
-/**
- * Measures the time of the first draw right after the given test case is added to an already
- * existing hierarchy.
- *
- * @param caseFactory a factory for [LayeredComposeTestCase]
- * @param allowPendingChanges in case `true`, the benchmark will allow pending changes after the
- * first draw. Otherwise, an [assertNoPendingChanges] will be called.
- */
-internal fun ComposeBenchmarkRule.benchmarkFirstDraw(
+internal fun ComposeBenchmarkRule.benchmarkDrawUntilStable(
caseFactory: () -> LayeredComposeTestCase,
- allowPendingChanges: Boolean
+ maxSteps: Int = 10,
) {
runBenchmarkFor(LayeredCaseAdapter.of(caseFactory)) {
measureRepeated {
runWithTimingDisabled {
- doFramesUntilNoChangesPending()
+ doFramesUntilNoChangesPending(maxSteps)
// Add the content to benchmark
getTestCase().addMeasuredContent()
- recomposeUntilNoChangesPending()
- requestLayout()
+ }
+
+ var loopCount = 0
+ while (hasPendingChanges()) {
+ loopCount++
+ runWithTimingDisabled {
+ recomposeUntilNoChangesPending(maxSteps)
+ requestLayout()
+ measure()
+ layout()
+ drawPrepare()
+ }
+
+ draw()
+
+ runWithTimingDisabled {
+ drawFinish()
+ }
+ }
+
+ if (loopCount == 1) {
+ throw AssertionError("Use benchmarkFirstDraw instead")
+ }
+
+ runWithTimingDisabled {
+ assertNoPendingChanges()
+ disposeContent()
+ }
+ }
+ }
+}
+
+internal fun ComposeBenchmarkRule.benchmarkMeasureUntilStable(
+ caseFactory: () -> LayeredComposeTestCase,
+ maxSteps: Int = MaxSteps,
+) {
+ runBenchmarkFor(LayeredCaseAdapter.of(caseFactory)) {
+ measureRepeated {
+ runWithTimingDisabled {
+ doFramesUntilNoChangesPending(maxSteps)
+ // Add the content to benchmark
+ getTestCase().addMeasuredContent()
+ }
+
+ var loopCount = 0
+ while (hasPendingChanges()) {
+ loopCount++
+ runWithTimingDisabled {
+ recomposeUntilNoChangesPending(maxSteps)
+ requestLayout()
+ }
+
measure()
+
+ runWithTimingDisabled {
+ layout()
+ drawPrepare()
+ draw()
+ drawFinish()
+ }
+ }
+
+ if (loopCount == 1) {
+ throw AssertionError("Use benchmarkFirstMeasure instead")
+ }
+
+ runWithTimingDisabled {
+ assertNoPendingChanges()
+ disposeContent()
+ }
+ }
+ }
+}
+
+internal fun ComposeBenchmarkRule.benchmarkLayoutUntilStable(
+ caseFactory: () -> LayeredComposeTestCase,
+ maxSteps: Int = MaxSteps,
+) {
+ runBenchmarkFor(LayeredCaseAdapter.of(caseFactory)) {
+ measureRepeated {
+ runWithTimingDisabled {
+ doFramesUntilNoChangesPending(maxSteps)
+ // Add the content to benchmark
+ getTestCase().addMeasuredContent()
+ }
+
+ var loopCount = 0
+ while (hasPendingChanges()) {
+ loopCount++
+ runWithTimingDisabled {
+ recomposeUntilNoChangesPending(maxSteps)
+ requestLayout()
+ measure()
+ }
+
layout()
- drawPrepare()
+
+ runWithTimingDisabled {
+ drawPrepare()
+ draw()
+ drawFinish()
+ }
}
- draw()
-
- runWithTimingDisabled {
- drawFinish()
- if (!allowPendingChanges) assertNoPendingChanges()
- disposeContent()
- }
- }
- }
-}
-
-/**
- * Measures the time of the first measure right after the given test case is added to an already
- * existing hierarchy.
- *
- * @param caseFactory a factory for [LayeredComposeTestCase]
- * @param allowPendingChanges in case `true`, the benchmark will allow pending changes after the
- * first measure. Otherwise, an [assertNoPendingChanges] will be called.
- */
-internal fun ComposeBenchmarkRule.benchmarkFirstMeasure(
- caseFactory: () -> LayeredComposeTestCase,
- allowPendingChanges: Boolean
-) {
- runBenchmarkFor(LayeredCaseAdapter.of(caseFactory)) {
- measureRepeated {
- runWithTimingDisabled {
- doFramesUntilNoChangesPending()
- // Add the content to benchmark
- getTestCase().addMeasuredContent()
- recomposeUntilNoChangesPending()
- requestLayout()
+ if (loopCount == 1) {
+ throw AssertionError("Use benchmarkFirstLayout instead")
}
- measure()
-
runWithTimingDisabled {
- if (!allowPendingChanges) assertNoPendingChanges()
- disposeContent()
- }
- }
- }
-}
-
-/**
- * Measures the time of the first layout right after the given test case is added to an already
- * existing hierarchy.
- *
- * @param caseFactory a factory for [LayeredComposeTestCase]
- * @param allowPendingChanges in case `true`, the benchmark will allow pending changes after the
- * first layout. Otherwise, an [assertNoPendingChanges] will be called.
- */
-internal fun ComposeBenchmarkRule.benchmarkFirstLayout(
- caseFactory: () -> LayeredComposeTestCase,
- allowPendingChanges: Boolean
-) {
- runBenchmarkFor(LayeredCaseAdapter.of(caseFactory)) {
- measureRepeated {
- runWithTimingDisabled {
- doFramesUntilNoChangesPending()
- // Add the content to benchmark
- getTestCase().addMeasuredContent()
- recomposeUntilNoChangesPending()
- requestLayout()
- measure()
- }
-
- layout()
-
- runWithTimingDisabled {
- if (!allowPendingChanges) assertNoPendingChanges()
+ assertNoPendingChanges()
disposeContent()
}
}
@@ -154,4 +179,4 @@
}
}
-private const val MaxAmountOfRecompositions = 10
+private const val MaxSteps = 10
diff --git a/compose/material3/material3-adaptive/api/current.txt b/compose/material3/material3-adaptive/api/current.txt
index 22e1264..bf5b32a 100644
--- a/compose/material3/material3-adaptive/api/current.txt
+++ b/compose/material3/material3-adaptive/api/current.txt
@@ -24,8 +24,8 @@
}
public final class AdaptiveLayoutDirectiveKt {
- method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public static androidx.compose.material3.adaptive.AdaptiveLayoutDirective calculateDenseAdaptiveLayoutDirective(androidx.compose.material3.adaptive.WindowAdaptiveInfo windowAdaptiveInfo);
- method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public static androidx.compose.material3.adaptive.AdaptiveLayoutDirective calculateStandardAdaptiveLayoutDirective(androidx.compose.material3.adaptive.WindowAdaptiveInfo windowAdaptiveInfo);
+ method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public static androidx.compose.material3.adaptive.AdaptiveLayoutDirective calculateDenseAdaptiveLayoutDirective(androidx.compose.material3.adaptive.WindowAdaptiveInfo windowAdaptiveInfo, optional int hingePolicy);
+ method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public static androidx.compose.material3.adaptive.AdaptiveLayoutDirective calculateStandardAdaptiveLayoutDirective(androidx.compose.material3.adaptive.WindowAdaptiveInfo windowAdaptiveInfo, optional int hingePolicy);
}
public final class AndroidPosture_androidKt {
@@ -53,6 +53,49 @@
property public final float outerVertical;
}
+ @androidx.compose.runtime.Immutable @kotlin.jvm.JvmInline public final value class HingePolicy {
+ field public static final androidx.compose.material3.adaptive.HingePolicy.Companion Companion;
+ }
+
+ public static final class HingePolicy.Companion {
+ method public int getAlwaysAvoid();
+ method public int getAvoidOccluding();
+ method public int getAvoidSeparating();
+ method public int getNeverAvoid();
+ property public final int AlwaysAvoid;
+ property public final int AvoidOccluding;
+ property public final int AvoidSeparating;
+ property public final int NeverAvoid;
+ }
+
+ @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public final class ListDetailPaneScaffoldDefaults {
+ method public androidx.compose.material3.adaptive.ThreePaneScaffoldAdaptStrategies adaptStrategies(optional androidx.compose.material3.adaptive.AdaptStrategy detailPaneAdaptStrategy, optional androidx.compose.material3.adaptive.AdaptStrategy listPaneAdaptStrategy, optional androidx.compose.material3.adaptive.AdaptStrategy extraPaneAdaptStrategy);
+ field public static final androidx.compose.material3.adaptive.ListDetailPaneScaffoldDefaults INSTANCE;
+ }
+
+ public final class ListDetailPaneScaffoldKt {
+ method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void ListDetailPaneScaffold(androidx.compose.material3.adaptive.ListDetailPaneScaffoldState layoutState, kotlin.jvm.functions.Function2<? super androidx.compose.material3.adaptive.ThreePaneScaffoldScope,? super androidx.compose.material3.adaptive.PaneAdaptedValue,kotlin.Unit> listPane, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function2<? super androidx.compose.material3.adaptive.ThreePaneScaffoldScope,? super androidx.compose.material3.adaptive.PaneAdaptedValue,kotlin.Unit>? extraPane, kotlin.jvm.functions.Function2<? super androidx.compose.material3.adaptive.ThreePaneScaffoldScope,? super androidx.compose.material3.adaptive.PaneAdaptedValue,kotlin.Unit> detailPane);
+ method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static androidx.compose.material3.adaptive.ListDetailPaneScaffoldState rememberListDetailPaneScaffoldState(optional androidx.compose.material3.adaptive.AdaptiveLayoutDirective layoutDirectives, optional androidx.compose.material3.adaptive.ThreePaneScaffoldAdaptStrategies adaptStrategies, optional androidx.compose.material3.adaptive.ListDetailPaneScaffoldRole initialFocus);
+ }
+
+ @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public enum ListDetailPaneScaffoldRole {
+ method public static androidx.compose.material3.adaptive.ListDetailPaneScaffoldRole valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
+ method public static androidx.compose.material3.adaptive.ListDetailPaneScaffoldRole[] values();
+ enum_constant public static final androidx.compose.material3.adaptive.ListDetailPaneScaffoldRole Detail;
+ enum_constant public static final androidx.compose.material3.adaptive.ListDetailPaneScaffoldRole Extra;
+ enum_constant public static final androidx.compose.material3.adaptive.ListDetailPaneScaffoldRole List;
+ }
+
+ @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Stable public interface ListDetailPaneScaffoldState {
+ method public boolean canNavigateBack(optional boolean layoutValueMustChange);
+ method public androidx.compose.material3.adaptive.AdaptiveLayoutDirective getLayoutDirective();
+ method public androidx.compose.material3.adaptive.ThreePaneScaffoldValue getLayoutValue();
+ method public boolean navigateBack(optional boolean popUntilLayoutValueChange);
+ method public void navigateTo(androidx.compose.material3.adaptive.ListDetailPaneScaffoldRole pane);
+ property public abstract androidx.compose.material3.adaptive.AdaptiveLayoutDirective layoutDirective;
+ property public abstract androidx.compose.material3.adaptive.ThreePaneScaffoldValue layoutValue;
+ }
+
@SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @kotlin.jvm.JvmInline public final value class PaneAdaptedValue {
field public static final androidx.compose.material3.adaptive.PaneAdaptedValue.Companion Companion;
}
@@ -69,13 +112,19 @@
}
@SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Immutable public final class Posture {
- ctor public Posture(optional boolean hasVerticalHinge, optional boolean isTabletop, optional boolean hasSeparatingHinge);
+ ctor public Posture(optional boolean hasVerticalHinge, optional boolean isTabletop, optional boolean hasSeparatingHinge, optional java.util.List<androidx.compose.ui.geometry.Rect> separatingHingeBounds, optional java.util.List<androidx.compose.ui.geometry.Rect> occludingHingeBounds, optional java.util.List<androidx.compose.ui.geometry.Rect> allHingeBounds);
+ method public java.util.List<androidx.compose.ui.geometry.Rect> getAllHingeBounds();
method public boolean getHasSeparatingHinge();
method public boolean getHasVerticalHinge();
+ method public java.util.List<androidx.compose.ui.geometry.Rect> getOccludingHingeBounds();
+ method public java.util.List<androidx.compose.ui.geometry.Rect> getSeparatingHingeBounds();
method public boolean isTabletop();
+ property public final java.util.List<androidx.compose.ui.geometry.Rect> allHingeBounds;
property public final boolean hasSeparatingHinge;
property public final boolean hasVerticalHinge;
property public final boolean isTabletop;
+ property public final java.util.List<androidx.compose.ui.geometry.Rect> occludingHingeBounds;
+ property public final java.util.List<androidx.compose.ui.geometry.Rect> separatingHingeBounds;
}
@SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public final class ThreePaneScaffoldAdaptStrategies {
diff --git a/compose/material3/material3-adaptive/api/restricted_current.txt b/compose/material3/material3-adaptive/api/restricted_current.txt
index 22e1264..bf5b32a 100644
--- a/compose/material3/material3-adaptive/api/restricted_current.txt
+++ b/compose/material3/material3-adaptive/api/restricted_current.txt
@@ -24,8 +24,8 @@
}
public final class AdaptiveLayoutDirectiveKt {
- method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public static androidx.compose.material3.adaptive.AdaptiveLayoutDirective calculateDenseAdaptiveLayoutDirective(androidx.compose.material3.adaptive.WindowAdaptiveInfo windowAdaptiveInfo);
- method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public static androidx.compose.material3.adaptive.AdaptiveLayoutDirective calculateStandardAdaptiveLayoutDirective(androidx.compose.material3.adaptive.WindowAdaptiveInfo windowAdaptiveInfo);
+ method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public static androidx.compose.material3.adaptive.AdaptiveLayoutDirective calculateDenseAdaptiveLayoutDirective(androidx.compose.material3.adaptive.WindowAdaptiveInfo windowAdaptiveInfo, optional int hingePolicy);
+ method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public static androidx.compose.material3.adaptive.AdaptiveLayoutDirective calculateStandardAdaptiveLayoutDirective(androidx.compose.material3.adaptive.WindowAdaptiveInfo windowAdaptiveInfo, optional int hingePolicy);
}
public final class AndroidPosture_androidKt {
@@ -53,6 +53,49 @@
property public final float outerVertical;
}
+ @androidx.compose.runtime.Immutable @kotlin.jvm.JvmInline public final value class HingePolicy {
+ field public static final androidx.compose.material3.adaptive.HingePolicy.Companion Companion;
+ }
+
+ public static final class HingePolicy.Companion {
+ method public int getAlwaysAvoid();
+ method public int getAvoidOccluding();
+ method public int getAvoidSeparating();
+ method public int getNeverAvoid();
+ property public final int AlwaysAvoid;
+ property public final int AvoidOccluding;
+ property public final int AvoidSeparating;
+ property public final int NeverAvoid;
+ }
+
+ @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public final class ListDetailPaneScaffoldDefaults {
+ method public androidx.compose.material3.adaptive.ThreePaneScaffoldAdaptStrategies adaptStrategies(optional androidx.compose.material3.adaptive.AdaptStrategy detailPaneAdaptStrategy, optional androidx.compose.material3.adaptive.AdaptStrategy listPaneAdaptStrategy, optional androidx.compose.material3.adaptive.AdaptStrategy extraPaneAdaptStrategy);
+ field public static final androidx.compose.material3.adaptive.ListDetailPaneScaffoldDefaults INSTANCE;
+ }
+
+ public final class ListDetailPaneScaffoldKt {
+ method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void ListDetailPaneScaffold(androidx.compose.material3.adaptive.ListDetailPaneScaffoldState layoutState, kotlin.jvm.functions.Function2<? super androidx.compose.material3.adaptive.ThreePaneScaffoldScope,? super androidx.compose.material3.adaptive.PaneAdaptedValue,kotlin.Unit> listPane, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function2<? super androidx.compose.material3.adaptive.ThreePaneScaffoldScope,? super androidx.compose.material3.adaptive.PaneAdaptedValue,kotlin.Unit>? extraPane, kotlin.jvm.functions.Function2<? super androidx.compose.material3.adaptive.ThreePaneScaffoldScope,? super androidx.compose.material3.adaptive.PaneAdaptedValue,kotlin.Unit> detailPane);
+ method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static androidx.compose.material3.adaptive.ListDetailPaneScaffoldState rememberListDetailPaneScaffoldState(optional androidx.compose.material3.adaptive.AdaptiveLayoutDirective layoutDirectives, optional androidx.compose.material3.adaptive.ThreePaneScaffoldAdaptStrategies adaptStrategies, optional androidx.compose.material3.adaptive.ListDetailPaneScaffoldRole initialFocus);
+ }
+
+ @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public enum ListDetailPaneScaffoldRole {
+ method public static androidx.compose.material3.adaptive.ListDetailPaneScaffoldRole valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
+ method public static androidx.compose.material3.adaptive.ListDetailPaneScaffoldRole[] values();
+ enum_constant public static final androidx.compose.material3.adaptive.ListDetailPaneScaffoldRole Detail;
+ enum_constant public static final androidx.compose.material3.adaptive.ListDetailPaneScaffoldRole Extra;
+ enum_constant public static final androidx.compose.material3.adaptive.ListDetailPaneScaffoldRole List;
+ }
+
+ @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Stable public interface ListDetailPaneScaffoldState {
+ method public boolean canNavigateBack(optional boolean layoutValueMustChange);
+ method public androidx.compose.material3.adaptive.AdaptiveLayoutDirective getLayoutDirective();
+ method public androidx.compose.material3.adaptive.ThreePaneScaffoldValue getLayoutValue();
+ method public boolean navigateBack(optional boolean popUntilLayoutValueChange);
+ method public void navigateTo(androidx.compose.material3.adaptive.ListDetailPaneScaffoldRole pane);
+ property public abstract androidx.compose.material3.adaptive.AdaptiveLayoutDirective layoutDirective;
+ property public abstract androidx.compose.material3.adaptive.ThreePaneScaffoldValue layoutValue;
+ }
+
@SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @kotlin.jvm.JvmInline public final value class PaneAdaptedValue {
field public static final androidx.compose.material3.adaptive.PaneAdaptedValue.Companion Companion;
}
@@ -69,13 +112,19 @@
}
@SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Immutable public final class Posture {
- ctor public Posture(optional boolean hasVerticalHinge, optional boolean isTabletop, optional boolean hasSeparatingHinge);
+ ctor public Posture(optional boolean hasVerticalHinge, optional boolean isTabletop, optional boolean hasSeparatingHinge, optional java.util.List<androidx.compose.ui.geometry.Rect> separatingHingeBounds, optional java.util.List<androidx.compose.ui.geometry.Rect> occludingHingeBounds, optional java.util.List<androidx.compose.ui.geometry.Rect> allHingeBounds);
+ method public java.util.List<androidx.compose.ui.geometry.Rect> getAllHingeBounds();
method public boolean getHasSeparatingHinge();
method public boolean getHasVerticalHinge();
+ method public java.util.List<androidx.compose.ui.geometry.Rect> getOccludingHingeBounds();
+ method public java.util.List<androidx.compose.ui.geometry.Rect> getSeparatingHingeBounds();
method public boolean isTabletop();
+ property public final java.util.List<androidx.compose.ui.geometry.Rect> allHingeBounds;
property public final boolean hasSeparatingHinge;
property public final boolean hasVerticalHinge;
property public final boolean isTabletop;
+ property public final java.util.List<androidx.compose.ui.geometry.Rect> occludingHingeBounds;
+ property public final java.util.List<androidx.compose.ui.geometry.Rect> separatingHingeBounds;
}
@SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public final class ThreePaneScaffoldAdaptStrategies {
diff --git a/compose/material3/material3-adaptive/src/androidAndroidTest/kotlin/androidx/compose/material3/adaptive/CalculatePostureTest.kt b/compose/material3/material3-adaptive/src/androidAndroidTest/kotlin/androidx/compose/material3/adaptive/CalculatePostureTest.kt
index 63a9792..a0e3f4d 100644
--- a/compose/material3/material3-adaptive/src/androidAndroidTest/kotlin/androidx/compose/material3/adaptive/CalculatePostureTest.kt
+++ b/compose/material3/material3-adaptive/src/androidAndroidTest/kotlin/androidx/compose/material3/adaptive/CalculatePostureTest.kt
@@ -17,6 +17,7 @@
package androidx.compose.material3.adaptive
import android.graphics.Rect
+import androidx.compose.ui.graphics.toComposeRect
import androidx.window.layout.FoldingFeature
import com.google.common.truth.Truth.assertThat
import org.junit.Test
@@ -161,6 +162,97 @@
assertThat(posture.isTabletop).isFalse()
}
+
+ @Test
+ fun test_calculatePosture_separatingBounds() {
+ val mockHingeBounds1 = Rect(1, 1, 2, 2)
+ val mockHingeBounds2 = Rect(2, 2, 3, 3)
+ val mockHingeBounds3 = Rect(3, 3, 4, 4)
+ val posture = calculatePosture(
+ listOf(
+ MockFoldingFeature(
+ isSeparating = false,
+ occlusionType = FoldingFeature.OcclusionType.FULL,
+ bounds = mockHingeBounds1
+ ),
+ MockFoldingFeature(
+ isSeparating = true,
+ occlusionType = FoldingFeature.OcclusionType.FULL,
+ bounds = mockHingeBounds2
+ ),
+ MockFoldingFeature(
+ isSeparating = true,
+ occlusionType = FoldingFeature.OcclusionType.NONE,
+ bounds = mockHingeBounds3
+ ),
+ )
+ )
+
+ assertThat(posture.separatingHingeBounds.size).isEqualTo(2)
+ assertThat(posture.separatingHingeBounds[0]).isEqualTo(mockHingeBounds2.toComposeRect())
+ assertThat(posture.separatingHingeBounds[1]).isEqualTo(mockHingeBounds3.toComposeRect())
+ }
+
+ @Test
+ fun test_calculatePosture_occludingBounds() {
+ val mockHingeBounds1 = Rect(1, 1, 2, 2)
+ val mockHingeBounds2 = Rect(2, 2, 3, 3)
+ val mockHingeBounds3 = Rect(3, 3, 4, 4)
+ val posture = calculatePosture(
+ listOf(
+ MockFoldingFeature(
+ isSeparating = false,
+ occlusionType = FoldingFeature.OcclusionType.FULL,
+ bounds = mockHingeBounds1
+ ),
+ MockFoldingFeature(
+ isSeparating = true,
+ occlusionType = FoldingFeature.OcclusionType.FULL,
+ bounds = mockHingeBounds2
+ ),
+ MockFoldingFeature(
+ isSeparating = true,
+ occlusionType = FoldingFeature.OcclusionType.NONE,
+ bounds = mockHingeBounds3
+ ),
+ )
+ )
+
+ assertThat(posture.occludingHingeBounds.size).isEqualTo(2)
+ assertThat(posture.occludingHingeBounds[0]).isEqualTo(mockHingeBounds1.toComposeRect())
+ assertThat(posture.occludingHingeBounds[1]).isEqualTo(mockHingeBounds2.toComposeRect())
+ }
+
+ @Test
+ fun test_calculatePosture_allBounds() {
+ val mockHingeBounds1 = Rect(1, 1, 2, 2)
+ val mockHingeBounds2 = Rect(2, 2, 3, 3)
+ val mockHingeBounds3 = Rect(3, 3, 4, 4)
+ val posture = calculatePosture(
+ listOf(
+ MockFoldingFeature(
+ isSeparating = false,
+ occlusionType = FoldingFeature.OcclusionType.FULL,
+ bounds = mockHingeBounds1
+ ),
+ MockFoldingFeature(
+ isSeparating = true,
+ occlusionType = FoldingFeature.OcclusionType.FULL,
+ bounds = mockHingeBounds2
+ ),
+ MockFoldingFeature(
+ isSeparating = true,
+ occlusionType = FoldingFeature.OcclusionType.NONE,
+ bounds = mockHingeBounds3
+ ),
+ )
+ )
+
+ assertThat(posture.allHingeBounds.size).isEqualTo(3)
+ assertThat(posture.allHingeBounds[0]).isEqualTo(mockHingeBounds1.toComposeRect())
+ assertThat(posture.allHingeBounds[1]).isEqualTo(mockHingeBounds2.toComposeRect())
+ assertThat(posture.allHingeBounds[2]).isEqualTo(mockHingeBounds3.toComposeRect())
+ }
}
internal class MockFoldingFeature(
diff --git a/compose/material3/material3-adaptive/src/androidAndroidTest/kotlin/androidx/compose/material3/adaptive/ListDetailPaneScaffoldStateTest.kt b/compose/material3/material3-adaptive/src/androidAndroidTest/kotlin/androidx/compose/material3/adaptive/ListDetailPaneScaffoldStateTest.kt
new file mode 100644
index 0000000..ad6c75f
--- /dev/null
+++ b/compose/material3/material3-adaptive/src/androidAndroidTest/kotlin/androidx/compose/material3/adaptive/ListDetailPaneScaffoldStateTest.kt
@@ -0,0 +1,170 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.material3.adaptive
+
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalMaterial3AdaptiveApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ListDetailPaneScaffoldStateTest {
+ @get:Rule
+ val composeRule = createComposeRule()
+
+ @Test
+ fun singlePaneLayout_navigateTo_makeFocusPaneExpanded() {
+ lateinit var layoutState: ListDetailPaneScaffoldState
+
+ composeRule.setContent {
+ layoutState = rememberListDetailPaneScaffoldState(
+ layoutDirectives = MockSinglePaneLayoutDirective
+ )
+ }
+
+ composeRule.runOnIdle {
+ assertThat(layoutState.layoutValue.secondary).isEqualTo(PaneAdaptedValue.Hidden)
+ layoutState.navigateTo(ListDetailPaneScaffoldRole.List)
+ }
+
+ composeRule.runOnIdle {
+ assertThat(layoutState.layoutValue.secondary).isEqualTo(PaneAdaptedValue.Expanded)
+ }
+ }
+
+ @Test
+ fun dualPaneLayout_navigateTo_keepFocusPaneExpanded() {
+ lateinit var layoutState: ListDetailPaneScaffoldState
+
+ composeRule.setContent {
+ layoutState = rememberListDetailPaneScaffoldState(
+ layoutDirectives = MockDualPaneLayoutDirective
+ )
+ }
+
+ composeRule.runOnIdle {
+ assertThat(layoutState.layoutValue.secondary).isEqualTo(PaneAdaptedValue.Expanded)
+ layoutState.navigateTo(ListDetailPaneScaffoldRole.List)
+ }
+
+ composeRule.runOnIdle {
+ assertThat(layoutState.layoutValue.secondary).isEqualTo(PaneAdaptedValue.Expanded)
+ }
+ }
+
+ @Test
+ fun singlePaneLayout_navigateBack_makeFocusPaneHidden() {
+ lateinit var layoutState: ListDetailPaneScaffoldState
+
+ composeRule.setContent {
+ layoutState = rememberListDetailPaneScaffoldState(
+ layoutDirectives = MockSinglePaneLayoutDirective
+ )
+ layoutState.navigateTo(ListDetailPaneScaffoldRole.List)
+ }
+
+ composeRule.runOnIdle {
+ assertThat(layoutState.layoutValue.secondary).isEqualTo(PaneAdaptedValue.Expanded)
+ assertThat(layoutState.canNavigateBack()).isTrue()
+ layoutState.navigateBack()
+ }
+
+ composeRule.runOnIdle {
+ assertThat(layoutState.layoutValue.secondary).isEqualTo(PaneAdaptedValue.Hidden)
+ }
+ }
+
+ @Test
+ fun dualPaneLayout_enforceLayoutValueChange_cannotNavigateBack() {
+ lateinit var layoutState: ListDetailPaneScaffoldState
+
+ composeRule.setContent {
+ layoutState = rememberListDetailPaneScaffoldState(
+ layoutDirectives = MockDualPaneLayoutDirective
+ )
+ layoutState.navigateTo(ListDetailPaneScaffoldRole.List)
+ }
+
+ composeRule.runOnIdle {
+ assertThat(layoutState.canNavigateBack()).isFalse()
+ }
+ }
+
+ @Test
+ fun dualPaneLayout_notEnforceLayoutValueChange_canNavigateBack() {
+ lateinit var layoutState: ListDetailPaneScaffoldState
+
+ composeRule.setContent {
+ layoutState = rememberListDetailPaneScaffoldState(
+ layoutDirectives = MockDualPaneLayoutDirective
+ )
+ layoutState.navigateTo(ListDetailPaneScaffoldRole.List)
+ }
+
+ composeRule.runOnIdle {
+ assertThat(layoutState.layoutValue.secondary).isEqualTo(PaneAdaptedValue.Expanded)
+ assertThat(layoutState.canNavigateBack(false)).isTrue()
+ layoutState.navigateBack(false)
+ }
+
+ composeRule.runOnIdle {
+ assertThat(layoutState.layoutValue.secondary).isEqualTo(PaneAdaptedValue.Expanded)
+ }
+ }
+
+ @Test
+ fun singlePaneToDualPaneLayout_enforceLayoutValueChange_cannotNavigateBack() {
+ lateinit var layoutState: ListDetailPaneScaffoldState
+ val mockCurrentLayoutDirective = mutableStateOf(MockSinglePaneLayoutDirective)
+
+ composeRule.setContent {
+ layoutState = rememberListDetailPaneScaffoldState(
+ layoutDirectives = mockCurrentLayoutDirective.value
+ )
+ layoutState.navigateTo(ListDetailPaneScaffoldRole.List)
+ }
+
+ composeRule.runOnIdle {
+ assertThat(layoutState.layoutValue.secondary).isEqualTo(PaneAdaptedValue.Expanded)
+ // Switches to dual pane
+ mockCurrentLayoutDirective.value = MockDualPaneLayoutDirective
+ }
+
+ composeRule.runOnIdle {
+ assertThat(layoutState.canNavigateBack()).isFalse()
+ }
+ }
+}
+
+@OptIn(ExperimentalMaterial3AdaptiveApi::class)
+private val MockSinglePaneLayoutDirective = AdaptiveLayoutDirective(
+ maxHorizontalPartitions = 1,
+ gutterSizes = GutterSizes(0.dp, 0.dp)
+)
+
+@OptIn(ExperimentalMaterial3AdaptiveApi::class)
+private val MockDualPaneLayoutDirective = AdaptiveLayoutDirective(
+ maxHorizontalPartitions = 2,
+ gutterSizes = GutterSizes(16.dp, 16.dp)
+)
diff --git a/compose/material3/material3-adaptive/src/androidMain/kotlin/androidx/compose/material3/adaptive/AndroidPosture.android.kt b/compose/material3/material3-adaptive/src/androidMain/kotlin/androidx/compose/material3/adaptive/AndroidPosture.android.kt
index cad4404..4a461e26 100644
--- a/compose/material3/material3-adaptive/src/androidMain/kotlin/androidx/compose/material3/adaptive/AndroidPosture.android.kt
+++ b/compose/material3/material3-adaptive/src/androidMain/kotlin/androidx/compose/material3/adaptive/AndroidPosture.android.kt
@@ -16,6 +16,8 @@
package androidx.compose.material3.adaptive
+import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.graphics.toComposeRect
import androidx.window.layout.FoldingFeature
/**
@@ -27,6 +29,9 @@
var hasVerticalHinge = false
var isTableTop = false
var hasSeparatingHinge = false
+ val separatingHingeBounds = mutableListOf<Rect>()
+ val occludingHingeBounds = mutableListOf<Rect>()
+ val allHingeBounds = mutableListOf<Rect>()
foldingFeatures.forEach {
if (it.orientation == FoldingFeature.Orientation.VERTICAL) {
hasVerticalHinge = true
@@ -38,6 +43,21 @@
it.state == FoldingFeature.State.HALF_OPENED) {
isTableTop = true
}
+ val hingeBounds = it.bounds.toComposeRect()
+ allHingeBounds.add(hingeBounds)
+ if (it.isSeparating) {
+ separatingHingeBounds.add(hingeBounds)
+ }
+ if (it.occlusionType == FoldingFeature.OcclusionType.FULL) {
+ occludingHingeBounds.add(hingeBounds)
+ }
}
- return Posture(hasVerticalHinge, isTableTop, hasSeparatingHinge)
+ return Posture(
+ hasVerticalHinge,
+ isTableTop,
+ hasSeparatingHinge,
+ separatingHingeBounds,
+ occludingHingeBounds,
+ allHingeBounds
+ )
}
diff --git a/compose/material3/material3-adaptive/src/androidMain/kotlin/androidx/compose/material3/adaptive/ListDetailPaneScaffold.kt b/compose/material3/material3-adaptive/src/androidMain/kotlin/androidx/compose/material3/adaptive/ListDetailPaneScaffold.kt
index 1c9d6f7..57d3e56 100644
--- a/compose/material3/material3-adaptive/src/androidMain/kotlin/androidx/compose/material3/adaptive/ListDetailPaneScaffold.kt
+++ b/compose/material3/material3-adaptive/src/androidMain/kotlin/androidx/compose/material3/adaptive/ListDetailPaneScaffold.kt
@@ -18,30 +18,37 @@
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.Stable
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Modifier
/**
* A Material opinionated implementation of [ThreePaneScaffold] that will display the provided three
* panes in a canonical list-detail layout.
+ *
+ * @param layoutState the state of the scaffold, which will decide the current layout directive
+ * and scaffold layout value, and perform navigation within the scaffold.
+ * @param listPane the list pane of the scaffold. See [ListDetailPaneScaffoldRole.List].
+ * @param modifier [Modifier] of the scaffold layout.
+ * @param extraPane the list pane of the scaffold. See [ListDetailPaneScaffoldRole.Extra].
+ * @param detailPane the list pane of the scaffold. See [ListDetailPaneScaffoldRole.Detail].
*/
@ExperimentalMaterial3AdaptiveApi
@Composable
-internal fun ListDetailPaneScaffold(
- modifier: Modifier = Modifier,
+fun ListDetailPaneScaffold(
+ layoutState: ListDetailPaneScaffoldState,
listPane: @Composable ThreePaneScaffoldScope.(PaneAdaptedValue) -> Unit,
+ modifier: Modifier = Modifier,
extraPane: (@Composable ThreePaneScaffoldScope.(PaneAdaptedValue) -> Unit)? = null,
detailPane: @Composable ThreePaneScaffoldScope.(PaneAdaptedValue) -> Unit
) {
- // TODO(conradchen): Add support of condition & strategy customization
- val layoutDirective = calculateStandardAdaptiveLayoutDirective(calculateWindowAdaptiveInfo())
- val scaffoldValue = calculateThreePaneScaffoldValue(
- layoutDirective.maxHorizontalPartitions,
- ListDetailPaneScaffoldDefaults.adaptStrategies()
- )
ThreePaneScaffold(
- modifier = Modifier.fillMaxSize().then(modifier),
- layoutDirective = layoutDirective,
- scaffoldValue = scaffoldValue,
+ modifier = modifier.fillMaxSize(),
+ layoutDirective = layoutState.layoutDirective,
+ scaffoldValue = layoutState.layoutValue,
arrangement = ThreePaneScaffoldDefaults.ListDetailLayoutArrangement,
secondaryPane = listPane,
tertiaryPane = extraPane,
@@ -53,7 +60,7 @@
* Provides default values of [ListDetailPaneScaffold].
*/
@ExperimentalMaterial3AdaptiveApi
-internal object ListDetailPaneScaffoldDefaults {
+object ListDetailPaneScaffoldDefaults {
/**
* Creates a default [ThreePaneScaffoldAdaptStrategies] for [ListDetailPaneScaffold].
*
@@ -72,3 +79,154 @@
extraPaneAdaptStrategy
)
}
+
+/**
+ * The state of [ListDetailPaneScaffold]. It provides the layout directive and value state that will
+ * be updated directly. It also provides functions to perform navigation.
+ *
+ * Use [rememberListDetailPaneScaffoldState] to get a remembered default instance of this interface,
+ * which works independently from any navigation frameworks. Developers can also integrate with
+ * other navigation frameworks by implementing this interface.
+ *
+ * @property layoutDirective the current layout directives that the associated
+ * [ListDetailPaneScaffold] needs to follow. It's supposed to be automatically updated
+ * when the window configuration changes.
+ * @property layoutValue the current layout value of the associated [ListDetailPaneScaffold], which
+ * represents unique layout states of the scaffold.
+ */
+@ExperimentalMaterial3AdaptiveApi
+@Stable
+interface ListDetailPaneScaffoldState {
+ val layoutDirective: AdaptiveLayoutDirective
+ val layoutValue: ThreePaneScaffoldValue
+
+ /**
+ * Navigates to a new focus.
+ */
+ fun navigateTo(pane: ListDetailPaneScaffoldRole)
+
+ /**
+ * Returns `true` if there is a previous focus to navigate back to.
+ *
+ * @param layoutValueMustChange `true` if the navigation operation should only be performed when
+ * there are actual layout value changes.
+ */
+ fun canNavigateBack(layoutValueMustChange: Boolean = true): Boolean
+
+ /**
+ * Navigates to the previous focus.
+ *
+ * @param popUntilLayoutValueChange `true` if the backstack should be popped until the layout
+ * value changes.
+ */
+ fun navigateBack(popUntilLayoutValueChange: Boolean = true): Boolean
+}
+
+@OptIn(ExperimentalMaterial3AdaptiveApi::class)
+private class DefaultListDetailPaneScaffoldState(
+ override val layoutDirective: AdaptiveLayoutDirective,
+ val adaptStrategies: ThreePaneScaffoldAdaptStrategies,
+ val focusHistory: MutableList<ListDetailPaneScaffoldRole>
+) : ListDetailPaneScaffoldState {
+ val currentFocus: ListDetailPaneScaffoldRole? get() = focusHistory.lastOrNull()
+ val currentValueState: MutableState<ThreePaneScaffoldValue> =
+ mutableStateOf(calculateCurrentScaffoldValue())
+
+ override val layoutValue: ThreePaneScaffoldValue
+ get() = currentValueState.value
+
+ override fun navigateTo(pane: ListDetailPaneScaffoldRole) {
+ focusHistory.add(pane)
+ currentValueState.value = calculateCurrentScaffoldValue()
+ }
+
+ override fun canNavigateBack(layoutValueMustChange: Boolean): Boolean =
+ getPreviousFocusIndex(layoutValueMustChange) >= 0
+
+ override fun navigateBack(popUntilLayoutValueChange: Boolean): Boolean {
+ val previousFocusIndex = getPreviousFocusIndex(popUntilLayoutValueChange)
+ if (previousFocusIndex < 0) {
+ focusHistory.clear()
+ return false
+ }
+ val targetSize = previousFocusIndex + 1
+ while (focusHistory.size > targetSize) {
+ focusHistory.removeLast()
+ }
+ currentValueState.value = calculateCurrentScaffoldValue()
+ return true
+ }
+
+ private fun getPreviousFocusIndex(withLayoutValueChange: Boolean): Int {
+ if (focusHistory.size <= 1) {
+ // No previous focus
+ return -1
+ }
+ if (!withLayoutValueChange) {
+ return focusHistory.lastIndex - 1
+ }
+ for (previousFocusIndex in focusHistory.lastIndex - 1 downTo 0) {
+ val newValue = calculateScaffoldValue(focusHistory[previousFocusIndex])
+ if (newValue != currentValueState.value) {
+ return previousFocusIndex
+ }
+ }
+ return -1
+ }
+
+ private fun calculateScaffoldValue(focus: ListDetailPaneScaffoldRole?): ThreePaneScaffoldValue =
+ calculateThreePaneScaffoldValue(
+ layoutDirective.maxHorizontalPartitions,
+ adaptStrategies,
+ focus?.threePaneScaffoldRole
+ )
+
+ private fun calculateCurrentScaffoldValue(): ThreePaneScaffoldValue =
+ calculateScaffoldValue(currentFocus)
+}
+
+/**
+ * Returns a remembered default implementation of [ListDetailPaneScaffoldState], which will
+ * be updated automatically when the input values change. The default state is supposed to be
+ * used independently from any navigation frameworks and it will address the navigation purely
+ * inside the [ListDetailPaneScaffold].
+ *
+ * @param layoutDirectives the current layout directives to follow. The default value will be
+ * Calculated with [calculateStandardAdaptiveLayoutDirective] using [WindowAdaptiveInfo]
+ * retrieved from the current context.
+ * @param adaptStrategies adaptation strategies of each pane.
+ * @param initialFocus the initial focus of the scaffold, by default it will be the detail pane.
+ */
+@ExperimentalMaterial3AdaptiveApi
+@Composable
+fun rememberListDetailPaneScaffoldState(
+ layoutDirectives: AdaptiveLayoutDirective =
+ calculateStandardAdaptiveLayoutDirective(calculateWindowAdaptiveInfo()),
+ adaptStrategies: ThreePaneScaffoldAdaptStrategies =
+ ListDetailPaneScaffoldDefaults.adaptStrategies(),
+ initialFocus: ListDetailPaneScaffoldRole = ListDetailPaneScaffoldRole.Detail
+): ListDetailPaneScaffoldState {
+ val focusHistory = rememberSaveable { mutableListOf(initialFocus) }
+ return remember(layoutDirectives, adaptStrategies) {
+ DefaultListDetailPaneScaffoldState(layoutDirectives, adaptStrategies, focusHistory)
+ }
+}
+
+/**
+ * The set of the available pane roles of [ListDetailPaneScaffold].
+ */
+@ExperimentalMaterial3AdaptiveApi
+enum class ListDetailPaneScaffoldRole(internal val threePaneScaffoldRole: ThreePaneScaffoldRole) {
+ /**
+ * The list pane of [ListDetailPaneScaffold]. It is mapped to [ThreePaneScaffoldRole.Secondary].
+ */
+ List(ThreePaneScaffoldRole.Secondary),
+ /**
+ * The detail pane of [ListDetailPaneScaffold]. It is mapped to [ThreePaneScaffoldRole.Primary].
+ */
+ Detail(ThreePaneScaffoldRole.Primary),
+ /**
+ * The extra pane of [ListDetailPaneScaffold]. It is mapped to [ThreePaneScaffoldRole.Tertiary].
+ */
+ Extra(ThreePaneScaffoldRole.Tertiary)
+}
diff --git a/compose/material3/material3-adaptive/src/commonMain/kotlin/androidx/compose/material3/adaptive/AdaptiveLayoutDirective.kt b/compose/material3/material3-adaptive/src/commonMain/kotlin/androidx/compose/material3/adaptive/AdaptiveLayoutDirective.kt
index c7df049..9fc0544 100644
--- a/compose/material3/material3-adaptive/src/commonMain/kotlin/androidx/compose/material3/adaptive/AdaptiveLayoutDirective.kt
+++ b/compose/material3/material3-adaptive/src/commonMain/kotlin/androidx/compose/material3/adaptive/AdaptiveLayoutDirective.kt
@@ -32,12 +32,14 @@
*
* @param windowAdaptiveInfo [WindowAdaptiveInfo] that collects useful information in making
* layout adaptation decisions like [WindowSizeClass].
+ * @param hingePolicy [HingePolicy] that decides how layouts are supposed to address hinges.
* @return an [AdaptiveLayoutDirective] to be used to decide adaptive layout states.
*/
// TODO(b/285144647): Add more details regarding the use scenarios of this function.
@ExperimentalMaterial3AdaptiveApi
fun calculateStandardAdaptiveLayoutDirective(
- windowAdaptiveInfo: WindowAdaptiveInfo
+ windowAdaptiveInfo: WindowAdaptiveInfo,
+ hingePolicy: HingePolicy = HingePolicy.AvoidSeparating
): AdaptiveLayoutDirective {
val maxHorizontalPartitions: Int
val gutterOuterVertical: Dp
@@ -76,7 +78,8 @@
GutterSizes(
gutterOuterVertical, gutterInnerVertical, innerHorizontal = gutterInnerHorizontal
),
- maxVerticalPartitions
+ maxVerticalPartitions,
+ getExcludedBounds(windowAdaptiveInfo.posture, hingePolicy)
)
}
@@ -90,12 +93,14 @@
*
* @param windowAdaptiveInfo [WindowAdaptiveInfo] that collects useful information in making
* layout adaptation decisions like [WindowSizeClass].
+ * @param hingePolicy [HingePolicy] that decides how layouts are supposed to address hinges.
* @return an [AdaptiveLayoutDirective] to be used to decide adaptive layout states.
*/
// TODO(b/285144647): Add more details regarding the use scenarios of this function.
@ExperimentalMaterial3AdaptiveApi
fun calculateDenseAdaptiveLayoutDirective(
- windowAdaptiveInfo: WindowAdaptiveInfo
+ windowAdaptiveInfo: WindowAdaptiveInfo,
+ hingePolicy: HingePolicy = HingePolicy.AvoidSeparating
): AdaptiveLayoutDirective {
val maxHorizontalPartitions: Int
val gutterOuterVertical: Dp
@@ -134,10 +139,21 @@
GutterSizes(
gutterOuterVertical, gutterInnerVertical, innerHorizontal = gutterInnerHorizontal
),
- maxVerticalPartitions
+ maxVerticalPartitions,
+ getExcludedBounds(windowAdaptiveInfo.posture, hingePolicy)
)
}
+@OptIn(ExperimentalMaterial3AdaptiveApi::class)
+private fun getExcludedBounds(posture: Posture, hingePolicy: HingePolicy): List<Rect> {
+ return when (hingePolicy) {
+ HingePolicy.AvoidSeparating -> posture.separatingHingeBounds
+ HingePolicy.AvoidOccluding -> posture.occludingHingeBounds
+ HingePolicy.AlwaysAvoid -> posture.allHingeBounds
+ else -> emptyList()
+ }
+}
+
/**
* Top-level directives about how an adaptive layout should be arranged and spaced, like how many
* partitions the layout can be split into and what should be the gutter size.
@@ -218,3 +234,35 @@
return result
}
}
+
+/** Policies that indicate how hinges are supposed to be addressed in an adaptive layout. */
+@Immutable
+@kotlin.jvm.JvmInline
+value class HingePolicy private constructor(private val value: Int) {
+ override fun toString(): String {
+ return "HingePolicy." + when (this) {
+ AlwaysAvoid -> "AlwaysAvoid"
+ AvoidSeparating -> "AvoidOccludingAndSeparating"
+ AvoidOccluding -> "AvoidOccludingOnly"
+ NeverAvoid -> "NeverAvoid"
+ else -> ""
+ }
+ }
+
+ companion object {
+ /** When rendering content in a layout, always avoid where hinges are. */
+ val AlwaysAvoid = HingePolicy(0)
+ /**
+ * When rendering content in a layout, avoid hinges that are separating. Note that an
+ * occluding hinge is supposed to be separating as well but not vice versa.
+ */
+ val AvoidSeparating = HingePolicy(1)
+ /**
+ * When rendering content in a layout, avoid hinges that are occluding. Note that an
+ * occluding hinge is supposed to be separating as well but not vice versa.
+ */
+ val AvoidOccluding = HingePolicy(2)
+ /** When rendering content in a layout, never avoid any hinges, separating or not. */
+ val NeverAvoid = HingePolicy(3)
+ }
+}
diff --git a/compose/material3/material3-adaptive/src/commonMain/kotlin/androidx/compose/material3/adaptive/Posture.kt b/compose/material3/material3-adaptive/src/commonMain/kotlin/androidx/compose/material3/adaptive/Posture.kt
index 3554e7b..1eb8189 100644
--- a/compose/material3/material3-adaptive/src/commonMain/kotlin/androidx/compose/material3/adaptive/Posture.kt
+++ b/compose/material3/material3-adaptive/src/commonMain/kotlin/androidx/compose/material3/adaptive/Posture.kt
@@ -17,6 +17,7 @@
package androidx.compose.material3.adaptive
import androidx.compose.runtime.Immutable
+import androidx.compose.ui.geometry.Rect
/**
* Posture info that can help make layout adaptation decisions. For example when
@@ -39,13 +40,19 @@
* separating, i.e., content cannot be displayed on the hinge area. When this is `true`
* developer may want to avoid showing anything around the hinge area because the content
* will be cut or not visible.
+ * @param separatingHingeBounds the list of hinge bounds that are separating.
+ * @param occludingHingeBounds the list of hinge bounds that are occluding.
+ * @param allHingeBounds the list of all hinge bounds.
*/
@ExperimentalMaterial3AdaptiveApi
@Immutable
class Posture(
val hasVerticalHinge: Boolean = false,
val isTabletop: Boolean = false,
- val hasSeparatingHinge: Boolean = false
+ val hasSeparatingHinge: Boolean = false,
+ val separatingHingeBounds: List<Rect> = emptyList(),
+ val occludingHingeBounds: List<Rect> = emptyList(),
+ val allHingeBounds: List<Rect> = emptyList()
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
@@ -53,6 +60,9 @@
if (hasVerticalHinge != other.hasVerticalHinge) return false
if (isTabletop != other.isTabletop) return false
if (hasSeparatingHinge != other.hasSeparatingHinge) return false
+ if (separatingHingeBounds != other.separatingHingeBounds) return false
+ if (occludingHingeBounds != other.occludingHingeBounds) return false
+ if (allHingeBounds != other.allHingeBounds) return false
return true
}
@@ -60,6 +70,9 @@
var result = hasVerticalHinge.hashCode()
result = 31 * result + isTabletop.hashCode()
result = 31 * result + hasSeparatingHinge.hashCode()
+ result = 31 * result + separatingHingeBounds.hashCode()
+ result = 31 * result + occludingHingeBounds.hashCode()
+ result = 31 * result + allHingeBounds.hashCode()
return result
}
}
diff --git a/compose/material3/material3-adaptive/src/commonMain/kotlin/androidx/compose/material3/adaptive/ThreePaneScaffold.kt b/compose/material3/material3-adaptive/src/commonMain/kotlin/androidx/compose/material3/adaptive/ThreePaneScaffold.kt
index d4ef1cb..a75073e 100644
--- a/compose/material3/material3-adaptive/src/commonMain/kotlin/androidx/compose/material3/adaptive/ThreePaneScaffold.kt
+++ b/compose/material3/material3-adaptive/src/commonMain/kotlin/androidx/compose/material3/adaptive/ThreePaneScaffold.kt
@@ -251,17 +251,16 @@
}.measuredWidth += allocatableWidth - totalPreferredWidth
} else if (allocatableWidth < totalPreferredWidth) {
// Scale down all panes to fit in the available space.
- val scale = allocatableWidth / totalPreferredWidth
+ val scale = allocatableWidth.toFloat() / totalPreferredWidth
measurables.forEach {
- it.measuredWidth *= scale
+ it.measuredWidth = (it.measuredWidth * scale).toInt()
}
}
- val spacerSizeInt = spacerSize.toInt()
var positionX = partitionBounds.left
measurables.forEach {
it.measure(Constraints.fixed(it.measuredWidth, partitionBounds.height))
.place(positionX, partitionBounds.top)
- positionX += it.measuredWidth + spacerSizeInt
+ positionX += it.measuredWidth + spacerSize
}
}
diff --git a/compose/material3/material3-adaptive/src/test/kotlin/androidx/compose/material3/adaptive/AdaptiveLayoutDirectiveTest.kt b/compose/material3/material3-adaptive/src/test/kotlin/androidx/compose/material3/adaptive/AdaptiveLayoutDirectiveTest.kt
index 6ae0180..63a9db7 100644
--- a/compose/material3/material3-adaptive/src/test/kotlin/androidx/compose/material3/adaptive/AdaptiveLayoutDirectiveTest.kt
+++ b/compose/material3/material3-adaptive/src/test/kotlin/androidx/compose/material3/adaptive/AdaptiveLayoutDirectiveTest.kt
@@ -18,6 +18,7 @@
import androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi
import androidx.compose.material3.windowsizeclass.WindowSizeClass
+import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
import com.google.common.truth.Truth.assertThat
@@ -163,4 +164,154 @@
assertThat(layoutDirective.gutterSizes.outerHorizontal).isEqualTo(24.dp)
assertThat(layoutDirective.gutterSizes.innerHorizontal).isEqualTo(24.dp)
}
+
+ @Test
+ fun test_calculateStandardAdaptiveLayoutDirective_alwaysAvoidHinge() {
+ val occludingHingeBounds = listOf(
+ Rect(0F, 0F, 1F, 1F),
+ Rect(1F, 1F, 2F, 2F),
+ )
+ val allHingeBounds = listOf(
+ Rect(0F, 0F, 1F, 1F),
+ Rect(1F, 1F, 2F, 2F),
+ Rect(2F, 2F, 3F, 3F)
+ )
+ val layoutDirective = calculateStandardAdaptiveLayoutDirective(
+ WindowAdaptiveInfo(
+ WindowSizeClass.calculateFromSize(DpSize(700.dp, 800.dp)),
+ Posture(
+ allHingeBounds = allHingeBounds,
+ occludingHingeBounds = occludingHingeBounds
+ )
+ ),
+ HingePolicy.AlwaysAvoid
+ )
+
+ assertThat(layoutDirective.excludedBounds).isEqualTo(allHingeBounds)
+ }
+
+ @Test
+ fun test_calculateStandardAdaptiveLayoutDirective_avoidOccludingHinge() {
+ val occludingHingeBounds = listOf(
+ Rect(0F, 0F, 1F, 1F),
+ Rect(1F, 1F, 2F, 2F),
+ )
+ val allHingeBounds = listOf(
+ Rect(0F, 0F, 1F, 1F),
+ Rect(1F, 1F, 2F, 2F),
+ Rect(2F, 2F, 3F, 3F)
+ )
+ val layoutDirective = calculateStandardAdaptiveLayoutDirective(
+ WindowAdaptiveInfo(
+ WindowSizeClass.calculateFromSize(DpSize(700.dp, 800.dp)),
+ Posture(
+ allHingeBounds = allHingeBounds,
+ occludingHingeBounds = occludingHingeBounds
+ )
+ ),
+ HingePolicy.AvoidOccluding
+ )
+
+ assertThat(layoutDirective.excludedBounds).isEqualTo(occludingHingeBounds)
+ }
+
+ @Test
+ fun test_calculateStandardAdaptiveLayoutDirective_neverAvoidHinge() {
+ val occludingHingeBounds = listOf(
+ Rect(0F, 0F, 1F, 1F),
+ Rect(1F, 1F, 2F, 2F),
+ )
+ val allHingeBounds = listOf(
+ Rect(0F, 0F, 1F, 1F),
+ Rect(1F, 1F, 2F, 2F),
+ Rect(2F, 2F, 3F, 3F)
+ )
+ val layoutDirective = calculateStandardAdaptiveLayoutDirective(
+ WindowAdaptiveInfo(
+ WindowSizeClass.calculateFromSize(DpSize(700.dp, 800.dp)),
+ Posture(
+ allHingeBounds = allHingeBounds,
+ occludingHingeBounds = occludingHingeBounds
+ )
+ ),
+ HingePolicy.NeverAvoid
+ )
+
+ assertThat(layoutDirective.excludedBounds).isEmpty()
+ }
+
+ @Test
+ fun test_calculateDenseAdaptiveLayoutDirective_alwaysAvoidHinge() {
+ val occludingHingeBounds = listOf(
+ Rect(0F, 0F, 1F, 1F),
+ Rect(1F, 1F, 2F, 2F),
+ )
+ val allHingeBounds = listOf(
+ Rect(0F, 0F, 1F, 1F),
+ Rect(1F, 1F, 2F, 2F),
+ Rect(2F, 2F, 3F, 3F)
+ )
+ val layoutDirective = calculateDenseAdaptiveLayoutDirective(
+ WindowAdaptiveInfo(
+ WindowSizeClass.calculateFromSize(DpSize(700.dp, 800.dp)),
+ Posture(
+ allHingeBounds = allHingeBounds,
+ occludingHingeBounds = occludingHingeBounds
+ )
+ ),
+ HingePolicy.AlwaysAvoid
+ )
+
+ assertThat(layoutDirective.excludedBounds).isEqualTo(allHingeBounds)
+ }
+
+ @Test
+ fun test_calculateDenseAdaptiveLayoutDirective_avoidOccludingHinge() {
+ val occludingHingeBounds = listOf(
+ Rect(0F, 0F, 1F, 1F),
+ Rect(1F, 1F, 2F, 2F),
+ )
+ val allHingeBounds = listOf(
+ Rect(0F, 0F, 1F, 1F),
+ Rect(1F, 1F, 2F, 2F),
+ Rect(2F, 2F, 3F, 3F)
+ )
+ val layoutDirective = calculateDenseAdaptiveLayoutDirective(
+ WindowAdaptiveInfo(
+ WindowSizeClass.calculateFromSize(DpSize(700.dp, 800.dp)),
+ Posture(
+ allHingeBounds = allHingeBounds,
+ occludingHingeBounds = occludingHingeBounds
+ )
+ ),
+ HingePolicy.AvoidOccluding
+ )
+
+ assertThat(layoutDirective.excludedBounds).isEqualTo(occludingHingeBounds)
+ }
+
+ @Test
+ fun test_calculateDenseAdaptiveLayoutDirective_neverAvoidHinge() {
+ val occludingHingeBounds = listOf(
+ Rect(0F, 0F, 1F, 1F),
+ Rect(1F, 1F, 2F, 2F),
+ )
+ val allHingeBounds = listOf(
+ Rect(0F, 0F, 1F, 1F),
+ Rect(1F, 1F, 2F, 2F),
+ Rect(2F, 2F, 3F, 3F)
+ )
+ val layoutDirective = calculateDenseAdaptiveLayoutDirective(
+ WindowAdaptiveInfo(
+ WindowSizeClass.calculateFromSize(DpSize(700.dp, 800.dp)),
+ Posture(
+ allHingeBounds = allHingeBounds,
+ occludingHingeBounds = occludingHingeBounds
+ )
+ ),
+ HingePolicy.NeverAvoid
+ )
+
+ assertThat(layoutDirective.excludedBounds).isEmpty()
+ }
}
diff --git a/compose/material3/material3/api/current.txt b/compose/material3/material3/api/current.txt
index 227827f..310ebce 100644
--- a/compose/material3/material3/api/current.txt
+++ b/compose/material3/material3/api/current.txt
@@ -225,7 +225,7 @@
}
@androidx.compose.runtime.Immutable public final class CheckboxColors {
- ctor public CheckboxColors(long checkedCheckmarkColor, long uncheckedCheckmarkColor, long checkedBoxColor, long uncheckedBoxColor, long disabledCheckedBoxColor, long disabledUncheckedBoxColor, long disabledIndeterminateBoxColor, long checkedBorderColor, long uncheckedBorderColor, long disabledBorderColor, long disabledIndeterminateBorderColor);
+ ctor public CheckboxColors(long checkedCheckmarkColor, long uncheckedCheckmarkColor, long checkedBoxColor, long uncheckedBoxColor, long disabledCheckedBoxColor, long disabledUncheckedBoxColor, long disabledIndeterminateBoxColor, long checkedBorderColor, long uncheckedBorderColor, long disabledBorderColor, long disabledUncheckedBorderColor, long disabledIndeterminateBorderColor);
method public long getCheckedBorderColor();
method public long getCheckedBoxColor();
method public long getCheckedCheckmarkColor();
@@ -233,6 +233,7 @@
method public long getDisabledCheckedBoxColor();
method public long getDisabledIndeterminateBorderColor();
method public long getDisabledIndeterminateBoxColor();
+ method public long getDisabledUncheckedBorderColor();
method public long getDisabledUncheckedBoxColor();
method public long getUncheckedBorderColor();
method public long getUncheckedBoxColor();
@@ -244,6 +245,7 @@
property public final long disabledCheckedBoxColor;
property public final long disabledIndeterminateBorderColor;
property public final long disabledIndeterminateBoxColor;
+ property public final long disabledUncheckedBorderColor;
property public final long disabledUncheckedBoxColor;
property public final long uncheckedBorderColor;
property public final long uncheckedBoxColor;
@@ -530,7 +532,7 @@
}
@SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public final class DismissState {
- ctor public DismissState(androidx.compose.material3.DismissValue initialValue, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.DismissValue,java.lang.Boolean> confirmValueChange, optional kotlin.jvm.functions.Function2<? super androidx.compose.ui.unit.Density,? super java.lang.Float,java.lang.Float> positionalThreshold);
+ ctor @Deprecated public DismissState(androidx.compose.material3.DismissValue initialValue, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.DismissValue,java.lang.Boolean> confirmValueChange, kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Float> positionalThreshold);
method public suspend Object? dismiss(androidx.compose.material3.DismissDirection direction, kotlin.coroutines.Continuation<? super kotlin.Unit>);
method public androidx.compose.material3.DismissValue getCurrentValue();
method public androidx.compose.material3.DismissDirection? getDismissDirection();
@@ -548,7 +550,8 @@
}
public static final class DismissState.Companion {
- method public androidx.compose.runtime.saveable.Saver<androidx.compose.material3.DismissState,androidx.compose.material3.DismissValue> Saver(kotlin.jvm.functions.Function1<? super androidx.compose.material3.DismissValue,java.lang.Boolean> confirmValueChange, kotlin.jvm.functions.Function2<? super androidx.compose.ui.unit.Density,? super java.lang.Float,java.lang.Float> positionalThreshold);
+ method @Deprecated public androidx.compose.runtime.saveable.Saver<androidx.compose.material3.DismissState,androidx.compose.material3.DismissValue> Saver(kotlin.jvm.functions.Function1<? super androidx.compose.material3.DismissValue,java.lang.Boolean> confirmValueChange, kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Float> positionalThreshold);
+ method public androidx.compose.runtime.saveable.Saver<androidx.compose.material3.DismissState,androidx.compose.material3.DismissValue> Saver(kotlin.jvm.functions.Function1<? super androidx.compose.material3.DismissValue,java.lang.Boolean> confirmValueChange, kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Float> positionalThreshold, androidx.compose.ui.unit.Density density);
}
@SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public enum DismissValue {
@@ -1094,7 +1097,7 @@
}
@SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Stable public final class RangeSliderState {
- ctor public RangeSliderState(optional float initialActiveRangeStart, optional float initialActiveRangeEnd, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.FloatRange,kotlin.Unit>? initialOnValueChange, optional int steps, optional kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> valueRange, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onValueChangeFinished);
+ ctor public RangeSliderState(optional float initialActiveRangeStart, optional float initialActiveRangeEnd, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.FloatRange,kotlin.Unit>? initialOnValueChange, optional @IntRange(from=0L) int steps, optional kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> valueRange, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onValueChangeFinished);
method public float getActiveRangeEnd();
method public float getActiveRangeStart();
method public kotlin.jvm.functions.Function0<kotlin.Unit>? getOnValueChangeFinished();
@@ -1286,8 +1289,12 @@
property public final androidx.compose.foundation.shape.CornerBasedShape small;
}
+ public final class SheetDefaultsKt {
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public static androidx.compose.material3.SheetState SheetState(boolean skipPartiallyExpanded, androidx.compose.ui.unit.Density density, optional androidx.compose.material3.SheetValue initialValue, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.SheetValue,java.lang.Boolean> confirmValueChange, optional boolean skipHiddenState);
+ }
+
@SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Stable public final class SheetState {
- ctor public SheetState(boolean skipPartiallyExpanded, optional androidx.compose.material3.SheetValue initialValue, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.SheetValue,java.lang.Boolean> confirmValueChange, optional boolean skipHiddenState);
+ ctor @Deprecated public SheetState(boolean skipPartiallyExpanded, optional androidx.compose.material3.SheetValue initialValue, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.SheetValue,java.lang.Boolean> confirmValueChange, optional boolean skipHiddenState);
method public suspend Object? expand(kotlin.coroutines.Continuation<? super kotlin.Unit>);
method public androidx.compose.material3.SheetValue getCurrentValue();
method public boolean getHasExpandedState();
@@ -1307,7 +1314,8 @@
}
public static final class SheetState.Companion {
- method public androidx.compose.runtime.saveable.Saver<androidx.compose.material3.SheetState,androidx.compose.material3.SheetValue> Saver(boolean skipPartiallyExpanded, kotlin.jvm.functions.Function1<? super androidx.compose.material3.SheetValue,java.lang.Boolean> confirmValueChange);
+ method @Deprecated public androidx.compose.runtime.saveable.Saver<androidx.compose.material3.SheetState,androidx.compose.material3.SheetValue> Saver(boolean skipPartiallyExpanded, kotlin.jvm.functions.Function1<? super androidx.compose.material3.SheetValue,java.lang.Boolean> confirmValueChange);
+ method public androidx.compose.runtime.saveable.Saver<androidx.compose.material3.SheetState,androidx.compose.material3.SheetValue> Saver(boolean skipPartiallyExpanded, kotlin.jvm.functions.Function1<? super androidx.compose.material3.SheetValue,java.lang.Boolean> confirmValueChange, androidx.compose.ui.unit.Density density);
}
@SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public enum SheetValue {
@@ -1356,11 +1364,11 @@
public final class SliderKt {
method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void RangeSlider(androidx.compose.material3.RangeSliderState state, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.material3.SliderColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource startInteractionSource, optional androidx.compose.foundation.interaction.MutableInteractionSource endInteractionSource, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.RangeSliderState,kotlin.Unit> startThumb, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.RangeSliderState,kotlin.Unit> endThumb, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.RangeSliderState,kotlin.Unit> track);
- method @androidx.compose.runtime.Composable public static void RangeSlider(kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> value, kotlin.jvm.functions.Function1<? super kotlin.ranges.ClosedFloatingPointRange<java.lang.Float>,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> valueRange, optional int steps, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onValueChangeFinished, optional androidx.compose.material3.SliderColors colors);
- method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void RangeSlider(long value, kotlin.jvm.functions.Function1<? super androidx.compose.material3.FloatRange,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> valueRange, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onValueChangeFinished, optional androidx.compose.material3.SliderColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource startInteractionSource, optional androidx.compose.foundation.interaction.MutableInteractionSource endInteractionSource, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.RangeSliderState,kotlin.Unit> startThumb, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.RangeSliderState,kotlin.Unit> endThumb, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.RangeSliderState,kotlin.Unit> track, optional int steps);
+ method @androidx.compose.runtime.Composable public static void RangeSlider(kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> value, kotlin.jvm.functions.Function1<? super kotlin.ranges.ClosedFloatingPointRange<java.lang.Float>,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> valueRange, optional @IntRange(from=0L) int steps, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onValueChangeFinished, optional androidx.compose.material3.SliderColors colors);
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void RangeSlider(long value, kotlin.jvm.functions.Function1<? super androidx.compose.material3.FloatRange,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> valueRange, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onValueChangeFinished, optional androidx.compose.material3.SliderColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource startInteractionSource, optional androidx.compose.foundation.interaction.MutableInteractionSource endInteractionSource, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.RangeSliderState,kotlin.Unit> startThumb, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.RangeSliderState,kotlin.Unit> endThumb, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.RangeSliderState,kotlin.Unit> track, optional @IntRange(from=0L) int steps);
method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void Slider(androidx.compose.material3.SliderState state, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.material3.SliderColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.SliderState,kotlin.Unit> thumb, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.SliderState,kotlin.Unit> track);
- method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void Slider(float value, kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onValueChangeFinished, optional androidx.compose.material3.SliderColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional int steps, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.SliderState,kotlin.Unit> thumb, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.SliderState,kotlin.Unit> track, optional kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> valueRange);
- method @androidx.compose.runtime.Composable public static void Slider(float value, kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> valueRange, optional int steps, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onValueChangeFinished, optional androidx.compose.material3.SliderColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource);
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void Slider(float value, kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onValueChangeFinished, optional androidx.compose.material3.SliderColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional @IntRange(from=0L) int steps, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.SliderState,kotlin.Unit> thumb, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.SliderState,kotlin.Unit> track, optional kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> valueRange);
+ method @androidx.compose.runtime.Composable public static void Slider(float value, kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> valueRange, optional @IntRange(from=0L) int steps, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onValueChangeFinished, optional androidx.compose.material3.SliderColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource);
}
@androidx.compose.runtime.Stable public final class SliderPositions {
@@ -1372,7 +1380,7 @@
}
@SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Stable public final class SliderState {
- ctor public SliderState(optional float initialValue, optional kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit>? initialOnValueChange, optional int steps, optional kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> valueRange, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onValueChangeFinished);
+ ctor public SliderState(optional float initialValue, optional kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit>? initialOnValueChange, optional @IntRange(from=0L) int steps, optional kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> valueRange, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onValueChangeFinished);
method public kotlin.jvm.functions.Function0<kotlin.Unit>? getOnValueChangeFinished();
method public int getSteps();
method public float getValue();
@@ -1476,14 +1484,15 @@
}
@SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public final class SwipeToDismissDefaults {
- method public kotlin.jvm.functions.Function2<androidx.compose.ui.unit.Density,java.lang.Float,java.lang.Float> getFixedPositionalThreshold();
- property public final kotlin.jvm.functions.Function2<androidx.compose.ui.unit.Density,java.lang.Float,java.lang.Float> FixedPositionalThreshold;
+ method @androidx.compose.runtime.Composable public kotlin.jvm.functions.Function1<java.lang.Float,java.lang.Float> getFixedPositionalThreshold();
+ property @androidx.compose.runtime.Composable public final kotlin.jvm.functions.Function1<java.lang.Float,java.lang.Float> fixedPositionalThreshold;
field public static final androidx.compose.material3.SwipeToDismissDefaults INSTANCE;
}
public final class SwipeToDismissKt {
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public static androidx.compose.material3.DismissState DismissState(androidx.compose.material3.DismissValue initialValue, androidx.compose.ui.unit.Density density, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.DismissValue,java.lang.Boolean> confirmValueChange, kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Float> positionalThreshold);
method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void SwipeToDismiss(androidx.compose.material3.DismissState state, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> background, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> dismissContent, optional androidx.compose.ui.Modifier modifier, optional java.util.Set<? extends androidx.compose.material3.DismissDirection> directions);
- method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static androidx.compose.material3.DismissState rememberDismissState(optional androidx.compose.material3.DismissValue initialValue, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.DismissValue,java.lang.Boolean> confirmValueChange, optional kotlin.jvm.functions.Function2<? super androidx.compose.ui.unit.Density,? super java.lang.Float,java.lang.Float> positionalThreshold);
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static androidx.compose.material3.DismissState rememberDismissState(optional androidx.compose.material3.DismissValue initialValue, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.DismissValue,java.lang.Boolean> confirmValueChange, optional kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Float> positionalThreshold);
}
@androidx.compose.runtime.Immutable public final class SwitchColors {
diff --git a/compose/material3/material3/api/restricted_current.txt b/compose/material3/material3/api/restricted_current.txt
index 227827f..310ebce 100644
--- a/compose/material3/material3/api/restricted_current.txt
+++ b/compose/material3/material3/api/restricted_current.txt
@@ -225,7 +225,7 @@
}
@androidx.compose.runtime.Immutable public final class CheckboxColors {
- ctor public CheckboxColors(long checkedCheckmarkColor, long uncheckedCheckmarkColor, long checkedBoxColor, long uncheckedBoxColor, long disabledCheckedBoxColor, long disabledUncheckedBoxColor, long disabledIndeterminateBoxColor, long checkedBorderColor, long uncheckedBorderColor, long disabledBorderColor, long disabledIndeterminateBorderColor);
+ ctor public CheckboxColors(long checkedCheckmarkColor, long uncheckedCheckmarkColor, long checkedBoxColor, long uncheckedBoxColor, long disabledCheckedBoxColor, long disabledUncheckedBoxColor, long disabledIndeterminateBoxColor, long checkedBorderColor, long uncheckedBorderColor, long disabledBorderColor, long disabledUncheckedBorderColor, long disabledIndeterminateBorderColor);
method public long getCheckedBorderColor();
method public long getCheckedBoxColor();
method public long getCheckedCheckmarkColor();
@@ -233,6 +233,7 @@
method public long getDisabledCheckedBoxColor();
method public long getDisabledIndeterminateBorderColor();
method public long getDisabledIndeterminateBoxColor();
+ method public long getDisabledUncheckedBorderColor();
method public long getDisabledUncheckedBoxColor();
method public long getUncheckedBorderColor();
method public long getUncheckedBoxColor();
@@ -244,6 +245,7 @@
property public final long disabledCheckedBoxColor;
property public final long disabledIndeterminateBorderColor;
property public final long disabledIndeterminateBoxColor;
+ property public final long disabledUncheckedBorderColor;
property public final long disabledUncheckedBoxColor;
property public final long uncheckedBorderColor;
property public final long uncheckedBoxColor;
@@ -530,7 +532,7 @@
}
@SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public final class DismissState {
- ctor public DismissState(androidx.compose.material3.DismissValue initialValue, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.DismissValue,java.lang.Boolean> confirmValueChange, optional kotlin.jvm.functions.Function2<? super androidx.compose.ui.unit.Density,? super java.lang.Float,java.lang.Float> positionalThreshold);
+ ctor @Deprecated public DismissState(androidx.compose.material3.DismissValue initialValue, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.DismissValue,java.lang.Boolean> confirmValueChange, kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Float> positionalThreshold);
method public suspend Object? dismiss(androidx.compose.material3.DismissDirection direction, kotlin.coroutines.Continuation<? super kotlin.Unit>);
method public androidx.compose.material3.DismissValue getCurrentValue();
method public androidx.compose.material3.DismissDirection? getDismissDirection();
@@ -548,7 +550,8 @@
}
public static final class DismissState.Companion {
- method public androidx.compose.runtime.saveable.Saver<androidx.compose.material3.DismissState,androidx.compose.material3.DismissValue> Saver(kotlin.jvm.functions.Function1<? super androidx.compose.material3.DismissValue,java.lang.Boolean> confirmValueChange, kotlin.jvm.functions.Function2<? super androidx.compose.ui.unit.Density,? super java.lang.Float,java.lang.Float> positionalThreshold);
+ method @Deprecated public androidx.compose.runtime.saveable.Saver<androidx.compose.material3.DismissState,androidx.compose.material3.DismissValue> Saver(kotlin.jvm.functions.Function1<? super androidx.compose.material3.DismissValue,java.lang.Boolean> confirmValueChange, kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Float> positionalThreshold);
+ method public androidx.compose.runtime.saveable.Saver<androidx.compose.material3.DismissState,androidx.compose.material3.DismissValue> Saver(kotlin.jvm.functions.Function1<? super androidx.compose.material3.DismissValue,java.lang.Boolean> confirmValueChange, kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Float> positionalThreshold, androidx.compose.ui.unit.Density density);
}
@SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public enum DismissValue {
@@ -1094,7 +1097,7 @@
}
@SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Stable public final class RangeSliderState {
- ctor public RangeSliderState(optional float initialActiveRangeStart, optional float initialActiveRangeEnd, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.FloatRange,kotlin.Unit>? initialOnValueChange, optional int steps, optional kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> valueRange, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onValueChangeFinished);
+ ctor public RangeSliderState(optional float initialActiveRangeStart, optional float initialActiveRangeEnd, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.FloatRange,kotlin.Unit>? initialOnValueChange, optional @IntRange(from=0L) int steps, optional kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> valueRange, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onValueChangeFinished);
method public float getActiveRangeEnd();
method public float getActiveRangeStart();
method public kotlin.jvm.functions.Function0<kotlin.Unit>? getOnValueChangeFinished();
@@ -1286,8 +1289,12 @@
property public final androidx.compose.foundation.shape.CornerBasedShape small;
}
+ public final class SheetDefaultsKt {
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public static androidx.compose.material3.SheetState SheetState(boolean skipPartiallyExpanded, androidx.compose.ui.unit.Density density, optional androidx.compose.material3.SheetValue initialValue, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.SheetValue,java.lang.Boolean> confirmValueChange, optional boolean skipHiddenState);
+ }
+
@SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Stable public final class SheetState {
- ctor public SheetState(boolean skipPartiallyExpanded, optional androidx.compose.material3.SheetValue initialValue, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.SheetValue,java.lang.Boolean> confirmValueChange, optional boolean skipHiddenState);
+ ctor @Deprecated public SheetState(boolean skipPartiallyExpanded, optional androidx.compose.material3.SheetValue initialValue, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.SheetValue,java.lang.Boolean> confirmValueChange, optional boolean skipHiddenState);
method public suspend Object? expand(kotlin.coroutines.Continuation<? super kotlin.Unit>);
method public androidx.compose.material3.SheetValue getCurrentValue();
method public boolean getHasExpandedState();
@@ -1307,7 +1314,8 @@
}
public static final class SheetState.Companion {
- method public androidx.compose.runtime.saveable.Saver<androidx.compose.material3.SheetState,androidx.compose.material3.SheetValue> Saver(boolean skipPartiallyExpanded, kotlin.jvm.functions.Function1<? super androidx.compose.material3.SheetValue,java.lang.Boolean> confirmValueChange);
+ method @Deprecated public androidx.compose.runtime.saveable.Saver<androidx.compose.material3.SheetState,androidx.compose.material3.SheetValue> Saver(boolean skipPartiallyExpanded, kotlin.jvm.functions.Function1<? super androidx.compose.material3.SheetValue,java.lang.Boolean> confirmValueChange);
+ method public androidx.compose.runtime.saveable.Saver<androidx.compose.material3.SheetState,androidx.compose.material3.SheetValue> Saver(boolean skipPartiallyExpanded, kotlin.jvm.functions.Function1<? super androidx.compose.material3.SheetValue,java.lang.Boolean> confirmValueChange, androidx.compose.ui.unit.Density density);
}
@SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public enum SheetValue {
@@ -1356,11 +1364,11 @@
public final class SliderKt {
method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void RangeSlider(androidx.compose.material3.RangeSliderState state, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.material3.SliderColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource startInteractionSource, optional androidx.compose.foundation.interaction.MutableInteractionSource endInteractionSource, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.RangeSliderState,kotlin.Unit> startThumb, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.RangeSliderState,kotlin.Unit> endThumb, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.RangeSliderState,kotlin.Unit> track);
- method @androidx.compose.runtime.Composable public static void RangeSlider(kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> value, kotlin.jvm.functions.Function1<? super kotlin.ranges.ClosedFloatingPointRange<java.lang.Float>,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> valueRange, optional int steps, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onValueChangeFinished, optional androidx.compose.material3.SliderColors colors);
- method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void RangeSlider(long value, kotlin.jvm.functions.Function1<? super androidx.compose.material3.FloatRange,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> valueRange, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onValueChangeFinished, optional androidx.compose.material3.SliderColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource startInteractionSource, optional androidx.compose.foundation.interaction.MutableInteractionSource endInteractionSource, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.RangeSliderState,kotlin.Unit> startThumb, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.RangeSliderState,kotlin.Unit> endThumb, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.RangeSliderState,kotlin.Unit> track, optional int steps);
+ method @androidx.compose.runtime.Composable public static void RangeSlider(kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> value, kotlin.jvm.functions.Function1<? super kotlin.ranges.ClosedFloatingPointRange<java.lang.Float>,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> valueRange, optional @IntRange(from=0L) int steps, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onValueChangeFinished, optional androidx.compose.material3.SliderColors colors);
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void RangeSlider(long value, kotlin.jvm.functions.Function1<? super androidx.compose.material3.FloatRange,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> valueRange, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onValueChangeFinished, optional androidx.compose.material3.SliderColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource startInteractionSource, optional androidx.compose.foundation.interaction.MutableInteractionSource endInteractionSource, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.RangeSliderState,kotlin.Unit> startThumb, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.RangeSliderState,kotlin.Unit> endThumb, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.RangeSliderState,kotlin.Unit> track, optional @IntRange(from=0L) int steps);
method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void Slider(androidx.compose.material3.SliderState state, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.material3.SliderColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.SliderState,kotlin.Unit> thumb, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.SliderState,kotlin.Unit> track);
- method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void Slider(float value, kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onValueChangeFinished, optional androidx.compose.material3.SliderColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional int steps, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.SliderState,kotlin.Unit> thumb, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.SliderState,kotlin.Unit> track, optional kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> valueRange);
- method @androidx.compose.runtime.Composable public static void Slider(float value, kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> valueRange, optional int steps, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onValueChangeFinished, optional androidx.compose.material3.SliderColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource);
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void Slider(float value, kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onValueChangeFinished, optional androidx.compose.material3.SliderColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional @IntRange(from=0L) int steps, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.SliderState,kotlin.Unit> thumb, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.SliderState,kotlin.Unit> track, optional kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> valueRange);
+ method @androidx.compose.runtime.Composable public static void Slider(float value, kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> valueRange, optional @IntRange(from=0L) int steps, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onValueChangeFinished, optional androidx.compose.material3.SliderColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource);
}
@androidx.compose.runtime.Stable public final class SliderPositions {
@@ -1372,7 +1380,7 @@
}
@SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Stable public final class SliderState {
- ctor public SliderState(optional float initialValue, optional kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit>? initialOnValueChange, optional int steps, optional kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> valueRange, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onValueChangeFinished);
+ ctor public SliderState(optional float initialValue, optional kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit>? initialOnValueChange, optional @IntRange(from=0L) int steps, optional kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> valueRange, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onValueChangeFinished);
method public kotlin.jvm.functions.Function0<kotlin.Unit>? getOnValueChangeFinished();
method public int getSteps();
method public float getValue();
@@ -1476,14 +1484,15 @@
}
@SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public final class SwipeToDismissDefaults {
- method public kotlin.jvm.functions.Function2<androidx.compose.ui.unit.Density,java.lang.Float,java.lang.Float> getFixedPositionalThreshold();
- property public final kotlin.jvm.functions.Function2<androidx.compose.ui.unit.Density,java.lang.Float,java.lang.Float> FixedPositionalThreshold;
+ method @androidx.compose.runtime.Composable public kotlin.jvm.functions.Function1<java.lang.Float,java.lang.Float> getFixedPositionalThreshold();
+ property @androidx.compose.runtime.Composable public final kotlin.jvm.functions.Function1<java.lang.Float,java.lang.Float> fixedPositionalThreshold;
field public static final androidx.compose.material3.SwipeToDismissDefaults INSTANCE;
}
public final class SwipeToDismissKt {
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public static androidx.compose.material3.DismissState DismissState(androidx.compose.material3.DismissValue initialValue, androidx.compose.ui.unit.Density density, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.DismissValue,java.lang.Boolean> confirmValueChange, kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Float> positionalThreshold);
method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void SwipeToDismiss(androidx.compose.material3.DismissState state, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> background, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> dismissContent, optional androidx.compose.ui.Modifier modifier, optional java.util.Set<? extends androidx.compose.material3.DismissDirection> directions);
- method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static androidx.compose.material3.DismissState rememberDismissState(optional androidx.compose.material3.DismissValue initialValue, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.DismissValue,java.lang.Boolean> confirmValueChange, optional kotlin.jvm.functions.Function2<? super androidx.compose.ui.unit.Density,? super java.lang.Float,java.lang.Float> positionalThreshold);
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static androidx.compose.material3.DismissState rememberDismissState(optional androidx.compose.material3.DismissValue initialValue, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.DismissValue,java.lang.Boolean> confirmValueChange, optional kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Float> positionalThreshold);
}
@androidx.compose.runtime.Immutable public final class SwitchColors {
diff --git a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/TimePickerSamples.kt b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/TimePickerSamples.kt
index 3a38e5e..5ee9ff0f 100644
--- a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/TimePickerSamples.kt
+++ b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/TimePickerSamples.kt
@@ -23,11 +23,9 @@
import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.fillMaxSize
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.material.icons.Icons
import androidx.compose.material.icons.outlined.Keyboard
@@ -54,13 +52,10 @@
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalConfiguration
-import androidx.compose.ui.semantics.isContainer
-import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
-import androidx.compose.ui.zIndex
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Locale
@@ -81,7 +76,9 @@
Button(
modifier = Modifier.align(Alignment.Center),
onClick = { showTimePicker = true }
- ) { Text("Set Time") }
+ ) {
+ Text("Set Time")
+ }
SnackbarHost(hostState = snackState)
}
@@ -119,7 +116,9 @@
Button(
modifier = Modifier.align(Alignment.Center),
onClick = { showTimePicker = true }
- ) { Text("Set Time") }
+ ) {
+ Text("Set Time")
+ }
SnackbarHost(hostState = snackState)
}
@@ -159,13 +158,19 @@
Button(
modifier = Modifier.align(Alignment.Center),
onClick = { showTimePicker = true }
- ) { Text("Set Time") }
+ ) {
+ Text("Set Time")
+ }
SnackbarHost(hostState = snackState)
}
if (showTimePicker) {
TimePickerDialog(
- title = if (showingPicker.value) { "Select Time " } else { "Enter Time" },
+ title = if (showingPicker.value) {
+ "Select Time "
+ } else {
+ "Enter Time"
+ },
onCancel = { showTimePicker = false },
onConfirm = {
val cal = Calendar.getInstance()
@@ -179,40 +184,20 @@
},
toggle = {
if (configuration.screenHeightDp > 400) {
- // Make this take the entire viewport. This will guarantee that Screen readers
- // focus the toggle first.
- Box(
- Modifier
- .fillMaxSize()
- .semantics {
- @Suppress("DEPRECATION")
- isContainer = true
+ IconButton(onClick = { showingPicker.value = !showingPicker.value }) {
+ val icon = if (showingPicker.value) {
+ Icons.Outlined.Keyboard
+ } else {
+ Icons.Outlined.Schedule
}
- ) {
- IconButton(
- modifier = Modifier
- // This is a workaround so that the Icon comes up first
- // in the talkback traversal order. So that users of a11y
- // services can use the text input. When talkback traversal
- // order is customizable we can remove this.
- .size(64.dp, 72.dp)
- .align(Alignment.BottomStart)
- .zIndex(5f),
- onClick = { showingPicker.value = !showingPicker.value }) {
- val icon = if (showingPicker.value) {
- Icons.Outlined.Keyboard
+ Icon(
+ icon,
+ contentDescription = if (showingPicker.value) {
+ "Switch to Text Input"
} else {
- Icons.Outlined.Schedule
+ "Switch to Touch Input"
}
- Icon(
- icon,
- contentDescription = if (showingPicker.value) {
- "Switch to Text Input"
- } else {
- "Switch to Touch Input"
- }
- )
- }
+ )
}
}
}
@@ -236,9 +221,7 @@
) {
Dialog(
onDismissRequest = onCancel,
- properties = DialogProperties(
- usePlatformDefaultWidth = false
- ),
+ properties = DialogProperties(usePlatformDefaultWidth = false),
) {
Surface(
shape = MaterialTheme.shapes.extraLarge,
@@ -251,7 +234,6 @@
color = MaterialTheme.colorScheme.surface
),
) {
- toggle()
Column(
modifier = Modifier.padding(24.dp),
horizontalAlignment = Alignment.CenterHorizontally
@@ -264,18 +246,18 @@
style = MaterialTheme.typography.labelMedium
)
content()
- Row(
- modifier = Modifier
- .height(40.dp)
- .fillMaxWidth()
+ Row(modifier = Modifier
+ .height(40.dp)
+ .fillMaxWidth()
) {
+ toggle()
Spacer(modifier = Modifier.weight(1f))
- TextButton(
- onClick = onCancel
- ) { Text("Cancel") }
- TextButton(
- onClick = onConfirm
- ) { Text("OK") }
+ TextButton(onClick = onCancel) {
+ Text("Cancel")
+ }
+ TextButton(onClick = onConfirm) {
+ Text("OK")
+ }
}
}
}
diff --git a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/BottomSheetScaffoldTest.kt b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/BottomSheetScaffoldTest.kt
index 71aa2de..6735b7d 100644
--- a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/BottomSheetScaffoldTest.kt
+++ b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/BottomSheetScaffoldTest.kt
@@ -253,6 +253,7 @@
skipPartiallyExpanded = false,
skipHiddenState = true,
initialValue = SheetValue.PartiallyExpanded,
+ density = rule.density
)
rule.setContent {
scope = rememberCoroutineScope()
diff --git a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/CheckboxScreenshotTest.kt b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/CheckboxScreenshotTest.kt
index 07f862f..a09e2a3 100644
--- a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/CheckboxScreenshotTest.kt
+++ b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/CheckboxScreenshotTest.kt
@@ -16,8 +16,11 @@
package androidx.compose.material3
import android.os.Build
+import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.wrapContentSize
+import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.testutils.assertAgainstGolden
import androidx.compose.ui.Alignment
@@ -25,6 +28,7 @@
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
+import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.InputMode
import androidx.compose.ui.input.InputModeManager
import androidx.compose.ui.platform.LocalInputModeManager
@@ -37,6 +41,7 @@
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.performMouseInput
import androidx.compose.ui.test.performTouchInput
+import androidx.compose.ui.unit.dp
import androidx.test.filters.MediumTest
import androidx.test.filters.SdkSuppress
import androidx.test.screenshot.AndroidXScreenshotTestRule
@@ -187,7 +192,7 @@
rule.mainClock.advanceTimeByFrame()
rule.waitForIdle() // Wait for measure
- rule.mainClock.advanceTimeBy(milliseconds = 80)
+ rule.mainClock.advanceTimeBy(milliseconds = 100)
assertToggeableAgainstGolden("checkBox_${scheme.name}_unchecked_animateToChecked")
}
@@ -216,7 +221,7 @@
rule.mainClock.advanceTimeByFrame()
rule.waitForIdle() // Wait for measure
- rule.mainClock.advanceTimeBy(milliseconds = 80)
+ rule.mainClock.advanceTimeBy(milliseconds = 100)
assertToggeableAgainstGolden("checkBox_${scheme.name}_checked_animateToUnchecked")
}
@@ -269,6 +274,107 @@
assertToggeableAgainstGolden("checkBox_${scheme.name}_focus")
}
+ @Test
+ fun checkBox_customColors() {
+ rule.setMaterialContent(scheme.colorScheme) {
+ Column(wrap.testTag(wrapperTestTag), verticalArrangement = Arrangement.spacedBy(2.dp)) {
+ val colors = CheckboxDefaults.colors(
+ checkedColor = Color.Red,
+ uncheckedColor = Color.Gray,
+ checkmarkColor = Color.Green,
+ disabledCheckedColor = Color.Red.copy(alpha = 0.38f),
+ disabledUncheckedColor = Color.Gray.copy(alpha = 0.38f),
+ disabledIndeterminateColor = Color.Magenta.copy(alpha = 0.38f)
+ )
+ Checkboxes(colors = colors)
+ }
+ }
+
+ rule.waitForIdle()
+
+ assertToggeableAgainstGolden("checkBox_${scheme.name}_customColors")
+ }
+
+ @Test
+ fun checkBox_customCheckboxColorsConstruct() {
+ rule.setMaterialContent(scheme.colorScheme) {
+ Column(wrap.testTag(wrapperTestTag), verticalArrangement = Arrangement.spacedBy(2.dp)) {
+ val colors = CheckboxColors(
+ checkedCheckmarkColor = Color.Black,
+ // Irrelevant for the test, as this color only appears when the check mark
+ // transitions from checked to unchecked.
+ uncheckedCheckmarkColor = Color.Transparent,
+ checkedBoxColor = Color.Green,
+ uncheckedBoxColor = Color.Yellow,
+ disabledCheckedBoxColor = Color.Green.copy(alpha = 0.38f),
+ disabledUncheckedBoxColor = Color.Yellow.copy(alpha = 0.38f),
+ disabledIndeterminateBoxColor = Color.Magenta.copy(alpha = 0.38f),
+ checkedBorderColor = Color.Red,
+ uncheckedBorderColor = Color.Black,
+ disabledBorderColor = Color.Red.copy(alpha = 0.38f),
+ disabledUncheckedBorderColor = Color.Blue,
+ disabledIndeterminateBorderColor = Color.LightGray
+ )
+ Checkboxes(colors = colors)
+ }
+ }
+
+ rule.waitForIdle()
+
+ assertToggeableAgainstGolden("checkBox_${scheme.name}_customCheckboxColorsConstruct")
+ }
+
+ @Composable
+ private fun Checkboxes(colors: CheckboxColors) {
+ TriStateCheckbox(state = ToggleableState.Off, onClick = { }, colors = colors)
+ TriStateCheckbox(
+ state = ToggleableState.Off,
+ onClick = { },
+ enabled = false,
+ colors = colors
+ )
+ TriStateCheckbox(state = ToggleableState.On, onClick = { }, colors = colors)
+ TriStateCheckbox(
+ state = ToggleableState.On,
+ onClick = { },
+ enabled = false,
+ colors = colors
+ )
+ TriStateCheckbox(
+ state = ToggleableState.Indeterminate,
+ onClick = { },
+ colors = colors
+ )
+ TriStateCheckbox(
+ state = ToggleableState.Indeterminate,
+ onClick = { },
+ enabled = false,
+ colors = colors
+ )
+ Checkbox(
+ checked = false,
+ onCheckedChange = { },
+ colors = colors
+ )
+ Checkbox(
+ checked = false,
+ onCheckedChange = { },
+ enabled = false,
+ colors = colors
+ )
+ Checkbox(
+ checked = true,
+ onCheckedChange = { },
+ colors = colors
+ )
+ Checkbox(
+ checked = true,
+ onCheckedChange = { },
+ enabled = false,
+ colors = colors
+ )
+ }
+
private fun assertToggeableAgainstGolden(goldenName: String) {
// TODO: replace with find(isToggeable()) after b/157687898 is fixed
rule.onNodeWithTag(wrapperTestTag)
diff --git a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/ModalBottomSheetTest.kt b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/ModalBottomSheetTest.kt
index 8a34a90..a3fcc88 100644
--- a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/ModalBottomSheetTest.kt
+++ b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/ModalBottomSheetTest.kt
@@ -107,7 +107,7 @@
@Test
fun modalBottomSheet_isDismissedOnTapOutside() {
var showBottomSheet by mutableStateOf(true)
- val sheetState = SheetState(skipPartiallyExpanded = false)
+ val sheetState = SheetState(skipPartiallyExpanded = false, density = rule.density)
rule.setContent {
val windowInsets = if (edgeToEdgeWrapper.edgeToEdgeEnabled)
@@ -294,7 +294,7 @@
@Test
fun modalBottomSheet_shortSheet_isDismissedOnBackPress() {
var showBottomSheet by mutableStateOf(true)
- val sheetState = SheetState(skipPartiallyExpanded = true)
+ val sheetState = SheetState(skipPartiallyExpanded = true, density = rule.density)
rule.setContent {
val dispatcher = LocalOnBackPressedDispatcherOwner.current!!.onBackPressedDispatcher
@@ -333,7 +333,7 @@
@Test
fun modalBottomSheet_tallSheet_isDismissedOnBackPress() {
var showBottomSheet by mutableStateOf(true)
- val sheetState = SheetState(skipPartiallyExpanded = false)
+ val sheetState = SheetState(skipPartiallyExpanded = false, density = rule.density)
rule.setContent {
val dispatcher = LocalOnBackPressedDispatcherOwner.current!!.onBackPressedDispatcher
@@ -565,7 +565,7 @@
fun modalBottomSheet_missingAnchors_findsClosest() {
val topTag = "ModalBottomSheetLayout"
var showShortContent by mutableStateOf(false)
- val sheetState = SheetState(skipPartiallyExpanded = false)
+ val sheetState = SheetState(skipPartiallyExpanded = false, density = rule.density)
lateinit var scope: CoroutineScope
rule.setContent {
@@ -809,6 +809,7 @@
lateinit var scope: CoroutineScope
val bottomSheetState = SheetState(
skipPartiallyExpanded = true,
+ density = rule.density
)
rule.setContent {
scope = rememberCoroutineScope()
@@ -1000,7 +1001,7 @@
@Test
fun modalBottomSheet_shortSheet_anchorChangeHandler_previousTargetNotInAnchors_reconciles() {
- val sheetState = SheetState(skipPartiallyExpanded = false)
+ val sheetState = SheetState(skipPartiallyExpanded = false, density = rule.density)
var hasSheetContent by mutableStateOf(false) // Start out with empty sheet content
lateinit var scope: CoroutineScope
rule.setContent {
@@ -1039,7 +1040,7 @@
@Test
fun modalBottomSheet_tallSheet_anchorChangeHandler_previousTargetNotInAnchors_reconciles() {
- val sheetState = SheetState(skipPartiallyExpanded = false)
+ val sheetState = SheetState(skipPartiallyExpanded = false, density = rule.density)
var hasSheetContent by mutableStateOf(false) // Start out with empty sheet content
lateinit var scope: CoroutineScope
rule.setContent {
@@ -1080,7 +1081,7 @@
fun modalBottomSheet_callsOnDismissRequest_onNestedScrollFling() {
var callCount by mutableStateOf(0)
val expectedCallCount = 1
- val sheetState = SheetState(skipPartiallyExpanded = true)
+ val sheetState = SheetState(skipPartiallyExpanded = true, density = rule.density)
val nestedScrollDispatcher = NestedScrollDispatcher()
val nestedScrollConnection = object : NestedScrollConnection {
diff --git a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/DynamicTonalPalette.kt b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/DynamicTonalPalette.kt
index 231f4d5..bafe835 100644
--- a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/DynamicTonalPalette.kt
+++ b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/DynamicTonalPalette.kt
@@ -20,6 +20,7 @@
import android.os.Build
import androidx.annotation.ColorRes
import androidx.annotation.DoNotInline
+import androidx.annotation.FloatRange
import androidx.annotation.RequiresApi
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.colorspace.ColorSpaces
@@ -240,7 +241,7 @@
* @param newLuminance 0 <= newLuminance <= 100; invalid values are corrected.
*/
internal fun Color.setLuminance(
- /*@FloatRange(from = 0.0, to = 100.0)*/
+ @FloatRange(from = 0.0, to = 100.0)
newLuminance: Float
): Color {
if ((newLuminance < 0.0001) or (newLuminance > 99.9999)) {
diff --git a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/ModalBottomSheet.android.kt b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/ModalBottomSheet.android.kt
index 1dfdc5a..abd7fee 100644
--- a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/ModalBottomSheet.android.kt
+++ b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/ModalBottomSheet.android.kt
@@ -48,6 +48,7 @@
import androidx.compose.runtime.CompositionContext
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@@ -64,6 +65,7 @@
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.AbstractComposeView
+import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.platform.ViewRootForInspector
@@ -135,6 +137,11 @@
windowInsets: WindowInsets = BottomSheetDefaults.windowInsets,
content: @Composable ColumnScope.() -> Unit,
) {
+ // b/291735717 Remove this once deprecated methods without density are removed
+ val density = LocalDensity.current
+ SideEffect {
+ sheetState.density = density
+ }
val scope = rememberCoroutineScope()
val animateToDismiss: () -> Unit = {
if (sheetState.swipeableState.confirmValueChange(Hidden)) {
@@ -247,8 +254,7 @@
}
} else if (hasPartiallyExpandedState) {
collapse(collapseActionLabel) {
- if (
- swipeableState.confirmValueChange(
+ if (swipeableState.confirmValueChange(
PartiallyExpanded
)
) {
@@ -329,12 +335,12 @@
screenHeight: Float,
onDragStopped: CoroutineScope.(velocity: Float) -> Unit,
) = draggable(
- state = sheetState.swipeableState.swipeDraggableState,
- orientation = Orientation.Vertical,
- enabled = sheetState.isVisible,
- startDragImmediately = sheetState.swipeableState.isAnimationRunning,
- onDragStopped = onDragStopped
- )
+ state = sheetState.swipeableState.swipeDraggableState,
+ orientation = Orientation.Vertical,
+ enabled = sheetState.isVisible,
+ startDragImmediately = sheetState.swipeableState.isAnimationRunning,
+ onDragStopped = onDragStopped
+)
.swipeAnchors(
state = sheetState.swipeableState,
anchorChangeHandler = anchorChangeHandler,
@@ -347,6 +353,7 @@
sheetState.skipPartiallyExpanded -> null
else -> screenHeight / 2f
}
+
Expanded -> if (sheetSize.height != 0) {
max(0f, screenHeight - sheetSize.height)
} else null
@@ -479,8 +486,8 @@
// Flags specific to modal bottom sheet.
flags = flags and (
WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES or
- WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM
- ).inv()
+ WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM
+ ).inv()
flags = flags or WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
}
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/BottomSheetScaffold.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/BottomSheetScaffold.kt
index 43e8c1a..0e30d0c 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/BottomSheetScaffold.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/BottomSheetScaffold.kt
@@ -28,6 +28,7 @@
import androidx.compose.material3.SheetValue.Hidden
import androidx.compose.material3.SheetValue.PartiallyExpanded
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.Stable
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
@@ -326,6 +327,11 @@
containerColor: Color,
contentColor: Color,
) {
+ // b/291735717 Remove this once deprecated methods without density are removed
+ val density = LocalDensity.current
+ SideEffect {
+ sheetState.density = density
+ }
SubcomposeLayout { constraints ->
val layoutWidth = constraints.maxWidth
val layoutHeight = constraints.maxHeight
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Checkbox.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Checkbox.kt
index 55571a3..3191c2f 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Checkbox.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Checkbox.kt
@@ -185,44 +185,39 @@
* Material specification.
*
* @param checkedColor the color that will be used for the border and box when checked
- * @param uncheckedColor color that will be used for the border when unchecked
+ * @param uncheckedColor color that will be used for the border when unchecked. By default, the
+ * inner box is transparent when unchecked.
* @param checkmarkColor color that will be used for the checkmark when checked
* @param disabledCheckedColor color that will be used for the box and border when disabled and
* checked
- * @param disabledUncheckedColor color that will be used for the box and border when disabled
- * and not checked
+ * @param disabledUncheckedColor color that will be used for the border when disabled and
+ * unchecked. By default, the inner box is transparent when unchecked.
* @param disabledIndeterminateColor color that will be used for the box and
- * border in a [TriStateCheckbox] when disabled AND in an [ToggleableState.Indeterminate] state.
+ * border in a [TriStateCheckbox] when disabled AND in an [ToggleableState.Indeterminate] state
*/
@Composable
fun colors(
- checkedColor: Color =
- MaterialTheme.colorScheme.fromToken(CheckboxTokens.SelectedContainerColor),
- uncheckedColor: Color =
- MaterialTheme.colorScheme.fromToken(CheckboxTokens.UnselectedOutlineColor),
- checkmarkColor: Color =
- MaterialTheme.colorScheme.fromToken(CheckboxTokens.SelectedIconColor),
- disabledCheckedColor: Color =
- MaterialTheme.colorScheme
- .fromToken(CheckboxTokens.SelectedDisabledContainerColor)
- .copy(alpha = CheckboxTokens.SelectedDisabledContainerOpacity),
- disabledUncheckedColor: Color =
- MaterialTheme.colorScheme
- .fromToken(CheckboxTokens.UnselectedDisabledOutlineColor)
- .copy(alpha = CheckboxTokens.UnselectedDisabledContainerOpacity),
+ checkedColor: Color = CheckboxTokens.SelectedContainerColor.value,
+ uncheckedColor: Color = CheckboxTokens.UnselectedOutlineColor.value,
+ checkmarkColor: Color = CheckboxTokens.SelectedIconColor.value,
+ disabledCheckedColor: Color = CheckboxTokens.SelectedDisabledContainerColor.value
+ .copy(alpha = CheckboxTokens.SelectedDisabledContainerOpacity),
+ disabledUncheckedColor: Color = CheckboxTokens.UnselectedDisabledOutlineColor.value
+ .copy(alpha = CheckboxTokens.UnselectedDisabledContainerOpacity),
disabledIndeterminateColor: Color = disabledCheckedColor
): CheckboxColors = CheckboxColors(
- checkedBorderColor = checkedColor,
- checkedBoxColor = checkedColor,
checkedCheckmarkColor = checkmarkColor,
- uncheckedCheckmarkColor = checkmarkColor.copy(alpha = 0f),
- uncheckedBoxColor = checkedColor.copy(alpha = 0f),
+ uncheckedCheckmarkColor = Color.Transparent,
+ checkedBoxColor = checkedColor,
+ uncheckedBoxColor = Color.Transparent,
disabledCheckedBoxColor = disabledCheckedColor,
- disabledUncheckedBoxColor = disabledUncheckedColor.copy(alpha = 0f),
+ disabledUncheckedBoxColor = Color.Transparent,
disabledIndeterminateBoxColor = disabledIndeterminateColor,
+ checkedBorderColor = checkedColor,
uncheckedBorderColor = uncheckedColor,
disabledBorderColor = disabledCheckedColor,
- disabledIndeterminateBorderColor = disabledIndeterminateColor,
+ disabledUncheckedBorderColor = disabledUncheckedColor,
+ disabledIndeterminateBorderColor = disabledIndeterminateColor
)
}
@@ -380,17 +375,17 @@
* @param uncheckedCheckmarkColor color that will be used for the checkmark when unchecked
* @param checkedBoxColor the color that will be used for the box when checked
* @param uncheckedBoxColor color that will be used for the box when unchecked
- * @param disabledCheckedBoxColor color that will be used for the box when disabled and
- * checked
- * @param disabledUncheckedBoxColor color that will be used for the box and border when disabled
- * and not checked
- * @param disabledIndeterminateBoxColor color that will be used for the box and
- * border in a [TriStateCheckbox] when disabled AND in an [ToggleableState.Indeterminate] state.
+ * @param disabledCheckedBoxColor color that will be used for the box when disabled and checked
+ * @param disabledUncheckedBoxColor color that will be used for the box when disabled and unchecked
+ * @param disabledIndeterminateBoxColor color that will be used for the box and border in a
+ * [TriStateCheckbox] when disabled AND in an [ToggleableState.Indeterminate] state.
* @param checkedBorderColor color that will be used for the border when checked
* @param uncheckedBorderColor color that will be used for the border when unchecked
- * @param disabledBorderColor color that will be used for the border when disabled
- * @param disabledIndeterminateBorderColor color that will be used for the border when in an
- * [ToggleableState.Indeterminate] state.
+ * @param disabledBorderColor color that will be used for the border when disabled and checked
+ * @param disabledUncheckedBorderColor color that will be used for the border when disabled and
+ * unchecked
+ * @param disabledIndeterminateBorderColor color that will be used for the border when disabled and
+ * in an [ToggleableState.Indeterminate] state.
*/
@Immutable
class CheckboxColors constructor(
@@ -404,6 +399,7 @@
val checkedBorderColor: Color,
val uncheckedBorderColor: Color,
val disabledBorderColor: Color,
+ val disabledUncheckedBorderColor: Color,
val disabledIndeterminateBorderColor: Color
) {
/**
@@ -471,7 +467,8 @@
} else {
when (state) {
ToggleableState.Indeterminate -> disabledIndeterminateBorderColor
- ToggleableState.On, ToggleableState.Off -> disabledBorderColor
+ ToggleableState.On -> disabledBorderColor
+ ToggleableState.Off -> disabledUncheckedBorderColor
}
}
@@ -499,6 +496,7 @@
if (checkedBorderColor != other.checkedBorderColor) return false
if (uncheckedBorderColor != other.uncheckedBorderColor) return false
if (disabledBorderColor != other.disabledBorderColor) return false
+ if (disabledUncheckedBorderColor != other.disabledUncheckedBorderColor) return false
if (disabledIndeterminateBorderColor != other.disabledIndeterminateBorderColor) return false
return true
@@ -515,6 +513,7 @@
result = 31 * result + checkedBorderColor.hashCode()
result = 31 * result + uncheckedBorderColor.hashCode()
result = 31 * result + disabledBorderColor.hashCode()
+ result = 31 * result + disabledUncheckedBorderColor.hashCode()
result = 31 * result + disabledIndeterminateBorderColor.hashCode()
return result
}
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SegmentedButton.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SegmentedButton.kt
index 0e4f6cd..8c6bb95 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SegmentedButton.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SegmentedButton.kt
@@ -635,7 +635,8 @@
override fun equals(other: Any?): Boolean {
if (this === other) return true
- if (javaClass != other?.javaClass) return false
+ if (other === null) return false
+ if (this::class != other::class) return false
other as SegmentedButtonColors
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SheetDefaults.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SheetDefaults.kt
index 5a6a3366..e59e502 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SheetDefaults.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SheetDefaults.kt
@@ -38,8 +38,10 @@
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
+import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.Velocity
import androidx.compose.ui.unit.dp
@@ -48,7 +50,37 @@
/**
* State of a sheet composable, such as [ModalBottomSheet]
*
- * Contains states relating to it's swipe position as well as animations between state values.
+ * Contains states relating to its swipe position as well as animations between state values.
+ *
+ * @param skipPartiallyExpanded Whether the partially expanded state, if the sheet is large
+ * enough, should be skipped. If true, the sheet will always expand to the [Expanded] state and move
+ * to the [Hidden] state if available when hiding the sheet, either programmatically or by user
+ * interaction.
+ * @param initialValue The initial value of the state.
+ * @param density The density that this state can use to convert values to and from dp.
+ * @param confirmValueChange Optional callback invoked to confirm or veto a pending state change.
+ * @param skipHiddenState Whether the hidden state should be skipped. If true, the sheet will always
+ * expand to the [Expanded] state and move to the [PartiallyExpanded] if available, either
+ * programmatically or by user interaction.
+ */
+@ExperimentalMaterial3Api
+@Suppress("Deprecation")
+fun SheetState(
+ skipPartiallyExpanded: Boolean,
+ density: Density,
+ initialValue: SheetValue = Hidden,
+ confirmValueChange: (SheetValue) -> Boolean = { true },
+ skipHiddenState: Boolean = false,
+) = SheetState(
+ skipPartiallyExpanded, initialValue, confirmValueChange, skipHiddenState
+).also {
+ it.density = density
+}
+
+/**
+ * State of a sheet composable, such as [ModalBottomSheet]
+ *
+ * Contains states relating to its swipe position as well as animations between state values.
*
* @param skipPartiallyExpanded Whether the partially expanded state, if the sheet is large
* enough, should be skipped. If true, the sheet will always expand to the [Expanded] state and move
@@ -62,7 +94,15 @@
*/
@Stable
@ExperimentalMaterial3Api
-class SheetState(
+class SheetState @Deprecated(
+ message = "This constructor is deprecated. " +
+ "Please use the constructor that provides a [Density]",
+ replaceWith = ReplaceWith(
+ "SheetState(" +
+ "skipPartiallyExpanded, LocalDensity.current, initialValue, " +
+ "confirmValueChange, skipHiddenState)"
+ )
+) constructor(
internal val skipPartiallyExpanded: Boolean,
initialValue: SheetValue = Hidden,
confirmValueChange: (SheetValue) -> Boolean = { true },
@@ -237,16 +277,46 @@
initialValue = initialValue,
animationSpec = SwipeableV2Defaults.AnimationSpec,
confirmValueChange = confirmValueChange,
+ positionalThreshold = { with(requireDensity()) { 56.dp.toPx() } },
+ velocityThreshold = { with(requireDensity()) { 125.dp.toPx() } }
)
internal val offset: Float? get() = swipeableState.offset
+ internal var density: Density? = null
+ private fun requireDensity() = requireNotNull(density) {
+ "SheetState did not have a density attached. Are you using SheetState with " +
+ "BottomSheetScaffold or ModalBottomSheet component?"
+ }
+
companion object {
/**
* The default [Saver] implementation for [SheetState].
*/
fun Saver(
skipPartiallyExpanded: Boolean,
+ confirmValueChange: (SheetValue) -> Boolean,
+ density: Density
+ ) = Saver<SheetState, SheetValue>(
+ save = { it.currentValue },
+ restore = { savedValue ->
+ SheetState(skipPartiallyExpanded, density, savedValue, confirmValueChange)
+ }
+ )
+
+ /**
+ * The default [Saver] implementation for [SheetState].
+ */
+ @Deprecated(
+ message = "This function is deprecated. Please use the overload where Density is" +
+ " provided.",
+ replaceWith = ReplaceWith(
+ "Saver(skipPartiallyExpanded, confirmValueChange, LocalDensity.current)"
+ )
+ )
+ @Suppress("Deprecation")
+ fun Saver(
+ skipPartiallyExpanded: Boolean,
confirmValueChange: (SheetValue) -> Boolean
) = Saver<SheetState, SheetValue>(
save = { it.currentValue },
@@ -287,17 +357,17 @@
/** The default shape for bottom sheets in a [Hidden] state. */
val HiddenShape: Shape
@Composable get() =
- SheetBottomTokens.DockedMinimizedContainerShape.value
+ SheetBottomTokens.DockedMinimizedContainerShape.value
/** The default shape for a bottom sheets in [PartiallyExpanded] and [Expanded] states. */
val ExpandedShape: Shape
@Composable get() =
- SheetBottomTokens.DockedContainerShape.value
+ SheetBottomTokens.DockedContainerShape.value
/** The default container color for a bottom sheet. */
val ContainerColor: Color
@Composable get() =
- SheetBottomTokens.DockedContainerColor.value
+ SheetBottomTokens.DockedContainerColor.value
/** The default elevation for a bottom sheet. */
val Elevation = SheetBottomTokens.DockedModalContainerElevation
@@ -305,7 +375,7 @@
/** The default color of the scrim overlay for background content. */
val ScrimColor: Color
@Composable get() =
- ScrimTokens.ContainerColor.value.copy(ScrimTokens.ContainerOpacity)
+ ScrimTokens.ContainerColor.value.copy(ScrimTokens.ContainerOpacity)
/**
* The default peek height used by [BottomSheetScaffold].
@@ -414,14 +484,23 @@
initialValue: SheetValue = Hidden,
skipHiddenState: Boolean = false,
): SheetState {
+
+ val density = LocalDensity.current
return rememberSaveable(
skipPartiallyExpanded, confirmValueChange,
saver = SheetState.Saver(
skipPartiallyExpanded = skipPartiallyExpanded,
- confirmValueChange = confirmValueChange
+ confirmValueChange = confirmValueChange,
+ density = density
)
) {
- SheetState(skipPartiallyExpanded, initialValue, confirmValueChange, skipHiddenState)
+ SheetState(
+ skipPartiallyExpanded,
+ density,
+ initialValue,
+ confirmValueChange,
+ skipHiddenState
+ )
}
}
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Slider.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Slider.kt
index a00e616..32ea286 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Slider.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Slider.kt
@@ -16,6 +16,7 @@
package androidx.compose.material3
+import androidx.annotation.IntRange
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.MutatePriority
import androidx.compose.foundation.MutatorMutex
@@ -153,7 +154,7 @@
modifier: Modifier = Modifier,
enabled: Boolean = true,
valueRange: ClosedFloatingPointRange<Float> = 0f..1f,
- /*@IntRange(from = 0)*/
+ @IntRange(from = 0)
steps: Int = 0,
onValueChangeFinished: (() -> Unit)? = null,
colors: SliderColors = SliderDefaults.colors(),
@@ -249,7 +250,7 @@
onValueChangeFinished: (() -> Unit)? = null,
colors: SliderColors = SliderDefaults.colors(),
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
- /*@IntRange(from = 0)*/
+ @IntRange(from = 0)
steps: Int = 0,
thumb: @Composable (SliderState) -> Unit = {
SliderDefaults.Thumb(
@@ -412,7 +413,7 @@
modifier: Modifier = Modifier,
enabled: Boolean = true,
valueRange: ClosedFloatingPointRange<Float> = 0f..1f,
- /*@IntRange(from = 0)*/
+ @IntRange(from = 0)
steps: Int = 0,
onValueChangeFinished: (() -> Unit)? = null,
colors: SliderColors = SliderDefaults.colors()
@@ -541,7 +542,7 @@
rangeSliderState = rangeSliderState
)
},
- /*@IntRange(from = 0)*/
+ @IntRange(from = 0)
steps: Int = 0
) {
val state = remember(
@@ -1789,7 +1790,7 @@
class SliderState(
initialValue: Float = 0f,
initialOnValueChange: ((Float) -> Unit)? = null,
- /*@IntRange(from = 0)*/
+ @IntRange(from = 0)
val steps: Int = 0,
val valueRange: ClosedFloatingPointRange<Float> = 0f..1f,
var onValueChangeFinished: (() -> Unit)? = null
@@ -1909,7 +1910,7 @@
initialActiveRangeStart: Float = 0f,
initialActiveRangeEnd: Float = 1f,
initialOnValueChange: ((FloatRange) -> Unit)? = null,
- /*@IntRange(from = 0)*/
+ @IntRange(from = 0)
val steps: Int = 0,
val valueRange: ClosedFloatingPointRange<Float> = 0f..1f,
var onValueChangeFinished: (() -> Unit)? = null,
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SwipeToDismiss.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SwipeToDismiss.kt
index bce5e38..4506e25 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SwipeToDismiss.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SwipeToDismiss.kt
@@ -23,14 +23,17 @@
import androidx.compose.foundation.layout.offset
import androidx.compose.material3.DismissDirection.EndToStart
import androidx.compose.material3.DismissDirection.StartToEnd
+import androidx.compose.material3.DismissState.Companion.Saver
import androidx.compose.material3.DismissValue.Default
import androidx.compose.material3.DismissValue.DismissedToEnd
import androidx.compose.material3.DismissValue.DismissedToStart
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.IntOffset
@@ -80,6 +83,7 @@
* State of the [SwipeToDismiss] composable.
*
* @param initialValue The initial value of the state.
+ * @param density The density that this state can use to convert values to and from dp.
* @param confirmValueChange Optional callback invoked to confirm or veto a pending state change.
* @param positionalThreshold The positional threshold to be used when calculating the target state
* while a swipe is in progress and when settling after the swipe ends. This is the distance from
@@ -87,17 +91,49 @@
* subtracted from/to the origin offset. It should always be a positive value.
*/
@ExperimentalMaterial3Api
-class DismissState(
+@Suppress("Deprecation", "PrimitiveInLambda")
+fun DismissState(
+ initialValue: DismissValue,
+ density: Density,
+ confirmValueChange: (DismissValue) -> Boolean = { true },
+ positionalThreshold: (totalDistance: Float) -> Float
+) = DismissState(
+ initialValue = initialValue,
+ confirmValueChange = confirmValueChange,
+ positionalThreshold = positionalThreshold
+).also {
+ it.density = density
+}
+
+/**
+ * State of the [SwipeToDismiss] composable.
+ *
+ * @param initialValue The initial value of the state.
+ * @param confirmValueChange Optional callback invoked to confirm or veto a pending state change.
+ * @param positionalThreshold The positional threshold to be used when calculating the target state
+ * while a swipe is in progress and when settling after the swipe ends. This is the distance from
+ * the start of a transition. It will be, depending on the direction of the interaction, added or
+ * subtracted from/to the origin offset. It should always be a positive value.
+ */
+@Suppress("PrimitiveInLambda")
+@ExperimentalMaterial3Api
+class DismissState @Deprecated(
+ message = "This constructor is deprecated. " +
+ "Please use the constructor that provides a [Density]",
+ replaceWith = ReplaceWith(
+ "DismissState(" +
+ "initialValue, LocalDensity.current, confirmValueChange, positionalThreshold)"
+ )
+) constructor(
initialValue: DismissValue,
confirmValueChange: (DismissValue) -> Boolean = { true },
- positionalThreshold: Density.(totalDistance: Float) -> Float =
- SwipeToDismissDefaults.FixedPositionalThreshold,
+ positionalThreshold: (totalDistance: Float) -> Float
) {
internal val swipeableState = SwipeableV2State(
initialValue = initialValue,
confirmValueChange = confirmValueChange,
positionalThreshold = positionalThreshold,
- velocityThreshold = DismissThreshold
+ velocityThreshold = { with(requireDensity()) { DismissThreshold.toPx() } }
)
internal val offset: Float? get() = swipeableState.offset
@@ -132,8 +168,10 @@
* If the composable is settled at the default state, then this will be null. Use this to
* change the background of the [SwipeToDismiss] if you want different actions on each side.
*/
- val dismissDirection: DismissDirection? get() =
- if (offset == 0f || offset == null) null else if (offset!! > 0f) StartToEnd else EndToStart
+ val dismissDirection: DismissDirection?
+ get() = if (offset == 0f || offset == null)
+ null
+ else if (offset!! > 0f) StartToEnd else EndToStart
/**
* Whether the component has been dismissed in the given [direction].
@@ -173,19 +211,52 @@
swipeableState.animateTo(targetValue = targetValue)
}
+ internal var density: Density? = null
+ private fun requireDensity() = requireNotNull(density) {
+ "DismissState did not have a density attached. Are you using DismissState with " +
+ "the SwipeToDismiss component?"
+ }
+
companion object {
+
/**
* The default [Saver] implementation for [DismissState].
*/
fun Saver(
confirmValueChange: (DismissValue) -> Boolean,
- positionalThreshold: Density.(totalDistance: Float) -> Float,
+ positionalThreshold: (totalDistance: Float) -> Float,
+ density: Density
) =
Saver<DismissState, DismissValue>(
save = { it.currentValue },
restore = {
DismissState(
- it, confirmValueChange, positionalThreshold)
+ it, density, confirmValueChange, positionalThreshold
+ )
+ }
+ )
+
+ /**
+ * The default [Saver] implementation for [DismissState].
+ */
+ @Deprecated(
+ message = "This function is deprecated. Please use the overload where Density is" +
+ " provided.",
+ replaceWith = ReplaceWith(
+ "Saver(confirmValueChange, positionalThreshold, LocalDensity.current)"
+ )
+ )
+ @Suppress("Deprecation")
+ fun Saver(
+ confirmValueChange: (DismissValue) -> Boolean,
+ positionalThreshold: (totalDistance: Float) -> Float,
+ ) =
+ Saver<DismissState, DismissValue>(
+ save = { it.currentValue },
+ restore = {
+ DismissState(
+ it, confirmValueChange, positionalThreshold
+ )
}
)
}
@@ -201,17 +272,24 @@
* the start of a transition. It will be, depending on the direction of the interaction, added or
* subtracted from/to the origin offset. It should always be a positive value.
*/
+@Suppress("PrimitiveInLambda")
@Composable
@ExperimentalMaterial3Api
fun rememberDismissState(
initialValue: DismissValue = Default,
confirmValueChange: (DismissValue) -> Boolean = { true },
- positionalThreshold: Density.(totalDistance: Float) -> Float =
- SwipeToDismissDefaults.FixedPositionalThreshold,
+ positionalThreshold: (totalDistance: Float) -> Float =
+ SwipeToDismissDefaults.fixedPositionalThreshold,
): DismissState {
+ val density = LocalDensity.current
return rememberSaveable(
- saver = DismissState.Saver(confirmValueChange, positionalThreshold)) {
- DismissState(initialValue, confirmValueChange, positionalThreshold)
+ saver = DismissState.Saver(
+ confirmValueChange = confirmValueChange,
+ density = density,
+ positionalThreshold = positionalThreshold
+ )
+ ) {
+ DismissState(initialValue, density, confirmValueChange, positionalThreshold)
}
}
@@ -236,6 +314,13 @@
modifier: Modifier = Modifier,
directions: Set<DismissDirection> = setOf(EndToStart, StartToEnd),
) {
+
+ // b/278692145 Remove this once deprecated methods without density are removed
+ val density = LocalDensity.current
+ SideEffect {
+ state.density = density
+ }
+
val isRtl = LocalLayoutDirection.current == LayoutDirection.Rtl
Box(
@@ -257,23 +342,27 @@
Default -> 0f
}
}
- ) {
- Row(
- content = background,
- modifier = Modifier.matchParentSize()
- )
- Row(
- content = dismissContent,
- modifier = Modifier.offset { IntOffset(state.requireOffset().roundToInt(), 0) }
- )
- }
+ ) {
+ Row(
+ content = background,
+ modifier = Modifier.matchParentSize()
+ )
+ Row(
+ content = dismissContent,
+ modifier = Modifier.offset { IntOffset(state.requireOffset().roundToInt(), 0) }
+ )
+ }
}
/** Contains default values for [SwipeToDismiss] and [DismissState]. */
+@Suppress("PrimitiveInLambda")
@ExperimentalMaterial3Api
object SwipeToDismissDefaults {
/** Default positional threshold of 56.dp for [DismissState]. */
- val FixedPositionalThreshold: Density.(totalDistance: Float) -> Float = { _ -> 56.dp.toPx() }
+ val fixedPositionalThreshold: (totalDistance: Float) -> Float
+ @Composable get() = with(LocalDensity.current) {
+ { 56.dp.toPx() }
+ }
}
private val DismissThreshold = 125.dp
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Swipeable.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Swipeable.kt
index e135720..f06b0e3 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Swipeable.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Swipeable.kt
@@ -16,6 +16,7 @@
package androidx.compose.material3
+import androidx.annotation.FloatRange
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.AnimationSpec
import androidx.compose.animation.core.SpringSpec
@@ -431,7 +432,7 @@
internal class SwipeProgress<T>(
val from: T,
val to: T,
- /*@FloatRange(from = 0.0, to = 1.0)*/
+ @FloatRange(from = 0.0, to = 1.0)
val fraction: Float
) {
override fun equals(other: Any?): Boolean {
@@ -536,7 +537,7 @@
* the new anchor. The target anchor is calculated based on the provided positional [thresholds].
*
* Swiping is constrained between the minimum and maximum anchors. If the user attempts to swipe
- * past these bounds, a resistance effect will be applied by default. The amount of resistance at
+ * past these bounds, a resistance effect will bfe applied by default. The amount of resistance at
* each edge is specified by the [resistance] config. To disable all resistance, set it to `null`.
*
* @param T The type of the state.
@@ -650,7 +651,7 @@
@Immutable
@ExperimentalMaterial3Api
internal data class FractionalThreshold(
- /*@FloatRange(from = 0.0, to = 1.0)*/
+ @FloatRange(from = 0.0, to = 1.0)
private val fraction: Float
) : ThresholdConfig {
override fun Density.computeThreshold(fromValue: Float, toValue: Float): Float {
@@ -682,11 +683,11 @@
*/
@Immutable
internal class ResistanceConfig(
- /*@FloatRange(from = 0.0, fromInclusive = false)*/
+ @FloatRange(from = 0.0, fromInclusive = false)
val basis: Float,
- /*@FloatRange(from = 0.0)*/
+ @FloatRange(from = 0.0)
val factorAtMin: Float = StandardResistanceFactor,
- /*@FloatRange(from = 0.0)*/
+ @FloatRange(from = 0.0)
val factorAtMax: Float = StandardResistanceFactor
) {
fun computeResistance(overflow: Float): Float {
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SwipeableV2.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SwipeableV2.kt
index 8db36b9..dfead31 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SwipeableV2.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SwipeableV2.kt
@@ -18,6 +18,7 @@
package androidx.compose.material3
+import androidx.annotation.FloatRange
import androidx.compose.animation.core.AnimationSpec
import androidx.compose.animation.core.SpringSpec
import androidx.compose.animation.core.animate
@@ -38,17 +39,8 @@
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
-import androidx.compose.ui.layout.LayoutModifier
-import androidx.compose.ui.layout.Measurable
-import androidx.compose.ui.layout.MeasureResult
-import androidx.compose.ui.layout.MeasureScope
-import androidx.compose.ui.layout.OnRemeasuredModifier
-import androidx.compose.ui.platform.InspectorInfo
-import androidx.compose.ui.platform.InspectorValueInfo
-import androidx.compose.ui.platform.debugInspectorInfo
-import androidx.compose.ui.unit.Constraints
-import androidx.compose.ui.unit.Density
-import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.layout.onSizeChanged
+import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp
import kotlin.math.abs
@@ -111,37 +103,27 @@
possibleValues: Set<T>,
anchorChangeHandler: AnchorChangeHandler<T>? = null,
calculateAnchor: (value: T, layoutSize: IntSize) -> Float?,
-) = this.then(SwipeAnchorsModifier(
- onDensityChanged = { state.density = it },
- onSizeChanged = { layoutSize ->
- val previousAnchors = state.anchors
- val newAnchors = mutableMapOf<T, Float>()
- possibleValues.forEach {
- val anchorValue = calculateAnchor(it, layoutSize)
- if (anchorValue != null) {
- newAnchors[it] = anchorValue
- }
+) = onSizeChanged { layoutSize ->
+ val previousAnchors = state.anchors
+ val newAnchors = mutableMapOf<T, Float>()
+ possibleValues.forEach {
+ val anchorValue = calculateAnchor(it, layoutSize)
+ if (anchorValue != null) {
+ newAnchors[it] = anchorValue
}
- if (previousAnchors != newAnchors) {
- val previousTarget = state.targetValue
- val stateRequiresCleanup = state.updateAnchors(newAnchors)
- if (stateRequiresCleanup) {
- anchorChangeHandler?.onAnchorsChanged(
- previousTarget,
- previousAnchors,
- newAnchors
- )
- }
- }
- },
- inspectorInfo = debugInspectorInfo {
- name = "swipeAnchors"
- properties["state"] = state
- properties["possibleValues"] = possibleValues
- properties["anchorChangeHandler"] = anchorChangeHandler
- properties["calculateAnchor"] = calculateAnchor
}
-))
+ if (previousAnchors != newAnchors) {
+ val previousTarget = state.targetValue
+ val stateRequiresCleanup = state.updateAnchors(newAnchors)
+ if (stateRequiresCleanup) {
+ anchorChangeHandler?.onAnchorsChanged(
+ previousTarget,
+ previousAnchors,
+ newAnchors
+ )
+ }
+ }
+}
/**
* State of the [swipeableV2] modifier.
@@ -153,24 +135,23 @@
* @param initialValue The initial value of the state.
* @param animationSpec The default animation that will be used to animate to a new state.
* @param confirmValueChange Optional callback invoked to confirm or veto a pending state change.
- * @param positionalThreshold The positional threshold to be used when calculating the target state
- * while a swipe is in progress and when settling after the swipe ends. This is the distance from
- * the start of a transition. It will be, depending on the direction of the interaction, added or
- * subtracted from/to the origin offset. It should always be a positive value. See the
- * [fractionalPositionalThreshold] and [fixedPositionalThreshold] methods.
- * @param velocityThreshold The velocity threshold (in dp per second) that the end velocity has to
+ * @param positionalThreshold The positional threshold, in px, to be used when calculating the
+ * target state while a swipe is in progress and when settling after the swipe ends. This is the
+ * distance from the start of a transition. It will be, depending on the direction of the
+ * interaction, added or subtracted from/to the origin offset. It should always be a positive value.
+ * @param velocityThreshold The velocity threshold (in px per second) that the end velocity has to
* exceed in order to animate to the next state, even if the [positionalThreshold] has not been
* reached.
*/
+@Suppress("PrimitiveInLambda")
@Stable
@ExperimentalMaterial3Api
internal class SwipeableV2State<T>(
initialValue: T,
+ internal val positionalThreshold: (totalDistance: Float) -> Float,
+ internal val velocityThreshold: () -> Float,
internal val animationSpec: AnimationSpec<Float> = SwipeableV2Defaults.AnimationSpec,
internal val confirmValueChange: (newValue: T) -> Boolean = { true },
- internal val positionalThreshold: Density.(totalDistance: Float) -> Float =
- SwipeableV2Defaults.PositionalThreshold,
- internal val velocityThreshold: Dp = SwipeableV2Defaults.VelocityThreshold,
) {
private val swipeMutex = InternalMutatorMutex()
@@ -250,7 +231,7 @@
* The fraction of the progress going from [currentValue] to [targetValue], within [0f..1f]
* bounds.
*/
- /*@FloatRange(from = 0f, to = 1f)*/
+ @get:FloatRange(from = 0.0, to = 1.0)
val progress: Float by derivedStateOf {
val a = anchors[currentValue] ?: 0f
val b = anchors[targetValue] ?: 0f
@@ -287,8 +268,6 @@
internal var anchors by mutableStateOf(emptyMap<T, Float>())
- internal var density: Density? = null
-
/**
* Update the anchors.
* If the previous set of anchors was empty, attempt to update the offset to match the initial
@@ -417,8 +396,7 @@
): T {
val currentAnchors = anchors
val currentAnchor = currentAnchors[currentValue]
- val currentDensity = requireDensity()
- val velocityThresholdPx = with(currentDensity) { velocityThreshold.toPx() }
+ val velocityThresholdPx = velocityThreshold()
return if (currentAnchor == offset || currentAnchor == null) {
currentValue
} else if (currentAnchor < offset) {
@@ -428,7 +406,7 @@
} else {
val upper = currentAnchors.closestAnchor(offset, true)
val distance = abs(currentAnchors.getValue(upper) - currentAnchor)
- val relativeThreshold = abs(positionalThreshold(currentDensity, distance))
+ val relativeThreshold = abs(positionalThreshold(distance))
val absoluteThreshold = abs(currentAnchor + relativeThreshold)
if (offset < absoluteThreshold) currentValue else upper
}
@@ -439,7 +417,7 @@
} else {
val lower = currentAnchors.closestAnchor(offset, false)
val distance = abs(currentAnchor - currentAnchors.getValue(lower))
- val relativeThreshold = abs(positionalThreshold(currentDensity, distance))
+ val relativeThreshold = abs(positionalThreshold(distance))
val absoluteThreshold = abs(currentAnchor - relativeThreshold)
if (offset < 0) {
// For negative offsets, larger absolute thresholds are closer to lower anchors
@@ -452,11 +430,6 @@
}
}
- private fun requireDensity() = requireNotNull(density) {
- "SwipeableState did not have a density attached. Are you using Modifier.swipeable with " +
- "this=$this SwipeableState?"
- }
-
private suspend fun swipe(
swipePriority: MutatePriority = MutatePriority.Default,
action: suspend () -> Unit
@@ -490,8 +463,8 @@
fun <T : Any> Saver(
animationSpec: AnimationSpec<Float>,
confirmValueChange: (T) -> Boolean,
- positionalThreshold: Density.(distance: Float) -> Float,
- velocityThreshold: Dp
+ positionalThreshold: (distance: Float) -> Float,
+ velocityThreshold: () -> Float
) = Saver<SwipeableV2State<T>, T>(
save = { it.currentValue },
restore = {
@@ -514,6 +487,7 @@
* @param animationSpec The default animation that will be used to animate to a new value.
* @param confirmValueChange Optional callback invoked to confirm or veto a pending value change.
*/
+@Suppress("PrimitiveInLambda")
@Composable
@ExperimentalMaterial3Api
internal fun <T : Any> rememberSwipeableV2State(
@@ -521,51 +495,32 @@
animationSpec: AnimationSpec<Float> = SwipeableV2Defaults.AnimationSpec,
confirmValueChange: (newValue: T) -> Boolean = { true }
): SwipeableV2State<T> {
+ val positionalThreshold = SwipeableV2Defaults.positionalThreshold
+ val velocityThreshold = SwipeableV2Defaults.velocityThreshold
+
return rememberSaveable(
- initialValue, animationSpec, confirmValueChange,
+ initialValue, animationSpec, confirmValueChange, positionalThreshold, velocityThreshold,
saver = SwipeableV2State.Saver(
animationSpec = animationSpec,
confirmValueChange = confirmValueChange,
- positionalThreshold = SwipeableV2Defaults.PositionalThreshold,
- velocityThreshold = SwipeableV2Defaults.VelocityThreshold
+ positionalThreshold = positionalThreshold,
+ velocityThreshold = velocityThreshold
),
) {
SwipeableV2State(
initialValue = initialValue,
animationSpec = animationSpec,
confirmValueChange = confirmValueChange,
- positionalThreshold = SwipeableV2Defaults.PositionalThreshold,
- velocityThreshold = SwipeableV2Defaults.VelocityThreshold
+ positionalThreshold = positionalThreshold,
+ velocityThreshold = velocityThreshold
)
}
}
/**
- * Expresses a fixed positional threshold of [threshold] dp. This will be the distance from an
- * anchor that needs to be reached for [SwipeableV2State] to settle to the next closest anchor.
- *
- * @see [fractionalPositionalThreshold] for a fractional positional threshold
- */
-@ExperimentalMaterial3Api
-internal fun fixedPositionalThreshold(threshold: Dp): Density.(distance: Float) -> Float = {
- threshold.toPx()
-}
-
-/**
- * Expresses a relative positional threshold of the [fraction] of the distance to the closest anchor
- * in the current direction. This will be the distance from an anchor that needs to be reached for
- * [SwipeableV2State] to settle to the next closest anchor.
- *
- * @see [fixedPositionalThreshold] for a fixed positional threshold
- */
-@ExperimentalMaterial3Api
-internal fun fractionalPositionalThreshold(
- fraction: Float
-): Density.(distance: Float) -> Float = { distance -> distance * fraction }
-
-/**
* Contains useful defaults for [swipeableV2] and [SwipeableV2State].
*/
+@Suppress("PrimitiveInLambda")
@Stable
@ExperimentalMaterial3Api
internal object SwipeableV2Defaults {
@@ -579,14 +534,17 @@
* The default velocity threshold (1.8 dp per millisecond) used by [rememberSwipeableV2State].
*/
@ExperimentalMaterial3Api
- val VelocityThreshold: Dp = 125.dp
+ val velocityThreshold: () -> Float
+ @Composable get() = with(LocalDensity.current) { { 125.dp.toPx() } }
/**
* The default positional threshold (56 dp) used by [rememberSwipeableV2State]
*/
@ExperimentalMaterial3Api
- val PositionalThreshold: Density.(totalDistance: Float) -> Float =
- fixedPositionalThreshold(56.dp)
+ val positionalThreshold: (totalDistance: Float) -> Float
+ @Composable get() = with(LocalDensity.current) {
+ { 56.dp.toPx() }
+ }
/**
* A [AnchorChangeHandler] implementation that attempts to reconcile an in-progress animation
@@ -646,37 +604,6 @@
)
}
-@Stable
-private class SwipeAnchorsModifier(
- private val onDensityChanged: (density: Density) -> Unit,
- private val onSizeChanged: (layoutSize: IntSize) -> Unit,
- inspectorInfo: InspectorInfo.() -> Unit,
-) : LayoutModifier, OnRemeasuredModifier, InspectorValueInfo(inspectorInfo) {
-
- private var lastDensity: Float = -1f
- private var lastFontScale: Float = -1f
-
- override fun MeasureScope.measure(
- measurable: Measurable,
- constraints: Constraints
- ): MeasureResult {
- if (density != lastDensity || fontScale != lastFontScale) {
- onDensityChanged(Density(density, fontScale))
- lastDensity = density
- lastFontScale = fontScale
- }
- val placeable = measurable.measure(constraints)
- return layout(placeable.width, placeable.height) { placeable.place(0, 0) }
- }
-
- override fun onRemeasured(size: IntSize) {
- onSizeChanged(size)
- }
-
- override fun toString() = "SwipeAnchorsModifierImpl(updateDensity=$onDensityChanged, " +
- "onSizeChanged=$onSizeChanged)"
-}
-
private fun <T> Map<T, Float>.closestAnchor(
offset: Float = 0f,
searchUpwards: Boolean = false
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TimePicker.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TimePicker.kt
index 83a3883..3f6e81e 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TimePicker.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TimePicker.kt
@@ -132,12 +132,13 @@
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.semantics.clearAndSetSemantics
import androidx.compose.ui.semantics.contentDescription
-import androidx.compose.ui.semantics.isContainer
+import androidx.compose.ui.semantics.isTraversalGroup
import androidx.compose.ui.semantics.onClick
import androidx.compose.ui.semantics.role
import androidx.compose.ui.semantics.selectableGroup
import androidx.compose.ui.semantics.selected
import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.semantics.traversalIndex
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.TextFieldValue
@@ -404,7 +405,8 @@
override fun equals(other: Any?): Boolean {
if (this === other) return true
- if (javaClass != other?.javaClass) return false
+ if (other === null) return false
+ if (this::class != other::class) return false
other as TimePickerColors
@@ -694,7 +696,10 @@
colors: TimePickerColors = TimePickerDefaults.colors(),
autoSwitchToMinute: Boolean
) {
- Column(modifier = modifier, horizontalAlignment = Alignment.CenterHorizontally) {
+ Column(
+ modifier = modifier.semantics { isTraversalGroup = true },
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
VerticalClockDisplay(state, colors)
Spacer(modifier = Modifier.height(ClockDisplayBottomMargin))
ClockFace(state, colors, autoSwitchToMinute)
@@ -916,7 +921,7 @@
val spacerPlaceable = spacer.measure(
constraints.copy(
minWidth = 0,
- maxWidth = TimePickerTokens.PeriodSelectorOutlineWidth.toPx().roundToInt(),
+ maxWidth = TimePickerTokens.PeriodSelectorOutlineWidth.roundToPx(),
)
)
@@ -959,7 +964,7 @@
val spacerPlaceable = spacer.measure(
constraints.copy(
minHeight = 0,
- maxHeight = TimePickerTokens.PeriodSelectorOutlineWidth.toPx().roundToInt()
+ maxHeight = TimePickerTokens.PeriodSelectorOutlineWidth.roundToPx()
)
)
@@ -1009,21 +1014,17 @@
Layout(
modifier = modifier
.semantics {
- @Suppress("DEPRECATION")
- isContainer = true
+ isTraversalGroup = true
this.contentDescription = contentDescription
}
.selectableGroup()
- .then(modifier)
.border(border = borderStroke, shape = shape),
measurePolicy = measurePolicy,
content = {
ToggleItem(
checked = !state.isAfternoonToggle,
shape = startShape,
- onClick = {
- state.isAfternoonToggle = false
- },
+ onClick = { state.isAfternoonToggle = false },
colors = colors,
) { Text(text = getString(string = Strings.TimePickerAM)) }
Spacer(
@@ -1037,9 +1038,7 @@
checked =
state.isAfternoonToggle,
shape = endShape,
- onClick = {
- state.isAfternoonToggle = true
- },
+ onClick = { state.isAfternoonToggle = true },
colors = colors,
) { Text(getString(string = Strings.TimePickerPM)) }
}
@@ -1161,11 +1160,7 @@
modifier = Modifier
.background(shape = CircleShape, color = colors.clockDialColor)
.size(ClockDialContainerSize)
- .semantics {
- @Suppress("DEPRECATION")
- isContainer = false
- selectableGroup()
- },
+ .semantics { selectableGroup() },
targetState = state.values,
animationSpec = tween(durationMillis = MotionTokens.DurationMedium3.toInt())
) { screen ->
@@ -1179,13 +1174,20 @@
CompositionLocalProvider(
LocalContentColor provides colors.clockDialContentColor(false)
) {
- repeat(screen.size) {
+ repeat(screen.size) { index ->
val outerValue = if (!state.is24hour || state.selection == Selection.Minute) {
- screen[it]
+ screen[index]
} else {
- screen[it] % 12
+ screen[index] % 12
}
- ClockText(state = state, value = outerValue, autoSwitchToMinute)
+ ClockText(
+ modifier = Modifier.semantics {
+ traversalIndex = index.toFloat()
+ },
+ state = state,
+ value = outerValue,
+ autoSwitchToMinute = autoSwitchToMinute
+ )
}
if (state.selection == Selection.Hour && state.is24hour) {
@@ -1196,9 +1198,16 @@
.background(shape = CircleShape, color = Color.Transparent),
radius = InnerCircleRadius
) {
- repeat(ExtraHours.size) {
- val innerValue = ExtraHours[it]
- ClockText(state = state, value = innerValue, autoSwitchToMinute)
+ repeat(ExtraHours.size) { index ->
+ val innerValue = ExtraHours[index]
+ ClockText(
+ modifier = Modifier.semantics {
+ traversalIndex = 12 + index.toFloat()
+ },
+ state = state,
+ value = innerValue,
+ autoSwitchToMinute = autoSwitchToMinute
+ )
}
}
}
@@ -1314,7 +1323,12 @@
}
@Composable
-private fun ClockText(state: TimePickerState, value: Int, autoSwitchToMinute: Boolean) {
+private fun ClockText(
+ modifier: Modifier,
+ state: TimePickerState,
+ value: Int,
+ autoSwitchToMinute: Boolean
+) {
val style = MaterialTheme.typography.fromToken(ClockDialLabelTextFont)
val maxDist = with(LocalDensity.current) { MaxDistance.toPx() }
var center by remember { mutableStateOf(Offset.Zero) }
@@ -1335,7 +1349,7 @@
Box(
contentAlignment = Alignment.Center,
- modifier = Modifier
+ modifier = modifier
.minimumInteractiveComponentSize()
.size(MinimumInteractiveSize)
.onGloballyPositioned { center = it.boundsInParent().center }
diff --git a/compose/runtime/runtime-lint/build.gradle b/compose/runtime/runtime-lint/build.gradle
index 467bc1d..ac9b31a 100644
--- a/compose/runtime/runtime-lint/build.gradle
+++ b/compose/runtime/runtime-lint/build.gradle
@@ -26,7 +26,7 @@
BundleInsideHelper.forInsideLintJar(project)
dependencies {
- compileOnly(libs.androidLintMinComposeApi)
+ compileOnly(libs.androidLintApi)
compileOnly(libs.kotlinStdlib)
bundleInside(projectOrArtifact(":compose:lint:common"))
diff --git a/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/AutoboxingStateCreationDetector.kt b/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/AutoboxingStateCreationDetector.kt
index f225ede..369aff4 100644
--- a/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/AutoboxingStateCreationDetector.kt
+++ b/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/AutoboxingStateCreationDetector.kt
@@ -21,7 +21,6 @@
import androidx.compose.lint.Name
import androidx.compose.lint.Names
import androidx.compose.lint.isInPackageName
-import androidx.compose.lint.resolveCall
import com.android.tools.lint.detector.api.Category
import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Implementation
@@ -34,12 +33,13 @@
import com.android.tools.lint.detector.api.SourceCodeScanner
import com.intellij.psi.PsiMethod
import java.util.EnumSet
-import org.jetbrains.kotlin.js.descriptorUtils.getJetTypeFqName
+import org.jetbrains.kotlin.analysis.api.analyze
+import org.jetbrains.kotlin.analysis.api.calls.singleFunctionCallOrNull
+import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.psi.KtTypeArgumentList
import org.jetbrains.kotlin.psi.KtValueArgumentList
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
import org.jetbrains.uast.UCallExpression
-import org.jetbrains.uast.kotlin.KotlinUFunctionCallExpression
import org.jetbrains.uast.skipParenthesizedExprDown
/**
@@ -66,8 +66,7 @@
override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {
if (!method.isInPackageName(Names.Runtime.PackageName)) return
- val replacement = getSuggestedReplacementName(node as KotlinUFunctionCallExpression)
- ?: return
+ val replacement = getSuggestedReplacementName(node) ?: return
context.report(
issue = AutoboxingStateCreation,
@@ -129,26 +128,33 @@
}
private fun getSuggestedReplacementName(
- invocation: KotlinUFunctionCallExpression
+ invocation: UCallExpression
): Name? {
if (!usesStructuralEqualityPolicy(invocation)) return null
- val resolvedCall = invocation.resolveCall() ?: return null
- val stateType = resolvedCall.typeArguments.asIterable().single().value
- return when {
- stateType.isMarkedNullable -> null
- else -> replacements[stateType.getJetTypeFqName(true)]
+ val sourcePsi = invocation.sourcePsi as? KtElement ?: return null
+ analyze(sourcePsi) {
+ val resolvedCall = sourcePsi.resolveCall()?.singleFunctionCallOrNull() ?: return null
+ val stateType = resolvedCall.typeArgumentsMapping.asIterable().single().value
+ return when {
+ stateType.isMarkedNullable -> null
+ else -> {
+ // NB: use expanded class symbol for type alias
+ val fqName = stateType.expandedClassSymbol?.classIdIfNonLocal?.asFqNameString()
+ replacements[fqName]
+ }
+ }
}
}
private fun usesStructuralEqualityPolicy(
- invocation: KotlinUFunctionCallExpression
+ invocation: UCallExpression
): Boolean {
val policyExpr = invocation.valueArguments.getOrNull(MUTATION_POLICY_PARAM_IDX)
?.skipParenthesizedExprDown()
?: return true // No argument passed; we're using the default policy
- val policyMethod = (policyExpr as? KotlinUFunctionCallExpression)?.resolve()
+ val policyMethod = (policyExpr as? UCallExpression)?.resolve()
?: return false // Argument isn't a direct function call. Assume it's a more complex
// policy, or isn't always the structural equality policy.
diff --git a/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/AutoboxingStateValuePropertyDetector.kt b/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/AutoboxingStateValuePropertyDetector.kt
index f82045e..ddb9810 100644
--- a/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/AutoboxingStateValuePropertyDetector.kt
+++ b/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/AutoboxingStateValuePropertyDetector.kt
@@ -16,6 +16,8 @@
package androidx.compose.runtime.lint
+import com.android.tools.lint.detector.api.AnnotationInfo
+import com.android.tools.lint.detector.api.AnnotationUsageInfo
import com.android.tools.lint.detector.api.AnnotationUsageType
import com.android.tools.lint.detector.api.Category
import com.android.tools.lint.detector.api.Detector
@@ -27,7 +29,6 @@
import com.android.tools.lint.detector.api.Severity
import com.android.tools.lint.detector.api.SourceCodeScanner
import com.android.tools.lint.detector.api.UastLintUtils
-import com.intellij.psi.PsiMethod
import java.util.EnumSet
import org.jetbrains.uast.UAnnotation
import org.jetbrains.uast.UElement
@@ -55,20 +56,15 @@
override fun visitAnnotationUsage(
context: JavaContext,
- usage: UElement,
- type: AnnotationUsageType,
- annotation: UAnnotation,
- qualifiedName: String,
- method: PsiMethod?,
- annotations: List<UAnnotation>,
- allMemberAnnotations: List<UAnnotation>,
- allClassAnnotations: List<UAnnotation>,
- allPackageAnnotations: List<UAnnotation>
+ element: UElement,
+ annotationInfo: AnnotationInfo,
+ usageInfo: AnnotationUsageInfo
) {
- val resolvedPropertyName = usage.identifier ?: "<unknown identifier>"
- val preferredPropertyName = annotation.preferredPropertyName ?: "<unknown replacement>"
+ val resolvedPropertyName = element.identifier ?: "<unknown identifier>"
+ val preferredPropertyName =
+ annotationInfo.annotation.preferredPropertyName ?: "<unknown replacement>"
- val accessKind = when (usage.resolvedName?.takeWhile { it.isLowerCase() }) {
+ val accessKind = when (element.resolvedName?.takeWhile { it.isLowerCase() }) {
"get" -> "Reading"
"set" -> "Assigning"
else -> "Accessing"
@@ -76,8 +72,8 @@
context.report(
AutoboxingStateValueProperty,
- usage,
- context.getLocation(usage),
+ element,
+ context.getLocation(element),
"$accessKind `$resolvedPropertyName` will cause an autoboxing operation. " +
"Use `$preferredPropertyName` to avoid unnecessary allocations.",
createPropertyReplacementQuickFix(
diff --git a/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/MutableCollectionMutableStateDetector.kt b/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/MutableCollectionMutableStateDetector.kt
index 6d2d7f1..1b650c1 100644
--- a/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/MutableCollectionMutableStateDetector.kt
+++ b/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/MutableCollectionMutableStateDetector.kt
@@ -30,12 +30,12 @@
import com.android.tools.lint.detector.api.SourceCodeScanner
import com.intellij.psi.PsiMethod
import java.util.EnumSet
-import org.jetbrains.kotlin.descriptors.containingPackage
+import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
+import org.jetbrains.kotlin.analysis.api.analyze
+import org.jetbrains.kotlin.analysis.api.types.KtNonErrorClassType
+import org.jetbrains.kotlin.analysis.api.types.KtType
import org.jetbrains.kotlin.psi.KtExpression
-import org.jetbrains.kotlin.types.KotlinType
-import org.jetbrains.kotlin.types.typeUtil.supertypes
import org.jetbrains.uast.UCallExpression
-import org.jetbrains.uast.kotlin.KotlinUastResolveProviderService
/**
* [Detector] that checks `mutableStateOf` calls to warn if the type is a mutable collection, as
@@ -54,29 +54,28 @@
val expression = node.sourcePsi as? KtExpression ?: return
- // PsiType will return the underlying JVM type for kotlin collections, so instead we need to
- // use the KotlinType to preserve the actual Kotlin type declared in source - that way we
+ // [PsiType] will return the underlying JVM type for kotlin collections, so instead we need
+ // to use the [KtType] to preserve the actual Kotlin type declared in source - that way we
// can disambiguate between MutableList and the underlying java.util.List that it will be
// converted to.
- val service = expression.project.getService(KotlinUastResolveProviderService::class.java)
- val bindingContext = service.getBindingContext(expression)
- val expressionType = bindingContext.getType(expression)
+ analyze(expression) {
+ val expressionType = expression.getKtType() as? KtNonErrorClassType ?: return
+ // expressionType will be MutableState<Foo>, so unwrap the argument to get the type we
+ // care about. We do this instead of looking at the inner expression type, to account
+ // for cases such as mutableStateOf<List<Int>>(mutableListOf(1)) or
+ // val foo: MutableState<List<Int>> = mutableStateOf(mutableListOf(1)) - the inner
+ // expression type is mutable but because the type of the mutableStateOf expression is
+ // not, we don't want to report a warning.
+ val type = expressionType.ownTypeArguments.firstOrNull()?.type ?: return
- // expressionType will be MutableState<Foo>, so unwrap the argument to get the type we care
- // about. We do this instead of looking at the inner expression type, to account for cases
- // such as mutableStateOf<List<Int>>(mutableListOf(1)) or
- // val foo: MutableState<List<Int>> = mutableStateOf(mutableListOf(1)) - the inner
- // expression type is mutable but because the type of the mutableStateOf expression is not,
- // we don't want to report a warning.
- val type = expressionType?.arguments?.firstOrNull()?.type ?: return
-
- if (type.isMutableCollection()) {
- context.report(
- MutableCollectionMutableState,
- node,
- context.getNameLocation(node),
- "Creating a MutableState object with a mutable collection type"
- )
+ if (isMutableCollection(type)) {
+ context.report(
+ MutableCollectionMutableState,
+ node,
+ context.getNameLocation(node),
+ "Creating a MutableState object with a mutable collection type"
+ )
+ }
}
}
@@ -115,7 +114,7 @@
* - [java.util.Collection]
* - [java.util.Map]
*/
-private fun KotlinType.isMutableCollection(): Boolean {
+private fun KtAnalysisSession.isMutableCollection(ktType: KtType): Boolean {
// MutableCollection::class.qualifiedName == Collection::class.qualifiedName, so using hardcoded
// strings instead
val kotlinImmutableTypes = listOf(
@@ -134,21 +133,19 @@
)
// Check `this`
- if (kotlinMutableTypes.any { it == fqn }) return true
- if (kotlinImmutableTypes.any { it == fqn }) return false
- if (javaMutableTypes.any { it == fqn }) return true
+ if (kotlinMutableTypes.any { it == fqn(ktType) }) return true
+ if (kotlinImmutableTypes.any { it == fqn(ktType) }) return false
+ if (javaMutableTypes.any { it == fqn(ktType) }) return true
// Check supertypes
- val supertypes = supertypes()
- if (supertypes.any { type -> kotlinMutableTypes.any { it == type.fqn } }) return true
- if (supertypes.any { type -> kotlinImmutableTypes.any { it == type.fqn } }) return false
- if (supertypes.any { type -> javaMutableTypes.any { it == type.fqn } }) return true
+ val supertypes = ktType.getAllSuperTypes()
+ if (supertypes.any { type -> kotlinMutableTypes.any { it == fqn(type) } }) return true
+ if (supertypes.any { type -> kotlinImmutableTypes.any { it == fqn(type) } }) return false
+ if (supertypes.any { type -> javaMutableTypes.any { it == fqn(type) } }) return true
return false
}
-private val KotlinType.fqn: String? get() {
- val descriptor = constructor.declarationDescriptor ?: return null
- val packageName = descriptor.containingPackage()?.asString() ?: return null
- return packageName + "." + descriptor.name.asString()
+private fun KtAnalysisSession.fqn(ktType: KtType): String? {
+ return ktType.expandedClassSymbol?.classIdIfNonLocal?.asFqNameString()
}
diff --git a/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/OpaqueUnitKeyDetector.kt b/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/OpaqueUnitKeyDetector.kt
index c950082..fb7a05b 100644
--- a/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/OpaqueUnitKeyDetector.kt
+++ b/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/OpaqueUnitKeyDetector.kt
@@ -217,7 +217,7 @@
}
private fun UExpression.isUnitLiteral(): Boolean {
- val expr = skipParenthesizedExprDown() ?: this
+ val expr = skipParenthesizedExprDown()
if (expr !is USimpleNameReferenceExpression) return false
return (expr.tryResolveUDeclaration() as? UClass)?.qualifiedName == FqUnitName
diff --git a/compose/runtime/runtime-lint/src/test/java/androidx/compose/runtime/lint/ApiLintVersionsTest.kt b/compose/runtime/runtime-lint/src/test/java/androidx/compose/runtime/lint/ApiLintVersionsTest.kt
index ad0785c..282fc5d 100644
--- a/compose/runtime/runtime-lint/src/test/java/androidx/compose/runtime/lint/ApiLintVersionsTest.kt
+++ b/compose/runtime/runtime-lint/src/test/java/androidx/compose/runtime/lint/ApiLintVersionsTest.kt
@@ -34,6 +34,6 @@
val registry = RuntimeIssueRegistry()
assertThat(registry.api).isEqualTo(CURRENT_API)
- assertThat(registry.minApi).isEqualTo(10)
+ assertThat(registry.minApi).isEqualTo(14)
}
}
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/changelist/Operation.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/changelist/Operation.kt
index 8aec129..9cbcd48 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/changelist/Operation.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/changelist/Operation.kt
@@ -48,7 +48,7 @@
) {
val name: String
- get() = javaClass.simpleName
+ get() = this::class.simpleName.orEmpty()
abstract fun OperationArgContainer.execute(
applier: Applier<*>,
diff --git a/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/CompositionTests.kt b/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/CompositionTests.kt
index f33bb2b..8f8615b 100644
--- a/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/CompositionTests.kt
+++ b/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/CompositionTests.kt
@@ -3865,6 +3865,65 @@
}
}
+ @Stable
+ class VarargConsumer(var invokeCount: Int = 0) {
+ @Composable fun Varargs(vararg ints: Int) {
+ invokeCount++
+ for (i in ints) {
+ use(i)
+ }
+ }
+ }
+
+ // Regression test for b/286132194
+ @Test
+ fun composableVarargs_skipped() = compositionTest {
+ val consumer = VarargConsumer()
+ var recomposeTrigger by mutableStateOf(0)
+ compose {
+ Linear {
+ use(recomposeTrigger)
+ consumer.Varargs(0, 1, 2, 3)
+ }
+ }
+
+ assertEquals(1, consumer.invokeCount)
+
+ recomposeTrigger = 1
+ advance()
+
+ assertEquals(1, consumer.invokeCount)
+ }
+
+ fun interface TestFunInterface {
+ fun compute(value: Int)
+ }
+
+ @Composable fun TestMemoizedFun(compute: TestFunInterface) {
+ val oldCompute = remember { compute }
+ assertEquals(oldCompute, compute)
+ }
+
+ @Test
+ fun funInterface_isMemoized() = compositionTest {
+ var recomposeTrigger by mutableStateOf(0)
+ val capture = 0
+ compose {
+ use(recomposeTrigger)
+ TestMemoizedFun {
+ // no captures
+ use(it)
+ }
+ TestMemoizedFun {
+ // stable captures
+ use(capture)
+ }
+ }
+
+ recomposeTrigger++
+ advance()
+ }
+
private inline fun CoroutineScope.withGlobalSnapshotManager(block: CoroutineScope.() -> Unit) {
val channel = Channel<Unit>(Channel.CONFLATED)
val job = launch {
diff --git a/compose/test-utils/src/commonMain/kotlin/androidx/compose/testutils/Expect.kt b/compose/test-utils/src/commonMain/kotlin/androidx/compose/testutils/Expect.kt
index 2ec325a..8c2e559 100644
--- a/compose/test-utils/src/commonMain/kotlin/androidx/compose/testutils/Expect.kt
+++ b/compose/test-utils/src/commonMain/kotlin/androidx/compose/testutils/Expect.kt
@@ -48,7 +48,7 @@
expectedMessage: String = ".*",
block: () -> Unit
) {
- val expectedClassName = T::class.java.simpleName
+ val expectedClassName = T::class.simpleName
try {
block()
} catch (thrown: Throwable) {
@@ -88,7 +88,7 @@
}
val expected = expectedClassName?.let { "a $it".plusMessage(expectedMessage) } ?: "nothing"
- val actual = thrown?.run { "a ${javaClass.simpleName}".plusMessage(message) } ?: "nothing"
+ val actual = thrown?.run { "a ${this::class.simpleName}".plusMessage(message) } ?: "nothing"
throw AssertionError(
"Expected that $expected would be thrown, but $actual was thrown$stackTrace"
)
diff --git a/compose/ui/ui-graphics/lint-baseline.xml b/compose/ui/ui-graphics/lint-baseline.xml
index 7a307a3..51329f0 100644
--- a/compose/ui/ui-graphics/lint-baseline.xml
+++ b/compose/ui/ui-graphics/lint-baseline.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.1.0-beta02" type="baseline" client="gradle" dependencies="false" name="AGP (8.1.0-beta02)" variant="all" version="8.1.0-beta02">
+<issues format="6" by="lint 8.1.0-beta05" type="baseline" client="gradle" dependencies="false" name="AGP (8.1.0-beta05)" variant="all" version="8.1.0-beta05">
<issue
id="PrimitiveInLambda"
diff --git a/compose/ui/ui-inspection/src/androidTest/java/androidx/compose/ui/inspection/ParametersTest.kt b/compose/ui/ui-inspection/src/androidTest/java/androidx/compose/ui/inspection/ParametersTest.kt
index bc87381..2e9c992 100644
--- a/compose/ui/ui-inspection/src/androidTest/java/androidx/compose/ui/inspection/ParametersTest.kt
+++ b/compose/ui/ui-inspection/src/androidTest/java/androidx/compose/ui/inspection/ParametersTest.kt
@@ -280,14 +280,7 @@
var column = composables.filter("Column").first()
var text = column.childrenList.single { strings[it.name] == "Text" }
- var paramsById = rule.inspectorTester.sendCommand(
- GetParametersByIdCommand(rule.rootId, text.id)
- ).getParametersResponse
- // We are using delayed parameter extractions so the cache does not have parameters
- // (The code should look for an anchor but the anchor is not specified.)
- assertThat(paramsById.parameterGroup.parameterList).isEmpty()
-
- // But looking up by anchor will find the parameters
+ // Lookup by anchor will find the parameters
var paramsByAnchor = rule.inspectorTester.sendCommand(
GetParametersByAnchorIdCommand(rule.rootId, text.anchorHash, text.id)
).getParametersResponse
@@ -296,6 +289,17 @@
var textValue = paramsByAnchor.find("text")
assertThat(strings[textValue.int32Value]).isEqualTo("four")
+ // Lookup parameters without anchor should fallback to the older approach and return
+ // the same parameters:
+ var paramsById = rule.inspectorTester.sendCommand(
+ GetParametersByIdCommand(rule.rootId, text.id)
+ ).getParametersResponse
+ // We are using delayed parameter extractions so the cache does not have parameters
+ // (The code should look for an anchor but the anchor is not specified.)
+ assertThat(paramsById.parameterGroup.parameterList).isNotEmpty()
+ textValue = paramsById.find("text")
+ assertThat(strings[textValue.int32Value]).isEqualTo("four")
+
val snapshot = rule.inspectorTester.sendCommand(
GetComposablesCommand(rule.rootId, extractAllParameters = true)
).getComposablesResponse
diff --git a/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/ComposeLayoutInspector.kt b/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/ComposeLayoutInspector.kt
index dbc5ced..6388008 100644
--- a/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/ComposeLayoutInspector.kt
+++ b/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/ComposeLayoutInspector.kt
@@ -29,6 +29,7 @@
import androidx.compose.ui.inspection.proto.StringTable
import androidx.compose.ui.inspection.proto.convert
import androidx.compose.ui.inspection.proto.toComposableRoot
+import androidx.compose.ui.inspection.util.NO_ANCHOR_ID
import androidx.compose.ui.inspection.util.ThreadUtils
import androidx.compose.ui.unit.IntOffset
import androidx.inspection.Connection
@@ -184,16 +185,19 @@
getParametersCommand: GetParametersCommand,
callback: CommandCallback
) {
- val foundComposable = if (delayParameterExtractions && !cachedHasAllParameters) {
- getComposableFromAnchor(getParametersCommand.anchorHash)
- } else {
- getComposableNodes(
- getParametersCommand.rootViewId,
- getParametersCommand.skipSystemComposables,
- true,
- getParametersCommand.generation
- )?.lookup?.get(getParametersCommand.composableId)
- }
+ val foundComposable =
+ if (delayParameterExtractions && !cachedHasAllParameters &&
+ getParametersCommand.anchorHash != NO_ANCHOR_ID
+ ) {
+ getComposableFromAnchor(getParametersCommand.anchorHash)
+ } else {
+ getComposableNodes(
+ getParametersCommand.rootViewId,
+ getParametersCommand.skipSystemComposables,
+ true,
+ getParametersCommand.generation
+ )?.lookup?.get(getParametersCommand.composableId)
+ }
val semanticsNode = getCachedComposableNodes(
getParametersCommand.rootViewId
)?.lookup?.get(getParametersCommand.composableId)
@@ -262,16 +266,19 @@
getParameterDetailsCommand.reference.parameterIndex,
getParameterDetailsCommand.reference.compositeIndexList
)
- val foundComposable = if (delayParameterExtractions && !cachedHasAllParameters) {
- getComposableFromAnchor(reference.anchorId)
- } else {
- getComposableNodes(
- getParameterDetailsCommand.rootViewId,
- getParameterDetailsCommand.skipSystemComposables,
- true,
- getParameterDetailsCommand.generation
- )?.lookup?.get(reference.nodeId)
- }
+ val foundComposable =
+ if (delayParameterExtractions && !cachedHasAllParameters &&
+ reference.anchorId != NO_ANCHOR_ID
+ ) {
+ getComposableFromAnchor(reference.anchorId)
+ } else {
+ getComposableNodes(
+ getParameterDetailsCommand.rootViewId,
+ getParameterDetailsCommand.skipSystemComposables,
+ true,
+ getParameterDetailsCommand.generation
+ )?.lookup?.get(reference.nodeId)
+ }
val semanticsNode = getCachedComposableNodes(
getParameterDetailsCommand.rootViewId
)?.lookup?.get(getParameterDetailsCommand.reference.composableId)
diff --git a/compose/ui/ui-test-junit4/lint-baseline.xml b/compose/ui/ui-test-junit4/lint-baseline.xml
index 1a69136..5b697f0 100644
--- a/compose/ui/ui-test-junit4/lint-baseline.xml
+++ b/compose/ui/ui-test-junit4/lint-baseline.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.0.0-beta03" type="baseline" client="gradle" dependencies="false" name="AGP (8.0.0-beta03)" variant="all" version="8.0.0-beta03">
+<issues format="6" by="lint 8.1.0-beta05" type="baseline" client="gradle" dependencies="false" name="AGP (8.1.0-beta05)" variant="all" version="8.1.0-beta05">
<issue
id="BanThreadSleep"
diff --git a/compose/ui/ui-text/lint-baseline.xml b/compose/ui/ui-text/lint-baseline.xml
index 4f7f4b5..c00a104 100644
--- a/compose/ui/ui-text/lint-baseline.xml
+++ b/compose/ui/ui-text/lint-baseline.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.1.0-beta02" type="baseline" client="gradle" dependencies="false" name="AGP (8.1.0-beta02)" variant="all" version="8.1.0-beta02">
+<issues format="6" by="lint 8.1.0-beta05" type="baseline" client="gradle" dependencies="false" name="AGP (8.1.0-beta05)" variant="all" version="8.1.0-beta05">
<issue
id="BanInlineOptIn"
diff --git a/compose/ui/ui/build.gradle b/compose/ui/ui/build.gradle
index 751a851..f88d2c1 100644
--- a/compose/ui/ui/build.gradle
+++ b/compose/ui/ui/build.gradle
@@ -198,7 +198,7 @@
// version of foundation will be at least this version. This will prevent the bug in
// foundation from occurring. This does _NOT_ require that the app have foundation as
// a dependency.
- implementation("androidx.compose.foundation:foundation:1.4.0") {
+ commonMainImplementation("androidx.compose.foundation:foundation:1.4.0") {
because 'prevents a critical bug in Text'
}
}
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidLayoutDrawTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidLayoutDrawTest.kt
index 3a416f3..93604d6 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidLayoutDrawTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidLayoutDrawTest.kt
@@ -2952,6 +2952,7 @@
@Test
fun instancesKeepDelegates() {
var color by mutableStateOf(Color.Red)
+ var size by mutableStateOf(30)
var m: Measurable? = null
val layoutCaptureModifier = object : LayoutModifier {
override fun MeasureScope.measure(
@@ -2960,15 +2961,22 @@
): MeasureResult {
m = measurable
val p = measurable.measure(constraints)
- drawLatch.countDown()
return layout(p.width, p.height) {
p.place(0, 0)
}
}
}
+ val drawCaptureModifier = object : DrawModifier {
+ override fun ContentDrawScope.draw() {
+ drawLatch.countDown()
+ }
+ }
activityTestRule.runOnUiThread {
activity.setContent {
- FixedSize(30, layoutCaptureModifier.background(color)) {}
+ FixedSize(
+ size = size,
+ modifier = layoutCaptureModifier.background(color).then(drawCaptureModifier)
+ ) {}
}
}
assertTrue(drawLatch.await(1, TimeUnit.SECONDS))
@@ -2977,6 +2985,7 @@
activityTestRule.runOnUiThread {
m = null
+ size = 40
color = Color.Blue
}
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/graphics/vector/VectorTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/graphics/vector/VectorTest.kt
index 8d021e3..f99c430 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/graphics/vector/VectorTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/graphics/vector/VectorTest.kt
@@ -543,6 +543,25 @@
}
}
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+ @Test
+ fun testVectorStrokeWidth() {
+ val strokeWidth = mutableStateOf(100)
+ rule.setContent {
+ VectorStroke(strokeWidth = strokeWidth.value)
+ }
+ takeScreenShot(200).apply {
+ assertEquals(Color.Yellow.toArgb(), getPixel(100, 25))
+ assertEquals(Color.Blue.toArgb(), getPixel(100, 75))
+ }
+ rule.runOnUiThread { strokeWidth.value = 200 }
+ rule.waitForIdle()
+ takeScreenShot(200).apply {
+ assertEquals(Color.Yellow.toArgb(), getPixel(100, 25))
+ assertEquals(Color.Yellow.toArgb(), getPixel(100, 75))
+ }
+ }
+
@Composable
private fun VectorTint(
size: Int = 200,
@@ -681,6 +700,47 @@
}
@Composable
+ private fun VectorStroke(
+ size: Int = 200,
+ strokeWidth: Int = 100,
+ minimumSize: Int = size,
+ alignment: Alignment = Alignment.Center
+ ) {
+ val sizePx = size.toFloat()
+ val sizeDp = (size / LocalDensity.current.density).dp
+ val strokeWidthPx = strokeWidth.toFloat()
+ val background = Modifier.paint(
+ rememberVectorPainter(
+ defaultWidth = sizeDp,
+ defaultHeight = sizeDp,
+ autoMirror = false
+ ) { _, _ ->
+ Path(
+ pathData = PathData {
+ lineTo(sizePx, 0.0f)
+ lineTo(sizePx, sizePx)
+ lineTo(0.0f, sizePx)
+ close()
+ },
+ fill = SolidColor(Color.Blue)
+ )
+ // A thick stroke
+ Path(
+ pathData = PathData {
+ moveTo(0.0f, 0.0f)
+ lineTo(sizePx, 0.0f)
+ },
+ stroke = SolidColor(Color.Yellow),
+ strokeLineWidth = strokeWidthPx,
+ )
+ },
+ alignment = alignment
+ )
+ AtLeastSize(size = minimumSize, modifier = background) {
+ }
+ }
+
+ @Composable
private fun VectorMirror(size: Int): VectorPainter {
val sizePx = size.toFloat()
val sizeDp = (size / LocalDensity.current.density).dp
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/graphics/vector/Vector.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/graphics/vector/Vector.kt
index 9c256c7..2eed50f 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/graphics/vector/Vector.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/graphics/vector/Vector.kt
@@ -214,6 +214,7 @@
var strokeLineWidth = DefaultStrokeLineWidth
set(value) {
field = value
+ isStrokeDirty = true
invalidate()
}
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/graphics/vector/VectorPainter.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/graphics/vector/VectorPainter.kt
index a6940e0..648babc7 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/graphics/vector/VectorPainter.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/graphics/vector/VectorPainter.kt
@@ -219,6 +219,7 @@
return next
}
+ // TODO replace with mutableStateOf(Unit, neverEqualPolicy()) after b/291647821 is addressed
private var invalidateCount by mutableIntStateOf(0)
@Composable
diff --git a/constraintlayout/constraintlayout-compose/integration-tests/compose-benchmark/build.gradle b/constraintlayout/constraintlayout-compose/integration-tests/compose-benchmark/build.gradle
new file mode 100644
index 0000000..57aa1a8
--- /dev/null
+++ b/constraintlayout/constraintlayout-compose/integration-tests/compose-benchmark/build.gradle
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+plugins {
+ id("AndroidXPlugin")
+ id("com.android.library")
+ id("AndroidXComposePlugin")
+ id("org.jetbrains.kotlin.android")
+ id("androidx.benchmark")
+}
+
+dependencies {
+ androidTestImplementation project(":constraintlayout:constraintlayout-compose")
+ androidTestImplementation project(":constraintlayout:constraintlayout-core")
+ androidTestImplementation project(":benchmark:benchmark-junit4")
+ androidTestImplementation project(":compose:runtime:runtime")
+ androidTestImplementation project(":compose:benchmark-utils")
+ androidTestImplementation(libs.testRules)
+ androidTestImplementation(libs.junit)
+ androidTestImplementation(libs.kotlinStdlib)
+ androidTestImplementation(libs.kotlinTestCommon)
+ androidTestImplementation(libs.truth)
+}
+
+android {
+ namespace "androidx.constraintlayout.compose.benchmark"
+}
\ No newline at end of file
diff --git a/constraintlayout/constraintlayout-compose/integration-tests/compose-benchmark/src/androidTest/java/androidx/constraintlayout/compose/benchmark/MotionSceneBenchmark.kt b/constraintlayout/constraintlayout-compose/integration-tests/compose-benchmark/src/androidTest/java/androidx/constraintlayout/compose/benchmark/MotionSceneBenchmark.kt
new file mode 100644
index 0000000..f3acb7c
--- /dev/null
+++ b/constraintlayout/constraintlayout-compose/integration-tests/compose-benchmark/src/androidTest/java/androidx/constraintlayout/compose/benchmark/MotionSceneBenchmark.kt
@@ -0,0 +1,290 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.constraintlayout.compose.benchmark
+
+import androidx.benchmark.junit4.BenchmarkRule
+import androidx.benchmark.junit4.measureRepeated
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.unit.dp
+import androidx.constraintlayout.compose.Dimension
+import androidx.constraintlayout.compose.Easing
+import androidx.constraintlayout.compose.MotionScene
+import androidx.constraintlayout.compose.OnSwipe
+import androidx.constraintlayout.compose.SwipeDirection
+import androidx.constraintlayout.compose.SwipeMode
+import androidx.constraintlayout.compose.SwipeSide
+import androidx.constraintlayout.compose.SwipeTouchUp
+import androidx.constraintlayout.compose.Visibility
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@LargeTest
+@RunWith(AndroidJUnit4::class)
+class MotionSceneBenchmark {
+ @get:Rule
+ val benchmarkRule = BenchmarkRule()
+
+ /**
+ * One of the most basics MotionScenes.
+ *
+ * Just a box moving from one corner to the other. Fairly minimal example.
+ */
+ @Test
+ fun motionScene_simple() {
+ benchmarkRule.measureRepeated {
+ MotionScene {
+ val boxRef = createRefFor("box")
+ defaultTransition(
+ from = constraintSet {
+ constrain(boxRef) {
+ width = 50.dp.asDimension()
+ height = 50.dp.asDimension()
+
+ top.linkTo(parent.top, 8.dp)
+ start.linkTo(parent.start, 8.dp)
+ }
+ },
+ to = constraintSet {
+ constrain(boxRef) {
+ width = 50.dp.asDimension()
+ height = 50.dp.asDimension()
+
+ bottom.linkTo(parent.bottom, 8.dp)
+ end.linkTo(parent.end, 8.dp)
+ }
+ }
+ )
+ }
+ }
+ }
+
+ /**
+ * The MotionScene was mostly a copy of `messageMotionScene()` from NewMessage.kt in the
+ * macrobenchmark-target module.
+ *
+ * It's been modified to represent a more complex scenario. Does not necessarily have to make
+ * sense since it's for benchmarking.
+ */
+ @Test
+ fun motionScene_complex() {
+ val primary = Color(0xFFF44336)
+ val primaryVariant = Color(0xFFE91E63)
+ val onPrimary = Color(0xFF673AB7)
+ val surface = Color(0xFF3F51B5)
+ val onSurface = Color(0xFF2196F3)
+
+ benchmarkRule.measureRepeated {
+ MotionScene {
+ val (box, minIcon, editClose, title, content) =
+ createRefsFor("box", "minIcon", "editClose", "title", "content")
+
+ val fab = constraintSet(NewMessageLayout.Fab.name) {
+ constrain(box) {
+ width = Dimension.value(50.dp)
+ height = Dimension.value(50.dp)
+ end.linkTo(parent.end, 12.dp)
+ bottom.linkTo(parent.bottom, 12.dp)
+
+ customColor("background", primary)
+
+ staggeredWeight = 1f
+ }
+ constrain(minIcon) {
+ width = Dimension.value(40.dp)
+ height = Dimension.value(40.dp)
+
+ end.linkTo(editClose.start, 8.dp)
+ top.linkTo(editClose.top)
+ customColor("content", onPrimary)
+ }
+ constrain(editClose) {
+ width = Dimension.value(40.dp)
+ height = Dimension.value(40.dp)
+
+ centerTo(box)
+
+ customColor("content", onPrimary)
+ }
+ constrain(title) {
+ width = Dimension.fillToConstraints
+ top.linkTo(box.top)
+ bottom.linkTo(editClose.bottom)
+ start.linkTo(box.start, 8.dp)
+ end.linkTo(minIcon.start, 8.dp)
+ customColor("content", onPrimary)
+
+ visibility = Visibility.Gone
+ }
+ constrain(content) {
+ width = Dimension.fillToConstraints
+ height = Dimension.fillToConstraints
+ start.linkTo(box.start, 8.dp)
+ end.linkTo(box.end, 8.dp)
+
+ top.linkTo(editClose.bottom, 8.dp)
+ bottom.linkTo(box.bottom, 8.dp)
+
+ visibility = Visibility.Gone
+ }
+ }
+ val full = constraintSet(NewMessageLayout.Full.name) {
+ constrain(box) {
+ width = Dimension.fillToConstraints
+ height = Dimension.fillToConstraints
+ start.linkTo(parent.start, 12.dp)
+ end.linkTo(parent.end, 12.dp)
+ bottom.linkTo(parent.bottom, 12.dp)
+ top.linkTo(parent.top, 40.dp)
+ customColor("background", surface)
+ }
+ constrain(minIcon) {
+ width = Dimension.value(40.dp)
+ height = Dimension.value(40.dp)
+
+ end.linkTo(editClose.start, 8.dp)
+ top.linkTo(editClose.top)
+ customColor("content", onSurface)
+ }
+ constrain(editClose) {
+ width = Dimension.value(40.dp)
+ height = Dimension.value(40.dp)
+
+ end.linkTo(box.end, 4.dp)
+ top.linkTo(box.top, 4.dp)
+ customColor("content", onSurface)
+ }
+ constrain(title) {
+ width = Dimension.fillToConstraints
+ top.linkTo(box.top)
+ bottom.linkTo(editClose.bottom)
+ start.linkTo(box.start, 8.dp)
+ end.linkTo(minIcon.start, 8.dp)
+ customColor("content", onSurface)
+ }
+ constrain(content) {
+ width = Dimension.fillToConstraints
+ height = Dimension.fillToConstraints
+ start.linkTo(box.start, 8.dp)
+ end.linkTo(box.end, 8.dp)
+ top.linkTo(editClose.bottom, 8.dp)
+ bottom.linkTo(box.bottom, 8.dp)
+ }
+ }
+ val mini = constraintSet(NewMessageLayout.Mini.name) {
+ constrain(box) {
+ width = Dimension.value(220.dp)
+ height = Dimension.value(50.dp)
+
+ end.linkTo(parent.end, 12.dp)
+ bottom.linkTo(parent.bottom, 12.dp)
+
+ customColor("background", primaryVariant)
+ }
+ constrain(minIcon) {
+ width = Dimension.value(40.dp)
+ height = Dimension.value(40.dp)
+
+ end.linkTo(editClose.start, 8.dp)
+ top.linkTo(editClose.top)
+
+ rotationZ = 180f
+
+ customColor("content", onPrimary)
+ }
+ constrain(editClose) {
+ width = Dimension.value(40.dp)
+ height = Dimension.value(40.dp)
+
+ end.linkTo(box.end, 4.dp)
+ top.linkTo(box.top, 4.dp)
+ customColor("content", onPrimary)
+ }
+ constrain(title) {
+ width = Dimension.fillToConstraints
+ top.linkTo(box.top)
+ bottom.linkTo(editClose.bottom)
+ start.linkTo(box.start, 8.dp)
+ end.linkTo(minIcon.start, 8.dp)
+ customColor("content", onPrimary)
+ }
+ constrain(content) {
+ width = Dimension.fillToConstraints
+ start.linkTo(box.start, 8.dp)
+ end.linkTo(box.end, 8.dp)
+
+ top.linkTo(editClose.bottom, 8.dp)
+ bottom.linkTo(box.bottom, 8.dp)
+
+ visibility = Visibility.Gone
+ }
+ }
+
+ fun constraintSetFor(layoutState: NewMessageLayout) =
+ when (layoutState) {
+ NewMessageLayout.Full -> full
+ NewMessageLayout.Mini -> mini
+ NewMessageLayout.Fab -> fab
+ }
+ defaultTransition(
+ from = constraintSetFor(NewMessageLayout.Fab),
+ to = constraintSetFor(NewMessageLayout.Full)
+ ) {
+ maxStaggerDelay = 0.6f
+
+ keyAttributes(title, content) {
+ frame(30) {
+ alpha = 0.5f
+ }
+ frame(60) {
+ alpha = 0.9f
+ }
+ }
+ }
+
+ transition(
+ from = constraintSetFor(NewMessageLayout.Full),
+ to = constraintSetFor(NewMessageLayout.Mini)
+ ) {
+ onSwipe = OnSwipe(
+ anchor = editClose,
+ side = SwipeSide.Middle,
+ direction = SwipeDirection.Down,
+ onTouchUp = SwipeTouchUp.AutoComplete,
+ mode = SwipeMode.spring(threshold = 0.001f)
+ )
+
+ keyCycles(minIcon) {
+ easing = Easing.cubic(x1 = 0.3f, y1 = 0.2f, x2 = 0.8f, y2 = 0.7f)
+ frame(50) {
+ rotationZ = 90f
+ period = 4f
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private enum class NewMessageLayout {
+ Full,
+ Mini,
+ Fab
+ }
+}
diff --git a/constraintlayout/constraintlayout-compose/src/androidAndroidTest/kotlin/androidx/constraintlayout/compose/MotionLayoutTest.kt b/constraintlayout/constraintlayout-compose/src/androidAndroidTest/kotlin/androidx/constraintlayout/compose/MotionLayoutTest.kt
index 7355336..da53a92 100644
--- a/constraintlayout/constraintlayout-compose/src/androidAndroidTest/kotlin/androidx/constraintlayout/compose/MotionLayoutTest.kt
+++ b/constraintlayout/constraintlayout-compose/src/androidAndroidTest/kotlin/androidx/constraintlayout/compose/MotionLayoutTest.kt
@@ -671,36 +671,16 @@
rule.setContent {
MotionLayout(
motionScene = remember {
- MotionScene {
- val boxRef = createRefFor(boxId)
-
- defaultTransition(
- from = constraintSet {
- constrain(boxRef) {
- width = boxSizePx.toDp().asDimension()
- height = boxSizePx.toDp().asDimension()
-
- top.linkTo(parent.top)
- start.linkTo(parent.start)
- }
- },
- to = constraintSet {
- constrain(boxRef) {
- width = boxSizePx.toDp().asDimension()
- height = boxSizePx.toDp().asDimension()
-
- top.linkTo(parent.top)
- end.linkTo(parent.end)
- }
- }
- ) {
- onSwipe = OnSwipe(
- anchor = boxRef,
- side = SwipeSide.End,
- direction = SwipeDirection.End,
- limitBoundsTo = boxRef
- )
- }
+ createCornerToCornerMotionScene(
+ boxId = boxId,
+ boxSizePx = boxSizePx
+ ) { boxRef ->
+ onSwipe = OnSwipe(
+ anchor = boxRef,
+ side = SwipeSide.End,
+ direction = SwipeDirection.End,
+ limitBoundsTo = boxRef
+ )
}
},
progress = 0f,
@@ -756,7 +736,7 @@
// Then wait for it to end
rule.waitForIdle()
// Box moved to end
- assertEquals(IntOffset(rootSizePx - boxSizePx, 0), boxPosition)
+ assertEquals(IntOffset(rootSizePx - boxSizePx, rootSizePx - boxSizePx), boxPosition)
}
@Test
@@ -844,6 +824,71 @@
assertEquals(25, actualTextSize.height.value.roundToInt())
}
+ @Test
+ fun testOnSwipe_withDragScale() = with(rule.density) {
+ val rootSizePx = 300
+ val boxSizePx = 30
+ val boxId = "box"
+ val dragScale = 3f
+ var boxPosition = IntOffset.Zero
+
+ rule.setContent {
+ MotionLayout(
+ motionScene = remember {
+ createCornerToCornerMotionScene(
+ boxId = boxId,
+ boxSizePx = boxSizePx
+ ) { boxRef ->
+ onSwipe = OnSwipe(
+ anchor = boxRef,
+ side = SwipeSide.Middle,
+ direction = SwipeDirection.Down,
+ onTouchUp = SwipeTouchUp.ToStart,
+ dragScale = dragScale
+ )
+ }
+ },
+ progress = 0f,
+ modifier = Modifier
+ .layoutTestId("MyMotion")
+ .size(rootSizePx.toDp())
+ ) {
+ Box(
+ Modifier
+ .background(Color.Red)
+ .layoutTestId(boxId)
+ .onGloballyPositioned {
+ boxPosition = it
+ .positionInParent()
+ .round()
+ }
+ )
+ }
+ }
+ rule.waitForIdle()
+ val motionSemantic = rule.onNodeWithTag("MyMotion")
+
+ motionSemantic
+ .assertExists()
+ .performSwipe(
+ from = {
+ Offset(center.x, top + (boxSizePx / 2f))
+ },
+ to = {
+ // Move only half-way, with a dragScale of 1f, it would be forced to
+ // return to the start position
+ val off = ((bottom - (boxSizePx / 2f)) - (top + (boxSizePx / 2f))) * 0.5f
+ Offset(center.x, (top + (boxSizePx / 2f)) + off)
+ }
+ )
+ // Wait a frame for the Touch Up animation to start
+ rule.mainClock.advanceTimeByFrame()
+ // Then wait for it to end
+ rule.waitForIdle()
+ // Box is at the ending position because of the increased dragScale
+ assertEquals(IntOffset(rootSizePx - boxSizePx, rootSizePx - boxSizePx), boxPosition)
+ }
+
private fun Color.toHexString(): String = toArgb().toUInt().toString(16)
}
@@ -912,3 +957,34 @@
content = content
)
}
+
+private fun Density.createCornerToCornerMotionScene(
+ boxId: String,
+ boxSizePx: Int,
+ transitionContent: TransitionScope.(boxRef: ConstrainedLayoutReference) -> Unit
+) = MotionScene {
+ val boxRef = createRefFor(boxId)
+
+ defaultTransition(
+ from = constraintSet {
+ constrain(boxRef) {
+ width = boxSizePx.toDp().asDimension()
+ height = boxSizePx.toDp().asDimension()
+
+ top.linkTo(parent.top)
+ start.linkTo(parent.start)
+ }
+ },
+ to = constraintSet {
+ constrain(boxRef) {
+ width = boxSizePx.toDp().asDimension()
+ height = boxSizePx.toDp().asDimension()
+
+ bottom.linkTo(parent.bottom)
+ end.linkTo(parent.end)
+ }
+ }
+ ) {
+ transitionContent(boxRef)
+ }
+}
diff --git a/constraintlayout/constraintlayout-compose/src/androidMain/kotlin/androidx/constraintlayout/compose/LateMotionLayout.kt b/constraintlayout/constraintlayout-compose/src/androidMain/kotlin/androidx/constraintlayout/compose/LateMotionLayout.kt
index 83926ef..1a0d0474d 100644
--- a/constraintlayout/constraintlayout-compose/src/androidMain/kotlin/androidx/constraintlayout/compose/LateMotionLayout.kt
+++ b/constraintlayout/constraintlayout-compose/src/androidMain/kotlin/androidx/constraintlayout/compose/LateMotionLayout.kt
@@ -130,7 +130,8 @@
measurables = measurables,
optimizationLevel = optimizationLevel,
progress = motionProgress.value,
- compositionSource = compositionSource.value ?: CompositionSource.Unknown
+ compositionSource = compositionSource.value ?: CompositionSource.Unknown,
+ invalidateOnConstraintsCallback = null
)
compositionSource.value = CompositionSource.Unknown // Reset after measuring
diff --git a/constraintlayout/constraintlayout-compose/src/androidMain/kotlin/androidx/constraintlayout/compose/MotionMeasurer.kt b/constraintlayout/constraintlayout-compose/src/androidMain/kotlin/androidx/constraintlayout/compose/MotionMeasurer.kt
index e3b4da5..dd09496 100644
--- a/constraintlayout/constraintlayout-compose/src/androidMain/kotlin/androidx/constraintlayout/compose/MotionMeasurer.kt
+++ b/constraintlayout/constraintlayout-compose/src/androidMain/kotlin/androidx/constraintlayout/compose/MotionMeasurer.kt
@@ -93,6 +93,7 @@
source = compositionSource,
invalidateOnConstraintsCallback = invalidateOnConstraintsCallback
)
+
if (lastProgressInInterpolation != progress ||
(layoutInformationReceiver?.getForcedWidth() != Int.MIN_VALUE &&
layoutInformationReceiver?.getForcedHeight() != Int.MIN_VALUE) ||
diff --git a/constraintlayout/constraintlayout-compose/src/androidMain/kotlin/androidx/constraintlayout/compose/TransitionScope.kt b/constraintlayout/constraintlayout-compose/src/androidMain/kotlin/androidx/constraintlayout/compose/TransitionScope.kt
index 48ce6c4..da4523a4 100644
--- a/constraintlayout/constraintlayout-compose/src/androidMain/kotlin/androidx/constraintlayout/compose/TransitionScope.kt
+++ b/constraintlayout/constraintlayout-compose/src/androidMain/kotlin/androidx/constraintlayout/compose/TransitionScope.kt
@@ -257,7 +257,7 @@
onSwipe?.let {
containerObject.put("onSwipe", onSwipeObject)
onSwipeObject.putString("direction", it.direction.name)
- onSwipeObject.putNumber("dragScale", it.dragScale)
+ onSwipeObject.putNumber("scale", it.dragScale)
it.dragAround?.id?.let { id ->
onSwipeObject.putString("around", id.toString())
}
diff --git a/coordinatorlayout/coordinatorlayout/src/androidTest/java/androidx/coordinatorlayout/widget/CoordinatorSnackbarWithButtonTest.java b/coordinatorlayout/coordinatorlayout/src/androidTest/java/androidx/coordinatorlayout/widget/CoordinatorSnackbarWithButtonTest.java
index 203ba8b..e66d965 100644
--- a/coordinatorlayout/coordinatorlayout/src/androidTest/java/androidx/coordinatorlayout/widget/CoordinatorSnackbarWithButtonTest.java
+++ b/coordinatorlayout/coordinatorlayout/src/androidTest/java/androidx/coordinatorlayout/widget/CoordinatorSnackbarWithButtonTest.java
@@ -40,6 +40,7 @@
import org.hamcrest.Matcher;
import org.junit.After;
+import org.junit.Ignore;
import org.junit.Test;
import java.util.concurrent.CountDownLatch;
@@ -132,6 +133,7 @@
verifyBarViewStacking(textView, 0);
}
+ @Ignore // b/292019798
@Test
public void testBehaviorBasedSlidingFromClassAnnotation() {
// Use a layout in which a custom child view has Behavior object configured via
@@ -160,6 +162,7 @@
verifyBarViewStacking(textView, 0);
}
+ @Ignore // b/292021877
@Test
public void testBehaviorBasedSlidingFromRuntimeApiCall() {
// Use a layout in which a TextView child doesn't have any configured Behavior
diff --git a/core/core-animation/src/main/java/androidx/core/animation/ValueAnimator.java b/core/core-animation/src/main/java/androidx/core/animation/ValueAnimator.java
index 3d468ca..20c2325 100644
--- a/core/core-animation/src/main/java/androidx/core/animation/ValueAnimator.java
+++ b/core/core-animation/src/main/java/androidx/core/animation/ValueAnimator.java
@@ -27,7 +27,6 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
-import androidx.tracing.Trace;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -1164,7 +1163,6 @@
}
// mReversing needs to be reset *after* notifying the listeners for the end callbacks.
mReversing = false;
- Trace.endSection();
}
/**
@@ -1172,9 +1170,6 @@
* called on the UI thread.
*/
private void startAnimation() {
-
- Trace.beginSection(getNameForTrace());
-
mAnimationEndRequested = false;
initAnimation();
mRunning = true;
diff --git a/core/core-performance-play-services/build.gradle b/core/core-performance-play-services/build.gradle
index f6643d6..42cab7be 100644
--- a/core/core-performance-play-services/build.gradle
+++ b/core/core-performance-play-services/build.gradle
@@ -25,7 +25,12 @@
dependencies {
api(libs.kotlinStdlib)
+ implementation(libs.playServicesDevicePerformance)
+
+ // Coroutines
implementation(libs.kotlinCoroutinesCore)
+ implementation("org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.1.1")
+
implementation(project(":core:core-performance"))
testImplementation(libs.testCore)
@@ -37,6 +42,9 @@
}
android {
+ defaultConfig {
+ minSdkVersion 24
+ }
namespace "androidx.core.performance.play.services"
}
diff --git a/core/core-performance-play-services/src/main/java/androidx/core/performance/play/services/PlayServicesDevicePerformanceRetriever.kt b/core/core-performance-play-services/src/main/java/androidx/core/performance/play/services/PlayServicesDevicePerformanceRetriever.kt
index 0682f5a..ea1ea30 100644
--- a/core/core-performance-play-services/src/main/java/androidx/core/performance/play/services/PlayServicesDevicePerformanceRetriever.kt
+++ b/core/core-performance-play-services/src/main/java/androidx/core/performance/play/services/PlayServicesDevicePerformanceRetriever.kt
@@ -18,6 +18,9 @@
import android.content.Context
import androidx.core.performance.DevicePerformanceRetriever
+import com.google.android.gms.deviceperformance.DevicePerformanceClient
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.tasks.await
/**
* A DevicePerformanceRetriever that uses Google Play Services to retrieve media performance class data.
@@ -31,10 +34,11 @@
);
companion object {
- @Suppress("UNUSED_PARAMETER")
@JvmStatic
fun getPerformanceClass(context: Context): Int {
- return 0
+ val client: DevicePerformanceClient =
+ com.google.android.gms.deviceperformance.DevicePerformance.getClient(context)
+ return runBlocking { client.mediaPerformanceClass().await() }
}
}
}
diff --git a/core/core-performance-testing/build.gradle b/core/core-performance-testing/build.gradle
index 2109a27..39b4dd1 100644
--- a/core/core-performance-testing/build.gradle
+++ b/core/core-performance-testing/build.gradle
@@ -36,6 +36,9 @@
}
android {
+ defaultConfig {
+ minSdkVersion 24
+ }
namespace "androidx.core.performance.testing"
}
diff --git a/core/core-performance-testing/src/test/java/androidx/core/performance/testing/FakeDevicePerformanceRetrieverTest.kt b/core/core-performance-testing/src/test/java/androidx/core/performance/testing/FakeDevicePerformanceRetrieverTest.kt
new file mode 100644
index 0000000..b45d2d7
--- /dev/null
+++ b/core/core-performance-testing/src/test/java/androidx/core/performance/testing/FakeDevicePerformanceRetrieverTest.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.core.performance.testing
+
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+
+/** Unit test for [FakeDevicePerformanceRetriever]. */
+class FakeDevicePerformanceRetrieverTest {
+
+ @Test
+ fun mediaPerformanceClass_30() {
+ val retriever = FakeDevicePerformanceRetriever(30)
+ val mpc = retriever.getPerformanceClass()
+ assertThat(mpc).isEqualTo(30)
+ }
+}
diff --git a/core/core-performance/build.gradle b/core/core-performance/build.gradle
index 044a5ad..3dd2a66 100644
--- a/core/core-performance/build.gradle
+++ b/core/core-performance/build.gradle
@@ -47,5 +47,8 @@
}
android {
+ defaultConfig {
+ minSdkVersion 24
+ }
namespace "androidx.core.performance"
}
diff --git a/core/core-performance/samples/build.gradle b/core/core-performance/samples/build.gradle
index cbd177a..0087990 100644
--- a/core/core-performance/samples/build.gradle
+++ b/core/core-performance/samples/build.gradle
@@ -38,5 +38,8 @@
}
android {
+ defaultConfig {
+ minSdkVersion 24
+ }
namespace "androidx.core.performance.samples"
}
diff --git a/core/core-performance/src/test/java/androidx/core/performance/MediaPerformanceTest.kt b/core/core-performance/src/test/java/androidx/core/performance/MediaPerformanceTest.kt
new file mode 100644
index 0000000..2f30c43
--- /dev/null
+++ b/core/core-performance/src/test/java/androidx/core/performance/MediaPerformanceTest.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.core.performance
+
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+
+/** Unit tests for [MediaPerformance]. */
+class MediaPerformanceTest {
+
+ @Test
+ fun mediaPerformanceClassWithUnspecifiedRetriever() {
+ val mpc = MediaPerformance.getPerformanceClass()
+ assertThat(mpc).isEqualTo(0)
+ }
+
+ @Test
+ fun mediaPerformanceClassWithSpecifiedDefaultRetriever() {
+ val mpc = MediaPerformance.getPerformanceClass(
+ DefaultDevicePerformanceRetriever()
+ )
+ assertThat(mpc).isEqualTo(0)
+ }
+}
diff --git a/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/InCallAudioTest.kt b/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/InCallAudioTest.kt
index 453ec4d..280fbdb 100644
--- a/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/InCallAudioTest.kt
+++ b/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/InCallAudioTest.kt
@@ -19,10 +19,12 @@
import android.media.AudioManager.MODE_IN_COMMUNICATION
import android.os.Build
import android.telecom.DisconnectCause
+import android.util.Log
import androidx.annotation.RequiresApi
import androidx.core.telecom.internal.utils.Utils
import androidx.core.telecom.test.utils.BaseTelecomTest
import androidx.core.telecom.test.utils.TestUtils
+import androidx.core.telecom.test.utils.TestUtils.getAudioModeName
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import androidx.test.filters.SdkSuppress
@@ -55,6 +57,7 @@
@RequiresApi(Build.VERSION_CODES.O)
@RunWith(AndroidJUnit4::class)
class InCallAudioTest : BaseTelecomTest() {
+ val LOG_TAG = "InCallAudioTest"
@Before
fun setUp() {
Utils.resetUtils()
@@ -117,8 +120,12 @@
val deferred = CompletableDeferred<Unit>()
assertWithinTimeout_addCall(deferred, TestUtils.OUTGOING_CALL_ATTRIBUTES) {
launch {
+ Log.i(LOG_TAG, "runBlocking_addCall_assertAudioModeInCommunication: " +
+ "initial AudioManager mode = ${getAudioModeName(mAudioManager.mode)}")
while (isActive /* aka within timeout window */ &&
mAudioManager.mode != MODE_IN_COMMUNICATION) {
+ Log.d(LOG_TAG, "runBlocking_addCall_assertAudioModeInCommunication: " +
+ "current AudioManager mode = ${getAudioModeName(mAudioManager.mode)}")
yield() // mechanism to stop the while loop if the coroutine is dead
delay(1) // sleep x millisecond(s) instead of spamming check
}
diff --git a/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/utils/BaseTelecomTest.kt b/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/utils/BaseTelecomTest.kt
index d7451f3..20abe0a 100644
--- a/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/utils/BaseTelecomTest.kt
+++ b/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/utils/BaseTelecomTest.kt
@@ -20,6 +20,7 @@
import android.content.pm.PackageManager
import android.media.AudioManager
import android.os.Build
+import android.telecom.DisconnectCause
import android.telecom.PhoneAccountHandle
import android.telecom.TelecomManager
import android.util.Log
@@ -117,6 +118,7 @@
ManagedConnectionService.mPendingConnectionRequests.clear()
}
MockInCallService.destroyAllCalls()
+ TestUtils.runShellCommand(TestUtils.COMMAND_CLEANUP_STUCK_CALLS)
}
private fun isInCallFromTelDumpsys(telecomDumpsysString: String): Pair<Boolean, String> {
@@ -146,11 +148,16 @@
setCallback: Boolean = true,
assertBlock: CallControlScope.() -> (Unit)
) {
+ Log.i(TestUtils.LOG_TAG, "assertWithinTimeout_addCall")
+ var callControlScope: CallControlScope? = null
try {
withTimeout(TestUtils.WAIT_ON_ASSERTS_TO_FINISH_TIMEOUT) {
mCallsManager.addCall(attributes) {
+ callControlScope = this
if (setCallback) {
setCallback(TestUtils.mCallControlCallbacksImpl)
+ Log.i(TestUtils.LOG_TAG, "assertWithinTimeout_addCall: setCallback " +
+ "to ${TestUtils.mCallControlCallbacksImpl}")
}
assertBlock()
}
@@ -161,6 +168,7 @@
} catch (timeout: TimeoutCancellationException) {
Log.i(TestUtils.LOG_TAG, "assertWithinTimeout: reached timeout; dumping telecom")
TestUtils.dumpTelecom()
+ callControlScope?.disconnect(DisconnectCause(DisconnectCause.LOCAL, "timeout in test"))
Assert.fail(TestUtils.VERIFICATION_TIMEOUT_MSG)
}
}
diff --git a/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/utils/TestUtils.kt b/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/utils/TestUtils.kt
index d38fd18..68c9de5 100644
--- a/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/utils/TestUtils.kt
+++ b/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/utils/TestUtils.kt
@@ -17,6 +17,7 @@
package androidx.core.telecom.test.utils
import android.content.Context
+import android.media.AudioManager
import android.net.Uri
import android.os.Build.VERSION_CODES
import android.os.UserHandle
@@ -46,8 +47,8 @@
const val TEST_PACKAGE = "androidx.core.telecom.test"
const val COMMAND_SET_DEFAULT_DIALER = "telecom set-default-dialer " // DO NOT REMOVE SPACE
const val COMMAND_GET_DEFAULT_DIALER = "telecom get-default-dialer"
- const val COMMAND_CLEANUP_STUCK_CALLS = "cleanup-stuck-calls"
const val COMMAND_ENABLE_PHONE_ACCOUNT = "telecom set-phone-account-enabled "
+ const val COMMAND_CLEANUP_STUCK_CALLS = "telecom cleanup-stuck-calls"
const val COMMAND_DUMP_TELECOM = "dumpsys telecom"
const val TEST_CALL_ATTRIB_NAME = "Elon Musk"
const val OUTGOING_NAME = "Larry Page"
@@ -225,6 +226,19 @@
)
}
+ fun getAudioModeName(mode: Int): String {
+ return when (mode) {
+ AudioManager.MODE_NORMAL -> "MODE_NORMAL"
+ AudioManager.MODE_RINGTONE -> "MODE_RINGTONE"
+ AudioManager.MODE_IN_CALL -> "MODE_IN_CALL"
+ AudioManager.MODE_IN_COMMUNICATION -> "MODE_IN_COMMUNICATION"
+ AudioManager.MODE_CALL_SCREENING -> "MODE_CALL_SCREENING"
+ AudioManager.MODE_CALL_REDIRECT -> "MODE_CALL_REDIRECT"
+ AudioManager.MODE_COMMUNICATION_REDIRECT -> "MODE_COMMUNICATION_REDIRECT"
+ else -> "UNKNOWN mode = <$mode>"
+ }
+ }
+
fun enablePhoneAccountHandle(context: Context, phoneAccountHandle: PhoneAccountHandle) {
val pn = phoneAccountHandle.componentName.packageName
val cn = phoneAccountHandle.componentName.className
diff --git a/core/core/api/1.12.0-beta01.txt b/core/core/api/1.12.0-beta01.txt
index 4bb3fb0..7ff5c2a 100644
--- a/core/core/api/1.12.0-beta01.txt
+++ b/core/core/api/1.12.0-beta01.txt
@@ -2390,6 +2390,7 @@
public class TypedValueCompat {
method public static float deriveDimension(int, float, android.util.DisplayMetrics);
method public static float dpToPx(float, android.util.DisplayMetrics);
+ method public static int getUnitFromComplexDimension(int);
method public static float pxToDp(float, android.util.DisplayMetrics);
method public static float pxToSp(float, android.util.DisplayMetrics);
method public static float spToPx(float, android.util.DisplayMetrics);
@@ -4199,6 +4200,7 @@
method public static void setFirstBaselineToTopHeight(android.widget.TextView, @IntRange(from=0) @Px int);
method public static void setLastBaselineToBottomHeight(android.widget.TextView, @IntRange(from=0) @Px int);
method public static void setLineHeight(android.widget.TextView, @IntRange(from=0) @Px int);
+ method public static void setLineHeight(android.widget.TextView, int, @FloatRange(from=0) float);
method public static void setPrecomputedText(android.widget.TextView, androidx.core.text.PrecomputedTextCompat);
method public static void setTextAppearance(android.widget.TextView, @StyleRes int);
method public static void setTextMetricsParams(android.widget.TextView, androidx.core.text.PrecomputedTextCompat.Params);
diff --git a/core/core/api/current.ignore b/core/core/api/current.ignore
index f623e8e..a3517f0 100644
--- a/core/core/api/current.ignore
+++ b/core/core/api/current.ignore
@@ -1,51 +1,5 @@
// Baseline format: 1.0
-AddedClass: androidx.core.view.accessibility.AccessibilityNodeInfoCompat.CollectionItemInfoCompat.Builder:
- Added class androidx.core.view.accessibility.AccessibilityNodeInfoCompat.CollectionItemInfoCompat.Builder
-
-
-AddedField: androidx.core.view.accessibility.AccessibilityEventCompat#CONTENT_CHANGE_TYPE_CONTENT_INVALID:
- Added field androidx.core.view.accessibility.AccessibilityEventCompat.CONTENT_CHANGE_TYPE_CONTENT_INVALID
-AddedField: androidx.core.view.accessibility.AccessibilityEventCompat#CONTENT_CHANGE_TYPE_ENABLED:
- Added field androidx.core.view.accessibility.AccessibilityEventCompat.CONTENT_CHANGE_TYPE_ENABLED
-AddedField: androidx.core.view.accessibility.AccessibilityEventCompat#CONTENT_CHANGE_TYPE_ERROR:
- Added field androidx.core.view.accessibility.AccessibilityEventCompat.CONTENT_CHANGE_TYPE_ERROR
-AddedField: androidx.core.view.accessibility.AccessibilityNodeInfoCompat#FLAG_PREFETCH_ANCESTORS:
- Added field androidx.core.view.accessibility.AccessibilityNodeInfoCompat.FLAG_PREFETCH_ANCESTORS
-AddedField: androidx.core.view.accessibility.AccessibilityNodeInfoCompat#FLAG_PREFETCH_DESCENDANTS_BREADTH_FIRST:
- Added field androidx.core.view.accessibility.AccessibilityNodeInfoCompat.FLAG_PREFETCH_DESCENDANTS_BREADTH_FIRST
-AddedField: androidx.core.view.accessibility.AccessibilityNodeInfoCompat#FLAG_PREFETCH_DESCENDANTS_DEPTH_FIRST:
- Added field androidx.core.view.accessibility.AccessibilityNodeInfoCompat.FLAG_PREFETCH_DESCENDANTS_DEPTH_FIRST
-AddedField: androidx.core.view.accessibility.AccessibilityNodeInfoCompat#FLAG_PREFETCH_DESCENDANTS_HYBRID:
- Added field androidx.core.view.accessibility.AccessibilityNodeInfoCompat.FLAG_PREFETCH_DESCENDANTS_HYBRID
-AddedField: androidx.core.view.accessibility.AccessibilityNodeInfoCompat#FLAG_PREFETCH_SIBLINGS:
- Added field androidx.core.view.accessibility.AccessibilityNodeInfoCompat.FLAG_PREFETCH_SIBLINGS
-AddedField: androidx.core.view.accessibility.AccessibilityNodeInfoCompat#FLAG_PREFETCH_UNINTERRUPTIBLE:
- Added field androidx.core.view.accessibility.AccessibilityNodeInfoCompat.FLAG_PREFETCH_UNINTERRUPTIBLE
-AddedField: androidx.core.view.accessibility.AccessibilityNodeInfoCompat#MAX_NUMBER_OF_PREFETCHED_NODES:
- Added field androidx.core.view.accessibility.AccessibilityNodeInfoCompat.MAX_NUMBER_OF_PREFETCHED_NODES
-AddedField: androidx.core.view.accessibility.AccessibilityWindowInfoCompat#TYPE_MAGNIFICATION_OVERLAY:
- Added field androidx.core.view.accessibility.AccessibilityWindowInfoCompat.TYPE_MAGNIFICATION_OVERLAY
-
-
-AddedMethod: androidx.core.view.accessibility.AccessibilityNodeInfoCompat#getBoundsInWindow(android.graphics.Rect):
- Added method androidx.core.view.accessibility.AccessibilityNodeInfoCompat.getBoundsInWindow(android.graphics.Rect)
-AddedMethod: androidx.core.view.accessibility.AccessibilityNodeInfoCompat#getChild(int, int):
- Added method androidx.core.view.accessibility.AccessibilityNodeInfoCompat.getChild(int,int)
-AddedMethod: androidx.core.view.accessibility.AccessibilityNodeInfoCompat#getContainerTitle():
- Added method androidx.core.view.accessibility.AccessibilityNodeInfoCompat.getContainerTitle()
-AddedMethod: androidx.core.view.accessibility.AccessibilityNodeInfoCompat#getParent(int):
- Added method androidx.core.view.accessibility.AccessibilityNodeInfoCompat.getParent(int)
-AddedMethod: androidx.core.view.accessibility.AccessibilityNodeInfoCompat#setBoundsInWindow(android.graphics.Rect):
- Added method androidx.core.view.accessibility.AccessibilityNodeInfoCompat.setBoundsInWindow(android.graphics.Rect)
-AddedMethod: androidx.core.view.accessibility.AccessibilityNodeInfoCompat#setContainerTitle(CharSequence):
- Added method androidx.core.view.accessibility.AccessibilityNodeInfoCompat.setContainerTitle(CharSequence)
-AddedMethod: androidx.core.view.accessibility.AccessibilityNodeInfoCompat#setQueryFromAppProcessEnabled(android.view.View, boolean):
- Added method androidx.core.view.accessibility.AccessibilityNodeInfoCompat.setQueryFromAppProcessEnabled(android.view.View,boolean)
-AddedMethod: androidx.core.view.accessibility.AccessibilityNodeInfoCompat.CollectionItemInfoCompat#getColumnTitle():
- Added method androidx.core.view.accessibility.AccessibilityNodeInfoCompat.CollectionItemInfoCompat.getColumnTitle()
-AddedMethod: androidx.core.view.accessibility.AccessibilityNodeInfoCompat.CollectionItemInfoCompat#getRowTitle():
- Added method androidx.core.view.accessibility.AccessibilityNodeInfoCompat.CollectionItemInfoCompat.getRowTitle()
-AddedMethod: androidx.core.view.accessibility.AccessibilityNodeInfoCompat.RangeInfoCompat#RangeInfoCompat(int, float, float, float):
- Added constructor androidx.core.view.accessibility.AccessibilityNodeInfoCompat.RangeInfoCompat(int,float,float,float)
-AddedMethod: androidx.core.view.accessibility.AccessibilityWindowInfoCompat#getRoot(int):
- Added method androidx.core.view.accessibility.AccessibilityWindowInfoCompat.getRoot(int)
+AddedMethod: androidx.core.util.TypedValueCompat#getUnitFromComplexDimension(int):
+ Added method androidx.core.util.TypedValueCompat.getUnitFromComplexDimension(int)
+AddedMethod: androidx.core.widget.TextViewCompat#setLineHeight(android.widget.TextView, int, float):
+ Added method androidx.core.widget.TextViewCompat.setLineHeight(android.widget.TextView,int,float)
diff --git a/core/core/api/current.txt b/core/core/api/current.txt
index 4bb3fb0..7ff5c2a 100644
--- a/core/core/api/current.txt
+++ b/core/core/api/current.txt
@@ -2390,6 +2390,7 @@
public class TypedValueCompat {
method public static float deriveDimension(int, float, android.util.DisplayMetrics);
method public static float dpToPx(float, android.util.DisplayMetrics);
+ method public static int getUnitFromComplexDimension(int);
method public static float pxToDp(float, android.util.DisplayMetrics);
method public static float pxToSp(float, android.util.DisplayMetrics);
method public static float spToPx(float, android.util.DisplayMetrics);
@@ -4199,6 +4200,7 @@
method public static void setFirstBaselineToTopHeight(android.widget.TextView, @IntRange(from=0) @Px int);
method public static void setLastBaselineToBottomHeight(android.widget.TextView, @IntRange(from=0) @Px int);
method public static void setLineHeight(android.widget.TextView, @IntRange(from=0) @Px int);
+ method public static void setLineHeight(android.widget.TextView, int, @FloatRange(from=0) float);
method public static void setPrecomputedText(android.widget.TextView, androidx.core.text.PrecomputedTextCompat);
method public static void setTextAppearance(android.widget.TextView, @StyleRes int);
method public static void setTextMetricsParams(android.widget.TextView, androidx.core.text.PrecomputedTextCompat.Params);
diff --git a/core/core/api/restricted_1.12.0-beta01.txt b/core/core/api/restricted_1.12.0-beta01.txt
index 102534f..7be35d5 100644
--- a/core/core/api/restricted_1.12.0-beta01.txt
+++ b/core/core/api/restricted_1.12.0-beta01.txt
@@ -2814,6 +2814,7 @@
public class TypedValueCompat {
method public static float deriveDimension(int, float, android.util.DisplayMetrics);
method public static float dpToPx(float, android.util.DisplayMetrics);
+ method public static int getUnitFromComplexDimension(int);
method public static float pxToDp(float, android.util.DisplayMetrics);
method public static float pxToSp(float, android.util.DisplayMetrics);
method public static float spToPx(float, android.util.DisplayMetrics);
@@ -4700,6 +4701,7 @@
method public static void setFirstBaselineToTopHeight(android.widget.TextView, @IntRange(from=0) @Px int);
method public static void setLastBaselineToBottomHeight(android.widget.TextView, @IntRange(from=0) @Px int);
method public static void setLineHeight(android.widget.TextView, @IntRange(from=0) @Px int);
+ method public static void setLineHeight(android.widget.TextView, int, @FloatRange(from=0) float);
method public static void setPrecomputedText(android.widget.TextView, androidx.core.text.PrecomputedTextCompat);
method public static void setTextAppearance(android.widget.TextView, @StyleRes int);
method public static void setTextMetricsParams(android.widget.TextView, androidx.core.text.PrecomputedTextCompat.Params);
diff --git a/core/core/api/restricted_current.ignore b/core/core/api/restricted_current.ignore
index f623e8e..a3517f0 100644
--- a/core/core/api/restricted_current.ignore
+++ b/core/core/api/restricted_current.ignore
@@ -1,51 +1,5 @@
// Baseline format: 1.0
-AddedClass: androidx.core.view.accessibility.AccessibilityNodeInfoCompat.CollectionItemInfoCompat.Builder:
- Added class androidx.core.view.accessibility.AccessibilityNodeInfoCompat.CollectionItemInfoCompat.Builder
-
-
-AddedField: androidx.core.view.accessibility.AccessibilityEventCompat#CONTENT_CHANGE_TYPE_CONTENT_INVALID:
- Added field androidx.core.view.accessibility.AccessibilityEventCompat.CONTENT_CHANGE_TYPE_CONTENT_INVALID
-AddedField: androidx.core.view.accessibility.AccessibilityEventCompat#CONTENT_CHANGE_TYPE_ENABLED:
- Added field androidx.core.view.accessibility.AccessibilityEventCompat.CONTENT_CHANGE_TYPE_ENABLED
-AddedField: androidx.core.view.accessibility.AccessibilityEventCompat#CONTENT_CHANGE_TYPE_ERROR:
- Added field androidx.core.view.accessibility.AccessibilityEventCompat.CONTENT_CHANGE_TYPE_ERROR
-AddedField: androidx.core.view.accessibility.AccessibilityNodeInfoCompat#FLAG_PREFETCH_ANCESTORS:
- Added field androidx.core.view.accessibility.AccessibilityNodeInfoCompat.FLAG_PREFETCH_ANCESTORS
-AddedField: androidx.core.view.accessibility.AccessibilityNodeInfoCompat#FLAG_PREFETCH_DESCENDANTS_BREADTH_FIRST:
- Added field androidx.core.view.accessibility.AccessibilityNodeInfoCompat.FLAG_PREFETCH_DESCENDANTS_BREADTH_FIRST
-AddedField: androidx.core.view.accessibility.AccessibilityNodeInfoCompat#FLAG_PREFETCH_DESCENDANTS_DEPTH_FIRST:
- Added field androidx.core.view.accessibility.AccessibilityNodeInfoCompat.FLAG_PREFETCH_DESCENDANTS_DEPTH_FIRST
-AddedField: androidx.core.view.accessibility.AccessibilityNodeInfoCompat#FLAG_PREFETCH_DESCENDANTS_HYBRID:
- Added field androidx.core.view.accessibility.AccessibilityNodeInfoCompat.FLAG_PREFETCH_DESCENDANTS_HYBRID
-AddedField: androidx.core.view.accessibility.AccessibilityNodeInfoCompat#FLAG_PREFETCH_SIBLINGS:
- Added field androidx.core.view.accessibility.AccessibilityNodeInfoCompat.FLAG_PREFETCH_SIBLINGS
-AddedField: androidx.core.view.accessibility.AccessibilityNodeInfoCompat#FLAG_PREFETCH_UNINTERRUPTIBLE:
- Added field androidx.core.view.accessibility.AccessibilityNodeInfoCompat.FLAG_PREFETCH_UNINTERRUPTIBLE
-AddedField: androidx.core.view.accessibility.AccessibilityNodeInfoCompat#MAX_NUMBER_OF_PREFETCHED_NODES:
- Added field androidx.core.view.accessibility.AccessibilityNodeInfoCompat.MAX_NUMBER_OF_PREFETCHED_NODES
-AddedField: androidx.core.view.accessibility.AccessibilityWindowInfoCompat#TYPE_MAGNIFICATION_OVERLAY:
- Added field androidx.core.view.accessibility.AccessibilityWindowInfoCompat.TYPE_MAGNIFICATION_OVERLAY
-
-
-AddedMethod: androidx.core.view.accessibility.AccessibilityNodeInfoCompat#getBoundsInWindow(android.graphics.Rect):
- Added method androidx.core.view.accessibility.AccessibilityNodeInfoCompat.getBoundsInWindow(android.graphics.Rect)
-AddedMethod: androidx.core.view.accessibility.AccessibilityNodeInfoCompat#getChild(int, int):
- Added method androidx.core.view.accessibility.AccessibilityNodeInfoCompat.getChild(int,int)
-AddedMethod: androidx.core.view.accessibility.AccessibilityNodeInfoCompat#getContainerTitle():
- Added method androidx.core.view.accessibility.AccessibilityNodeInfoCompat.getContainerTitle()
-AddedMethod: androidx.core.view.accessibility.AccessibilityNodeInfoCompat#getParent(int):
- Added method androidx.core.view.accessibility.AccessibilityNodeInfoCompat.getParent(int)
-AddedMethod: androidx.core.view.accessibility.AccessibilityNodeInfoCompat#setBoundsInWindow(android.graphics.Rect):
- Added method androidx.core.view.accessibility.AccessibilityNodeInfoCompat.setBoundsInWindow(android.graphics.Rect)
-AddedMethod: androidx.core.view.accessibility.AccessibilityNodeInfoCompat#setContainerTitle(CharSequence):
- Added method androidx.core.view.accessibility.AccessibilityNodeInfoCompat.setContainerTitle(CharSequence)
-AddedMethod: androidx.core.view.accessibility.AccessibilityNodeInfoCompat#setQueryFromAppProcessEnabled(android.view.View, boolean):
- Added method androidx.core.view.accessibility.AccessibilityNodeInfoCompat.setQueryFromAppProcessEnabled(android.view.View,boolean)
-AddedMethod: androidx.core.view.accessibility.AccessibilityNodeInfoCompat.CollectionItemInfoCompat#getColumnTitle():
- Added method androidx.core.view.accessibility.AccessibilityNodeInfoCompat.CollectionItemInfoCompat.getColumnTitle()
-AddedMethod: androidx.core.view.accessibility.AccessibilityNodeInfoCompat.CollectionItemInfoCompat#getRowTitle():
- Added method androidx.core.view.accessibility.AccessibilityNodeInfoCompat.CollectionItemInfoCompat.getRowTitle()
-AddedMethod: androidx.core.view.accessibility.AccessibilityNodeInfoCompat.RangeInfoCompat#RangeInfoCompat(int, float, float, float):
- Added constructor androidx.core.view.accessibility.AccessibilityNodeInfoCompat.RangeInfoCompat(int,float,float,float)
-AddedMethod: androidx.core.view.accessibility.AccessibilityWindowInfoCompat#getRoot(int):
- Added method androidx.core.view.accessibility.AccessibilityWindowInfoCompat.getRoot(int)
+AddedMethod: androidx.core.util.TypedValueCompat#getUnitFromComplexDimension(int):
+ Added method androidx.core.util.TypedValueCompat.getUnitFromComplexDimension(int)
+AddedMethod: androidx.core.widget.TextViewCompat#setLineHeight(android.widget.TextView, int, float):
+ Added method androidx.core.widget.TextViewCompat.setLineHeight(android.widget.TextView,int,float)
diff --git a/core/core/api/restricted_current.txt b/core/core/api/restricted_current.txt
index 102534f..7be35d5 100644
--- a/core/core/api/restricted_current.txt
+++ b/core/core/api/restricted_current.txt
@@ -2814,6 +2814,7 @@
public class TypedValueCompat {
method public static float deriveDimension(int, float, android.util.DisplayMetrics);
method public static float dpToPx(float, android.util.DisplayMetrics);
+ method public static int getUnitFromComplexDimension(int);
method public static float pxToDp(float, android.util.DisplayMetrics);
method public static float pxToSp(float, android.util.DisplayMetrics);
method public static float spToPx(float, android.util.DisplayMetrics);
@@ -4700,6 +4701,7 @@
method public static void setFirstBaselineToTopHeight(android.widget.TextView, @IntRange(from=0) @Px int);
method public static void setLastBaselineToBottomHeight(android.widget.TextView, @IntRange(from=0) @Px int);
method public static void setLineHeight(android.widget.TextView, @IntRange(from=0) @Px int);
+ method public static void setLineHeight(android.widget.TextView, int, @FloatRange(from=0) float);
method public static void setPrecomputedText(android.widget.TextView, androidx.core.text.PrecomputedTextCompat);
method public static void setTextAppearance(android.widget.TextView, @StyleRes int);
method public static void setTextMetricsParams(android.widget.TextView, androidx.core.text.PrecomputedTextCompat.Params);
diff --git a/core/core/build.gradle b/core/core/build.gradle
index 149662a..967e12e 100644
--- a/core/core/build.gradle
+++ b/core/core/build.gradle
@@ -57,6 +57,7 @@
androidTestImplementation(project(":internal-testutils-mockito"))
testImplementation(libs.junit)
+ testImplementation(libs.testExtJunit)
testImplementation(libs.testCore)
testImplementation(libs.testRunner)
testImplementation(libs.truth)
diff --git a/core/core/src/androidTest/java/androidx/core/math/MathUtilsTest.java b/core/core/src/androidTest/java/androidx/core/math/MathUtilsTest.java
index 856acd4..a717d3d 100644
--- a/core/core/src/androidTest/java/androidx/core/math/MathUtilsTest.java
+++ b/core/core/src/androidTest/java/androidx/core/math/MathUtilsTest.java
@@ -17,7 +17,6 @@
package androidx.core.math;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertThrows;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
@@ -30,78 +29,6 @@
public class MathUtilsTest {
@Test
- public void testAddExact() {
- assertEquals(2, MathUtils.addExact(1, 1));
- assertEquals(2L, MathUtils.addExact(1L, 1L));
- assertEquals(0, MathUtils.addExact(1, -1));
- assertEquals(0L, MathUtils.addExact(1L, -1L));
- assertThrows(ArithmeticException.class, () -> MathUtils.addExact(Integer.MAX_VALUE, 1));
- assertThrows(ArithmeticException.class, () -> MathUtils.addExact(Long.MAX_VALUE, 1L));
- assertThrows(ArithmeticException.class, () -> MathUtils.addExact(Integer.MIN_VALUE, -1));
- assertThrows(ArithmeticException.class, () -> MathUtils.addExact(Long.MIN_VALUE, -1L));
- }
-
- @Test
- public void testSubtractExact() {
- assertEquals(0, MathUtils.subtractExact(1, 1));
- assertEquals(0L, MathUtils.subtractExact(1L, 1L));
- assertEquals(2, MathUtils.subtractExact(1, -1));
- assertEquals(2L, MathUtils.subtractExact(1L, -1L));
- assertThrows(ArithmeticException.class,
- () -> MathUtils.subtractExact(Integer.MAX_VALUE, -1));
- assertThrows(ArithmeticException.class, () -> MathUtils.subtractExact(Long.MAX_VALUE, -1L));
- assertThrows(ArithmeticException.class,
- () -> MathUtils.subtractExact(Integer.MIN_VALUE, 1));
- assertThrows(ArithmeticException.class, () -> MathUtils.subtractExact(Long.MIN_VALUE, 1L));
- }
-
- @Test
- public void testMultiplyExact() {
- assertEquals(4, MathUtils.multiplyExact(2, 2));
- assertEquals(4L, MathUtils.multiplyExact(2L, 2L));
- assertEquals(0, MathUtils.multiplyExact(2, 0));
- assertEquals(0L, MathUtils.multiplyExact(2L, 0L));
- assertEquals(-4, MathUtils.multiplyExact(2, -2));
- assertEquals(-4L, MathUtils.multiplyExact(2L, -2L));
- assertThrows(ArithmeticException.class,
- () -> MathUtils.multiplyExact(Integer.MAX_VALUE, 2));
- assertThrows(ArithmeticException.class, () -> MathUtils.multiplyExact(Long.MAX_VALUE, 2L));
- assertThrows(ArithmeticException.class,
- () -> MathUtils.multiplyExact(Integer.MIN_VALUE, 2));
- assertThrows(ArithmeticException.class, () -> MathUtils.multiplyExact(Long.MIN_VALUE, 2L));
- }
-
- @Test
- public void testIncrementExact() {
- assertEquals(1, MathUtils.incrementExact(0));
- assertEquals(1L, MathUtils.incrementExact(0L));
- assertThrows(ArithmeticException.class, () -> MathUtils.incrementExact(Integer.MAX_VALUE));
- assertThrows(ArithmeticException.class, () -> MathUtils.incrementExact(Long.MAX_VALUE));
- }
-
- @Test
- public void testDecrementExact() {
- assertEquals(-1, MathUtils.decrementExact(0));
- assertEquals(-1L, MathUtils.decrementExact(0L));
- assertThrows(ArithmeticException.class, () -> MathUtils.decrementExact(Integer.MIN_VALUE));
- assertThrows(ArithmeticException.class, () -> MathUtils.decrementExact(Long.MIN_VALUE));
- }
-
- @Test
- public void testNegateExact() {
- assertEquals(Integer.MIN_VALUE + 1, MathUtils.negateExact(Integer.MAX_VALUE));
- assertEquals(Long.MIN_VALUE + 1, MathUtils.negateExact(Long.MAX_VALUE));
- assertThrows(ArithmeticException.class, () -> MathUtils.negateExact(Integer.MIN_VALUE));
- assertThrows(ArithmeticException.class, () -> MathUtils.negateExact(Long.MIN_VALUE));
- }
-
- @Test
- public void testToIntExact() {
- assertEquals(1, MathUtils.toIntExact(1L));
- assertThrows(ArithmeticException.class, () -> MathUtils.toIntExact(Long.MAX_VALUE));
- }
-
- @Test
public void testClamp() {
// Int
assertEquals(0, MathUtils.clamp(-4, 0, 7));
diff --git a/core/core/src/main/java/androidx/core/content/ContextCompat.java b/core/core/src/main/java/androidx/core/content/ContextCompat.java
index bb53f32..d283059 100644
--- a/core/core/src/main/java/androidx/core/content/ContextCompat.java
+++ b/core/core/src/main/java/androidx/core/content/ContextCompat.java
@@ -806,10 +806,10 @@
* @param context Context to retrieve service from.
* @param receiver The BroadcastReceiver to handle the broadcast.
* @param filter Selects the Intent broadcasts to be received.
- * @param flags Specify one of {@link #RECEIVER_EXPORTED}, if you wish for your receiver
- * to be able to receiver broadcasts from other applications, or
- * {@link #RECEIVER_NOT_EXPORTED} if you only want your receiver to be able
- * to receive broadcasts from the system or your own app.
+ * @param flags If this receiver is listening for broadcasts sent from the system or from
+ * other apps—even other apps that you own—use the {@link #RECEIVER_EXPORTED}
+ * flag. If instead this receiver is listening only for broadcasts sent by your
+ * app, use the {@link #RECEIVER_NOT_EXPORTED} flag.
* @return The first sticky intent found that matches <var>filter</var>,
* or null if there are none.
* @see Context#registerReceiver(BroadcastReceiver, IntentFilter, int)
@@ -832,11 +832,11 @@
* required.
* @param scheduler Handler identifying the thread will receive the Intent. If
* null, the main thread of the process will be used.
- * @param flags Specify one of {@link #RECEIVER_EXPORTED}, if you wish for your
- * receiver to be able to receiver broadcasts from other
- * applications, or {@link #RECEIVER_NOT_EXPORTED} if you only want
- * your receiver to be able to receive broadcasts from the system
- * or your own app.
+ * @param flags If this receiver is listening for broadcasts sent from the
+ * system or from other apps—even other apps that you own—use the
+ * {@link #RECEIVER_EXPORTED} flag. If instead this receiver is
+ * listening only for broadcasts sent by your app, use the
+ * {@link #RECEIVER_NOT_EXPORTED} flag.
* @return The first sticky intent found that matches <var>filter</var>,
* or null if there are none.
* @see Context#registerReceiver(BroadcastReceiver, IntentFilter, String, Handler, int)
diff --git a/core/core/src/main/java/androidx/core/math/MathUtils.java b/core/core/src/main/java/androidx/core/math/MathUtils.java
index 8a42777..f6129cc 100644
--- a/core/core/src/main/java/androidx/core/math/MathUtils.java
+++ b/core/core/src/main/java/androidx/core/math/MathUtils.java
@@ -24,172 +24,225 @@
private MathUtils() {}
/**
- * See {@link Math#addExact(int, int)}.
+ * Returns the sum of its arguments, throwing an exception if the result overflows an
+ * {@code int}.
+ *
+ * @param x the first value
+ * @param y the second value
+ * @return the result
+ * @throws ArithmeticException if the result overflows an int
*/
public static int addExact(int x, int y) {
- // copied from Math.java
- int r = x + y;
- // HD 2-12 Overflow iff both arguments have the opposite sign of the result
- if (((x ^ r) & (y ^ r)) < 0) {
+ int sum = x + y;
+ // If x and y have the same sign, their sum should have the same sign as well
+ if ((x >= 0 == y >= 0) && (x >= 0 != sum >= 0)) {
throw new ArithmeticException("integer overflow");
+ } else {
+ return sum;
}
- return r;
}
/**
- * See {@link Math#addExact(long, long)}.
+ * Returns the sum of its arguments, throwing an exception if the result overflows a
+ * {@code long}.
+ *
+ * @param x the first value
+ * @param y the second value
+ * @return the result
+ * @throws ArithmeticException if the result overflows a long
*/
public static long addExact(long x, long y) {
- // copied from Math.java
- long r = x + y;
- // HD 2-12 Overflow iff both arguments have the opposite sign of the result
- if (((x ^ r) & (y ^ r)) < 0) {
- throw new ArithmeticException("long overflow");
+ long sum = x + y;
+ // If x and y have the same sign, their sum should have the same sign as well
+ if ((x >= 0 == y >= 0) && (x >= 0 != sum >= 0)) {
+ throw new ArithmeticException("integer overflow");
+ } else {
+ return sum;
}
- return r;
}
-
/**
- * See {@link Math#subtractExact(int, int)}.
+ * Returns the difference of the arguments, throwing an exception if the result overflows an
+ * {@code int}.
+ *
+ * @param x the first value
+ * @param y the second value to subtract from the first
+ * @return the result
+ * @throws ArithmeticException if the result overflows an int
*/
public static int subtractExact(int x, int y) {
- // copied from Math.java
- int r = x - y;
- // HD 2-12 Overflow iff the arguments have different signs and
- // the sign of the result is different than the sign of x
- if (((x ^ y) & (x ^ r)) < 0) {
+ int difference = x - y;
+ // If only one of x or y is negative, the difference should have the same sign as x
+ if ((x < 0 != y < 0) && (x < 0 != difference < 0)) {
throw new ArithmeticException("integer overflow");
}
- return r;
+ return difference;
}
/**
- * See {@link Math#subtractExact(long, long)}.
+ * Returns the difference of the arguments, throwing an exception if the result overflows a
+ * {@code long}.
+ *
+ * @param x the first value
+ * @param y the second value to subtract from the first
+ * @return the result
+ * @throws ArithmeticException if the result overflows a long
*/
public static long subtractExact(long x, long y) {
- // copied from Math.java
- long r = x - y;
- // HD 2-12 Overflow iff the arguments have different signs and
- // the sign of the result is different than the sign of x
- if (((x ^ y) & (x ^ r)) < 0) {
- throw new ArithmeticException("long overflow");
+ long difference = x - y;
+ // If only one of x or y is negative, the difference should have the same sign as x
+ if ((x < 0 != y < 0) && (x < 0 != difference < 0)) {
+ throw new ArithmeticException("integer overflow");
}
- return r;
+ return difference;
}
/**
- * See {@link Math#multiplyExact(int, int)}.
+ * Returns the product of the arguments, throwing an exception if the result overflows an
+ * {@code int}.
+ *
+ * @param x the first value
+ * @param y the second value
+ * @return the result
+ * @throws ArithmeticException if the result overflows an int
*/
public static int multiplyExact(int x, int y) {
- // copied from Math.java
- long r = (long) x * (long) y;
- if ((int) r != r) {
+ int product = x * y;
+ // Dividing back by one of x or y should get the other back unless there was overflow
+ if (x != 0 && y != 0 && (product / x != y || product / y != x)) {
throw new ArithmeticException("integer overflow");
}
- return (int) r;
+ return product;
}
/**
- * See {@link Math#multiplyExact(long, long)}.
+ * Returns the product of the arguments, throwing an exception if the result overflows a
+ * {@code long}.
+ *
+ * @param x the first value
+ * @param y the second value
+ * @return the result
+ * @throws ArithmeticException if the result overflows a long
*/
public static long multiplyExact(long x, long y) {
- // copied from Math.java
- long r = x * y;
- long ax = Math.abs(x);
- long ay = Math.abs(y);
- if (((ax | ay) >>> 31 != 0)) {
- // Some bits greater than 2^31 that might cause overflow
- // Check the result using the divide operator
- // and check for the special case of Long.MIN_VALUE * -1
- if (((y != 0) && (r / y != x)) || (x == Long.MIN_VALUE && y == -1)) {
- throw new ArithmeticException("long overflow");
- }
+ long product = x * y;
+ // Dividing back by one of x or y should get the other back unless there was overflow
+ if (x != 0 && y != 0 && (product / x != y || product / y != x)) {
+ throw new ArithmeticException("integer overflow");
}
- return r;
+ return product;
}
/**
- * See {@link Math#incrementExact(int)}.
+ * Returns the argument incremented by one, throwing an exception if the result overflows an
+ * {@code int}. The overflow only occurs for {@linkplain Integer#MAX_VALUE the maximum value}.
+ *
+ * @param a the value to increment
+ * @return the result
+ * @throws ArithmeticException if the result overflows an int
*/
public static int incrementExact(int a) {
- // copied from Math.java
if (a == Integer.MAX_VALUE) {
throw new ArithmeticException("integer overflow");
+ } else {
+ return a + 1;
}
-
- return a + 1;
}
/**
- * See {@link Math#incrementExact(long)}.
+ * Returns the argument incremented by one, throwing an exception if the result overflows a
+ * {@code long}. The overflow only occurs for {@linkplain Long#MAX_VALUE the maximum value}.
+ *
+ * @param a the value to increment
+ * @return the result
+ * @throws ArithmeticException if the result overflows a long
*/
public static long incrementExact(long a) {
- // copied from Math.java
if (a == Long.MAX_VALUE) {
- throw new ArithmeticException("long overflow");
+ throw new ArithmeticException("integer overflow");
+ } else {
+ return a + 1;
}
-
- return a + 1L;
}
/**
- * See {@link Math#decrementExact(int)}.
+ * Returns the argument decremented by one, throwing an exception if the result overflows an
+ * {@code int}. The overflow only occurs for {@linkplain Integer#MIN_VALUE the minimum value}.
+ *
+ * @param a the value to decrement
+ * @return the result
+ * @throws ArithmeticException if the result overflows an int
*/
public static int decrementExact(int a) {
- // copied from Math.java
if (a == Integer.MIN_VALUE) {
throw new ArithmeticException("integer overflow");
+ } else {
+ return a - 1;
}
-
- return a - 1;
}
/**
- * See {@link Math#decrementExact(long)}.
+ * Returns the argument decremented by one, throwing an exception if the result overflows a
+ * {@code long}. The overflow only occurs for {@linkplain Long#MIN_VALUE the minimum value}.
+ *
+ * @param a the value to decrement
+ * @return the result
+ * @throws ArithmeticException if the result overflows a long
*/
public static long decrementExact(long a) {
- // copied from Math.java
if (a == Long.MIN_VALUE) {
- throw new ArithmeticException("long overflow");
+ throw new ArithmeticException("integer overflow");
+ } else {
+ return a - 1;
}
-
- return a - 1L;
}
/**
- * See {@link Math#negateExact(int)}.
+ * Returns the negation of the argument, throwing an exception if the result overflows an
+ * {@code int}. The overflow only occurs for {@linkplain Integer#MIN_VALUE the minimum value}.
+ *
+ * @param a the value to negate
+ * @return the result
+ * @throws ArithmeticException if the result overflows an int
*/
public static int negateExact(int a) {
- // copied from Math.java
if (a == Integer.MIN_VALUE) {
throw new ArithmeticException("integer overflow");
+ } else {
+ return -a;
}
-
- return -a;
}
/**
- * See {@link Math#negateExact(long)}.
+ * Returns the negation of the argument, throwing an exception if the result overflows a
+ * {@code long}. The overflow only occurs for {@linkplain Long#MIN_VALUE the minimum value}.
+ *
+ * @param a the value to negate
+ * @return the result
+ * @throws ArithmeticException if the result overflows a long
*/
public static long negateExact(long a) {
- // copied from Math.java
if (a == Long.MIN_VALUE) {
- throw new ArithmeticException("long overflow");
+ throw new ArithmeticException("integer overflow");
+ } else {
+ return -a;
}
-
- return -a;
}
/**
- * See {@link Math#toIntExact(long)}.
+ * Returns the value of the {@code long} argument, throwing an exception if the value
+ * overflows an {@code int}.
+ *
+ * @param value the long value
+ * @return the argument as an int
+ * @throws ArithmeticException if the {@code argument} overflows an int
*/
public static int toIntExact(long value) {
- // copied from Math.java
- if ((int) value != value) {
+ if (value > Integer.MAX_VALUE || value < Integer.MIN_VALUE) {
throw new ArithmeticException("integer overflow");
+ } else {
+ return (int) value;
}
- return (int) value;
}
/**
diff --git a/core/core/src/main/java/androidx/core/util/TypedValueCompat.java b/core/core/src/main/java/androidx/core/util/TypedValueCompat.java
index 49f3bab..55f102d 100644
--- a/core/core/src/main/java/androidx/core/util/TypedValueCompat.java
+++ b/core/core/src/main/java/androidx/core/util/TypedValueCompat.java
@@ -44,6 +44,17 @@
private TypedValueCompat() {}
/**
+ * Return the complex unit type for the given complex dimension. For example, a dimen type
+ * with value 12sp will return {@link TypedValue#COMPLEX_UNIT_SP}.
+ *
+ * @param complexDimension the dimension, typically {@link TypedValue#data}
+ * @return The complex unit type
+ */
+ public static int getUnitFromComplexDimension(int complexDimension) {
+ return TypedValue.COMPLEX_UNIT_MASK & (complexDimension >> TypedValue.COMPLEX_UNIT_SHIFT);
+ }
+
+ /**
* Converts a pixel value to the given dimension, e.g. PX to DP.
*
* <p>This is the inverse of {@link TypedValue#applyDimension(int, float, DisplayMetrics)}
diff --git a/core/core/src/main/java/androidx/core/widget/TextViewCompat.java b/core/core/src/main/java/androidx/core/widget/TextViewCompat.java
index 915d0af2..0e58694 100644
--- a/core/core/src/main/java/androidx/core/widget/TextViewCompat.java
+++ b/core/core/src/main/java/androidx/core/widget/TextViewCompat.java
@@ -54,6 +54,7 @@
import androidx.annotation.DoNotInline;
import androidx.annotation.DrawableRes;
+import androidx.annotation.FloatRange;
import androidx.annotation.IntDef;
import androidx.annotation.IntRange;
import androidx.annotation.NonNull;
@@ -806,6 +807,7 @@
* Sets an explicit line height for this TextView. This is equivalent to the vertical distance
* between subsequent baselines in the TextView.
*
+ * @param textView the TextView to modify
* @param lineHeight the line height in pixels
*
* @see TextView#setLineSpacing(float, float)
@@ -827,6 +829,38 @@
}
/**
+ * Sets an explicit line height to a given unit and value for the TextView. This is equivalent
+ * to the vertical distance between subsequent baselines in the TextView. See {@link
+ * TypedValue} for the possible dimension units.
+ *
+ * @param textView the TextView to modify
+ * @param unit The desired dimension unit. SP units are strongly recommended so that line height
+ * stays proportional to the text size when fonts are scaled up for accessibility.
+ * @param lineHeight The desired line height in the given units.
+ *
+ * @see TextView#setLineSpacing(float, float)
+ * @see TextView#getLineSpacingExtra()
+ *
+ * @attr ref android.R.styleable#TextView_lineHeight
+ */
+ public static void setLineHeight(
+ @NonNull TextView textView,
+ int unit,
+ @FloatRange(from = 0) float lineHeight
+ ) {
+ if (Build.VERSION.SDK_INT >= 34) {
+ Api34Impl.setLineHeight(textView, unit, lineHeight);
+ } else {
+ float lineHeightPx = TypedValue.applyDimension(
+ unit,
+ lineHeight,
+ textView.getResources().getDisplayMetrics()
+ );
+ setLineHeight(textView, Math.round(lineHeightPx));
+ }
+ }
+
+ /**
* Gets the parameters for text layout precomputation, for use with
* {@link PrecomputedTextCompat}.
*
@@ -1283,4 +1317,20 @@
return DecimalFormatSymbols.getInstance(locale);
}
}
+
+ @RequiresApi(34)
+ static class Api34Impl {
+ private Api34Impl() {
+ // This class is not instantiable.
+ }
+
+ @DoNotInline
+ public static void setLineHeight(
+ @NonNull TextView textView,
+ int unit,
+ @FloatRange(from = 0) float lineHeight
+ ) {
+ textView.setLineHeight(unit, lineHeight);
+ }
+ }
}
diff --git a/core/core/src/test/java/androidx/core/math/MathUtilsTest.kt b/core/core/src/test/java/androidx/core/math/MathUtilsTest.kt
new file mode 100644
index 0000000..3074b56
--- /dev/null
+++ b/core/core/src/test/java/androidx/core/math/MathUtilsTest.kt
@@ -0,0 +1,354 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.core.math
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import org.junit.Assert
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class MathUtilsTest {
+
+ @Test
+ fun testAddExact() {
+ // zero + zero
+ Assert.assertEquals(0, MathUtils.addExact(0, 0).toLong())
+ Assert.assertEquals(0L, MathUtils.addExact(0L, 0L))
+ // positive + positive
+ Assert.assertEquals(2, MathUtils.addExact(1, 1).toLong())
+ Assert.assertEquals(2L, MathUtils.addExact(1L, 1L))
+ // negative + negative
+ Assert.assertEquals(-2, MathUtils.addExact(-1, -1).toLong())
+ Assert.assertEquals(-2L, MathUtils.addExact(-1L, -1L))
+ // positive + negative
+ Assert.assertEquals(0, MathUtils.addExact(1, -1).toLong())
+ Assert.assertEquals(0L, MathUtils.addExact(1L, -1L))
+ Assert.assertEquals(-1, MathUtils.addExact(1, -2).toLong())
+ Assert.assertEquals(-1L, MathUtils.addExact(1L, -2L))
+ Assert.assertEquals(1, MathUtils.addExact(2, -1).toLong())
+ Assert.assertEquals(1L, MathUtils.addExact(2L, -1L))
+ // negative + positive
+ Assert.assertEquals(0, MathUtils.addExact(-1, 1).toLong())
+ Assert.assertEquals(0L, MathUtils.addExact(-1L, 1L))
+ Assert.assertEquals(1, MathUtils.addExact(-1, 2).toLong())
+ Assert.assertEquals(1L, MathUtils.addExact(-1L, 2L))
+ Assert.assertEquals(-1, MathUtils.addExact(-2, 1).toLong())
+ Assert.assertEquals(-1L, MathUtils.addExact(-2L, 1L))
+ // zero + positive, positive + zero
+ Assert.assertEquals(1, MathUtils.addExact(0, 1).toLong())
+ Assert.assertEquals(1L, MathUtils.addExact(0L, 1L))
+ Assert.assertEquals(1, MathUtils.addExact(1, 0).toLong())
+ Assert.assertEquals(1L, MathUtils.addExact(1L, 0L))
+ // zero + negative, negative + zero
+ Assert.assertEquals(-1, MathUtils.addExact(0, -1).toLong())
+ Assert.assertEquals(-1L, MathUtils.addExact(0L, -1L))
+ Assert.assertEquals(-1, MathUtils.addExact(-1, 0).toLong())
+ Assert.assertEquals(-1L, MathUtils.addExact(-1L, 0L))
+ Assert.assertThrows(
+ ArithmeticException::class.java
+ ) {
+ MathUtils.addExact(
+ Int.MAX_VALUE,
+ 1
+ )
+ }
+ Assert.assertThrows(
+ ArithmeticException::class.java
+ ) {
+ MathUtils.addExact(
+ Long.MAX_VALUE,
+ 1L
+ )
+ }
+ Assert.assertThrows(
+ ArithmeticException::class.java
+ ) {
+ MathUtils.addExact(
+ Int.MIN_VALUE,
+ -1
+ )
+ }
+ Assert.assertThrows(
+ ArithmeticException::class.java
+ ) {
+ MathUtils.addExact(
+ Long.MIN_VALUE,
+ -1L
+ )
+ }
+ Assert.assertThrows(
+ ArithmeticException::class.java
+ ) {
+ MathUtils.addExact(
+ Integer.MIN_VALUE,
+ Integer.MIN_VALUE
+ )
+ }
+ Assert.assertThrows(
+ ArithmeticException::class.java
+ ) {
+ MathUtils.addExact(
+ Long.MIN_VALUE,
+ Long.MIN_VALUE
+ )
+ }
+ Assert.assertThrows(
+ ArithmeticException::class.java
+ ) {
+ MathUtils.addExact(
+ Integer.MAX_VALUE,
+ Integer.MAX_VALUE
+ )
+ }
+ Assert.assertThrows(
+ ArithmeticException::class.java
+ ) {
+ MathUtils.addExact(
+ Long.MAX_VALUE,
+ Long.MAX_VALUE
+ )
+ }
+ }
+
+ @Test
+ fun testSubtractExact() {
+ // zero - zero
+ Assert.assertEquals(0, MathUtils.subtractExact(0, 0).toLong())
+ Assert.assertEquals(0L, MathUtils.subtractExact(0L, 0L))
+ // positive - positive
+ Assert.assertEquals(0, MathUtils.subtractExact(1, 1).toLong())
+ Assert.assertEquals(0L, MathUtils.subtractExact(1L, 1L))
+ Assert.assertEquals(1, MathUtils.subtractExact(2, 1).toLong())
+ Assert.assertEquals(1L, MathUtils.subtractExact(2L, 1L))
+ Assert.assertEquals(-1, MathUtils.subtractExact(1, 2).toLong())
+ Assert.assertEquals(-1L, MathUtils.subtractExact(1L, 2L))
+ // negative - negative
+ Assert.assertEquals(0, MathUtils.subtractExact(-1, -1).toLong())
+ Assert.assertEquals(0L, MathUtils.subtractExact(-1L, -1L))
+ Assert.assertEquals(-1, MathUtils.subtractExact(-2, -1).toLong())
+ Assert.assertEquals(-1L, MathUtils.subtractExact(-2L, -1L))
+ Assert.assertEquals(1, MathUtils.subtractExact(-1, -2).toLong())
+ Assert.assertEquals(1L, MathUtils.subtractExact(-1L, -2L))
+ // positive - negative, negative - positive
+ Assert.assertEquals(2, MathUtils.subtractExact(1, -1).toLong())
+ Assert.assertEquals(2L, MathUtils.subtractExact(1L, -1L))
+ Assert.assertEquals(-2, MathUtils.subtractExact(-1, 1).toLong())
+ Assert.assertEquals(-2L, MathUtils.subtractExact(-1L, 1L))
+ // zero - positive, positive - zero
+ Assert.assertEquals(-1, MathUtils.subtractExact(0, 1).toLong())
+ Assert.assertEquals(-1L, MathUtils.subtractExact(0L, 1L))
+ Assert.assertEquals(1, MathUtils.subtractExact(1, 0).toLong())
+ Assert.assertEquals(1L, MathUtils.subtractExact(1L, 0L))
+ // zero - negative, negative - zero
+ Assert.assertEquals(1, MathUtils.subtractExact(0, -1).toLong())
+ Assert.assertEquals(1L, MathUtils.subtractExact(0L, -1L))
+ Assert.assertEquals(-1, MathUtils.subtractExact(-1, 0).toLong())
+ Assert.assertEquals(-1L, MathUtils.subtractExact(-1L, 0))
+ Assert.assertThrows(
+ ArithmeticException::class.java
+ ) {
+ MathUtils.subtractExact(
+ Int.MAX_VALUE,
+ -1
+ )
+ }
+ Assert.assertThrows(
+ ArithmeticException::class.java
+ ) {
+ MathUtils.subtractExact(
+ Long.MAX_VALUE,
+ -1L
+ )
+ }
+ Assert.assertThrows(
+ ArithmeticException::class.java
+ ) {
+ MathUtils.subtractExact(
+ Int.MIN_VALUE,
+ 1
+ )
+ }
+ Assert.assertThrows(
+ ArithmeticException::class.java
+ ) {
+ MathUtils.subtractExact(
+ Long.MIN_VALUE,
+ 1L
+ )
+ }
+ Assert.assertThrows(
+ ArithmeticException::class.java
+ ) {
+ MathUtils.subtractExact(
+ 0,
+ Int.MIN_VALUE
+ )
+ }
+ Assert.assertThrows(
+ ArithmeticException::class.java
+ ) {
+ MathUtils.subtractExact(
+ 0,
+ Long.MIN_VALUE
+ )
+ }
+ }
+
+ @Test
+ fun testMultiplyExact() {
+ Assert.assertEquals(0, MathUtils.multiplyExact(0, 0).toLong())
+ Assert.assertEquals(4, MathUtils.multiplyExact(2, 2).toLong())
+ Assert.assertEquals(0L, MathUtils.multiplyExact(0L, 0L))
+ Assert.assertEquals(4L, MathUtils.multiplyExact(2L, 2L))
+ Assert.assertEquals(0, MathUtils.multiplyExact(2, 0).toLong())
+ Assert.assertEquals(0L, MathUtils.multiplyExact(2L, 0L))
+ Assert.assertEquals(-4, MathUtils.multiplyExact(2, -2).toLong())
+ Assert.assertEquals(-4L, MathUtils.multiplyExact(2L, -2L))
+ Assert.assertThrows(
+ ArithmeticException::class.java
+ ) {
+ MathUtils.multiplyExact(
+ Int.MAX_VALUE,
+ 2
+ )
+ }
+ Assert.assertThrows(
+ ArithmeticException::class.java
+ ) {
+ MathUtils.multiplyExact(
+ Long.MAX_VALUE,
+ 2L
+ )
+ }
+ Assert.assertThrows(
+ ArithmeticException::class.java
+ ) {
+ MathUtils.multiplyExact(
+ Int.MIN_VALUE,
+ 2
+ )
+ }
+ Assert.assertThrows(
+ ArithmeticException::class.java
+ ) {
+ MathUtils.multiplyExact(
+ Long.MIN_VALUE,
+ 2L
+ )
+ }
+ Assert.assertThrows(
+ ArithmeticException::class.java
+ ) {
+ MathUtils.multiplyExact(
+ Int.MAX_VALUE / 2 + 1,
+ 2
+ )
+ }
+ Assert.assertThrows(
+ ArithmeticException::class.java
+ ) {
+ MathUtils.multiplyExact(
+ Long.MAX_VALUE / 2L + 1L,
+ 2L
+ )
+ }
+ Assert.assertThrows(
+ ArithmeticException::class.java
+ ) {
+ MathUtils.multiplyExact(
+ Int.MIN_VALUE / 2 - 1,
+ 2
+ )
+ }
+ Assert.assertThrows(
+ ArithmeticException::class.java
+ ) {
+ MathUtils.multiplyExact(
+ Long.MIN_VALUE / 2L - 1L,
+ 2L
+ )
+ }
+ Assert.assertThrows(
+ ArithmeticException::class.java
+ ) {
+ MathUtils.multiplyExact(
+ Int.MIN_VALUE,
+ -1
+ )
+ }
+ Assert.assertThrows(
+ ArithmeticException::class.java
+ ) {
+ MathUtils.multiplyExact(
+ Long.MIN_VALUE,
+ -1L
+ )
+ }
+ }
+
+ @Test
+ fun testIncrementExact() {
+ Assert.assertEquals(1, MathUtils.incrementExact(0).toLong())
+ Assert.assertEquals(1L, MathUtils.incrementExact(0L))
+ Assert.assertThrows(
+ ArithmeticException::class.java
+ ) { MathUtils.incrementExact(Int.MAX_VALUE) }
+ Assert.assertThrows(
+ ArithmeticException::class.java
+ ) { MathUtils.incrementExact(Long.MAX_VALUE) }
+ }
+
+ @Test
+ fun testDecrementExact() {
+ Assert.assertEquals(-1, MathUtils.decrementExact(0).toLong())
+ Assert.assertEquals(-1L, MathUtils.decrementExact(0L))
+ Assert.assertThrows(
+ ArithmeticException::class.java
+ ) { MathUtils.decrementExact(Int.MIN_VALUE) }
+ Assert.assertThrows(
+ ArithmeticException::class.java
+ ) { MathUtils.decrementExact(Long.MIN_VALUE) }
+ }
+
+ @Test
+ fun testNegateExact() {
+ Assert.assertEquals(
+ (Int.MIN_VALUE + 1).toLong(),
+ MathUtils.negateExact(Int.MAX_VALUE).toLong()
+ )
+ Assert.assertEquals(Long.MIN_VALUE + 1, MathUtils.negateExact(Long.MAX_VALUE))
+ Assert.assertThrows(
+ ArithmeticException::class.java
+ ) { MathUtils.negateExact(Int.MIN_VALUE) }
+ Assert.assertThrows(
+ ArithmeticException::class.java
+ ) { MathUtils.negateExact(Long.MIN_VALUE) }
+ }
+
+ @Test
+ fun testToIntExact() {
+ Assert.assertEquals(1, MathUtils.toIntExact(1L).toLong())
+ Assert.assertThrows(
+ ArithmeticException::class.java
+ ) { MathUtils.toIntExact(Long.MAX_VALUE) }
+ Assert.assertThrows(
+ ArithmeticException::class.java
+ ) { MathUtils.toIntExact(Long.MIN_VALUE) }
+ }
+}
diff --git a/credentials/credentials-play-services-auth/src/androidTest/AndroidManifest.xml b/credentials/credentials-play-services-auth/src/androidTest/AndroidManifest.xml
index 73badf7..d7b0e91 100644
--- a/credentials/credentials-play-services-auth/src/androidTest/AndroidManifest.xml
+++ b/credentials/credentials-play-services-auth/src/androidTest/AndroidManifest.xml
@@ -21,9 +21,5 @@
android:name="androidx.credentials.playservices.TestCredentialsActivity"
android:exported="false"
/>
- <activity
- android:name="androidx.credentials.playservices.TestCredentialsFragmentActivity"
- android:exported="false"
- />
</application>
</manifest>
\ No newline at end of file
diff --git a/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/TestCredentialsFragmentActivity.kt b/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/TestCredentialsFragmentActivity.kt
deleted file mode 100644
index f7ad106..0000000
--- a/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/TestCredentialsFragmentActivity.kt
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.credentials.playservices
-
-import android.os.Build
-import androidx.annotation.RequiresApi
-import androidx.fragment.app.FragmentActivity
-
-/**
- * This is a test activity used by the Robolectric Activity Scenario tests. It acts as a calling
- * activity in our test cases. This activity uses fragments.
- */
-@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP_MR1)
-class TestCredentialsFragmentActivity : FragmentActivity()
diff --git a/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/beginsignin/CredentialProviderBeginSignInControllerJavaTest.java b/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/beginsignin/CredentialProviderBeginSignInControllerJavaTest.java
index acbfe57..e4cddda 100644
--- a/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/beginsignin/CredentialProviderBeginSignInControllerJavaTest.java
+++ b/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/beginsignin/CredentialProviderBeginSignInControllerJavaTest.java
@@ -20,14 +20,12 @@
import static org.junit.Assert.assertThrows;
-import android.app.Activity;
-import android.os.Build;
-
import androidx.credentials.GetCredentialRequest;
import androidx.credentials.GetPasswordOption;
import androidx.credentials.playservices.TestCredentialsActivity;
import androidx.credentials.playservices.controllers.BeginSignIn.CredentialProviderBeginSignInController;
import androidx.test.core.app.ActivityScenario;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.google.android.gms.auth.api.identity.BeginSignInRequest;
@@ -35,56 +33,19 @@
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
import java.util.HashSet;
import java.util.List;
-@RunWith(Parameterized.class)
+@RunWith(AndroidJUnit4.class)
@SmallTest
@SuppressWarnings("deprecation")
public class CredentialProviderBeginSignInControllerJavaTest {
-
- private final boolean mUseFragmentActivity;
-
- @Parameterized.Parameters
- public static Object[] data() {
- return new Object[] {true, false};
- }
-
- public CredentialProviderBeginSignInControllerJavaTest(final boolean useFragmentActivity)
- throws Throwable {
- mUseFragmentActivity = useFragmentActivity;
- }
-
- interface TestActivityListener {
- void onActivity(Activity a);
- }
-
- private void launchTestActivity(TestActivityListener listener) {
- if (mUseFragmentActivity && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) {
- ActivityScenario<androidx.credentials.playservices.TestCredentialsFragmentActivity>
- activityScenario =
- ActivityScenario.launch(
- androidx.credentials.playservices
- .TestCredentialsFragmentActivity.class);
- activityScenario.onActivity(
- activity -> {
- listener.onActivity((Activity) activity);
- });
- } else {
- ActivityScenario<TestCredentialsActivity> activityScenario =
- ActivityScenario.launch(TestCredentialsActivity.class);
- activityScenario.onActivity(
- activity -> {
- listener.onActivity((Activity) activity);
- });
- }
- }
-
@Test
public void convertRequestToPlayServices_setPasswordOptionRequestAndFalseAutoSelect_success() {
- launchTestActivity(
+ ActivityScenario<TestCredentialsActivity> activityScenario =
+ ActivityScenario.launch(TestCredentialsActivity.class);
+ activityScenario.onActivity(
activity -> {
BeginSignInRequest actualResponse =
CredentialProviderBeginSignInController.getInstance(activity)
@@ -99,7 +60,9 @@
@Test
public void convertRequestToPlayServices_setPasswordOptionRequestAndTrueAutoSelect_success() {
- launchTestActivity(
+ ActivityScenario<TestCredentialsActivity> activityScenario =
+ ActivityScenario.launch(TestCredentialsActivity.class);
+ activityScenario.onActivity(
activity -> {
BeginSignInRequest actualResponse =
CredentialProviderBeginSignInController.getInstance(activity)
@@ -116,7 +79,9 @@
@Test
public void convertRequestToPlayServices_nullRequest_throws() {
- launchTestActivity(
+ ActivityScenario<TestCredentialsActivity> activityScenario =
+ ActivityScenario.launch(TestCredentialsActivity.class);
+ activityScenario.onActivity(
activity -> {
assertThrows(
"null get credential request must throw exception",
@@ -129,7 +94,9 @@
@Test
public void convertResponseToCredentialManager_nullRequest_throws() {
- launchTestActivity(
+ ActivityScenario<TestCredentialsActivity> activityScenario =
+ ActivityScenario.launch(TestCredentialsActivity.class);
+ activityScenario.onActivity(
activity -> {
assertThrows(
"null sign in credential response must throw exception",
@@ -142,6 +109,9 @@
@Test
public void convertRequestToPlayServices_setGoogleIdOptionRequestAndTrueAutoSelect_success() {
+ ActivityScenario<TestCredentialsActivity> activityScenario =
+ ActivityScenario.launch(TestCredentialsActivity.class);
+
GetGoogleIdOption option =
new GetGoogleIdOption.Builder()
.setServerClientId("server_client_id")
@@ -152,7 +122,7 @@
.setAutoSelectEnabled(true)
.build();
- launchTestActivity(
+ activityScenario.onActivity(
activity -> {
BeginSignInRequest actualRequest =
CredentialProviderBeginSignInController.getInstance(activity)
@@ -181,7 +151,9 @@
@Test
public void duplicateGetInstance_shouldBeEqual() {
- launchTestActivity(
+ ActivityScenario<TestCredentialsActivity> activityScenario =
+ ActivityScenario.launch(TestCredentialsActivity.class);
+ activityScenario.onActivity(
activity -> {
CredentialProviderBeginSignInController firstInstance =
CredentialProviderBeginSignInController.getInstance(activity);
diff --git a/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/beginsignin/CredentialProviderBeginSignInControllerTest.kt b/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/beginsignin/CredentialProviderBeginSignInControllerTest.kt
index 5e05605..d245c4b 100644
--- a/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/beginsignin/CredentialProviderBeginSignInControllerTest.kt
+++ b/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/beginsignin/CredentialProviderBeginSignInControllerTest.kt
@@ -16,55 +16,32 @@
package androidx.credentials.playservices.beginsignin
-import android.app.Activity
import android.os.Build
import androidx.annotation.RequiresApi
import androidx.credentials.GetCredentialRequest
import androidx.credentials.GetPasswordOption
import androidx.credentials.playservices.TestCredentialsActivity
-import androidx.credentials.playservices.TestCredentialsFragmentActivity
import androidx.credentials.playservices.controllers.BeginSignIn.CredentialProviderBeginSignInController.Companion.getInstance
import androidx.test.core.app.ActivityScenario
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.google.android.libraries.identity.googleid.GetGoogleIdOption
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
-@RunWith(Parameterized::class)
+@RunWith(AndroidJUnit4::class)
@SmallTest
@Suppress("deprecation")
@RequiresApi(api = Build.VERSION_CODES.O)
-class CredentialProviderBeginSignInControllerTest(val useFragmentActivity: Boolean) {
-
- companion object {
- @JvmStatic
- @Parameterized.Parameters(name = "{0}")
- fun initParameters() = listOf(true, false)
- }
-
- private fun launchTestActivity(callback: (activity: Activity) -> Unit) {
- if (useFragmentActivity && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) {
- var activityScenario =
- ActivityScenario.launch(
- androidx.credentials.playservices
- .TestCredentialsFragmentActivity::class.java)
- activityScenario.onActivity { activity: Activity ->
- callback.invoke(activity)
- }
- } else {
- var activityScenario = ActivityScenario.launch(TestCredentialsActivity::class.java)
- activityScenario.onActivity { activity: Activity ->
- callback.invoke(activity)
- }
- }
- }
-
+class CredentialProviderBeginSignInControllerTest {
@Test
fun convertRequestToPlayServices_setPasswordOptionRequestAndFalseAutoSelect_success() {
- launchTestActivity { activity: Activity ->
- val actualResponse = getInstance(activity)
+ val activityScenario = ActivityScenario.launch(
+ TestCredentialsActivity::class.java
+ )
+ activityScenario.onActivity { activity: TestCredentialsActivity? ->
+ val actualResponse = getInstance(activity!!)
.convertRequestToPlayServices(
GetCredentialRequest(
listOf(
@@ -81,8 +58,11 @@
@Test
fun convertRequestToPlayServices_setPasswordOptionRequestAndTrueAutoSelect_success() {
- launchTestActivity { activity: Activity ->
- val actualResponse = getInstance(activity)
+ val activityScenario = ActivityScenario.launch(
+ TestCredentialsActivity::class.java
+ )
+ activityScenario.onActivity { activity: TestCredentialsActivity? ->
+ val actualResponse = getInstance(activity!!)
.convertRequestToPlayServices(
GetCredentialRequest(
listOf(
@@ -99,6 +79,10 @@
@Test
fun convertRequestToPlayServices_setGoogleIdOptionRequest_success() {
+ val activityScenario = ActivityScenario.launch(
+ TestCredentialsActivity::class.java
+ )
+
val option = GetGoogleIdOption.Builder()
.setServerClientId("server_client_id")
.setNonce("nonce")
@@ -108,8 +92,8 @@
.setAutoSelectEnabled(true)
.build()
- launchTestActivity { activity: Activity ->
- val actualRequest = getInstance(activity)
+ activityScenario.onActivity { activity: TestCredentialsActivity? ->
+ val actualRequest = getInstance(activity!!)
.convertRequestToPlayServices(
GetCredentialRequest(
listOf(
@@ -136,9 +120,12 @@
@Test
fun duplicateGetInstance_shouldBeEqual() {
- launchTestActivity { activity: Activity ->
+ val activityScenario = ActivityScenario.launch(
+ TestCredentialsActivity::class.java
+ )
+ activityScenario.onActivity { activity: TestCredentialsActivity? ->
- val firstInstance = getInstance(activity)
+ val firstInstance = getInstance(activity!!)
val secondInstance = getInstance(activity)
assertThat(firstInstance).isEqualTo(secondInstance)
}
diff --git a/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/createpassword/CredentialProviderCreatePasswordControllerJavaTest.java b/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/createpassword/CredentialProviderCreatePasswordControllerJavaTest.java
index f0d5a85..00b86ba 100644
--- a/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/createpassword/CredentialProviderCreatePasswordControllerJavaTest.java
+++ b/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/createpassword/CredentialProviderCreatePasswordControllerJavaTest.java
@@ -20,8 +20,6 @@
import static org.junit.Assert.assertThrows;
-import android.app.Activity;
-import android.os.Build;
import android.os.Bundle;
import androidx.credentials.CreateCredentialResponse;
@@ -31,6 +29,7 @@
import androidx.credentials.playservices.TestUtils;
import androidx.credentials.playservices.controllers.CreatePassword.CredentialProviderCreatePasswordController;
import androidx.test.core.app.ActivityScenario;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.google.android.gms.auth.api.identity.SignInPassword;
@@ -39,53 +38,17 @@
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-@RunWith(Parameterized.class)
+@RunWith(AndroidJUnit4.class)
@SmallTest
public class CredentialProviderCreatePasswordControllerJavaTest {
- private final boolean mUseFragmentActivity;
-
- @Parameterized.Parameters
- public static Object[] data() {
- return new Object[] {true, false};
- }
-
- public CredentialProviderCreatePasswordControllerJavaTest(final boolean useFragmentActivity)
- throws Throwable {
- mUseFragmentActivity = useFragmentActivity;
- }
-
- interface TestActivityListener {
- void onActivity(Activity a);
- }
-
- private void launchTestActivity(TestActivityListener listener) {
- if (mUseFragmentActivity && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) {
- ActivityScenario<androidx.credentials.playservices.TestCredentialsFragmentActivity>
- activityScenario =
- ActivityScenario.launch(
- androidx.credentials.playservices
- .TestCredentialsFragmentActivity.class);
- activityScenario.onActivity(
- activity -> {
- listener.onActivity((Activity) activity);
- });
- } else {
- ActivityScenario<TestCredentialsActivity> activityScenario =
- ActivityScenario.launch(TestCredentialsActivity.class);
- activityScenario.onActivity(
- activity -> {
- listener.onActivity((Activity) activity);
- });
- }
- }
-
@Test
public void convertResponseToCredentialManager_unitInput_success() {
+ ActivityScenario<TestCredentialsActivity> activityScenario =
+ ActivityScenario.launch(TestCredentialsActivity.class);
String expectedResponseType = new CreatePasswordResponse().getType();
- launchTestActivity(
+ activityScenario.onActivity(
activity -> {
CreateCredentialResponse actualResponse =
CredentialProviderCreatePasswordController.getInstance(activity)
@@ -99,9 +62,11 @@
@Test
public void convertRequestToPlayServices_createPasswordRequest_success() {
+ ActivityScenario<TestCredentialsActivity> activityScenario =
+ ActivityScenario.launch(TestCredentialsActivity.class);
String expectedId = "LM";
String expectedPassword = "SodaButton";
- launchTestActivity(
+ activityScenario.onActivity(
activity -> {
SignInPassword actualRequest =
CredentialProviderCreatePasswordController.getInstance(activity)
@@ -118,7 +83,7 @@
public void convertRequestToPlayServices_nullRequest_throws() {
ActivityScenario<TestCredentialsActivity> activityScenario =
ActivityScenario.launch(TestCredentialsActivity.class);
- launchTestActivity(
+ activityScenario.onActivity(
activity -> {
assertThrows(
"null create password request must throw exception",
@@ -132,7 +97,9 @@
@Test
public void convertResponseToCredentialManager_nullRequest_throws() {
- launchTestActivity(
+ ActivityScenario<TestCredentialsActivity> activityScenario =
+ ActivityScenario.launch(TestCredentialsActivity.class);
+ activityScenario.onActivity(
activity -> {
assertThrows(
"null unit response must throw exception",
@@ -145,7 +112,9 @@
@Test
public void duplicateGetInstance_shouldBeEqual() {
- launchTestActivity(
+ ActivityScenario<TestCredentialsActivity> activityScenario =
+ ActivityScenario.launch(TestCredentialsActivity.class);
+ activityScenario.onActivity(
activity -> {
CredentialProviderCreatePasswordController firstInstance =
CredentialProviderCreatePasswordController.getInstance(activity);
diff --git a/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/createpassword/CredentialProviderCreatePasswordControllerTest.kt b/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/createpassword/CredentialProviderCreatePasswordControllerTest.kt
index cc2e365..0710e37 100644
--- a/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/createpassword/CredentialProviderCreatePasswordControllerTest.kt
+++ b/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/createpassword/CredentialProviderCreatePasswordControllerTest.kt
@@ -16,56 +16,31 @@
package androidx.credentials.playservices.createpassword
-import android.app.Activity
-import android.os.Build
import android.os.Bundle
-import androidx.annotation.DoNotInline
import androidx.credentials.CreatePasswordRequest
import androidx.credentials.CreatePasswordResponse
import androidx.credentials.playservices.TestCredentialsActivity
import androidx.credentials.playservices.TestUtils.Companion.equals
import androidx.credentials.playservices.controllers.CreatePassword.CredentialProviderCreatePasswordController.Companion.getInstance
import androidx.test.core.app.ActivityScenario
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
-@RunWith(Parameterized::class)
+@RunWith(AndroidJUnit4::class)
@SmallTest
-class CredentialProviderCreatePasswordControllerTest(val useFragmentActivity: Boolean) {
-
- companion object {
- @JvmStatic
- @Parameterized.Parameters(name = "{0}")
- fun initParameters() = listOf(true, false)
- }
-
- @DoNotInline
- private fun launchTestActivity(callback: (activity: Activity) -> Unit) {
- if (useFragmentActivity && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) {
- var activityScenario =
- ActivityScenario.launch(
- androidx.credentials.playservices
- .TestCredentialsFragmentActivity::class.java)
- activityScenario.onActivity { activity: Activity ->
- callback.invoke(activity)
- }
- } else {
- var activityScenario = ActivityScenario.launch(TestCredentialsActivity::class.java)
- activityScenario.onActivity { activity: Activity ->
- callback.invoke(activity)
- }
- }
- }
-
+class CredentialProviderCreatePasswordControllerTest {
@Test
fun convertResponseToCredentialManager_unitInput_success() {
+ val activityScenario = ActivityScenario.launch(
+ TestCredentialsActivity::class.java
+ )
val expectedResponseType = CreatePasswordResponse().type
- launchTestActivity { activity: Activity ->
+ activityScenario.onActivity { activity: TestCredentialsActivity? ->
- val actualResponse = getInstance(activity)
+ val actualResponse = getInstance(activity!!)
.convertResponseToCredentialManager(Unit)
assertThat(actualResponse.type)
@@ -76,11 +51,14 @@
@Test
fun convertRequestToPlayServices_createPasswordRequest_success() {
+ val activityScenario = ActivityScenario.launch(
+ TestCredentialsActivity::class.java
+ )
val expectedId = "LM"
val expectedPassword = "SodaButton"
- launchTestActivity { activity: Activity ->
+ activityScenario.onActivity { activity: TestCredentialsActivity? ->
- val actualRequest = getInstance(activity)
+ val actualRequest = getInstance(activity!!)
.convertRequestToPlayServices(CreatePasswordRequest(
expectedId, expectedPassword)).signInPassword
@@ -92,9 +70,12 @@
@Test
fun duplicateGetInstance_shouldBeEqual() {
- launchTestActivity { activity: Activity ->
+ val activityScenario = ActivityScenario.launch(
+ TestCredentialsActivity::class.java
+ )
+ activityScenario.onActivity { activity: TestCredentialsActivity? ->
- val firstInstance = getInstance(activity)
+ val firstInstance = getInstance(activity!!)
val secondInstance = getInstance(activity)
assertThat(firstInstance).isEqualTo(secondInstance)
}
diff --git a/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/createpublickeycredential/CredentialProviderCreatePublicKeyCredentialControllerJavaTest.java b/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/createpublickeycredential/CredentialProviderCreatePublicKeyCredentialControllerJavaTest.java
index 48d07d7..a814d5f 100644
--- a/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/createpublickeycredential/CredentialProviderCreatePublicKeyCredentialControllerJavaTest.java
+++ b/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/createpublickeycredential/CredentialProviderCreatePublicKeyCredentialControllerJavaTest.java
@@ -16,6 +16,7 @@
package androidx.credentials.playservices.createpublickeycredential;
+
import static androidx.credentials.playservices.createkeycredential.CreatePublicKeyCredentialControllerTestUtils.ALL_REQUIRED_AND_OPTIONAL_SIGNATURE;
import static androidx.credentials.playservices.createkeycredential.CreatePublicKeyCredentialControllerTestUtils.ALL_REQUIRED_FIELDS_SIGNATURE;
import static androidx.credentials.playservices.createkeycredential.CreatePublicKeyCredentialControllerTestUtils.MAIN_CREATE_JSON_ALL_REQUIRED_AND_OPTIONAL_FIELDS_PRESENT;
@@ -33,14 +34,12 @@
import static org.junit.Assert.assertThrows;
-import android.app.Activity;
-import android.os.Build;
-
import androidx.credentials.CreatePublicKeyCredentialRequest;
import androidx.credentials.playservices.TestCredentialsActivity;
import androidx.credentials.playservices.TestUtils;
import androidx.credentials.playservices.controllers.CreatePublicKeyCredential.CredentialProviderCreatePublicKeyCredentialController;
import androidx.test.core.app.ActivityScenario;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.google.android.gms.fido.fido2.api.common.PublicKeyCredentialCreationOptions;
@@ -49,212 +48,156 @@
import org.json.JSONObject;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-@RunWith(Parameterized.class)
+@RunWith(AndroidJUnit4.class)
@SmallTest
public class CredentialProviderCreatePublicKeyCredentialControllerJavaTest {
-
- private final boolean mUseFragmentActivity;
-
- @Parameterized.Parameters
- public static Object[] data() {
- return new Object[] {true, false};
- }
-
- public CredentialProviderCreatePublicKeyCredentialControllerJavaTest(
- final boolean useFragmentActivity) throws Throwable {
- mUseFragmentActivity = useFragmentActivity;
- }
-
- interface TestActivityListener {
- void onActivity(Activity a);
- }
-
- private void launchTestActivity(TestActivityListener listener) {
- if (mUseFragmentActivity && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) {
- ActivityScenario<androidx.credentials.playservices.TestCredentialsFragmentActivity>
- activityScenario =
- ActivityScenario.launch(
- androidx.credentials.playservices
- .TestCredentialsFragmentActivity.class);
- activityScenario.onActivity(
- activity -> {
- listener.onActivity((Activity) activity);
- });
- } else {
- ActivityScenario<TestCredentialsActivity> activityScenario =
- ActivityScenario.launch(TestCredentialsActivity.class);
- activityScenario.onActivity(
- activity -> {
- listener.onActivity((Activity) activity);
- });
- }
- }
-
- private PublicKeyCredentialCreationOptions convertRequestToPlayServices(
- Activity activity, String type) {
- CreatePublicKeyCredentialRequest pubKeyRequest = new CreatePublicKeyCredentialRequest(type);
- return CredentialProviderCreatePublicKeyCredentialController.getInstance(activity)
- .convertRequestToPlayServices(pubKeyRequest);
- }
-
@Test
public void convertRequestToPlayServices_correctRequiredOnlyRequest_success() {
- launchTestActivity(
- activity -> {
- try {
- JSONObject expectedJson =
- new JSONObject(MAIN_CREATE_JSON_ALL_REQUIRED_FIELDS_PRESENT);
+ ActivityScenario<TestCredentialsActivity> activityScenario =
+ ActivityScenario.launch(TestCredentialsActivity.class);
+ activityScenario.onActivity(activity -> {
+ try {
+ JSONObject expectedJson = new JSONObject(
+ MAIN_CREATE_JSON_ALL_REQUIRED_FIELDS_PRESENT);
- PublicKeyCredentialCreationOptions actualResponse =
- convertRequestToPlayServices(
- activity, MAIN_CREATE_JSON_ALL_REQUIRED_FIELDS_PRESENT);
- JSONObject actualJson =
- createJsonObjectFromPublicKeyCredentialCreationOptions(
- actualResponse);
- JSONObject requiredKeys = new JSONObject(ALL_REQUIRED_FIELDS_SIGNATURE);
+ PublicKeyCredentialCreationOptions actualResponse =
+ CredentialProviderCreatePublicKeyCredentialController.getInstance(activity)
+ .convertRequestToPlayServices(
+ new CreatePublicKeyCredentialRequest(
+ MAIN_CREATE_JSON_ALL_REQUIRED_FIELDS_PRESENT));
+ JSONObject actualJson = createJsonObjectFromPublicKeyCredentialCreationOptions(
+ actualResponse);
+ JSONObject requiredKeys = new JSONObject(ALL_REQUIRED_FIELDS_SIGNATURE);
- assertThat(
- TestUtils.Companion.isSubsetJson(
- expectedJson, actualJson, requiredKeys))
- .isTrue();
- } catch (JSONException e) {
- throw new RuntimeException(e);
- }
- });
+ assertThat(TestUtils.Companion.isSubsetJson(expectedJson, actualJson,
+ requiredKeys)).isTrue();
+ // TODO("Add remaining tests in detail after discussing ideal form")
+ } catch (JSONException e) {
+ throw new RuntimeException(e);
+ }
+ });
}
@Test
public void convertRequestToPlayServices_correctRequiredAndOptionalRequest_success() {
- launchTestActivity(
- activity -> {
- try {
- JSONObject expectedJson =
- new JSONObject(
- MAIN_CREATE_JSON_ALL_REQUIRED_AND_OPTIONAL_FIELDS_PRESENT);
+ ActivityScenario<TestCredentialsActivity> activityScenario =
+ ActivityScenario.launch(TestCredentialsActivity.class);
+ activityScenario.onActivity(activity -> {
+ try {
+ JSONObject expectedJson = new JSONObject(
+ MAIN_CREATE_JSON_ALL_REQUIRED_AND_OPTIONAL_FIELDS_PRESENT);
- PublicKeyCredentialCreationOptions actualResponse =
- convertRequestToPlayServices(
- activity,
- MAIN_CREATE_JSON_ALL_REQUIRED_AND_OPTIONAL_FIELDS_PRESENT);
- JSONObject actualJson =
- createJsonObjectFromPublicKeyCredentialCreationOptions(
- actualResponse);
- JSONObject requiredKeys =
- new JSONObject(ALL_REQUIRED_AND_OPTIONAL_SIGNATURE);
+ PublicKeyCredentialCreationOptions actualResponse =
+ CredentialProviderCreatePublicKeyCredentialController.getInstance(activity)
+ .convertRequestToPlayServices(new CreatePublicKeyCredentialRequest(
+ MAIN_CREATE_JSON_ALL_REQUIRED_AND_OPTIONAL_FIELDS_PRESENT));
+ JSONObject actualJson =
+ createJsonObjectFromPublicKeyCredentialCreationOptions(
+ actualResponse);
+ JSONObject requiredKeys = new JSONObject(ALL_REQUIRED_AND_OPTIONAL_SIGNATURE);
- assertThat(
- TestUtils.Companion.isSubsetJson(
- expectedJson, actualJson, requiredKeys))
- .isTrue();
- } catch (JSONException e) {
- throw new RuntimeException(e);
- }
- });
+ assertThat(TestUtils.Companion.isSubsetJson(expectedJson, actualJson,
+ requiredKeys)).isTrue();
+ // TODO("Add remaining tests in detail after discussing ideal form")
+ } catch (JSONException e) {
+ throw new RuntimeException(e);
+ }
+ });
}
-
@Test
public void convertRequestToPlayServices_missingRequired_throws() {
- launchTestActivity(
- activity -> {
- try {
- PublicKeyCredentialCreationOptions actualResponse =
- convertRequestToPlayServices(
- activity, MAIN_CREATE_JSON_ALL_REQUIRED_FIELDS_PRESENT);
-
- CreatePublicKeyCredentialRequest pubKeyRequest =
+ ActivityScenario<TestCredentialsActivity> activityScenario =
+ ActivityScenario.launch(TestCredentialsActivity.class);
+ activityScenario.onActivity(activity -> {
+ try {
+ CredentialProviderCreatePublicKeyCredentialController
+ .getInstance(activity)
+ .convertRequestToPlayServices(
new CreatePublicKeyCredentialRequest(
- MAIN_CREATE_JSON_ALL_REQUIRED_FIELDS_PRESENT);
- CredentialProviderCreatePublicKeyCredentialController.getInstance(activity)
- .convertRequestToPlayServices(
- new CreatePublicKeyCredentialRequest(
- MAIN_CREATE_JSON_MISSING_REQUIRED_FIELD));
+ MAIN_CREATE_JSON_MISSING_REQUIRED_FIELD));
- // Should not reach here.
- assertWithMessage("Exception should be thrown").that(true).isFalse();
- } catch (Exception e) {
- assertThat(e.getMessage().contains("No value for id")).isTrue();
- assertThat(e.getClass().getName().contains("JSONException")).isTrue();
- }
- });
+ // Should not reach here.
+ assertWithMessage("Exception should be thrown").that(true).isFalse();
+ } catch (Exception e) {
+ assertThat(e.getMessage().contains("No value for id")).isTrue();
+ assertThat(e.getClass().getName().contains("JSONException")).isTrue();
+ }
+ });
}
@Test
public void convertRequestToPlayServices_emptyRequired_throws() {
- launchTestActivity(
- activity -> {
- assertThrows(
- "Expected bad required json to throw",
- JSONException.class,
- () ->
- convertRequestToPlayServices(
- activity, MAIN_CREATE_JSON_REQUIRED_FIELD_EMPTY));
- });
+ ActivityScenario<TestCredentialsActivity> activityScenario =
+ ActivityScenario.launch(TestCredentialsActivity.class);
+ activityScenario.onActivity(activity -> {
+
+ assertThrows("Expected bad required json to throw",
+ JSONException.class,
+ () -> CredentialProviderCreatePublicKeyCredentialController
+ .getInstance(activity).convertRequestToPlayServices(
+ new CreatePublicKeyCredentialRequest(
+ MAIN_CREATE_JSON_REQUIRED_FIELD_EMPTY)));
+ });
}
@Test
public void convertRequestToPlayServices_missingOptionalRequired_throws() {
- launchTestActivity(
- activity -> {
- assertThrows(
- "Expected bad required json to throw",
- JSONException.class,
- () ->
- convertRequestToPlayServices(
- activity, OPTIONAL_FIELD_MISSING_REQUIRED_SUBFIELD));
- });
+ ActivityScenario<TestCredentialsActivity> activityScenario =
+ ActivityScenario.launch(TestCredentialsActivity.class);
+ activityScenario.onActivity(activity -> {
+
+ assertThrows("Expected bad required json to throw",
+ JSONException.class,
+ () -> CredentialProviderCreatePublicKeyCredentialController
+ .getInstance(activity)
+ .convertRequestToPlayServices(
+ new CreatePublicKeyCredentialRequest(
+ OPTIONAL_FIELD_MISSING_REQUIRED_SUBFIELD)));
+ });
}
@Test
public void convertRequestToPlayServices_emptyOptionalRequired_throws() {
- launchTestActivity(
- activity -> {
- assertThrows(
- "Expected bad required json to throw",
- JSONException.class,
- () ->
- convertRequestToPlayServices(
- activity, OPTIONAL_FIELD_WITH_EMPTY_REQUIRED_SUBFIELD));
- });
+ ActivityScenario<TestCredentialsActivity> activityScenario =
+ ActivityScenario.launch(TestCredentialsActivity.class);
+ activityScenario.onActivity(activity -> {
+
+ assertThrows("Expected bad required json to throw",
+ JSONException.class,
+ () -> CredentialProviderCreatePublicKeyCredentialController
+ .getInstance(activity)
+ .convertRequestToPlayServices(
+ new CreatePublicKeyCredentialRequest(
+ OPTIONAL_FIELD_WITH_EMPTY_REQUIRED_SUBFIELD)));
+ });
}
@Test
public void convertRequestToPlayServices_missingOptionalNotRequired_success() {
- launchTestActivity(
- activity -> {
- try {
- JSONObject expectedJson =
- new JSONObject(OPTIONAL_FIELD_MISSING_OPTIONAL_SUBFIELD);
+ ActivityScenario<TestCredentialsActivity> activityScenario =
+ ActivityScenario.launch(TestCredentialsActivity.class);
+ activityScenario.onActivity(activity -> {
+ try {
+ JSONObject expectedJson = new JSONObject(
+ OPTIONAL_FIELD_MISSING_OPTIONAL_SUBFIELD);
- PublicKeyCredentialCreationOptions actualResponse =
- convertRequestToPlayServices(
- activity, OPTIONAL_FIELD_MISSING_OPTIONAL_SUBFIELD);
- JSONObject actualJson =
- createJsonObjectFromPublicKeyCredentialCreationOptions(
- actualResponse);
- JSONObject requiredKeys =
- new JSONObject(OPTIONAL_FIELD_MISSING_OPTIONAL_SUBFIELD_SIGNATURE);
+ PublicKeyCredentialCreationOptions actualResponse =
+ CredentialProviderCreatePublicKeyCredentialController.getInstance(activity)
+ .convertRequestToPlayServices(
+ new CreatePublicKeyCredentialRequest(
+ OPTIONAL_FIELD_MISSING_OPTIONAL_SUBFIELD));
+ JSONObject actualJson = createJsonObjectFromPublicKeyCredentialCreationOptions(
+ actualResponse);
+ JSONObject requiredKeys = new
+ JSONObject(OPTIONAL_FIELD_MISSING_OPTIONAL_SUBFIELD_SIGNATURE);
- assertThat(
- TestUtils.Companion.isSubsetJson(
- expectedJson, actualJson, requiredKeys))
- .isTrue();
- } catch (JSONException e) {
- throw new RuntimeException(e);
- }
- });
- }
-
- @Test
- public void getInstanceRepeatedTest() {
- launchTestActivity(
- a -> {
- CredentialProviderCreatePublicKeyCredentialController firstInstance =
- CredentialProviderCreatePublicKeyCredentialController.getInstance(a);
- CredentialProviderCreatePublicKeyCredentialController secondInstance =
- CredentialProviderCreatePublicKeyCredentialController.getInstance(a);
- assertThat(firstInstance).isEqualTo(secondInstance);
- });
+ assertThat(TestUtils.Companion.isSubsetJson(expectedJson, actualJson,
+ requiredKeys)).isTrue();
+ // TODO("Add remaining tests in detail after discussing ideal form")
+ } catch (JSONException e) {
+ throw new RuntimeException(e);
+ }
+ });
}
}
diff --git a/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/createpublickeycredential/CredentialProviderCreatePublicKeyCredentialControllerTest.kt b/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/createpublickeycredential/CredentialProviderCreatePublicKeyCredentialControllerTest.kt
index 0e8a50e..3a9ae60 100644
--- a/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/createpublickeycredential/CredentialProviderCreatePublicKeyCredentialControllerTest.kt
+++ b/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/createpublickeycredential/CredentialProviderCreatePublicKeyCredentialControllerTest.kt
@@ -16,9 +16,6 @@
package androidx.credentials.playservices.createpublickeycredential
-import android.app.Activity
-import android.os.Build
-import androidx.annotation.DoNotInline
import androidx.credentials.CreatePublicKeyCredentialRequest
import androidx.credentials.playservices.TestCredentialsActivity
import androidx.credentials.playservices.TestUtils.Companion.isSubsetJson
@@ -35,6 +32,7 @@
import androidx.credentials.playservices.createkeycredential.CreatePublicKeyCredentialControllerTestUtils.Companion.OPTIONAL_FIELD_WITH_EMPTY_REQUIRED_SUBFIELD
import androidx.credentials.playservices.createkeycredential.CreatePublicKeyCredentialControllerTestUtils.Companion.createJsonObjectFromPublicKeyCredentialCreationOptions
import androidx.test.core.app.ActivityScenario
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.google.common.truth.Truth.assertThat
import org.json.JSONException
@@ -43,51 +41,29 @@
import org.junit.Test
import org.junit.function.ThrowingRunnable
import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
-@RunWith(Parameterized::class)
+@RunWith(AndroidJUnit4::class)
@SmallTest
-class CredentialProviderCreatePublicKeyCredentialControllerTest(val useFragmentActivity: Boolean) {
-
- companion object {
- @JvmStatic
- @Parameterized.Parameters(name = "{0}")
- fun initParameters() = listOf(true, false)
- }
-
- @DoNotInline
- private fun launchTestActivity(callback: (activity: Activity) -> Unit) {
- if (useFragmentActivity && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) {
- var activityScenario =
- ActivityScenario.launch(
- androidx.credentials.playservices
- .TestCredentialsFragmentActivity::class.java)
- activityScenario.onActivity { activity: Activity ->
- callback.invoke(activity)
- }
- } else {
- var activityScenario = ActivityScenario.launch(TestCredentialsActivity::class.java)
- activityScenario.onActivity { activity: Activity ->
- callback.invoke(activity)
- }
- }
- }
-
+class CredentialProviderCreatePublicKeyCredentialControllerTest {
@Test
fun convertRequestToPlayServices_correctRequiredOnlyRequest_success() {
- launchTestActivity { activity: Activity ->
+ val activityScenario = ActivityScenario.launch(
+ TestCredentialsActivity::class.java
+ )
+ activityScenario.onActivity { activity: TestCredentialsActivity? ->
try {
val expectedJson = JSONObject(MAIN_CREATE_JSON_ALL_REQUIRED_FIELDS_PRESENT)
- val actualResponse = getInstance(activity).convertRequestToPlayServices(
- CreatePublicKeyCredentialRequest(
- MAIN_CREATE_JSON_ALL_REQUIRED_FIELDS_PRESENT))
+ val actualResponse = getInstance(activity!!).convertRequestToPlayServices(
+ CreatePublicKeyCredentialRequest(
+ MAIN_CREATE_JSON_ALL_REQUIRED_FIELDS_PRESENT))
val actualJson =
createJsonObjectFromPublicKeyCredentialCreationOptions(actualResponse)
val requiredKeys =
JSONObject(ALL_REQUIRED_FIELDS_SIGNATURE)
assertThat(isSubsetJson(expectedJson, actualJson, requiredKeys)).isTrue()
+ // TODO("Add remaining tests in detail after discussing ideal form")
} catch (e: JSONException) {
throw RuntimeException(e)
}
@@ -96,20 +72,24 @@
@Test
fun convertRequestToPlayServices_correctRequiredAndOptionalRequest_success() {
- launchTestActivity { activity: Activity ->
+ val activityScenario = ActivityScenario.launch(
+ TestCredentialsActivity::class.java
+ )
+ activityScenario.onActivity { activity: TestCredentialsActivity? ->
try {
val expectedJson = JSONObject(
MAIN_CREATE_JSON_ALL_REQUIRED_AND_OPTIONAL_FIELDS_PRESENT)
- val actualResponse = getInstance(activity)
- .convertRequestToPlayServices(CreatePublicKeyCredentialRequest(
- MAIN_CREATE_JSON_ALL_REQUIRED_AND_OPTIONAL_FIELDS_PRESENT))
+ val actualResponse = getInstance(activity!!)
+ .convertRequestToPlayServices(CreatePublicKeyCredentialRequest(
+ MAIN_CREATE_JSON_ALL_REQUIRED_AND_OPTIONAL_FIELDS_PRESENT))
val actualJson =
createJsonObjectFromPublicKeyCredentialCreationOptions(actualResponse)
val requiredKeys =
JSONObject(ALL_REQUIRED_AND_OPTIONAL_SIGNATURE)
assertThat(isSubsetJson(expectedJson, actualJson, requiredKeys)).isTrue()
+ // TODO("Add remaining tests in detail after discussing ideal form")
} catch (e: JSONException) {
throw java.lang.RuntimeException(e)
}
@@ -118,88 +98,94 @@
@Test
fun convertRequestToPlayServices_missingRequired_throws() {
- launchTestActivity { activity: Activity ->
+ val activityScenario = ActivityScenario.launch(
+ TestCredentialsActivity::class.java
+ )
+ activityScenario.onActivity { activity: TestCredentialsActivity? ->
Assert.assertThrows("Expected bad required json to throw",
JSONException::class.java,
ThrowingRunnable {
getInstance(
- activity
+ activity!!
).convertRequestToPlayServices(
- CreatePublicKeyCredentialRequest(
- MAIN_CREATE_JSON_MISSING_REQUIRED_FIELD
- )) })
+ CreatePublicKeyCredentialRequest(
+ MAIN_CREATE_JSON_MISSING_REQUIRED_FIELD
+ )) })
}
}
@Test
fun convertRequestToPlayServices_emptyRequired_throws() {
- launchTestActivity { activity: Activity ->
+ val activityScenario = ActivityScenario.launch(
+ TestCredentialsActivity::class.java
+ )
+ activityScenario.onActivity { activity: TestCredentialsActivity? ->
Assert.assertThrows("Expected bad required json to throw",
JSONException::class.java,
- ThrowingRunnable { getInstance(activity
- ).convertRequestToPlayServices(CreatePublicKeyCredentialRequest(
- MAIN_CREATE_JSON_REQUIRED_FIELD_EMPTY)) })
+ ThrowingRunnable { getInstance(activity!!
+ ).convertRequestToPlayServices(CreatePublicKeyCredentialRequest(
+ MAIN_CREATE_JSON_REQUIRED_FIELD_EMPTY)) })
}
}
@Test
fun convertRequestToPlayServices_missingOptionalRequired_throws() {
- launchTestActivity { activity: Activity ->
+ val activityScenario = ActivityScenario.launch(
+ TestCredentialsActivity::class.java
+ )
+ activityScenario.onActivity { activity: TestCredentialsActivity? ->
Assert.assertThrows("Expected bad required json to throw",
JSONException::class.java,
ThrowingRunnable {
getInstance(
- activity
+ activity!!
).convertRequestToPlayServices(
- CreatePublicKeyCredentialRequest(
- OPTIONAL_FIELD_MISSING_REQUIRED_SUBFIELD)) })
+ CreatePublicKeyCredentialRequest(
+ OPTIONAL_FIELD_MISSING_REQUIRED_SUBFIELD)) })
}
}
@Test
fun convertRequestToPlayServices_emptyOptionalRequired_throws() {
- launchTestActivity { activity: Activity ->
+ val activityScenario = ActivityScenario.launch(
+ TestCredentialsActivity::class.java
+ )
+ activityScenario.onActivity { activity: TestCredentialsActivity? ->
Assert.assertThrows("Expected bad required json to throw",
JSONException::class.java,
- ThrowingRunnable { getInstance(activity).convertRequestToPlayServices(
- CreatePublicKeyCredentialRequest(
- OPTIONAL_FIELD_WITH_EMPTY_REQUIRED_SUBFIELD)) })
+ ThrowingRunnable { getInstance(activity!!).convertRequestToPlayServices(
+ CreatePublicKeyCredentialRequest(
+ OPTIONAL_FIELD_WITH_EMPTY_REQUIRED_SUBFIELD)) })
}
}
@Test
fun convertRequestToPlayServices_missingOptionalNotRequired_success() {
- launchTestActivity { activity: Activity ->
+ val activityScenario = ActivityScenario.launch(
+ TestCredentialsActivity::class.java
+ )
+ activityScenario.onActivity { activity: TestCredentialsActivity? ->
try {
val expectedJson = JSONObject(OPTIONAL_FIELD_MISSING_OPTIONAL_SUBFIELD)
val actualResponse =
- getInstance(activity)
+ getInstance(activity!!)
.convertRequestToPlayServices(
CreatePublicKeyCredentialRequest(
OPTIONAL_FIELD_MISSING_OPTIONAL_SUBFIELD))
val actualJson = createJsonObjectFromPublicKeyCredentialCreationOptions(
- actualResponse)
+ actualResponse)
val requiredKeys =
JSONObject(OPTIONAL_FIELD_MISSING_OPTIONAL_SUBFIELD_SIGNATURE)
assertThat(isSubsetJson(expectedJson, actualJson, requiredKeys)).isTrue()
+ // TODO("Add remaining tests in detail after discussing ideal form")
} catch (e: JSONException) {
throw java.lang.RuntimeException(e)
}
}
}
-
- @Test
- fun getInstanceRepeatedTest() {
- launchTestActivity { activity: Activity ->
-
- val firstInstance = getInstance(activity)
- val secondInstance = getInstance(activity)
- assertThat(firstInstance).isEqualTo(secondInstance)
- }
- }
}
diff --git a/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/CredentialProviderFragment.kt b/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/CredentialProviderFragment.kt
deleted file mode 100644
index 2d7f1e9..0000000
--- a/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/CredentialProviderFragment.kt
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.credentials.playservices
-
-import android.content.Intent
-import android.os.Bundle
-import android.os.ResultReceiver
-import androidx.annotation.RestrictTo
-import androidx.credentials.playservices.controllers.CredentialProviderBaseController
-import androidx.fragment.app.Fragment
-
-/** A fragment used if we are passed a fragment activity. */
-@RestrictTo(RestrictTo.Scope.LIBRARY)
-@Suppress("Deprecation")
-open class CredentialProviderFragment : Fragment() {
-
- private var resultReceiver: ResultReceiver? = null
- private var mWaitingForActivityResult = false
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
-
- resultReceiver = getResultReceiver()
- if (resultReceiver == null) {
- return
- }
-
- restoreState(savedInstanceState)
- if (mWaitingForActivityResult) {
- return // Past call still active
- }
- }
-
- private fun getResultReceiver(): ResultReceiver? {
- if (getArguments() == null) {
- return null
- }
-
- return getArguments()!!.getParcelable(CredentialProviderBaseController.RESULT_RECEIVER_TAG)
- as? ResultReceiver
- }
-
- private fun restoreState(savedInstanceState: Bundle?) {
- if (savedInstanceState != null) {
- mWaitingForActivityResult = savedInstanceState.getBoolean(KEY_AWAITING_RESULT, false)
- }
- }
-
- override fun onSaveInstanceState(outState: Bundle) {
- outState.putBoolean(KEY_AWAITING_RESULT, mWaitingForActivityResult)
- super.onSaveInstanceState(outState)
- }
-
- override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
- super.onActivityResult(requestCode, resultCode, data)
- val bundle = Bundle()
- bundle.putBoolean(CredentialProviderBaseController.FAILURE_RESPONSE_TAG, false)
- bundle.putInt(CredentialProviderBaseController.ACTIVITY_REQUEST_CODE_TAG, requestCode)
- bundle.putParcelable(CredentialProviderBaseController.RESULT_DATA_TAG, data)
- resultReceiver?.send(resultCode, bundle)
- mWaitingForActivityResult = false
- }
-
- companion object {
- private const val TAG = "CredentialProviderFragment"
- private const val KEY_AWAITING_RESULT = "androidx.credentials.playservices.AWAITING_RESULT"
-
- fun createFrom(resultReceiver: ResultReceiver): CredentialProviderFragment {
- val f = CredentialProviderFragment()
-
- // Supply index input as an argument.
- val args = Bundle()
- args.putParcelable(CredentialProviderBaseController.RESULT_RECEIVER_TAG, resultReceiver)
- f.setArguments(args)
-
- return f
- }
- }
-}
diff --git a/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/GmsCoreUtils.kt b/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/GmsCoreUtils.kt
deleted file mode 100644
index 112599d..0000000
--- a/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/GmsCoreUtils.kt
+++ /dev/null
@@ -1,266 +0,0 @@
-/*
- * Copyright 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.credentials.playservices
-
-import android.app.Activity
-import android.app.PendingIntent
-import android.content.IntentSender
-import android.os.Bundle
-import android.os.ResultReceiver
-import android.util.Log
-import androidx.annotation.RestrictTo
-import androidx.credentials.playservices.controllers.CredentialProviderBaseController
-import androidx.credentials.playservices.controllers.CredentialProviderBaseController.Companion.CREATE_INTERRUPTED
-import androidx.credentials.playservices.controllers.CredentialProviderBaseController.Companion.CREATE_UNKNOWN
-import androidx.credentials.playservices.controllers.CredentialProviderBaseController.Companion.GET_INTERRUPTED
-import androidx.credentials.playservices.controllers.CredentialProviderBaseController.Companion.GET_NO_CREDENTIALS
-import androidx.credentials.playservices.controllers.CredentialProviderBaseController.Companion.GET_UNKNOWN
-import androidx.fragment.app.Fragment
-import androidx.fragment.app.FragmentActivity
-import com.google.android.gms.auth.api.identity.BeginSignInRequest
-import com.google.android.gms.auth.api.identity.CredentialSavingClient
-import com.google.android.gms.auth.api.identity.SavePasswordRequest
-import com.google.android.gms.auth.api.identity.SignInClient
-import com.google.android.gms.common.api.ApiException
-import com.google.android.gms.fido.fido2.Fido2ApiClient
-import com.google.android.gms.fido.fido2.api.common.PublicKeyCredentialCreationOptions
-
-/** A util class for interacting with GmsCore. */
-@RestrictTo(RestrictTo.Scope.LIBRARY)
-@Suppress("Deprecation", "ForbiddenSuperClass")
-open class GmsCoreUtils {
-
- class GmsCoreUtilsResult(var waitingForActivityResult: Boolean, var hasFinished: Boolean)
-
- internal companion object {
- private const val TAG = "GmsCoreUtils"
-
- const val DEFAULT_REQUEST_CODE = 1
-
- class FragmentCreationException() : Exception("Failed to create exception")
-
- fun handleCreatePublicKeyCredential(
- apiClient: Fido2ApiClient,
- resultReceiver: ResultReceiver,
- fidoRegistrationRequest: PublicKeyCredentialCreationOptions?,
- requestCode: Int,
- activity: Activity
- ): GmsCoreUtilsResult {
- var waitingForActivityResult = false
- var hasActivityFinished = false
- var fragment = setupFragmentActivity(activity, resultReceiver)
-
- fidoRegistrationRequest?.let {
- apiClient
- .getRegisterPendingIntent(fidoRegistrationRequest)
- .addOnSuccessListener { result: PendingIntent ->
- try {
- startIntentSender(
- activity,
- result.intentSender,
- requestCode,
- fragment,
- )
- } catch (e: IntentSender.SendIntentException) {
- setupFailure(
- resultReceiver,
- CREATE_UNKNOWN,
- "During public key credential, found IntentSender " +
- "failure on public key creation: ${e.message}"
- )
- }
- }
- .addOnFailureListener { e: Exception ->
- var errName: String = CREATE_UNKNOWN
- if (e is ApiException && e.statusCode in CredentialProviderBaseController.retryables) {
- errName = CREATE_INTERRUPTED
- }
- setupFailure(
- resultReceiver,
- errName,
- "During create public key credential, fido registration " + "failure: ${e.message}"
- )
- }
- }
- ?: run {
- Log.w(
- TAG,
- "During create public key credential, request is null, so nothing to " +
- "launch for public key credentials"
- )
- hasActivityFinished = true
- }
- return GmsCoreUtilsResult(waitingForActivityResult, hasActivityFinished)
- }
-
- fun handleBeginSignIn(
- apiClient: SignInClient,
- resultReceiver: ResultReceiver,
- params: BeginSignInRequest?,
- requestCode: Int,
- activity: Activity
- ): GmsCoreUtilsResult {
- var waitingForActivityResult = false
- var hasFinished = false
- var fragment = setupFragmentActivity(activity, resultReceiver)
-
- params?.let {
- apiClient
- .beginSignIn(params)
- .addOnSuccessListener {
- try {
- waitingForActivityResult = true
- startIntentSender(
- activity,
- it.pendingIntent.intentSender,
- requestCode,
- fragment,
- )
- } catch (e: IntentSender.SendIntentException) {
- setupFailure(
- resultReceiver,
- GET_UNKNOWN,
- "During begin sign in, one tap ui intent sender " + "failure: ${e.message}"
- )
- }
- }
- .addOnFailureListener { e: Exception ->
- var errName: String = GET_NO_CREDENTIALS
- if (e is ApiException && e.statusCode in CredentialProviderBaseController.retryables) {
- errName = GET_INTERRUPTED
- }
- setupFailure(
- resultReceiver,
- errName,
- "During begin sign in, failure response from one tap: ${e.message}"
- )
- }
- }
- ?: run {
- Log.i(
- TAG,
- "During begin sign in, params is null, nothing to launch for " + "begin sign in"
- )
- hasFinished = true
- }
- return GmsCoreUtilsResult(waitingForActivityResult, hasFinished)
- }
-
- fun handleCreatePassword(
- apiClient: CredentialSavingClient,
- resultReceiver: ResultReceiver,
- params: SavePasswordRequest?,
- requestCode: Int,
- activity: Activity
- ): GmsCoreUtilsResult {
- var waitingForActivityResult = false
- var hasFinished = false
- var fragment = setupFragmentActivity(activity, resultReceiver)
-
- params?.let {
- apiClient
- .savePassword(params)
- .addOnSuccessListener {
- try {
- waitingForActivityResult = true
- startIntentSender(
- activity,
- it.pendingIntent.intentSender,
- requestCode,
- fragment,
- )
- } catch (e: IntentSender.SendIntentException) {
- setupFailure(
- resultReceiver,
- CREATE_UNKNOWN,
- "During save password, found UI intent sender " + "failure: ${e.message}"
- )
- }
- }
- .addOnFailureListener { e: Exception ->
- var errName: String = CREATE_UNKNOWN
- if (e is ApiException && e.statusCode in CredentialProviderBaseController.retryables) {
- errName = CREATE_INTERRUPTED
- }
- setupFailure(
- resultReceiver,
- errName,
- "During save password, found " + "password failure response from one tap ${e.message}"
- )
- }
- }
- ?: run {
- Log.i(
- TAG,
- "During save password, params is null, nothing to launch for create" + " password"
- )
- hasFinished = true
- }
- return GmsCoreUtilsResult(waitingForActivityResult, hasFinished)
- }
-
- private fun setupFragmentActivity(
- activity: Activity,
- resultReceiver: ResultReceiver
- ): Fragment? {
- if (activity is FragmentActivity) {
- val fragment = CredentialProviderFragment.createFrom(resultReceiver)
- val manager = activity.getSupportFragmentManager()
- manager.beginTransaction().add(fragment, "credman").commit()
-
- if (!fragment.isAdded()) {
- throw FragmentCreationException()
- }
-
- return fragment
- }
-
- return null
- }
-
- private fun startIntentSender(
- activity: Activity,
- intentSender: IntentSender,
- requestCode: Int,
- fragment: Fragment?,
- ) {
- if (fragment != null && fragment.isAdded() && activity is FragmentActivity) {
- activity.startIntentSenderFromFragment(
- fragment,
- intentSender,
- requestCode,
- null,
- 0,
- 0,
- 0,
- null,
- )
- return
- }
-
- activity.startIntentSenderForResult(intentSender, requestCode, null, 0, 0, 0, null)
- }
-
- private fun setupFailure(resultReceiver: ResultReceiver, errName: String, errMsg: String) {
- val bundle = Bundle()
- bundle.putBoolean(CredentialProviderBaseController.FAILURE_RESPONSE_TAG, true)
- bundle.putString(CredentialProviderBaseController.EXCEPTION_TYPE_TAG, errName)
- bundle.putString(CredentialProviderBaseController.EXCEPTION_MESSAGE_TAG, errMsg)
- resultReceiver.send(Integer.MAX_VALUE, bundle)
- }
- }
-}
diff --git a/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/HiddenActivity.kt b/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/HiddenActivity.kt
index 8586305..bde5471 100644
--- a/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/HiddenActivity.kt
+++ b/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/HiddenActivity.kt
@@ -17,150 +17,230 @@
package androidx.credentials.playservices
import android.app.Activity
+import android.app.PendingIntent
import android.content.Intent
+import android.content.IntentSender
import android.os.Bundle
import android.os.ResultReceiver
import android.util.Log
import androidx.annotation.RestrictTo
import androidx.credentials.playservices.controllers.CredentialProviderBaseController
+import androidx.credentials.playservices.controllers.CredentialProviderBaseController.Companion.CREATE_INTERRUPTED
+import androidx.credentials.playservices.controllers.CredentialProviderBaseController.Companion.CREATE_UNKNOWN
+import androidx.credentials.playservices.controllers.CredentialProviderBaseController.Companion.GET_INTERRUPTED
+import androidx.credentials.playservices.controllers.CredentialProviderBaseController.Companion.GET_NO_CREDENTIALS
+import androidx.credentials.playservices.controllers.CredentialProviderBaseController.Companion.GET_UNKNOWN
import com.google.android.gms.auth.api.identity.BeginSignInRequest
import com.google.android.gms.auth.api.identity.Identity
import com.google.android.gms.auth.api.identity.SavePasswordRequest
+import com.google.android.gms.common.api.ApiException
import com.google.android.gms.fido.Fido
import com.google.android.gms.fido.fido2.api.common.PublicKeyCredentialCreationOptions
-/** An activity used to ensure all required API versions work as intended. */
+/**
+ * An activity used to ensure all required API versions work as intended.
+ */
@RestrictTo(RestrictTo.Scope.LIBRARY)
@Suppress("Deprecation", "ForbiddenSuperClass")
open class HiddenActivity : Activity() {
- private var resultReceiver: ResultReceiver? = null
- private var mWaitingForActivityResult = false
+ private var resultReceiver: ResultReceiver? = null
+ private var mWaitingForActivityResult = false
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- overridePendingTransition(0, 0)
- val type: String? = intent.getStringExtra(CredentialProviderBaseController.TYPE_TAG)
- resultReceiver = intent.getParcelableExtra(CredentialProviderBaseController.RESULT_RECEIVER_TAG)
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ overridePendingTransition(0, 0)
+ val type: String? = intent.getStringExtra(CredentialProviderBaseController.TYPE_TAG)
+ resultReceiver = intent.getParcelableExtra(
+ CredentialProviderBaseController.RESULT_RECEIVER_TAG)
- if (resultReceiver == null) {
- finish()
+ if (resultReceiver == null) {
+ finish()
+ }
+
+ restoreState(savedInstanceState)
+ if (mWaitingForActivityResult) {
+ return; // Past call still active
+ }
+
+ when (type) {
+ CredentialProviderBaseController.BEGIN_SIGN_IN_TAG -> {
+ handleBeginSignIn()
+ }
+ CredentialProviderBaseController.CREATE_PASSWORD_TAG -> {
+ handleCreatePassword()
+ }
+ CredentialProviderBaseController.CREATE_PUBLIC_KEY_CREDENTIAL_TAG -> {
+ handleCreatePublicKeyCredential()
+ } else -> {
+ Log.w(TAG, "Activity handed an unsupported type")
+ finish()
+ }
+ }
}
- restoreState(savedInstanceState)
- if (mWaitingForActivityResult) {
- return
- // Past call still active
+ private fun restoreState(savedInstanceState: Bundle?) {
+ if (savedInstanceState != null) {
+ mWaitingForActivityResult = savedInstanceState.getBoolean(KEY_AWAITING_RESULT, false)
+ }
}
- when (type) {
- CredentialProviderBaseController.BEGIN_SIGN_IN_TAG -> {
- handleBeginSignIn(intent, resultReceiver)
- }
- CredentialProviderBaseController.CREATE_PASSWORD_TAG -> {
- handleCreatePassword(intent, resultReceiver)
- }
- CredentialProviderBaseController.CREATE_PUBLIC_KEY_CREDENTIAL_TAG -> {
- handleCreatePublicKeyCredential(intent, resultReceiver)
- }
- else -> {
- Log.w(TAG, "Activity handed an unsupported type")
+ private fun handleCreatePublicKeyCredential() {
+ val fidoRegistrationRequest: PublicKeyCredentialCreationOptions? = intent
+ .getParcelableExtra(CredentialProviderBaseController.REQUEST_TAG)
+ val requestCode: Int = intent.getIntExtra(
+ CredentialProviderBaseController.ACTIVITY_REQUEST_CODE_TAG,
+ DEFAULT_VALUE)
+ fidoRegistrationRequest?.let {
+ Fido.getFido2ApiClient(this)
+ .getRegisterPendingIntent(fidoRegistrationRequest)
+ .addOnSuccessListener { result: PendingIntent ->
+ try {
+ mWaitingForActivityResult = true
+ startIntentSenderForResult(
+ result.intentSender,
+ requestCode,
+ null, /* fillInIntent= */
+ 0, /* flagsMask= */
+ 0, /* flagsValue= */
+ 0, /* extraFlags= */
+ null /* options= */
+ )
+ } catch (e: IntentSender.SendIntentException) {
+ setupFailure(resultReceiver!!,
+ CREATE_UNKNOWN,
+ "During public key credential, found IntentSender " +
+ "failure on public key creation: ${e.message}")
+ }
+ }
+ .addOnFailureListener { e: Exception ->
+ var errName: String = CREATE_UNKNOWN
+ if (e is ApiException && e.statusCode in
+ CredentialProviderBaseController.retryables) {
+ errName = CREATE_INTERRUPTED
+ }
+ setupFailure(resultReceiver!!, errName,
+ "During create public key credential, fido registration " +
+ "failure: ${e.message}")
+ }
+ } ?: run {
+ Log.w(TAG, "During create public key credential, request is null, so nothing to " +
+ "launch for public key credentials")
+ finish()
+ }
+ }
+
+ private fun setupFailure(resultReceiver: ResultReceiver, errName: String, errMsg: String) {
+ val bundle = Bundle()
+ bundle.putBoolean(CredentialProviderBaseController.FAILURE_RESPONSE_TAG, true)
+ bundle.putString(CredentialProviderBaseController.EXCEPTION_TYPE_TAG, errName)
+ bundle.putString(CredentialProviderBaseController.EXCEPTION_MESSAGE_TAG, errMsg)
+ resultReceiver.send(Integer.MAX_VALUE, bundle)
finish()
- }
}
- }
- private fun restoreState(savedInstanceState: Bundle?) {
- if (savedInstanceState != null) {
- mWaitingForActivityResult = savedInstanceState.getBoolean(KEY_AWAITING_RESULT, false)
+ override fun onSaveInstanceState(outState: Bundle) {
+ outState.putBoolean(KEY_AWAITING_RESULT, mWaitingForActivityResult)
+ super.onSaveInstanceState(outState)
}
- }
- private fun handleBeginSignIn(intent: Intent, resultReceiver: ResultReceiver?) {
- val params: BeginSignInRequest? =
- intent.getParcelableExtra(CredentialProviderBaseController.REQUEST_TAG)
- val requestCode: Int =
- intent.getIntExtra(CredentialProviderBaseController.ACTIVITY_REQUEST_CODE_TAG, DEFAULT_VALUE)
+ private fun handleBeginSignIn() {
+ val params: BeginSignInRequest? = intent.getParcelableExtra(
+ CredentialProviderBaseController.REQUEST_TAG)
+ val requestCode: Int = intent.getIntExtra(
+ CredentialProviderBaseController.ACTIVITY_REQUEST_CODE_TAG,
+ DEFAULT_VALUE)
+ params?.let {
+ Identity.getSignInClient(this).beginSignIn(params).addOnSuccessListener {
+ try {
+ mWaitingForActivityResult = true
+ startIntentSenderForResult(
+ it.pendingIntent.intentSender,
+ requestCode,
+ null,
+ 0,
+ 0,
+ 0,
+ null
+ )
+ } catch (e: IntentSender.SendIntentException) {
+ setupFailure(resultReceiver!!,
+ GET_UNKNOWN,
+ "During begin sign in, one tap ui intent sender " +
+ "failure: ${e.message}")
+ }
+ }.addOnFailureListener { e: Exception ->
+ var errName: String = GET_NO_CREDENTIALS
+ if (e is ApiException && e.statusCode in
+ CredentialProviderBaseController.retryables) {
+ errName = GET_INTERRUPTED
+ }
+ setupFailure(resultReceiver!!, errName,
+ "During begin sign in, failure response from one tap: ${e.message}")
+ }
+ } ?: run {
+ Log.i(TAG, "During begin sign in, params is null, nothing to launch for " +
+ "begin sign in")
+ finish()
+ }
+ }
- if (intent.hasExtra(CredentialProviderBaseController.REQUEST_TAG) && resultReceiver != null) {
- val result =
- GmsCoreUtils.handleBeginSignIn(
- Identity.getSignInClient(this),
- resultReceiver,
- params!!,
- requestCode,
- this
- )
- mWaitingForActivityResult = result.waitingForActivityResult
- if (result.hasFinished) {
+ private fun handleCreatePassword() {
+ val params: SavePasswordRequest? = intent.getParcelableExtra(
+ CredentialProviderBaseController.REQUEST_TAG)
+ val requestCode: Int = intent.getIntExtra(
+ CredentialProviderBaseController.ACTIVITY_REQUEST_CODE_TAG,
+ DEFAULT_VALUE)
+ params?.let {
+ Identity.getCredentialSavingClient(this).savePassword(params)
+ .addOnSuccessListener {
+ try {
+ mWaitingForActivityResult = true
+ startIntentSenderForResult(
+ it.pendingIntent.intentSender,
+ requestCode,
+ null,
+ 0,
+ 0,
+ 0,
+ null
+ )
+ } catch (e: IntentSender.SendIntentException) {
+ setupFailure(resultReceiver!!,
+ CREATE_UNKNOWN,
+ "During save password, found UI intent sender " +
+ "failure: ${e.message}")
+ }
+ }.addOnFailureListener { e: Exception ->
+ var errName: String = CREATE_UNKNOWN
+ if (e is ApiException && e.statusCode in
+ CredentialProviderBaseController.retryables) {
+ errName = CREATE_INTERRUPTED
+ }
+ setupFailure(resultReceiver!!, errName, "During save password, found " +
+ "password failure response from one tap ${e.message}")
+ }
+ } ?: run {
+ Log.i(TAG, "During save password, params is null, nothing to launch for create" +
+ " password")
+ finish()
+ }
+ }
+
+ override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+ super.onActivityResult(requestCode, resultCode, data)
+ val bundle = Bundle()
+ bundle.putBoolean(CredentialProviderBaseController.FAILURE_RESPONSE_TAG, false)
+ bundle.putInt(CredentialProviderBaseController.ACTIVITY_REQUEST_CODE_TAG, requestCode)
+ bundle.putParcelable(CredentialProviderBaseController.RESULT_DATA_TAG, data)
+ resultReceiver?.send(resultCode, bundle)
+ mWaitingForActivityResult = false
finish()
- }
}
- }
- private fun handleCreatePassword(intent: Intent, resultReceiver: ResultReceiver?) {
- val params: SavePasswordRequest? =
- intent.getParcelableExtra(CredentialProviderBaseController.REQUEST_TAG)
- val requestCode: Int =
- intent.getIntExtra(CredentialProviderBaseController.ACTIVITY_REQUEST_CODE_TAG, DEFAULT_VALUE)
-
- if (intent.hasExtra(CredentialProviderBaseController.REQUEST_TAG) && resultReceiver != null) {
- val result =
- GmsCoreUtils.handleCreatePassword(
- Identity.getCredentialSavingClient(this),
- resultReceiver,
- params!!,
- requestCode,
- this
- )
- mWaitingForActivityResult = result.waitingForActivityResult
- if (result.hasFinished) {
- finish()
- }
+ companion object {
+ private const val DEFAULT_VALUE: Int = 1
+ private const val TAG = "HiddenActivity"
+ private const val KEY_AWAITING_RESULT = "androidx.credentials.playservices.AWAITING_RESULT"
}
- }
-
- private fun handleCreatePublicKeyCredential(intent: Intent, resultReceiver: ResultReceiver?) {
- val fidoRegistrationRequest: PublicKeyCredentialCreationOptions? =
- intent.getParcelableExtra(CredentialProviderBaseController.REQUEST_TAG)
- val requestCode: Int =
- intent.getIntExtra(CredentialProviderBaseController.ACTIVITY_REQUEST_CODE_TAG, DEFAULT_VALUE)
-
- if (intent.hasExtra(CredentialProviderBaseController.REQUEST_TAG) && resultReceiver != null) {
- val result =
- GmsCoreUtils.handleCreatePublicKeyCredential(
- Fido.getFido2ApiClient(this),
- resultReceiver,
- fidoRegistrationRequest!!,
- requestCode,
- this
- )
- mWaitingForActivityResult = result.waitingForActivityResult
- if (result.hasFinished) {
- finish()
- }
- }
- }
-
- override fun onSaveInstanceState(outState: Bundle) {
- outState.putBoolean(KEY_AWAITING_RESULT, mWaitingForActivityResult)
- super.onSaveInstanceState(outState)
- }
-
- override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
- super.onActivityResult(requestCode, resultCode, data)
- val bundle = Bundle()
- bundle.putBoolean(CredentialProviderBaseController.FAILURE_RESPONSE_TAG, false)
- bundle.putInt(CredentialProviderBaseController.ACTIVITY_REQUEST_CODE_TAG, requestCode)
- bundle.putParcelable(CredentialProviderBaseController.RESULT_DATA_TAG, data)
- resultReceiver?.send(resultCode, bundle)
- mWaitingForActivityResult = false
- finish()
- }
-
- companion object {
- private const val DEFAULT_VALUE: Int = 1
- private const val TAG = "HiddenActivity"
- private const val KEY_AWAITING_RESULT = "androidx.credentials.playservices.AWAITING_RESULT"
- }
}
diff --git a/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/controllers/BeginSignIn/CredentialProviderBeginSignInController.kt b/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/controllers/BeginSignIn/CredentialProviderBeginSignInController.kt
index 18f8a16..b0d9453 100644
--- a/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/controllers/BeginSignIn/CredentialProviderBeginSignInController.kt
+++ b/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/controllers/BeginSignIn/CredentialProviderBeginSignInController.kt
@@ -36,13 +36,11 @@
import androidx.credentials.exceptions.GetCredentialInterruptedException
import androidx.credentials.exceptions.GetCredentialUnknownException
import androidx.credentials.playservices.CredentialProviderPlayServicesImpl
-import androidx.credentials.playservices.GmsCoreUtils
import androidx.credentials.playservices.HiddenActivity
import androidx.credentials.playservices.controllers.BeginSignIn.BeginSignInControllerUtility.Companion.constructBeginSignInRequest
import androidx.credentials.playservices.controllers.CreatePublicKeyCredential.PublicKeyCredentialControllerUtility
import androidx.credentials.playservices.controllers.CredentialProviderBaseController
import androidx.credentials.playservices.controllers.CredentialProviderController
-import androidx.fragment.app.FragmentActivity
import com.google.android.gms.auth.api.identity.BeginSignInRequest
import com.google.android.gms.auth.api.identity.Identity
import com.google.android.gms.auth.api.identity.SignInCredential
@@ -119,20 +117,6 @@
}
val convertedRequest: BeginSignInRequest = this.convertRequestToPlayServices(request)
-
- // If we were passed a fragment activity use that instead of a hidden one.
- if (context is FragmentActivity) {
- try {
- GmsCoreUtils.handleBeginSignIn(
- Identity.getSignInClient(context),
- resultReceiver, convertedRequest, GmsCoreUtils.DEFAULT_REQUEST_CODE,
- context)
- return
- } catch (e: Exception) {
- Log.e(TAG, "Failed to use fragment flow", e)
- }
- }
-
val hiddenIntent = Intent(context, HiddenActivity::class.java)
hiddenIntent.putExtra(REQUEST_TAG, convertedRequest)
generateHiddenActivityIntent(resultReceiver, hiddenIntent, BEGIN_SIGN_IN_TAG)
diff --git a/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/controllers/CreatePassword/CredentialProviderCreatePasswordController.kt b/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/controllers/CreatePassword/CredentialProviderCreatePasswordController.kt
index ee4c3bb..360b0c6 100644
--- a/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/controllers/CreatePassword/CredentialProviderCreatePasswordController.kt
+++ b/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/controllers/CreatePassword/CredentialProviderCreatePasswordController.kt
@@ -32,12 +32,9 @@
import androidx.credentials.exceptions.CreateCredentialException
import androidx.credentials.exceptions.CreateCredentialUnknownException
import androidx.credentials.playservices.CredentialProviderPlayServicesImpl
-import androidx.credentials.playservices.GmsCoreUtils
import androidx.credentials.playservices.HiddenActivity
import androidx.credentials.playservices.controllers.CredentialProviderBaseController
import androidx.credentials.playservices.controllers.CredentialProviderController
-import androidx.fragment.app.FragmentActivity
-import com.google.android.gms.auth.api.identity.Identity
import com.google.android.gms.auth.api.identity.SavePasswordRequest
import com.google.android.gms.auth.api.identity.SignInPassword
import java.util.concurrent.Executor
@@ -103,20 +100,6 @@
}
val convertedRequest: SavePasswordRequest = this.convertRequestToPlayServices(request)
-
- // If we were passed a fragment activity use that instead of a hidden one.
- if (context is FragmentActivity) {
- try {
- GmsCoreUtils.handleCreatePassword(
- Identity.getCredentialSavingClient(context),
- resultReceiver, convertedRequest, GmsCoreUtils.DEFAULT_REQUEST_CODE,
- context)
- return
- } catch (e: Exception) {
- Log.e(TAG, "Failed to use fragment flow", e)
- }
- }
-
val hiddenIntent = Intent(context, HiddenActivity::class.java)
hiddenIntent.putExtra(REQUEST_TAG, convertedRequest)
generateHiddenActivityIntent(resultReceiver, hiddenIntent, CREATE_PASSWORD_TAG)
diff --git a/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/controllers/CreatePublicKeyCredential/CredentialProviderCreatePublicKeyCredentialController.kt b/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/controllers/CreatePublicKeyCredential/CredentialProviderCreatePublicKeyCredentialController.kt
index 17a5b34..1302a81 100644
--- a/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/controllers/CreatePublicKeyCredential/CredentialProviderCreatePublicKeyCredentialController.kt
+++ b/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/controllers/CreatePublicKeyCredential/CredentialProviderCreatePublicKeyCredentialController.kt
@@ -35,11 +35,9 @@
import androidx.credentials.exceptions.domerrors.UnknownError
import androidx.credentials.exceptions.publickeycredential.CreatePublicKeyCredentialDomException
import androidx.credentials.playservices.CredentialProviderPlayServicesImpl
-import androidx.credentials.playservices.GmsCoreUtils
import androidx.credentials.playservices.HiddenActivity
import androidx.credentials.playservices.controllers.CredentialProviderBaseController
import androidx.credentials.playservices.controllers.CredentialProviderController
-import androidx.fragment.app.FragmentActivity
import com.google.android.gms.fido.Fido
import com.google.android.gms.fido.fido2.api.common.PublicKeyCredential
import com.google.android.gms.fido.fido2.api.common.PublicKeyCredentialCreationOptions
@@ -121,19 +119,6 @@
if (CredentialProviderPlayServicesImpl.cancellationReviewer(cancellationSignal)) {
return
}
-
- // If we were passed a fragment activity use that instead of a hidden one.
- if (context is FragmentActivity) {
- try {
- GmsCoreUtils.handleCreatePublicKeyCredential(Fido.getFido2ApiClient(context),
- resultReceiver, fidoRegistrationRequest, GmsCoreUtils.DEFAULT_REQUEST_CODE,
- context)
- return
- } catch (e: Exception) {
- Log.e(TAG, "Failed to use fragment flow", e)
- }
- }
-
val hiddenIntent = Intent(context, HiddenActivity::class.java)
hiddenIntent.putExtra(REQUEST_TAG, fidoRegistrationRequest)
generateHiddenActivityIntent(resultReceiver, hiddenIntent,
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/CredentialProviderServiceJavaTest.java b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/CredentialProviderServiceJavaTest.java
index a2c44d0..e8d55f0 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/CredentialProviderServiceJavaTest.java
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/CredentialProviderServiceJavaTest.java
@@ -23,16 +23,22 @@
import android.os.CancellationSignal;
import android.os.OutcomeReceiver;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SdkSuppress;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
import java.util.ArrayList;
+import java.util.List;
-//@RunWith(AndroidJUnit4.class)
-//@SmallTest
+@RunWith(AndroidJUnit4.class)
+@SmallTest
@SdkSuppress(minSdkVersion = 34)
public class CredentialProviderServiceJavaTest {
- //@Test
+ @Test
public void test_createRequest() {
CredentialProviderServiceTestImpl service = new CredentialProviderServiceTestImpl();
service.setTestMode(true);
@@ -60,54 +66,59 @@
assertThat(service.getLastCreateRequest()).isNotNull();
}
- //@Test
+ @Test
public void test_getRequest() {
CredentialProviderServiceTestImpl service = new CredentialProviderServiceTestImpl();
service.setTestMode(true);
- BeginGetCredentialRequest request =
- new BeginGetCredentialRequest(new ArrayList<BeginGetCredentialOption>());
- OutcomeReceiver<
- androidx.credentials.provider.BeginGetCredentialResponse,
- androidx.credentials.exceptions.GetCredentialException>
- outcome =
- new OutcomeReceiver<
- androidx.credentials.provider.BeginGetCredentialResponse,
- androidx.credentials.exceptions.GetCredentialException>() {
- public void onResult(
- androidx.credentials.provider.BeginGetCredentialResponse
- response) {}
+ android.service.credentials.BeginGetCredentialOption option =
+ new android.service.credentials.BeginGetCredentialOption(
+ "id", "type", new Bundle());
+ List<android.service.credentials.BeginGetCredentialOption> options = new ArrayList<>();
+ options.add(option);
- public void onError(
- androidx.credentials.exceptions.GetCredentialException error) {}
+ android.service.credentials.BeginGetCredentialRequest request =
+ new android.service.credentials.BeginGetCredentialRequest.Builder()
+ .setBeginGetCredentialOptions(options).build();
+ OutcomeReceiver<
+ android.service.credentials.BeginGetCredentialResponse,
+ android.credentials.GetCredentialException>
+ outcome = new OutcomeReceiver<
+ android.service.credentials.BeginGetCredentialResponse,
+ android.credentials.GetCredentialException>() {
+ public void onResult(
+ android.service.credentials.BeginGetCredentialResponse response) {}
+
+ public void onError(android.credentials.GetCredentialException error) {}
};
// Call the service.
assertThat(service.getLastGetRequest()).isNull();
- service.onBeginGetCredentialRequest(request, new CancellationSignal(), outcome);
+ service.onBeginGetCredential(request, new CancellationSignal(), outcome);
assertThat(service.getLastGetRequest()).isNotNull();
}
- //@Test
+ @Test
public void test_clearRequest() {
CredentialProviderServiceTestImpl service = new CredentialProviderServiceTestImpl();
service.setTestMode(true);
- ProviderClearCredentialStateRequest request =
- new ProviderClearCredentialStateRequest(
- new CallingAppInfo("name", new SigningInfo()));
- OutcomeReceiver<Void, androidx.credentials.exceptions.ClearCredentialException> outcome =
+ android.service.credentials.ClearCredentialStateRequest request =
+ new android.service.credentials.ClearCredentialStateRequest(
+ new android.service.credentials.CallingAppInfo(
+ "name", new SigningInfo()), new Bundle());
+ OutcomeReceiver<Void, android.credentials.ClearCredentialStateException> outcome =
new OutcomeReceiver<
- Void, androidx.credentials.exceptions.ClearCredentialException>() {
+ Void, android.credentials.ClearCredentialStateException>() {
public void onResult(Void response) {}
public void onError(
- androidx.credentials.exceptions.ClearCredentialException error) {}
+ android.credentials.ClearCredentialStateException error) {}
};
// Call the service.
assertThat(service.getLastClearRequest()).isNull();
- service.onClearCredentialStateRequest(request, new CancellationSignal(), outcome);
+ service.onClearCredentialState(request, new CancellationSignal(), outcome);
assertThat(service.getLastClearRequest()).isNotNull();
}
}
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/CredentialProviderServiceTest.kt b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/CredentialProviderServiceTest.kt
index 700c7bd..df32b01 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/CredentialProviderServiceTest.kt
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/CredentialProviderServiceTest.kt
@@ -21,17 +21,21 @@
import android.os.CancellationSignal
import android.os.OutcomeReceiver
import android.util.Log
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SdkSuppress
+import androidx.test.filters.SmallTest
import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
@SdkSuppress(minSdkVersion = 34)
-// @RunWith(AndroidJUnit4::class)
-// @SmallTest
+@RunWith(AndroidJUnit4::class)
+@SmallTest
class CredentialProviderServiceTest {
private val LOG_TAG = "CredentialProviderServiceTest"
- // @Test
+ @Test
fun test_createRequest() {
var service = CredentialProviderServiceTestImpl()
service.isTestMode = true
@@ -54,49 +58,53 @@
assertThat(service.lastCreateRequest).isNotNull()
}
- // @Test
+ @Test
fun test_getRequest() {
var service = CredentialProviderServiceTestImpl()
service.isTestMode = true
- var request = BeginGetCredentialRequest(listOf<BeginGetCredentialOption>())
- val outcome = OutcomeReceiver<androidx.credentials.provider.BeginGetCredentialResponse,
- androidx.credentials.exceptions.GetCredentialException> {
- fun onResult(response: androidx.credentials.provider.BeginGetCredentialResponse) {
+ var option = android.service.credentials.BeginGetCredentialOption("id", "type", Bundle())
+ var request = android.service.credentials.BeginGetCredentialRequest.Builder()
+ .setBeginGetCredentialOptions(listOf(option)).build()
+ val outcome = OutcomeReceiver<
+ android.service.credentials.BeginGetCredentialResponse,
+ android.credentials.GetCredentialException> {
+ fun onResult(response: android.service.credentials.BeginGetCredentialResponse) {
Log.i(LOG_TAG, "get request: " + response.toString())
}
- fun onError(error: androidx.credentials.exceptions.GetCredentialException) {
+ fun onError(error: android.credentials.GetCredentialException) {
Log.e(LOG_TAG, "get request error", error)
}
}
// Call the service.
assertThat(service.lastGetRequest).isNull()
- service.onBeginGetCredentialRequest(request, CancellationSignal(), outcome)
+ service.onBeginGetCredential(request, CancellationSignal(), outcome)
assertThat(service.lastGetRequest).isNotNull()
}
- // @Test
+ @Test
fun test_clearRequest() {
var service = CredentialProviderServiceTestImpl()
service.isTestMode = true
- var request = ProviderClearCredentialStateRequest(CallingAppInfo("name", SigningInfo()))
- val outcome = OutcomeReceiver<Void?,
- androidx.credentials.exceptions.ClearCredentialException> {
- fun onResult(response: Void?) {
+ var request = android.service.credentials.ClearCredentialStateRequest(
+ android.service.credentials.CallingAppInfo("name", SigningInfo()), Bundle())
+ val outcome = OutcomeReceiver<Void,
+ android.credentials.ClearCredentialStateException> {
+ fun onResult(response: Void) {
Log.i(LOG_TAG, "clear request: " + response.toString())
}
- fun onError(error: androidx.credentials.exceptions.ClearCredentialException) {
+ fun onError(error: android.credentials.ClearCredentialStateException) {
Log.e(LOG_TAG, "clear request error", error)
}
}
// Call the service.
assertThat(service.lastClearRequest).isNull()
- service.onClearCredentialStateRequest(request, CancellationSignal(), outcome)
+ service.onClearCredentialState(request, CancellationSignal(), outcome)
assertThat(service.lastClearRequest).isNotNull()
}
}
diff --git a/credentials/credentials/src/main/java/androidx/credentials/CredentialProviderFrameworkImpl.kt b/credentials/credentials/src/main/java/androidx/credentials/CredentialProviderFrameworkImpl.kt
index c61120c..2843f46 100644
--- a/credentials/credentials/src/main/java/androidx/credentials/CredentialProviderFrameworkImpl.kt
+++ b/credentials/credentials/src/main/java/androidx/credentials/CredentialProviderFrameworkImpl.kt
@@ -241,14 +241,6 @@
val builder = android.credentials.GetCredentialRequest.Builder(
GetCredentialRequest.toRequestDataBundle(request))
request.credentialOptions.forEach {
- if (request.preferImmediatelyAvailableCredentials &&
- it is GetPublicKeyCredentialOption) {
- it.requestData.putBoolean(
- "androidx.credentials.BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS",
- true,
- )
- }
-
builder.addCredentialOption(
android.credentials.CredentialOption.Builder(
it.type, it.requestData, it.candidateQueryData
diff --git a/credentials/credentials/src/main/java/androidx/credentials/provider/Action.kt b/credentials/credentials/src/main/java/androidx/credentials/provider/Action.kt
index 7fa6e48..c314e36 100644
--- a/credentials/credentials/src/main/java/androidx/credentials/provider/Action.kt
+++ b/credentials/credentials/src/main/java/androidx/credentials/provider/Action.kt
@@ -57,8 +57,10 @@
*
* @param title the title of the entry
* @param pendingIntent the [PendingIntent] that will get invoked when the user selects this
- * authentication entry on the UI, must be created with flag [PendingIntent.FLAG_MUTABLE] so
- * that the system can add the complete request to the extras of the associated intent
+ * entry, must be created with a unique request code per entry,
+ * with flag [PendingIntent.FLAG_MUTABLE] to allow the Android system to attach the
+ * final request, and NOT with flag [PendingIntent.FLAG_ONE_SHOT] as it can be invoked multiple
+ * times
* @param subtitle the optional subtitle that is displayed on the entry
*
* @see android.service.credentials.BeginGetCredentialResponse for usage.
@@ -80,8 +82,11 @@
* A builder for [Action]
*
* @param title the title of this action entry
- * @param pendingIntent the [PendingIntent] that will be fired when the user selects
- * this action entry
+ * @param pendingIntent the [PendingIntent] that will get invoked when the user selects this
+ * entry, must be created with a unique request code per entry,
+ * with flag [PendingIntent.FLAG_MUTABLE] to allow the Android system to attach the
+ * final request, and NOT with flag [PendingIntent.FLAG_ONE_SHOT] as it can be invoked multiple
+ * times
*/
class Builder constructor(
private val title: CharSequence,
diff --git a/credentials/credentials/src/main/java/androidx/credentials/provider/AuthenticationAction.kt b/credentials/credentials/src/main/java/androidx/credentials/provider/AuthenticationAction.kt
index beb3038..857735d 100644
--- a/credentials/credentials/src/main/java/androidx/credentials/provider/AuthenticationAction.kt
+++ b/credentials/credentials/src/main/java/androidx/credentials/provider/AuthenticationAction.kt
@@ -51,8 +51,10 @@
*
* @param title the title to be shown with this entry on the account selector UI
* @param pendingIntent the [PendingIntent] that will get invoked when the user selects this
- * authentication entry on the UI, must be created with flag [PendingIntent.FLAG_MUTABLE] so
- * that the system can add the complete request to the extras of the associated intent
+ * entry, must be created with a unique request code per entry,
+ * with flag [PendingIntent.FLAG_MUTABLE] to allow the Android system to attach the
+ * final request, and NOT with flag [PendingIntent.FLAG_ONE_SHOT] as it can be invoked multiple
+ * times
*
* @see android.service.credentials.BeginGetCredentialResponse
* for more usage details.
@@ -72,8 +74,11 @@
* A builder for [AuthenticationAction]
*
* @param title the title to be displayed with this authentication action entry
- * @param pendingIntent the [PendingIntent] that will be fired when the user selects
- * this entry
+ * @param pendingIntent the [PendingIntent] that will get invoked when the user selects this
+ * entry, must be created with a unique request code per entry,
+ * with flag [PendingIntent.FLAG_MUTABLE] to allow the Android system to attach the
+ * final request, and NOT with flag [PendingIntent.FLAG_ONE_SHOT] as it can be invoked multiple
+ * times
*/
class Builder constructor(
private val title: CharSequence,
diff --git a/credentials/credentials/src/main/java/androidx/credentials/provider/CreateEntry.kt b/credentials/credentials/src/main/java/androidx/credentials/provider/CreateEntry.kt
index 0b4ff95..bf1e49a 100644
--- a/credentials/credentials/src/main/java/androidx/credentials/provider/CreateEntry.kt
+++ b/credentials/credentials/src/main/java/androidx/credentials/provider/CreateEntry.kt
@@ -60,8 +60,10 @@
*
* @param accountName the name of the account where the credential will be saved
* @param pendingIntent the [PendingIntent] that will get invoked when the user selects this
- * entry, must be created with flag [PendingIntent.FLAG_MUTABLE] to allow the Android
- * system to attach the final request
+ * entry, must be created with a unique request code per entry,
+ * with flag [PendingIntent.FLAG_MUTABLE] to allow the Android system to attach the
+ * final request, and NOT with flag [PendingIntent.FLAG_ONE_SHOT] as it can be invoked multiple
+ * times
* @param description the localized description shown on UI about where the credential is stored
* @param icon the icon to be displayed with this entry on the UI, must be created using
* [Icon.createWithResource] when possible, and especially not with [Icon.createWithBitmap] as
@@ -146,8 +148,11 @@
* @constructor constructs an instance of [CreateEntry.Builder]
*
* @param accountName the name of the account where the credential will be registered
- * @param pendingIntent the [PendingIntent] that will be fired when the user selects
- * this entry
+ * @param pendingIntent the [PendingIntent] that will get invoked when the user selects this
+ * entry, must be created with a unique request code per entry,
+ * with flag [PendingIntent.FLAG_MUTABLE] to allow the Android system to attach the
+ * final request, and NOT with flag [PendingIntent.FLAG_ONE_SHOT] as it can be invoked multiple
+ * times
*/
class Builder constructor(
private val accountName: CharSequence,
@@ -444,6 +449,13 @@
private const val REVISION_ID = 1
+ /**
+ * Converts an instance of [CreateEntry] to a [Slice].
+ *
+ * This method is only expected to be called on an API > 28
+ * impl, hence returning null for other levels as the
+ * visibility is only restricted to the library.
+ */
@RestrictTo(RestrictTo.Scope.LIBRARY)
@JvmStatic
fun toSlice(
diff --git a/credentials/credentials/src/main/java/androidx/credentials/provider/CustomCredentialEntry.kt b/credentials/credentials/src/main/java/androidx/credentials/provider/CustomCredentialEntry.kt
index a8a5951..a0351d8 100644
--- a/credentials/credentials/src/main/java/androidx/credentials/provider/CustomCredentialEntry.kt
+++ b/credentials/credentials/src/main/java/androidx/credentials/provider/CustomCredentialEntry.kt
@@ -331,6 +331,13 @@
private const val REVISION_ID = 1
+ /**
+ * Converts an instance of [CustomCredentialEntry] to a [Slice].
+ *
+ * This method is only expected to be called on an API > 28
+ * impl, hence returning null for other levels as the
+ * visibility is only restricted to the library.
+ */
@RestrictTo(RestrictTo.Scope.LIBRARY)
@JvmStatic
fun toSlice(
diff --git a/credentials/credentials/src/main/java/androidx/credentials/provider/PasswordCredentialEntry.kt b/credentials/credentials/src/main/java/androidx/credentials/provider/PasswordCredentialEntry.kt
index 7700bdb0..fd4e96c 100644
--- a/credentials/credentials/src/main/java/androidx/credentials/provider/PasswordCredentialEntry.kt
+++ b/credentials/credentials/src/main/java/androidx/credentials/provider/PasswordCredentialEntry.kt
@@ -54,9 +54,11 @@
* [Icon.createWithResource] when possible, and especially not with [Icon.createWithBitmap] as
* the latter consumes more memory and may cause undefined behavior due to memory implications
* on internal transactions; defaulted to a fallback password credential icon if not provided
- * @property pendingIntent the [PendingIntent] that will get invoked when the user selects this
- * entry, must be created with flag [PendingIntent.FLAG_MUTABLE] to allow the Android
- * system to attach the final request
+ * @param pendingIntent the [PendingIntent] that will get invoked when the user selects this
+ * entry, must be created with a unique request code per entry,
+ * with flag [PendingIntent.FLAG_MUTABLE] to allow the Android system to attach the
+ * final request, and NOT with flag [PendingIntent.FLAG_ONE_SHOT] as it can be invoked multiple
+ * times
* @property isAutoSelectAllowed whether this entry is allowed to be auto
* selected if it is the only one on the UI. Note that setting this value
* to true does not guarantee this behavior. The developer must also set this
@@ -333,6 +335,13 @@
private const val REVISION_ID = 1
+ /**
+ * Converts an instance of [PasswordCredentialEntry] to a [Slice].
+ *
+ * This method is only expected to be called on an API > 28
+ * impl, hence returning null for other levels as the
+ * visibility is only restricted to the library.
+ */
@RestrictTo(RestrictTo.Scope.LIBRARY)
@JvmStatic
fun toSlice(
@@ -364,8 +373,10 @@
* @param context the context of the calling app, required to retrieve fallback resources
* @param username the username of the account holding the password credential
* @param pendingIntent the [PendingIntent] that will get invoked when the user selects this
- * entry, must be created with flag [PendingIntent.FLAG_MUTABLE] to allow the Android
- * system to attach the final request
+ * entry, must be created with a unique request code per entry,
+ * with flag [PendingIntent.FLAG_MUTABLE] to allow the Android system to attach the
+ * final request, and NOT with flag [PendingIntent.FLAG_ONE_SHOT] as it can be invoked multiple
+ * times
* @param beginGetPasswordOption the option from the original [BeginGetCredentialResponse],
* for which this credential entry is being added
*
diff --git a/credentials/credentials/src/main/java/androidx/credentials/provider/PublicKeyCredentialEntry.kt b/credentials/credentials/src/main/java/androidx/credentials/provider/PublicKeyCredentialEntry.kt
index 7f6f45e..0df1fe1 100644
--- a/credentials/credentials/src/main/java/androidx/credentials/provider/PublicKeyCredentialEntry.kt
+++ b/credentials/credentials/src/main/java/androidx/credentials/provider/PublicKeyCredentialEntry.kt
@@ -54,9 +54,11 @@
* [Icon.createWithResource] when possible, and especially not with [Icon.createWithBitmap] as
* the latter consumes more memory and may cause undefined behavior due to memory implications
* on internal transactions; defaulted to a fallback public key credential icon if not provided
- * @property pendingIntent the [PendingIntent] that will get invoked when the user selects this
- * authentication entry on the UI, must be created with flag [PendingIntent.FLAG_MUTABLE] so
- * that the system can add the complete request to the extras of the associated intent
+ * @param pendingIntent the [PendingIntent] that will get invoked when the user selects this
+ * entry, must be created with a unique request code per entry,
+ * with flag [PendingIntent.FLAG_MUTABLE] to allow the Android system to attach the
+ * final request, and NOT with flag [PendingIntent.FLAG_ONE_SHOT] as it can be invoked multiple
+ * times
* @property isAutoSelectAllowed whether this entry is allowed to be auto
* selected if it is the only one on the UI. Note that setting this value
* to true does not guarantee this behavior. The developer must also set this
@@ -92,8 +94,10 @@
* @param context the context of the calling app, required to retrieve fallback resources
* @param username the username of the account holding the public key credential
* @param pendingIntent the [PendingIntent] that will get invoked when the user selects this
- * entry, must be created with flag [PendingIntent.FLAG_MUTABLE] to allow the Android
- * system to attach the final request
+ * entry, must be created with a unique request code per entry,
+ * with flag [PendingIntent.FLAG_MUTABLE] to allow the Android system to attach the
+ * final request, and NOT with flag [PendingIntent.FLAG_ONE_SHOT] as it can be invoked multiple
+ * times
* @param beginGetPublicKeyCredentialOption the option from the original
* [BeginGetCredentialResponse], for which this credential entry is being added
* @param displayName the displayName of the account holding the public key credential
@@ -332,6 +336,13 @@
private const val REVISION_ID = 1
+ /**
+ * Converts an instance of [PublicKeyCredentialEntry] to a [Slice].
+ *
+ * This method is only expected to be called on an API > 28
+ * impl, hence returning null for other levels as the
+ * visibility is only restricted to the library.
+ */
@RestrictTo(RestrictTo.Scope.LIBRARY)
@JvmStatic
fun toSlice(
diff --git a/credentials/credentials/src/main/java/androidx/credentials/provider/RemoteEntry.kt b/credentials/credentials/src/main/java/androidx/credentials/provider/RemoteEntry.kt
index 84dac25..060aa4e 100644
--- a/credentials/credentials/src/main/java/androidx/credentials/provider/RemoteEntry.kt
+++ b/credentials/credentials/src/main/java/androidx/credentials/provider/RemoteEntry.kt
@@ -55,8 +55,10 @@
* A builder for [RemoteEntry]
*
* @param pendingIntent the [PendingIntent] that will get invoked when the user selects this
- * entry, must be created with flag [PendingIntent.FLAG_MUTABLE] to allow the Android
- * system to attach the final request
+ * entry, must be created with a unique request code per entry,
+ * with flag [PendingIntent.FLAG_MUTABLE] to allow the Android system to attach the
+ * final request, and NOT with flag [PendingIntent.FLAG_ONE_SHOT] as it can be invoked multiple
+ * times
*/
class Builder constructor(
private val pendingIntent: PendingIntent
@@ -79,6 +81,13 @@
private const val REVISION_ID = 1
+ /**
+ * Converts an instance of [RemoteEntry] to a [Slice].
+ *
+ * This method is only expected to be called on an API > 28
+ * impl, hence returning null for other levels as the
+ * visibility is only restricted to the library.
+ */
@RestrictTo(RestrictTo.Scope.LIBRARY)
@RequiresApi(28)
@JvmStatic
diff --git a/datastore/datastore-core/lint-baseline.xml b/datastore/datastore-core/lint-baseline.xml
new file mode 100644
index 0000000..b8e7866
--- /dev/null
+++ b/datastore/datastore-core/lint-baseline.xml
@@ -0,0 +1,121 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="6" by="lint 8.1.0-beta05" type="baseline" client="gradle" dependencies="false" name="AGP (8.1.0-beta05)" variant="all" version="8.1.0-beta05">
+
+ <issue
+ id="SyntheticAccessor"
+ message="Access to `private` method `getCoordinator` of class `DataStoreImpl` requires synthetic accessor"
+ errorLine1=" coordinator.lock {"
+ errorLine2=" ~~~~~~~~~~~">
+ <location
+ file="src/commonMain/kotlin/androidx/datastore/core/DataStoreImpl.kt"/>
+ </issue>
+
+ <issue
+ id="SyntheticAccessor"
+ message="Access to `private` method `getCoordinator` of class `DataStoreImpl` requires synthetic accessor"
+ errorLine1=" version = coordinator.getVersion()"
+ errorLine2=" ~~~~~~~~~~~">
+ <location
+ file="src/commonMain/kotlin/androidx/datastore/core/DataStoreImpl.kt"/>
+ </issue>
+
+ <issue
+ id="SyntheticAccessor"
+ message="Access to `private` method `getCoordinator` of class `DataStoreImpl` requires synthetic accessor"
+ errorLine1=" coordinator.updateNotifications.conflate().collect {"
+ errorLine2=" ~~~~~~~~~~~">
+ <location
+ file="src/commonMain/kotlin/androidx/datastore/core/DataStoreImpl.kt"/>
+ </issue>
+
+ <issue
+ id="SyntheticAccessor"
+ message="Access to `private` method `readDataAndUpdateCache` of class `DataStoreImpl` requires synthetic accessor"
+ errorLine1=" readDataAndUpdateCache(requireLock = true)"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/commonMain/kotlin/androidx/datastore/core/DataStoreImpl.kt"/>
+ </issue>
+
+ <issue
+ id="SyntheticAccessor"
+ message="Access to `private` method `getCoordinator` of class `DataStoreImpl` requires synthetic accessor"
+ errorLine1=" coordinator.lock { block() }"
+ errorLine2=" ~~~~~~~~~~~">
+ <location
+ file="src/commonMain/kotlin/androidx/datastore/core/DataStoreImpl.kt"/>
+ </issue>
+
+ <issue
+ id="SyntheticAccessor"
+ message="Access to `private` method `readDataFromFileOrDefault` of class `DataStoreImpl` requires synthetic accessor"
+ errorLine1=" val data = readDataFromFileOrDefault()"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/commonMain/kotlin/androidx/datastore/core/DataStoreImpl.kt"/>
+ </issue>
+
+ <issue
+ id="SyntheticAccessor"
+ message="Access to `private` method `getCoordinator` of class `DataStoreImpl` requires synthetic accessor"
+ errorLine1=" Data(data, data.hashCode(), version = coordinator.getVersion())"
+ errorLine2=" ~~~~~~~~~~~">
+ <location
+ file="src/commonMain/kotlin/androidx/datastore/core/DataStoreImpl.kt"/>
+ </issue>
+
+ <issue
+ id="SyntheticAccessor"
+ message="Access to `private` method `getCoordinator` of class `DataStoreImpl` requires synthetic accessor"
+ errorLine1=" val preLockVersion = coordinator.getVersion()"
+ errorLine2=" ~~~~~~~~~~~">
+ <location
+ file="src/commonMain/kotlin/androidx/datastore/core/DataStoreImpl.kt"/>
+ </issue>
+
+ <issue
+ id="SyntheticAccessor"
+ message="Access to `private` method `getCoordinator` of class `DataStoreImpl` requires synthetic accessor"
+ errorLine1=" coordinator.tryLock { locked ->"
+ errorLine2=" ~~~~~~~~~~~">
+ <location
+ file="src/commonMain/kotlin/androidx/datastore/core/DataStoreImpl.kt"/>
+ </issue>
+
+ <issue
+ id="SyntheticAccessor"
+ message="Access to `private` method `readDataFromFileOrDefault` of class `DataStoreImpl` requires synthetic accessor"
+ errorLine1=" val data = readDataFromFileOrDefault()"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/commonMain/kotlin/androidx/datastore/core/DataStoreImpl.kt"/>
+ </issue>
+
+ <issue
+ id="SyntheticAccessor"
+ message="Access to `private` method `getCoordinator` of class `DataStoreImpl` requires synthetic accessor"
+ errorLine1=" val version = if (locked) coordinator.getVersion() else preLockVersion"
+ errorLine2=" ~~~~~~~~~~~">
+ <location
+ file="src/commonMain/kotlin/androidx/datastore/core/DataStoreImpl.kt"/>
+ </issue>
+
+ <issue
+ id="SyntheticAccessor"
+ message="Access to `private` method `readDataFromFileOrDefault` of class `DataStoreImpl` requires synthetic accessor"
+ errorLine1=" newData = readDataFromFileOrDefault()"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/commonMain/kotlin/androidx/datastore/core/DataStoreImpl.kt"/>
+ </issue>
+
+ <issue
+ id="SyntheticAccessor"
+ message="Access to `private` method `getCoordinator` of class `DataStoreImpl` requires synthetic accessor"
+ errorLine1=" version = coordinator.getVersion()"
+ errorLine2=" ~~~~~~~~~~~">
+ <location
+ file="src/commonMain/kotlin/androidx/datastore/core/DataStoreImpl.kt"/>
+ </issue>
+
+</issues>
diff --git a/datastore/datastore-core/src/androidMain/kotlin/androidx/datastore/core/MultiProcessCoordinator.kt b/datastore/datastore-core/src/androidMain/kotlin/androidx/datastore/core/MultiProcessCoordinator.kt
index 48f575b..f5314a2 100644
--- a/datastore/datastore-core/src/androidMain/kotlin/androidx/datastore/core/MultiProcessCoordinator.kt
+++ b/datastore/datastore-core/src/androidMain/kotlin/androidx/datastore/core/MultiProcessCoordinator.kt
@@ -22,6 +22,7 @@
import java.io.FileOutputStream
import java.io.IOException
import java.nio.channels.FileLock
+import kotlin.contracts.ExperimentalContracts
import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.channels.trySendBlocking
@@ -72,6 +73,7 @@
// run block with an attempt to get the exclusive lock, still run even if
// attempt fails. Pass a boolean to indicate if the attempt succeeds.
+ @OptIn(ExperimentalContracts::class) // withTryLock
override suspend fun <T> tryLock(block: suspend (Boolean) -> T): T {
inMemoryMutex.withTryLock<T> {
if (it == false) {
diff --git a/datastore/datastore-core/src/commonMain/kotlin/androidx/datastore/core/MutexUtils.kt b/datastore/datastore-core/src/commonMain/kotlin/androidx/datastore/core/MutexUtils.kt
index 528fa08..4a5250a 100644
--- a/datastore/datastore-core/src/commonMain/kotlin/androidx/datastore/core/MutexUtils.kt
+++ b/datastore/datastore-core/src/commonMain/kotlin/androidx/datastore/core/MutexUtils.kt
@@ -28,7 +28,7 @@
*
* [block] is guaranteed to be called once and only once by this function.
*/
-@OptIn(ExperimentalContracts::class)
+@ExperimentalContracts
internal inline fun <R> Mutex.withTryLock(owner: Any? = null, block: (Boolean) -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
diff --git a/datastore/datastore-core/src/commonMain/kotlin/androidx/datastore/core/SingleProcessCoordinator.kt b/datastore/datastore-core/src/commonMain/kotlin/androidx/datastore/core/SingleProcessCoordinator.kt
index 68ce1c0..83e2072 100644
--- a/datastore/datastore-core/src/commonMain/kotlin/androidx/datastore/core/SingleProcessCoordinator.kt
+++ b/datastore/datastore-core/src/commonMain/kotlin/androidx/datastore/core/SingleProcessCoordinator.kt
@@ -16,6 +16,7 @@
package androidx.datastore.core
+import kotlin.contracts.ExperimentalContracts
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.sync.Mutex
@@ -40,6 +41,7 @@
// run block with an attempt to get the exclusive lock, still run even if
// attempt fails. Pass a boolean to indicate if the attempt succeeds.
+ @OptIn(ExperimentalContracts::class) // withTryLock
override suspend fun <T> tryLock(block: suspend (Boolean) -> T): T {
return mutex.withTryLock {
block(it)
diff --git a/development/diagnose-build-failure/diagnose-build-failure.sh b/development/diagnose-build-failure/diagnose-build-failure.sh
index 6baa715..36504c2 100755
--- a/development/diagnose-build-failure/diagnose-build-failure.sh
+++ b/development/diagnose-build-failure/diagnose-build-failure.sh
@@ -376,9 +376,9 @@
# command for running a build
buildCommand="$(getBuildCommand "./gradlew --no-daemon $gradleArgs")"
# command for moving state, running build, and moving state back
-fullFiltererCommand="$(getTestStateCommand $buildCommand)"
+fullFiltererCommand="$(getTestStateCommand --invert $buildCommand)"
-if $supportRoot/development/file-utils/diff-filterer.py $timeoutArg --assume-input-states-are-correct --work-path $tempDir $successState $tempDir/prev "$fullFiltererCommand"; then
+if $supportRoot/development/file-utils/diff-filterer.py $timeoutArg --assume-input-states-are-correct --work-path $tempDir $tempDir/prev $successState "$fullFiltererCommand"; then
echo >&2
echo "There should be something wrong with the above file state" >&2
echo "Hopefully the output from diff-filterer.py above is enough information for you to figure out what is wrong" >&2
diff --git a/development/referenceDocs/switcher.py b/development/referenceDocs/switcher.py
index 63195ae..562d433 100755
--- a/development/referenceDocs/switcher.py
+++ b/development/referenceDocs/switcher.py
@@ -81,14 +81,14 @@
stub = doc.replace(java_source_abs_path, kotlin_source_abs_path)
# Always add the switcher for java files, switch to the package summary if
# the page itself doesn't exist in kotlin
- slug1 = "sed -i 's/<\/h1>/{}/' {}".format("<\/h1>\\n{% setvar page_path %}_page_path_{% endsetvar %}\\n{% setvar can_switch %}1{% endsetvar %}\\n{% include \"reference\/_java_switcher2.md\" %}",doc)
+ slug1 = "sed -i 's/<div id=\"refdoc-switcher-placeholder\">/{}/' {}".format("\\n{% setvar page_path %}_page_path_{% endsetvar %}\\n{% setvar can_switch %}1{% endsetvar %}\\n{% include \"reference\/_java_switcher2.md\" %}",doc)
else:
file_path = doc[len(kotlin_ref_root)+1:]
stub = doc.replace(kotlin_source_abs_path, java_source_abs_path)
if (both):
- slug1 = "sed -i 's/<\/h1>/{}/' {}".format("<\/h1>\\n{% setvar page_path %}_page_path_{% endsetvar %}\\n{% setvar can_switch %}1{% endsetvar %}\\n{% include \"reference\/_kotlin_switcher2.md\" %}",doc)
+ slug1 = "sed -i 's/<div id=\"refdoc-switcher-placeholder\">/{}/' {}".format("\\n{% setvar page_path %}_page_path_{% endsetvar %}\\n{% setvar can_switch %}1{% endsetvar %}\\n{% include \"reference\/_kotlin_switcher2.md\" %}",doc)
else:
- slug1 = "sed -i 's/<\/h1>/{}/' {}".format("<\/h1>\\n{% include \"reference\/_kotlin_switcher2.md\" %}",doc)
+ slug1 = "sed -i 's/<div id=\"refdoc-switcher-placeholder\">/{}/' {}".format("\\n{% include \"reference\/_kotlin_switcher2.md\" %}",doc)
os.system(slug1)
if both or java:
diff --git a/docs-public/build.gradle b/docs-public/build.gradle
index 79a19af..1e62f7f 100644
--- a/docs-public/build.gradle
+++ b/docs-public/build.gradle
@@ -144,11 +144,11 @@
docs("androidx.drawerlayout:drawerlayout:1.2.0")
docs("androidx.dynamicanimation:dynamicanimation:1.1.0-alpha02")
docs("androidx.dynamicanimation:dynamicanimation-ktx:1.0.0-alpha03")
- docs("androidx.emoji2:emoji2:1.4.0-beta05")
- docs("androidx.emoji2:emoji2-bundled:1.4.0-beta05")
- docs("androidx.emoji2:emoji2-emojipicker:1.4.0-beta05")
- docs("androidx.emoji2:emoji2-views:1.4.0-beta05")
- docs("androidx.emoji2:emoji2-views-helper:1.4.0-beta05")
+ docs("androidx.emoji2:emoji2:1.4.0-rc01")
+ docs("androidx.emoji2:emoji2-bundled:1.4.0-rc01")
+ docs("androidx.emoji2:emoji2-emojipicker:1.4.0-rc01")
+ docs("androidx.emoji2:emoji2-views:1.4.0-rc01")
+ docs("androidx.emoji2:emoji2-views-helper:1.4.0-rc01")
docs("androidx.emoji:emoji:1.2.0-alpha03")
docs("androidx.emoji:emoji-appcompat:1.2.0-alpha03")
docs("androidx.emoji:emoji-bundled:1.2.0-alpha03")
diff --git a/docs/api_guidelines/modules.md b/docs/api_guidelines/modules.md
index c125cc5..edbead0 100644
--- a/docs/api_guidelines/modules.md
+++ b/docs/api_guidelines/modules.md
@@ -359,7 +359,25 @@
external developers and should be considered a last-resort when backporting
behavior is not feasible.
-### Extension libraries (`-ktx`, `-guava`, etc.) {#module-ktx}
+### Platform extension (sidecar JAR) libraries {#module-extension}
+
+Platform extension or "sidecar JAR" libraries ship as part of the Android system
+image and are made available to developers through the `<uses-library>` manifest
+tag.
+
+Interfaces for platform extension libraries *may* be defined in Jetpack, like
+`androidx.window.extensions`, but must be implemented in the Android platform
+via AOSP or by device manufacturers. See
+[WindowManager Extensions](https://source.android.com/docs/core/display/windowmanager-extensions)
+for more details on the platform-side implementation of extension libraries,
+including motivations for their use.
+
+See
+[Platform extension (sidecar JAR) dependencies](/company/teams/androidx/api_guidelines#dependencies-sidecar)
+for guidelines on depending on extension libraries defined externally or within
+Jetpack.
+
+### Framework- and language-specific libraries (`-ktx`, `-guava`, etc.) {#module-ktx}
New libraries should prefer Kotlin sources with built-in Java compatibility via
`@JvmName` and other affordances of the Kotlin language. They may optionally
diff --git a/exifinterface/exifinterface/src/androidTest/java/androidx/exifinterface/media/ExifInterfaceTest.java b/exifinterface/exifinterface/src/androidTest/java/androidx/exifinterface/media/ExifInterfaceTest.java
index 35c63ca..ca2a19f 100644
--- a/exifinterface/exifinterface/src/androidTest/java/androidx/exifinterface/media/ExifInterfaceTest.java
+++ b/exifinterface/exifinterface/src/androidTest/java/androidx/exifinterface/media/ExifInterfaceTest.java
@@ -579,97 +579,36 @@
}
@Test
- @LargeTest
- @Ignore("b/290382533")
+ @SmallTest
public void testDoNotFailOnCorruptedImage() throws Throwable {
- // ExifInterface shouldn't raise any exceptions except an IOException when unable to open
- // a file, even with a corrupted image. Generates randomly corrupted image stream for
- // testing. Uses Epoch date count as random seed so that we can reproduce a broken test.
- long seed = System.currentTimeMillis() / (86400 * 1000);
- Log.d(TAG, "testDoNotFailOnCorruptedImage random seed: " + seed);
- Random random = new Random(seed);
+ Random random = new Random(/* seed= */ 0);
byte[] bytes = new byte[8096];
+ random.nextBytes(bytes);
+ // Overwrite the start of the random bytes with some JPEG-like data, so it starts like a
+ // plausible image with EXIF data.
ByteBuffer buffer = ByteBuffer.wrap(bytes);
- for (int i = 0; i < TEST_NUMBER_OF_CORRUPTED_IMAGE_STREAMS; i++) {
- buffer.clear();
- random.nextBytes(bytes);
- if (!randomlyCorrupted(random)) {
- buffer.put(ExifInterface.JPEG_SIGNATURE);
- }
- if (!randomlyCorrupted(random)) {
- buffer.put(ExifInterface.MARKER_APP1);
- }
- buffer.putShort((short) (random.nextInt(100) + 300));
- if (!randomlyCorrupted(random)) {
- buffer.put(ExifInterface.IDENTIFIER_EXIF_APP1);
- }
- if (!randomlyCorrupted(random)) {
- buffer.putShort(ExifInterface.BYTE_ALIGN_MM);
- }
- if (!randomlyCorrupted(random)) {
- buffer.put((byte) 0);
- buffer.put(ExifInterface.START_CODE);
- }
- buffer.putInt(8);
+ buffer.put(ExifInterface.JPEG_SIGNATURE);
+ buffer.put(ExifInterface.MARKER_APP1);
+ buffer.putShort((short) 350);
+ buffer.put(ExifInterface.IDENTIFIER_EXIF_APP1);
+ buffer.putShort(ExifInterface.BYTE_ALIGN_MM);
+ buffer.put((byte) 0);
+ buffer.put(ExifInterface.START_CODE);
+ buffer.putInt(8);
+ // Number of primary tag directories
+ buffer.putShort((short) 1);
+ // Corruption starts here
- // Primary Tags
- int numberOfDirectory = random.nextInt(8) + 1;
- if (!randomlyCorrupted(random)) {
- buffer.putShort((short) numberOfDirectory);
- }
- for (int j = 0; j < numberOfDirectory; j++) {
- generateRandomExifTag(buffer, ExifInterface.IFD_TYPE_PRIMARY, random);
- }
- if (!randomlyCorrupted(random)) {
- buffer.putInt(buffer.position() - 8);
- }
-
- // Thumbnail Tags
- numberOfDirectory = random.nextInt(8) + 1;
- if (!randomlyCorrupted(random)) {
- buffer.putShort((short) numberOfDirectory);
- }
- for (int j = 0; j < numberOfDirectory; j++) {
- generateRandomExifTag(buffer, ExifInterface.IFD_TYPE_THUMBNAIL, random);
- }
- if (!randomlyCorrupted(random)) {
- buffer.putInt(buffer.position() - 8);
- }
-
- // Preview Tags
- numberOfDirectory = random.nextInt(8) + 1;
- if (!randomlyCorrupted(random)) {
- buffer.putShort((short) numberOfDirectory);
- }
- for (int j = 0; j < numberOfDirectory; j++) {
- generateRandomExifTag(buffer, ExifInterface.IFD_TYPE_PREVIEW, random);
- }
- if (!randomlyCorrupted(random)) {
- buffer.putInt(buffer.position() - 8);
- }
-
- if (!randomlyCorrupted(random)) {
- buffer.put(ExifInterface.MARKER);
- }
- if (!randomlyCorrupted(random)) {
- buffer.put(ExifInterface.MARKER_EOI);
- }
-
- try {
- new ExifInterface(new ByteArrayInputStream(bytes));
- // Always success
- } catch (IOException e) {
- fail("Should not reach here!");
- }
- }
+ ExifInterface exifInterface = new ExifInterface(new ByteArrayInputStream(bytes));
+ exifInterface.getAttribute(ExifInterface.TAG_ARTIST);
+ // Test will fail if the ExifInterface constructor or getter throw an exception.
}
@Test
@SmallTest
- @Ignore("b/290382533")
public void testSetGpsInfo() throws IOException {
final String provider = "ExifInterfaceTest";
- final long timestamp = System.currentTimeMillis();
+ final long timestamp = 1689328448000L; // 2023-07-14T09:54:32.000Z
final float speedInMeterPerSec = 36.627533f;
Location location = new Location(provider);
location.setLatitude(TEST_LATITUDE_VALID_VALUES[TEST_LATITUDE_VALID_VALUES.length - 1]);
@@ -761,7 +700,6 @@
*/
@Test
@SmallTest
- @Ignore("b/290382533")
public void testGetSetDateTime() throws IOException {
final long expectedGetDatetimeValue =
1454027547000L /* TAG_DATETIME value ("2016:01:29 18:32:27") converted to msec */
@@ -788,12 +726,12 @@
exif.getAttribute(ExifInterface.TAG_OFFSET_TIME_DIGITIZED));
// Test setting datetime values
- final long currentTimeStamp = System.currentTimeMillis();
+ final long newTimestamp = 1689328448000L; // 2023-07-14T09:54:32.000Z
final long expectedDatetimeOffsetLongValue = 32400000L;
- exif.setDateTime(currentTimeStamp);
+ exif.setDateTime(newTimestamp);
exif.saveAttributes();
exif = new ExifInterface(imageFile.getAbsolutePath());
- assertEquals(currentTimeStamp - expectedDatetimeOffsetLongValue, (long) exif.getDateTime());
+ assertEquals(newTimestamp - expectedDatetimeOffsetLongValue, (long) exif.getDateTime());
// Test that setting null throws NPE
try {
@@ -1433,31 +1371,6 @@
assertEquals(expectedValue.thumbnailHeight, thumbnailBitmap.getHeight());
}
- private void generateRandomExifTag(ByteBuffer buffer, int ifdType, Random random) {
- ExifInterface.ExifTag[] tagGroup = ExifInterface.EXIF_TAGS[ifdType];
- ExifInterface.ExifTag tag = tagGroup[random.nextInt(tagGroup.length)];
- if (!randomlyCorrupted(random)) {
- buffer.putShort((short) tag.number);
- }
- int dataFormat = random.nextInt(ExifInterface.IFD_FORMAT_NAMES.length);
- if (!randomlyCorrupted(random)) {
- buffer.putShort((short) dataFormat);
- }
- buffer.putInt(1);
- int dataLength = ExifInterface.IFD_FORMAT_BYTES_PER_FORMAT[dataFormat];
- if (dataLength > 4) {
- buffer.putShort((short) random.nextInt(8096 - dataLength));
- buffer.position(buffer.position() + 2);
- } else {
- buffer.position(buffer.position() + 4);
- }
- }
-
- private boolean randomlyCorrupted(Random random) {
- // Corrupts somewhere in a possibility of 1/500.
- return random.nextInt(500) == 0;
- }
-
private void closeQuietly(Closeable closeable) {
if (closeable != null) {
try {
diff --git a/fragment/fragment-ktx/build.gradle b/fragment/fragment-ktx/build.gradle
index 2c3859d..05b9bce 100644
--- a/fragment/fragment-ktx/build.gradle
+++ b/fragment/fragment-ktx/build.gradle
@@ -25,7 +25,7 @@
dependencies {
api(project(":fragment:fragment"))
- api(projectOrArtifact(":activity:activity-ktx")) {
+ api("androidx.activity:activity-ktx:1.8.0-alpha06") {
because "Mirror fragment dependency graph for -ktx artifacts"
}
api("androidx.core:core-ktx:1.2.0") {
diff --git a/fragment/fragment/build.gradle b/fragment/fragment/build.gradle
index ed38f46..c3738bc 100644
--- a/fragment/fragment/build.gradle
+++ b/fragment/fragment/build.gradle
@@ -29,7 +29,7 @@
api("androidx.collection:collection:1.1.0")
api("androidx.viewpager:viewpager:1.0.0")
api("androidx.loader:loader:1.0.0")
- api(projectOrArtifact(":activity:activity"))
+ api("androidx.activity:activity:1.8.0-alpha06")
api("androidx.lifecycle:lifecycle-runtime:2.6.1")
api("androidx.lifecycle:lifecycle-livedata-core:2.6.1")
api("androidx.lifecycle:lifecycle-viewmodel:2.6.1")
diff --git a/fragment/fragment/src/main/java/androidx/fragment/app/DefaultSpecialEffectsController.kt b/fragment/fragment/src/main/java/androidx/fragment/app/DefaultSpecialEffectsController.kt
index a22ff32..c8954a0 100644
--- a/fragment/fragment/src/main/java/androidx/fragment/app/DefaultSpecialEffectsController.kt
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/DefaultSpecialEffectsController.kt
@@ -69,7 +69,6 @@
// and transitions that need to be executed
val animations = mutableListOf<AnimationInfo>()
val transitions = mutableListOf<TransitionInfo>()
- val awaitingContainerChanges = operations.toMutableList()
// sync animations together before we start loading them.
syncAnimations(operations)
@@ -90,31 +89,45 @@
// Ensure that if the Operation is synchronously complete, we still
// apply the container changes before the Operation completes
operation.addCompletionListener {
- if (awaitingContainerChanges.contains(operation)) {
- awaitingContainerChanges.remove(operation)
- applyContainerChanges(operation)
- }
+ applyContainerChangesToOperations { operations }
}
}
// Start transition special effects
- val startedTransitions = startTransitions(transitions, awaitingContainerChanges, isPop,
- firstOut, lastIn)
+ val startedTransitions = startTransitions(transitions, isPop, firstOut, lastIn)
val startedAnyTransition = startedTransitions.containsValue(true)
- // Start animation special effects
- startAnimations(animations, awaitingContainerChanges, startedAnyTransition,
- startedTransitions)
- for (operation: Operation in awaitingContainerChanges) {
- applyContainerChanges(operation)
+ // Collect Animation and Animator Effects
+ startAnimations(animations, startedAnyTransition, startedTransitions)
+
+ // Run all of the Animation, Animator, and NoOp Effects we have collected
+ for (i in operations.indices) {
+ val operation = operations[i]
+ for (j in operation.effects.indices) {
+ val effect = operation.effects[j]
+ effect.onStart(container)
+ effect.onCommit(container)
+ }
}
- awaitingContainerChanges.clear()
+
+ applyContainerChangesToOperations { operations }
if (FragmentManager.isLoggingEnabled(Log.VERBOSE)) {
Log.v(FragmentManager.TAG,
"Completed executing operations from $firstOut to $lastIn")
}
}
+ private inline fun applyContainerChangesToOperations(operationsInput: () -> List<Operation>) {
+ val operations = operationsInput.invoke()
+ for (i in operations.indices) {
+ val currentOperation = operations[i]
+ if (currentOperation.isAwaitingContainerChanges) {
+ applyContainerChanges(currentOperation)
+ currentOperation.isAwaitingContainerChanges = false
+ }
+ }
+ }
+
/**
* Syncs the animations of all other operations with the animations of the last operation.
*/
@@ -135,7 +148,6 @@
@SuppressLint("NewApi", "PrereleaseSdkCoreDependency")
private fun startAnimations(
animationInfos: List<AnimationInfo>,
- awaitingContainerChanges: MutableList<Operation>,
startedAnyTransition: Boolean,
startedTransitions: Map<Operation, Boolean>
) {
@@ -144,19 +156,19 @@
// Find all Animators and add the effect to the operation
for (animatorInfo: AnimationInfo in animationInfos) {
val context = container.context
+ val operation: Operation = animatorInfo.operation
if (animatorInfo.isVisibilityUnchanged) {
// No change in visibility, so we can immediately complete the animation
- animatorInfo.completeSpecialEffect()
+ operation.effects.add(NoOpEffect(animatorInfo))
continue
}
val anim = animatorInfo.getAnimation(context)
if (anim == null) {
// No Animator or Animation, so we can immediately complete the animation
- animatorInfo.completeSpecialEffect()
+ operation.effects.add(NoOpEffect(animatorInfo))
continue
}
val animator = anim.animator
- val operation: Operation = animatorInfo.operation
if (animator == null) {
// We must have an Animation to run. Save those for a second pass
animationsToRun.add(animatorInfo)
@@ -173,7 +185,7 @@
"Ignoring Animator set on $fragment as this Fragment was involved " +
"in a Transition.")
}
- animatorInfo.completeSpecialEffect()
+ operation.effects.add(NoOpEffect(animatorInfo))
continue
}
startedAnyAnimator = true
@@ -182,7 +194,7 @@
// We don't want to immediately applyState() to hide operations as that
// immediately stops the Animator. Instead we'll applyState() manually
// when the Animator ends.
- awaitingContainerChanges.remove(operation)
+ operation.isAwaitingContainerChanges = false
}
operation.effects.add(AnimatorEffect(animatorInfo))
}
@@ -197,7 +209,7 @@
"Ignoring Animation set on $fragment as Animations cannot " +
"run alongside Transitions.")
}
- animationInfo.completeSpecialEffect()
+ animationInfo.operation.effects.add(NoOpEffect(animationInfo))
continue
}
// Then make sure we haven't already started any Animator
@@ -207,25 +219,15 @@
"Ignoring Animation set on $fragment as Animations cannot " +
"run alongside Animators.")
}
- animationInfo.completeSpecialEffect()
+ animationInfo.operation.effects.add(NoOpEffect(animationInfo))
continue
}
operation.effects.add(AnimationEffect(animationInfo))
}
-
- // Run all effects
- for (anim: AnimationInfo in animationInfos) {
- val operation: Operation = anim.operation
- operation.effects.forEach { effect ->
- effect.onStart(container)
- effect.onCommit(container)
- }
- }
}
private fun startTransitions(
transitionInfos: List<TransitionInfo>,
- awaitingContainerChanges: MutableList<Operation>,
isPop: Boolean,
firstOut: Operation?,
lastIn: Operation?
@@ -252,14 +254,14 @@
// There were no transitions at all so we can just complete all of them
for (transitionInfo: TransitionInfo in transitionInfos) {
startedTransitions[transitionInfo.operation] = false
- transitionInfo.completeSpecialEffect()
+ transitionInfo.operation.effects.add(NoOpEffect(transitionInfo))
}
return startedTransitions
}
val transitionEffect =
- TransitionEffect(transitionInfos, awaitingContainerChanges, isPop, firstOut,
- lastIn, transitionImpl, startedTransitions)
+ TransitionEffect(transitionInfos, isPop, firstOut, lastIn, transitionImpl,
+ startedTransitions)
transitionEffect.onStart(container)
transitionEffect.onCommit(container)
@@ -407,7 +409,7 @@
// This means we can't use setAnimationListener() without overriding
// any listener that the Fragment has set themselves, so we
// just mark the special effect as complete immediately.
- animationInfo.completeSpecialEffect()
+ operation.effects.add(NoOpEffect(animationInfo))
} else {
container.startViewTransition(viewToAnimate)
val animation: Animation = FragmentAnim.EndViewTransitionAnimation(anim,
@@ -563,7 +565,6 @@
private class TransitionEffect(
val transitionInfos: List<TransitionInfo>,
- val awaitingContainerChanges: MutableList<Operation>,
val isPop: Boolean,
val firstOut: Operation?,
val lastIn: Operation?,
@@ -773,14 +774,14 @@
var mergedNonOverlappingTransition: Any? = null
// Now iterate through the set of transitions and merge them together
for (transitionInfo: TransitionInfo in transitionInfos) {
+ val operation: Operation = transitionInfo.operation
if (transitionInfo.isVisibilityUnchanged) {
// No change in visibility, so we can immediately complete the transition
startedTransitions[transitionInfo.operation] = false
- transitionInfo.completeSpecialEffect()
+ operation.effects.add(NoOpEffect(transitionInfo))
continue
}
val transition = transitionImpl.cloneTransition(transitionInfo.transition)
- val operation: Operation = transitionInfo.operation
val involvedInSharedElementTransition = (sharedElementTransition != null &&
(operation === firstOut || operation === lastIn))
if (transition == null) {
@@ -790,7 +791,7 @@
// in the shared element transition (as otherwise we need to wait
// for that to finish)
startedTransitions[operation] = false
- transitionInfo.completeSpecialEffect()
+ operation.effects.add(NoOpEffect(transitionInfo))
}
} else {
// Target the Transition to *only* the set of transitioning views
@@ -814,7 +815,7 @@
// We're hiding the Fragment. This requires a bit of extra work
// First, we need to avoid immediately applying the container change as
// that will stop the Transition from occurring.
- awaitingContainerChanges.remove(operation)
+ operation.isAwaitingContainerChanges = false
// Then schedule the actual hide of the fragment's view,
// essentially doing what applyState() would do for us
val transitioningViewsToHide = ArrayList(transitioningViews)
@@ -884,7 +885,7 @@
"SpecialEffectsController: Container $container has not been " +
"laid out. Completing operation $operation")
}
- transitionInfo.completeSpecialEffect()
+ operation.effects.add(NoOpEffect(transitionInfo))
} else {
transitionImpl.setListenerForTransitionEnd(
transitionInfo.operation.fragment,
@@ -993,6 +994,12 @@
}
}
+ private class NoOpEffect(val info: SpecialEffectsInfo) : Effect() {
+ override fun onCommit(container: ViewGroup) {
+ info.completeSpecialEffect()
+ }
+ }
+
@RequiresApi(24)
internal object Api24Impl {
@DoNotInline
diff --git a/fragment/fragment/src/main/java/androidx/fragment/app/SpecialEffectsController.kt b/fragment/fragment/src/main/java/androidx/fragment/app/SpecialEffectsController.kt
index ac472f0..ad6ee5d 100644
--- a/fragment/fragment/src/main/java/androidx/fragment/app/SpecialEffectsController.kt
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/SpecialEffectsController.kt
@@ -530,6 +530,8 @@
var isStarted = false
private set
+ var isAwaitingContainerChanges = true
+
val effects = mutableListOf<Effect>()
init {
diff --git a/glance/glance-appwidget/build.gradle b/glance/glance-appwidget/build.gradle
index 4b59ee3..a8e7d1c 100644
--- a/glance/glance-appwidget/build.gradle
+++ b/glance/glance-appwidget/build.gradle
@@ -117,7 +117,6 @@
LayoutGeneratorTask.registerLayoutGenerator(
project,
- android,
/* containerLayoutDirectory= */ file("src/main/layoutTemplates"),
/* childLayoutDirectory= */ file("src/main/res/layout")
)
diff --git a/glance/glance-appwidget/glance-layout-generator/src/main/kotlin/androidx/glance/appwidget/layoutgenerator/gradle/LayoutGeneratorTask.kt b/glance/glance-appwidget/glance-layout-generator/src/main/kotlin/androidx/glance/appwidget/layoutgenerator/gradle/LayoutGeneratorTask.kt
index 75f6129..a54cc8a 100644
--- a/glance/glance-appwidget/glance-layout-generator/src/main/kotlin/androidx/glance/appwidget/layoutgenerator/gradle/LayoutGeneratorTask.kt
+++ b/glance/glance-appwidget/glance-layout-generator/src/main/kotlin/androidx/glance/appwidget/layoutgenerator/gradle/LayoutGeneratorTask.kt
@@ -20,7 +20,7 @@
import androidx.glance.appwidget.layoutgenerator.LayoutGenerator
import androidx.glance.appwidget.layoutgenerator.cleanResources
import androidx.glance.appwidget.layoutgenerator.generateRegistry
-import com.android.build.gradle.LibraryExtension
+import com.android.build.api.variant.AndroidComponentsExtension
import java.io.File
import org.gradle.api.DefaultTask
import org.gradle.api.Project
@@ -57,6 +57,10 @@
@TaskAction
fun execute() {
+
+ outputSourceDir.asFile.get().mkdirs()
+ outputResourcesDir.asFile.get().mkdirs()
+
val generatedLayouts = LayoutGenerator().generateAllFiles(
checkNotNull(containerLayoutDirectory.get().asFile.listFiles()).asList(),
checkNotNull(childLayoutDirectory.get().asFile.listFiles()).asList(),
@@ -70,54 +74,51 @@
outputSourceDir = outputSourceDir.get().asFile
)
cleanResources(
- outputResourcesDir.get().asFile,
- generatedLayouts.extractGeneratedFiles()
+ outputResourcesDir.get().asFile, generatedLayouts.extractGeneratedFiles()
)
}
private fun GeneratedFiles.extractGeneratedFiles(): Set<File> =
generatedContainers.values.flatMap { container ->
container.map { it.generatedFile }
- }.toSet() +
- generatedBoxChildren.values.flatMap { child ->
+ }.toSet() + generatedBoxChildren.values.flatMap { child ->
child.map { it.generatedFile }
- }.toSet() +
- generatedRowColumnChildren.values.flatMap { child ->
+ }.toSet() + generatedRowColumnChildren.values.flatMap { child ->
child.map { it.generatedFile }
- }.toSet() +
- extraFiles
+ }.toSet() + extraFiles
companion object {
/**
- * Registers [LayoutGeneratorTask] in [project] for all variants in [libraryExtension].
+ * Registers [LayoutGeneratorTask] in [project] for all variants.
*/
@JvmStatic
fun registerLayoutGenerator(
project: Project,
- libraryExtension: LibraryExtension,
containerLayoutDirectory: File,
childLayoutDirectory: File,
) {
- libraryExtension.libraryVariants.all { variant ->
- val variantName = variant.name
- val outputDirectory = project.buildDir.resolve("generatedLayouts/$variantName")
- val outputResourcesDir = outputDirectory.resolve("res/layouts")
- val outputSourceDir = outputDirectory.resolve("kotlin")
- val taskName =
- "generateLayouts" + variantName.replaceFirstChar { it.uppercaseChar() }
- outputResourcesDir.mkdirs()
- outputSourceDir.mkdirs()
- val task = project.tasks.register(taskName, LayoutGeneratorTask::class.java) {
- it.containerLayoutDirectory.set(containerLayoutDirectory)
- it.childLayoutDirectory.set(childLayoutDirectory)
- it.outputResourcesDir.set(outputResourcesDir)
- it.outputSourceDir.set(outputSourceDir)
- }
- variant.registerGeneratedResFolders(
- project.files(outputResourcesDir).builtBy(task)
- )
- variant.registerJavaGeneratingTask(task, outputSourceDir)
+
+ val outputDirectory = "generatedLayouts"
+ val buildDirectory = project.layout.buildDirectory
+
+ val taskName = "generateLayouts"
+
+ val task = project.tasks.register(taskName, LayoutGeneratorTask::class.java) {
+ it.containerLayoutDirectory.set(containerLayoutDirectory)
+ it.childLayoutDirectory.set(childLayoutDirectory)
+ it.outputSourceDir.set(buildDirectory.dir("$outputDirectory/kotlin"))
+ it.outputResourcesDir.set(buildDirectory.dir("$outputDirectory/res/layouts"))
}
+
+ project.extensions.getByType(AndroidComponentsExtension::class.java)
+ .onVariants { variant ->
+ variant.sources.java?.addGeneratedSourceDirectory(
+ task, LayoutGeneratorTask::outputSourceDir
+ )
+ variant.sources.res?.addGeneratedSourceDirectory(
+ task, LayoutGeneratorTask::outputResourcesDir
+ )
+ }
}
}
}
diff --git a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/CheckBox.kt b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/CheckBox.kt
index 0ec5659..f9a902b 100644
--- a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/CheckBox.kt
+++ b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/CheckBox.kt
@@ -19,6 +19,7 @@
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
import androidx.glance.Emittable
+import androidx.glance.EmittableCheckable
import androidx.glance.ExperimentalGlanceApi
import androidx.glance.GlanceModifier
import androidx.glance.GlanceNode
@@ -229,12 +230,8 @@
internal class EmittableCheckBox(
var colors: CheckBoxColors
-) : Emittable {
+) : EmittableCheckable() {
override var modifier: GlanceModifier = GlanceModifier
- var checked: Boolean = false
- var text: String = ""
- var style: TextStyle? = null
- var maxLines: Int = Int.MAX_VALUE
override fun copy(): Emittable = EmittableCheckBox(colors = colors).also {
it.modifier = modifier
diff --git a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/RadioButton.kt b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/RadioButton.kt
index 63e99e3..aca8172 100644
--- a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/RadioButton.kt
+++ b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/RadioButton.kt
@@ -19,6 +19,7 @@
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
import androidx.glance.Emittable
+import androidx.glance.EmittableCheckable
import androidx.glance.ExperimentalGlanceApi
import androidx.glance.GlanceModifier
import androidx.glance.GlanceNode
@@ -39,13 +40,9 @@
internal class EmittableRadioButton(
var colors: RadioButtonColors
-) : Emittable {
+) : EmittableCheckable() {
override var modifier: GlanceModifier = GlanceModifier
- var checked: Boolean = false
var enabled: Boolean = true
- var text: String = ""
- var style: TextStyle? = null
- var maxLines: Int = Int.MAX_VALUE
override fun copy(): Emittable = EmittableRadioButton(colors = colors).also {
it.modifier = modifier
diff --git a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/Switch.kt b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/Switch.kt
index 2eb8a9f..dab1746 100644
--- a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/Switch.kt
+++ b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/Switch.kt
@@ -18,6 +18,7 @@
import androidx.compose.runtime.Composable
import androidx.glance.Emittable
+import androidx.glance.EmittableCheckable
import androidx.glance.ExperimentalGlanceApi
import androidx.glance.GlanceModifier
import androidx.glance.GlanceNode
@@ -226,12 +227,8 @@
internal class EmittableSwitch(
var colors: SwitchColors
-) : Emittable {
+) : EmittableCheckable() {
override var modifier: GlanceModifier = GlanceModifier
- var checked: Boolean = false
- var text: String = ""
- var style: TextStyle? = null
- var maxLines: Int = Int.MAX_VALUE
override fun copy(): Emittable = EmittableSwitch(colors = colors).also {
it.modifier = modifier
diff --git a/glance/glance/api/current.txt b/glance/glance/api/current.txt
index d63e993..8f1b4c1 100644
--- a/glance/glance/api/current.txt
+++ b/glance/glance/api/current.txt
@@ -405,13 +405,17 @@
public final class SemanticsProperties {
method public androidx.glance.semantics.SemanticsPropertyKey<java.util.List<java.lang.String>> getContentDescription();
+ method public androidx.glance.semantics.SemanticsPropertyKey<java.lang.String> getTestTag();
property public final androidx.glance.semantics.SemanticsPropertyKey<java.util.List<java.lang.String>> ContentDescription;
+ property public final androidx.glance.semantics.SemanticsPropertyKey<java.lang.String> TestTag;
field public static final androidx.glance.semantics.SemanticsProperties INSTANCE;
}
public final class SemanticsPropertiesKt {
method public static String getContentDescription(androidx.glance.semantics.SemanticsPropertyReceiver);
+ method public static String getTestTag(androidx.glance.semantics.SemanticsPropertyReceiver);
method public static void setContentDescription(androidx.glance.semantics.SemanticsPropertyReceiver, String);
+ method public static void setTestTag(androidx.glance.semantics.SemanticsPropertyReceiver, String);
}
public final class SemanticsPropertyKey<T> {
diff --git a/glance/glance/api/restricted_current.txt b/glance/glance/api/restricted_current.txt
index d63e993..8f1b4c1 100644
--- a/glance/glance/api/restricted_current.txt
+++ b/glance/glance/api/restricted_current.txt
@@ -405,13 +405,17 @@
public final class SemanticsProperties {
method public androidx.glance.semantics.SemanticsPropertyKey<java.util.List<java.lang.String>> getContentDescription();
+ method public androidx.glance.semantics.SemanticsPropertyKey<java.lang.String> getTestTag();
property public final androidx.glance.semantics.SemanticsPropertyKey<java.util.List<java.lang.String>> ContentDescription;
+ property public final androidx.glance.semantics.SemanticsPropertyKey<java.lang.String> TestTag;
field public static final androidx.glance.semantics.SemanticsProperties INSTANCE;
}
public final class SemanticsPropertiesKt {
method public static String getContentDescription(androidx.glance.semantics.SemanticsPropertyReceiver);
+ method public static String getTestTag(androidx.glance.semantics.SemanticsPropertyReceiver);
method public static void setContentDescription(androidx.glance.semantics.SemanticsPropertyReceiver, String);
+ method public static void setTestTag(androidx.glance.semantics.SemanticsPropertyReceiver, String);
}
public final class SemanticsPropertyKey<T> {
diff --git a/glance/glance/src/main/java/androidx/glance/Emittables.kt b/glance/glance/src/main/java/androidx/glance/Emittables.kt
index 52a538a..5b03dbf 100644
--- a/glance/glance/src/main/java/androidx/glance/Emittables.kt
+++ b/glance/glance/src/main/java/androidx/glance/Emittables.kt
@@ -18,6 +18,7 @@
import androidx.annotation.RestrictTo
import androidx.glance.layout.Alignment
+import androidx.glance.text.TextStyle
/** @suppress */
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@@ -43,3 +44,17 @@
abstract class EmittableLazyItemWithChildren : EmittableWithChildren() {
var alignment: Alignment = Alignment.CenterStart
}
+
+/** @suppress */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+abstract class EmittableWithText : Emittable {
+ var text: String = ""
+ var style: TextStyle? = null
+ var maxLines: Int = Int.MAX_VALUE
+}
+
+/** @suppress */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+abstract class EmittableCheckable : EmittableWithText() {
+ var checked: Boolean = false
+}
diff --git a/glance/glance/src/main/java/androidx/glance/semantics/SemanticsProperties.kt b/glance/glance/src/main/java/androidx/glance/semantics/SemanticsProperties.kt
index e40a921..e6ca8a1 100644
--- a/glance/glance/src/main/java/androidx/glance/semantics/SemanticsProperties.kt
+++ b/glance/glance/src/main/java/androidx/glance/semantics/SemanticsProperties.kt
@@ -32,6 +32,17 @@
parentValue?.toMutableList()?.also { it.addAll(childValue) } ?: childValue
}
)
+
+ /**
+ * @see SemanticsPropertyReceiver.testTag
+ */
+ val TestTag = SemanticsPropertyKey<String>(
+ name = "TestTag",
+ mergePolicy = { parentValue, _ ->
+ // No merge
+ parentValue
+ }
+ )
}
/**
@@ -76,6 +87,24 @@
set(value) { set(SemanticsProperties.ContentDescription, listOf(value)) }
/**
+ * Test tag attached to this Glance composable node.
+ *
+ * This is a free form String and can be used to find nodes in testing frameworks.
+ */
+var SemanticsPropertyReceiver.testTag: String
+ /**
+ * Throws [UnsupportedOperationException]. Should not be called.
+ */
+ get() {
+ throw UnsupportedOperationException(
+ "You cannot retrieve a semantics property directly"
+ )
+ }
+ set(value) {
+ set(SemanticsProperties.TestTag, value)
+ }
+
+/**
* Describes the semantics information associated with the owning component.
*/
class SemanticsConfiguration : SemanticsPropertyReceiver {
diff --git a/glance/glance/src/main/java/androidx/glance/text/Text.kt b/glance/glance/src/main/java/androidx/glance/text/Text.kt
index ace0cfb..2f9974a 100644
--- a/glance/glance/src/main/java/androidx/glance/text/Text.kt
+++ b/glance/glance/src/main/java/androidx/glance/text/Text.kt
@@ -1,4 +1,3 @@
-
/*
* Copyright 2021 The Android Open Source Project
*
@@ -21,6 +20,7 @@
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
import androidx.glance.Emittable
+import androidx.glance.EmittableWithText
import androidx.glance.GlanceModifier
import androidx.glance.GlanceNode
import androidx.glance.text.TextDefaults.defaultTextStyle
@@ -60,11 +60,8 @@
/** @suppress */
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-class EmittableText : Emittable {
+class EmittableText : EmittableWithText() {
override var modifier: GlanceModifier = GlanceModifier
- var text: String = ""
- var style: TextStyle? = null
- var maxLines: Int = Int.MAX_VALUE
override fun copy(): Emittable = EmittableText().also {
it.modifier = modifier
diff --git a/glance/glance/src/test/kotlin/androidx/glance/semantics/SemanticsTest.kt b/glance/glance/src/test/kotlin/androidx/glance/semantics/SemanticsTest.kt
index b336b0b..1c6caa6 100644
--- a/glance/glance/src/test/kotlin/androidx/glance/semantics/SemanticsTest.kt
+++ b/glance/glance/src/test/kotlin/androidx/glance/semantics/SemanticsTest.kt
@@ -16,20 +16,59 @@
package androidx.glance.semantics
+import androidx.compose.ui.unit.dp
import androidx.glance.GlanceModifier
import androidx.glance.findModifier
+import androidx.glance.layout.size
import com.google.common.truth.Truth.assertThat
import org.junit.Test
class SemanticsTest {
@Test
- fun testModifier() {
- val modifiers = GlanceModifier.semantics({ contentDescription = "test_description" })
+ fun contentDescription() {
+ val modifiers = GlanceModifier.semantics { contentDescription = "test_description" }
val semanticsModifier = checkNotNull(modifiers.findModifier<SemanticsModifier>())
assertThat(
- semanticsModifier.configuration
- .get(SemanticsProperties.ContentDescription).joinToString())
- .isEqualTo("test_description")
+ semanticsModifier.configuration.getOrNull(SemanticsProperties.ContentDescription)
+ ?.joinToString()
+ ).isEqualTo("test_description")
+ }
+
+ @Test
+ fun noContentDescription() {
+ val modifiers = GlanceModifier.semantics { testTag = "test" }
+
+ val semanticsModifier = checkNotNull(modifiers.findModifier<SemanticsModifier>())
+ assertThat(
+ semanticsModifier.configuration.getOrNull(SemanticsProperties.ContentDescription)
+ ).isNull()
+ }
+
+ @Test
+ fun testTag() {
+ val modifiers = GlanceModifier.semantics { testTag = "test_tag" }
+
+ val semanticsModifier = checkNotNull(modifiers.findModifier<SemanticsModifier>())
+ assertThat(
+ semanticsModifier.configuration.getOrNull(SemanticsProperties.TestTag)
+ ).isEqualTo("test_tag")
+ }
+
+ @Test
+ fun noTestTag() {
+ val modifiers = GlanceModifier.semantics { contentDescription = "desc" }
+
+ val semanticsModifier = checkNotNull(modifiers.findModifier<SemanticsModifier>())
+ assertThat(
+ semanticsModifier.configuration.getOrNull(SemanticsProperties.TestTag)
+ ).isNull()
+ }
+
+ @Test
+ fun noSemantics() {
+ val modifiers = GlanceModifier.size(10.dp)
+
+ assertThat(modifiers.findModifier<SemanticsModifier>()).isNull()
}
}
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index abe0e47..a4025c9 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -104,7 +104,7 @@
checkerframework = { module = "org.checkerframework:checker-qual", version = "2.5.3" }
checkmark = { module = "net.saff.checkmark:checkmark", version = "0.1.6" }
constraintLayout = { module = "androidx.constraintlayout:constraintlayout", version = "2.0.1"}
-dackka = { module = "com.google.devsite:dackka", version = "1.3.4" }
+dackka = { module = "com.google.devsite:dackka", version = "1.3.5" }
dagger = { module = "com.google.dagger:dagger", version.ref = "dagger" }
daggerCompiler = { module = "com.google.dagger:dagger-compiler", version.ref = "dagger" }
dexmakerMockito = { module = "com.linkedin.dexmaker:dexmaker-mockito", version.ref = "dexmaker" }
@@ -194,8 +194,8 @@
kotlinTestJunit = { module = "org.jetbrains.kotlin:kotlin-test-junit" }
kotlinTestJs = { module = "org.jetbrains.kotlin:kotlin-test-js" }
kotlinReflect = { module = "org.jetbrains.kotlin:kotlin-reflect" }
-kotlinPoet = { module = "com.squareup:kotlinpoet", version = "1.12.0" }
-kotlinPoetJavaPoet = { module = "com.squareup:kotlinpoet-javapoet", version = "1.12.0" }
+kotlinPoet = { module = "com.squareup:kotlinpoet", version = "1.14.2" }
+kotlinPoetJavaPoet = { module = "com.squareup:kotlinpoet-javapoet", version = "1.14.2" }
kotlinXHtml = { module = "org.jetbrains.kotlinx:kotlinx-html-jvm", version = "0.7.3" }
ksp = { module = "com.google.devtools.ksp:symbol-processing", version.ref = "ksp" }
kspApi = { module = "com.google.devtools.ksp:symbol-processing-api", version.ref = "ksp" }
diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml
index 1a0a755..e094844 100644
--- a/gradle/verification-metadata.xml
+++ b/gradle/verification-metadata.xml
@@ -405,6 +405,7 @@
</trusted-key>
<trusted-key id="db0597e3144342256bc81e3ec727d053c4481cf5" group="org.tensorflow"/>
<trusted-key id="dbd744ace7ade6aa50dd591f66b50994442d2d40">
+ <trusting group="com.squareup"/>
<trusting group="com.squareup.okhttp3"/>
<trusting group="com.squareup.okio"/>
<trusting group="com.squareup.wire"/>
diff --git a/health/connect/connect-client/api/current.txt b/health/connect/connect-client/api/current.txt
index 9332a76..e82f8fb 100644
--- a/health/connect/connect-client/api/current.txt
+++ b/health/connect/connect-client/api/current.txt
@@ -109,10 +109,10 @@
package androidx.health.connect.client.contracts {
- public final class ExerciseRouteRequestContract extends androidx.activity.result.contract.ActivityResultContract<java.lang.String,androidx.health.connect.client.records.ExerciseRoute.Data> {
+ public final class ExerciseRouteRequestContract extends androidx.activity.result.contract.ActivityResultContract<java.lang.String,androidx.health.connect.client.records.ExerciseRoute> {
ctor public ExerciseRouteRequestContract();
method public android.content.Intent createIntent(android.content.Context context, String input);
- method public androidx.health.connect.client.records.ExerciseRoute.Data? parseResult(int resultCode, android.content.Intent? intent);
+ method public androidx.health.connect.client.records.ExerciseRoute? parseResult(int resultCode, android.content.Intent? intent);
}
public final class HealthPermissionsRequestContract extends androidx.activity.result.contract.ActivityResultContract<java.util.Set<? extends java.lang.String>,java.util.Set<? extends java.lang.String>> {
@@ -442,15 +442,8 @@
property public final java.time.Instant startTime;
}
- public abstract class ExerciseRoute {
- }
-
- public static final class ExerciseRoute.ConsentRequired extends androidx.health.connect.client.records.ExerciseRoute {
- ctor public ExerciseRoute.ConsentRequired();
- }
-
- public static final class ExerciseRoute.Data extends androidx.health.connect.client.records.ExerciseRoute {
- ctor public ExerciseRoute.Data(java.util.List<androidx.health.connect.client.records.ExerciseRoute.Location> route);
+ public final class ExerciseRoute {
+ ctor public ExerciseRoute(java.util.List<androidx.health.connect.client.records.ExerciseRoute.Location> route);
method public java.util.List<androidx.health.connect.client.records.ExerciseRoute.Location> getRoute();
property public final java.util.List<androidx.health.connect.client.records.ExerciseRoute.Location> route;
}
@@ -475,8 +468,21 @@
public static final class ExerciseRoute.Location.Companion {
}
- public static final class ExerciseRoute.NoData extends androidx.health.connect.client.records.ExerciseRoute {
- ctor public ExerciseRoute.NoData();
+ public abstract class ExerciseRouteResult {
+ }
+
+ public static final class ExerciseRouteResult.ConsentRequired extends androidx.health.connect.client.records.ExerciseRouteResult {
+ ctor public ExerciseRouteResult.ConsentRequired();
+ }
+
+ public static final class ExerciseRouteResult.Data extends androidx.health.connect.client.records.ExerciseRouteResult {
+ ctor public ExerciseRouteResult.Data(androidx.health.connect.client.records.ExerciseRoute exerciseRoute);
+ method public androidx.health.connect.client.records.ExerciseRoute getExerciseRoute();
+ property public final androidx.health.connect.client.records.ExerciseRoute exerciseRoute;
+ }
+
+ public static final class ExerciseRouteResult.NoData extends androidx.health.connect.client.records.ExerciseRouteResult {
+ ctor public ExerciseRouteResult.NoData();
}
public final class ExerciseSegment {
@@ -570,10 +576,10 @@
ctor public ExerciseSessionRecord(java.time.Instant startTime, java.time.ZoneOffset? startZoneOffset, java.time.Instant endTime, java.time.ZoneOffset? endZoneOffset, int exerciseType, optional String? title, optional String? notes, optional androidx.health.connect.client.records.metadata.Metadata metadata);
ctor public ExerciseSessionRecord(java.time.Instant startTime, java.time.ZoneOffset? startZoneOffset, java.time.Instant endTime, java.time.ZoneOffset? endZoneOffset, int exerciseType, optional String? title, optional String? notes, optional androidx.health.connect.client.records.metadata.Metadata metadata, optional java.util.List<androidx.health.connect.client.records.ExerciseSegment> segments);
ctor public ExerciseSessionRecord(java.time.Instant startTime, java.time.ZoneOffset? startZoneOffset, java.time.Instant endTime, java.time.ZoneOffset? endZoneOffset, int exerciseType, optional String? title, optional String? notes, optional androidx.health.connect.client.records.metadata.Metadata metadata, optional java.util.List<androidx.health.connect.client.records.ExerciseSegment> segments, optional java.util.List<androidx.health.connect.client.records.ExerciseLap> laps);
- ctor public ExerciseSessionRecord(java.time.Instant startTime, java.time.ZoneOffset? startZoneOffset, java.time.Instant endTime, java.time.ZoneOffset? endZoneOffset, int exerciseType, optional String? title, optional String? notes, optional androidx.health.connect.client.records.metadata.Metadata metadata, optional java.util.List<androidx.health.connect.client.records.ExerciseSegment> segments, optional java.util.List<androidx.health.connect.client.records.ExerciseLap> laps, optional androidx.health.connect.client.records.ExerciseRoute.Data? exerciseRouteData);
+ ctor public ExerciseSessionRecord(java.time.Instant startTime, java.time.ZoneOffset? startZoneOffset, java.time.Instant endTime, java.time.ZoneOffset? endZoneOffset, int exerciseType, optional String? title, optional String? notes, optional androidx.health.connect.client.records.metadata.Metadata metadata, optional java.util.List<androidx.health.connect.client.records.ExerciseSegment> segments, optional java.util.List<androidx.health.connect.client.records.ExerciseLap> laps, optional androidx.health.connect.client.records.ExerciseRoute? exerciseRoute);
method public java.time.Instant getEndTime();
method public java.time.ZoneOffset? getEndZoneOffset();
- method public androidx.health.connect.client.records.ExerciseRoute getExerciseRoute();
+ method public androidx.health.connect.client.records.ExerciseRouteResult getExerciseRouteResult();
method public int getExerciseType();
method public java.util.List<androidx.health.connect.client.records.ExerciseLap> getLaps();
method public androidx.health.connect.client.records.metadata.Metadata getMetadata();
@@ -584,7 +590,7 @@
method public String? getTitle();
property public java.time.Instant endTime;
property public java.time.ZoneOffset? endZoneOffset;
- property public final androidx.health.connect.client.records.ExerciseRoute exerciseRoute;
+ property public final androidx.health.connect.client.records.ExerciseRouteResult exerciseRouteResult;
property public final int exerciseType;
property public final java.util.List<androidx.health.connect.client.records.ExerciseLap> laps;
property public androidx.health.connect.client.records.metadata.Metadata metadata;
diff --git a/health/connect/connect-client/api/restricted_current.txt b/health/connect/connect-client/api/restricted_current.txt
index 41f97b3..582c44b 100644
--- a/health/connect/connect-client/api/restricted_current.txt
+++ b/health/connect/connect-client/api/restricted_current.txt
@@ -109,10 +109,10 @@
package androidx.health.connect.client.contracts {
- public final class ExerciseRouteRequestContract extends androidx.activity.result.contract.ActivityResultContract<java.lang.String,androidx.health.connect.client.records.ExerciseRoute.Data> {
+ public final class ExerciseRouteRequestContract extends androidx.activity.result.contract.ActivityResultContract<java.lang.String,androidx.health.connect.client.records.ExerciseRoute> {
ctor public ExerciseRouteRequestContract();
method public android.content.Intent createIntent(android.content.Context context, String input);
- method public androidx.health.connect.client.records.ExerciseRoute.Data? parseResult(int resultCode, android.content.Intent? intent);
+ method public androidx.health.connect.client.records.ExerciseRoute? parseResult(int resultCode, android.content.Intent? intent);
}
public final class HealthPermissionsRequestContract extends androidx.activity.result.contract.ActivityResultContract<java.util.Set<? extends java.lang.String>,java.util.Set<? extends java.lang.String>> {
@@ -442,15 +442,8 @@
property public final java.time.Instant startTime;
}
- public abstract class ExerciseRoute {
- }
-
- public static final class ExerciseRoute.ConsentRequired extends androidx.health.connect.client.records.ExerciseRoute {
- ctor public ExerciseRoute.ConsentRequired();
- }
-
- public static final class ExerciseRoute.Data extends androidx.health.connect.client.records.ExerciseRoute {
- ctor public ExerciseRoute.Data(java.util.List<androidx.health.connect.client.records.ExerciseRoute.Location> route);
+ public final class ExerciseRoute {
+ ctor public ExerciseRoute(java.util.List<androidx.health.connect.client.records.ExerciseRoute.Location> route);
method public java.util.List<androidx.health.connect.client.records.ExerciseRoute.Location> getRoute();
property public final java.util.List<androidx.health.connect.client.records.ExerciseRoute.Location> route;
}
@@ -475,8 +468,21 @@
public static final class ExerciseRoute.Location.Companion {
}
- public static final class ExerciseRoute.NoData extends androidx.health.connect.client.records.ExerciseRoute {
- ctor public ExerciseRoute.NoData();
+ public abstract class ExerciseRouteResult {
+ }
+
+ public static final class ExerciseRouteResult.ConsentRequired extends androidx.health.connect.client.records.ExerciseRouteResult {
+ ctor public ExerciseRouteResult.ConsentRequired();
+ }
+
+ public static final class ExerciseRouteResult.Data extends androidx.health.connect.client.records.ExerciseRouteResult {
+ ctor public ExerciseRouteResult.Data(androidx.health.connect.client.records.ExerciseRoute exerciseRoute);
+ method public androidx.health.connect.client.records.ExerciseRoute getExerciseRoute();
+ property public final androidx.health.connect.client.records.ExerciseRoute exerciseRoute;
+ }
+
+ public static final class ExerciseRouteResult.NoData extends androidx.health.connect.client.records.ExerciseRouteResult {
+ ctor public ExerciseRouteResult.NoData();
}
public final class ExerciseSegment {
@@ -570,10 +576,10 @@
ctor public ExerciseSessionRecord(java.time.Instant startTime, java.time.ZoneOffset? startZoneOffset, java.time.Instant endTime, java.time.ZoneOffset? endZoneOffset, int exerciseType, optional String? title, optional String? notes, optional androidx.health.connect.client.records.metadata.Metadata metadata);
ctor public ExerciseSessionRecord(java.time.Instant startTime, java.time.ZoneOffset? startZoneOffset, java.time.Instant endTime, java.time.ZoneOffset? endZoneOffset, int exerciseType, optional String? title, optional String? notes, optional androidx.health.connect.client.records.metadata.Metadata metadata, optional java.util.List<androidx.health.connect.client.records.ExerciseSegment> segments);
ctor public ExerciseSessionRecord(java.time.Instant startTime, java.time.ZoneOffset? startZoneOffset, java.time.Instant endTime, java.time.ZoneOffset? endZoneOffset, int exerciseType, optional String? title, optional String? notes, optional androidx.health.connect.client.records.metadata.Metadata metadata, optional java.util.List<androidx.health.connect.client.records.ExerciseSegment> segments, optional java.util.List<androidx.health.connect.client.records.ExerciseLap> laps);
- ctor public ExerciseSessionRecord(java.time.Instant startTime, java.time.ZoneOffset? startZoneOffset, java.time.Instant endTime, java.time.ZoneOffset? endZoneOffset, int exerciseType, optional String? title, optional String? notes, optional androidx.health.connect.client.records.metadata.Metadata metadata, optional java.util.List<androidx.health.connect.client.records.ExerciseSegment> segments, optional java.util.List<androidx.health.connect.client.records.ExerciseLap> laps, optional androidx.health.connect.client.records.ExerciseRoute.Data? exerciseRouteData);
+ ctor public ExerciseSessionRecord(java.time.Instant startTime, java.time.ZoneOffset? startZoneOffset, java.time.Instant endTime, java.time.ZoneOffset? endZoneOffset, int exerciseType, optional String? title, optional String? notes, optional androidx.health.connect.client.records.metadata.Metadata metadata, optional java.util.List<androidx.health.connect.client.records.ExerciseSegment> segments, optional java.util.List<androidx.health.connect.client.records.ExerciseLap> laps, optional androidx.health.connect.client.records.ExerciseRoute? exerciseRoute);
method public java.time.Instant getEndTime();
method public java.time.ZoneOffset? getEndZoneOffset();
- method public androidx.health.connect.client.records.ExerciseRoute getExerciseRoute();
+ method public androidx.health.connect.client.records.ExerciseRouteResult getExerciseRouteResult();
method public int getExerciseType();
method public java.util.List<androidx.health.connect.client.records.ExerciseLap> getLaps();
method public androidx.health.connect.client.records.metadata.Metadata getMetadata();
@@ -584,7 +590,7 @@
method public String? getTitle();
property public java.time.Instant endTime;
property public java.time.ZoneOffset? endZoneOffset;
- property public final androidx.health.connect.client.records.ExerciseRoute exerciseRoute;
+ property public final androidx.health.connect.client.records.ExerciseRouteResult exerciseRouteResult;
property public final int exerciseType;
property public final java.util.List<androidx.health.connect.client.records.ExerciseLap> laps;
property public androidx.health.connect.client.records.metadata.Metadata metadata;
diff --git a/health/connect/connect-client/build.gradle b/health/connect/connect-client/build.gradle
index 16260ad..fc33af3 100644
--- a/health/connect/connect-client/build.gradle
+++ b/health/connect/connect-client/build.gradle
@@ -40,7 +40,7 @@
implementation(libs.guavaAndroid)
implementation(libs.kotlinCoroutinesAndroid)
implementation(libs.kotlinCoroutinesGuava)
- implementation("androidx.core:core-ktx:1.8.0")
+ implementation("androidx.core:core-ktx:1.12.0-alpha05")
testImplementation(libs.testCore)
testImplementation(libs.testRunner)
diff --git a/health/connect/connect-client/samples/src/main/java/androidx/health/connect/client/samples/InsertRecordsSamples.kt b/health/connect/connect-client/samples/src/main/java/androidx/health/connect/client/samples/InsertRecordsSamples.kt
index 4361146..f22f57d 100644
--- a/health/connect/connect-client/samples/src/main/java/androidx/health/connect/client/samples/InsertRecordsSamples.kt
+++ b/health/connect/connect-client/samples/src/main/java/androidx/health/connect/client/samples/InsertRecordsSamples.kt
@@ -113,9 +113,9 @@
val latitudeDeltaPerSecond = (endLatitude - startLatitude) / sessionDuration.seconds
val longitudeDeltaPerSecond = (endLongitude - startLongitude) / sessionDuration.seconds
- val exerciseRouteData =
+ val exerciseRoute =
if (grantedPermissions.contains(PERMISSION_WRITE_EXERCISE_ROUTE)) {
- ExerciseRoute.Data(
+ ExerciseRoute(
List(sessionDuration.seconds.toInt()) { timeSeconds ->
ExerciseRoute.Location(
time = sessionStartTime.plusSeconds(timeSeconds.toLong()),
@@ -140,7 +140,7 @@
exerciseType = ExerciseSessionRecord.EXERCISE_TYPE_RUNNING,
title = "Morning Run",
notes = "A nice run in a park",
- exerciseRouteData = exerciseRouteData
+ exerciseRoute = exerciseRoute
)
healthConnectClient.insertRecords(listOf(exerciseSessionRecord))
diff --git a/health/connect/connect-client/samples/src/main/java/androidx/health/connect/client/samples/ReadRecordsSamples.kt b/health/connect/connect-client/samples/src/main/java/androidx/health/connect/client/samples/ReadRecordsSamples.kt
index 8f5cca7..578fd0e 100644
--- a/health/connect/connect-client/samples/src/main/java/androidx/health/connect/client/samples/ReadRecordsSamples.kt
+++ b/health/connect/connect-client/samples/src/main/java/androidx/health/connect/client/samples/ReadRecordsSamples.kt
@@ -23,6 +23,7 @@
import androidx.health.connect.client.HealthConnectClient
import androidx.health.connect.client.contracts.ExerciseRouteRequestContract
import androidx.health.connect.client.records.ExerciseRoute
+import androidx.health.connect.client.records.ExerciseRouteResult
import androidx.health.connect.client.records.ExerciseSessionRecord
import androidx.health.connect.client.records.HeartRateRecord
import androidx.health.connect.client.records.SleepSessionRecord
@@ -86,14 +87,14 @@
suspend fun ReadExerciseRoute(
activityResultCaller: ActivityResultCaller,
healthConnectClient: HealthConnectClient,
- displayExerciseRoute: (ExerciseRoute.Data) -> Unit,
+ displayExerciseRoute: (ExerciseRoute) -> Unit,
recordId: String
) {
// See https://developer.android.com/training/basics/intents/result#launch for appropriately
// handling ActivityResultContract.
val requestExerciseRoute =
activityResultCaller.registerForActivityResult(ExerciseRouteRequestContract()) {
- exerciseRoute: ExerciseRoute.Data? ->
+ exerciseRoute: ExerciseRoute? ->
if (exerciseRoute != null) {
displayExerciseRoute(exerciseRoute)
} else {
@@ -105,10 +106,10 @@
val exerciseSessionRecord =
healthConnectClient.readRecord(ExerciseSessionRecord::class, recordId).record
- when (val exerciseRoute = exerciseSessionRecord.exerciseRoute) {
- is ExerciseRoute.Data -> displayExerciseRoute(exerciseRoute)
- is ExerciseRoute.ConsentRequired -> requestExerciseRoute.launch(recordId)
- is ExerciseRoute.NoData -> Unit // No exercise route to show
+ when (val exerciseRouteResult = exerciseSessionRecord.exerciseRouteResult) {
+ is ExerciseRouteResult.Data -> displayExerciseRoute(exerciseRouteResult.exerciseRoute)
+ is ExerciseRouteResult.ConsentRequired -> requestExerciseRoute.launch(recordId)
+ is ExerciseRouteResult.NoData -> Unit // No exercise route to show
else -> Unit
}
}
diff --git a/health/connect/connect-client/src/androidTest/java/androidx/health/connect/client/impl/RequestExerciseRouteUpsideDownCakeTest.kt b/health/connect/connect-client/src/androidTest/java/androidx/health/connect/client/impl/RequestExerciseRouteUpsideDownCakeTest.kt
index 23160ad..ea4548d 100644
--- a/health/connect/connect-client/src/androidTest/java/androidx/health/connect/client/impl/RequestExerciseRouteUpsideDownCakeTest.kt
+++ b/health/connect/connect-client/src/androidTest/java/androidx/health/connect/client/impl/RequestExerciseRouteUpsideDownCakeTest.kt
@@ -80,7 +80,7 @@
val intent = Intent()
intent.putExtra(HealthConnectManager.EXTRA_EXERCISE_ROUTE, PlatformExerciseRoute(listOf()))
val result = requestRouteContract.parseResult(0, intent)
- assertThat(result).isEqualTo(ExerciseRoute.Data(listOf()))
+ assertThat(result).isEqualTo(ExerciseRoute(listOf()))
}
@Test
@@ -103,7 +103,7 @@
val result = requestRouteContract.parseResult(0, intent)
assertThat(result)
.isEqualTo(
- ExerciseRoute.Data(
+ ExerciseRoute(
listOf(
ExerciseRoute.Location(
time = Instant.ofEpochMilli(1234L),
diff --git a/health/connect/connect-client/src/androidTest/java/androidx/health/connect/client/impl/platform/records/RecordConvertersTest.kt b/health/connect/connect-client/src/androidTest/java/androidx/health/connect/client/impl/platform/records/RecordConvertersTest.kt
index d0cf773..953b19b 100644
--- a/health/connect/connect-client/src/androidTest/java/androidx/health/connect/client/impl/platform/records/RecordConvertersTest.kt
+++ b/health/connect/connect-client/src/androidTest/java/androidx/health/connect/client/impl/platform/records/RecordConvertersTest.kt
@@ -35,6 +35,7 @@
import androidx.health.connect.client.records.ElevationGainedRecord
import androidx.health.connect.client.records.ExerciseLap
import androidx.health.connect.client.records.ExerciseRoute
+import androidx.health.connect.client.records.ExerciseRouteResult
import androidx.health.connect.client.records.ExerciseSegment
import androidx.health.connect.client.records.ExerciseSessionRecord
import androidx.health.connect.client.records.FloorsClimbedRecord
@@ -408,7 +409,7 @@
)
),
exerciseRoute =
- ExerciseRoute.Data(
+ ExerciseRoute(
listOf(
ExerciseRoute.Location(
START_TIME,
@@ -1261,17 +1262,19 @@
10
)
)
- assertThat(exerciseRoute as ExerciseRoute.Data)
+ assertThat(exerciseRouteResult as ExerciseRouteResult.Data)
.isEqualTo(
- ExerciseRoute.Data(
- listOf(
- ExerciseRoute.Location(
- time = START_TIME,
- latitude = 23.4,
- longitude = -23.4,
- altitude = Length.meters(10.0),
- horizontalAccuracy = Length.meters(2.0),
- verticalAccuracy = Length.meters(3.0)
+ ExerciseRouteResult.Data(
+ ExerciseRoute(
+ listOf(
+ ExerciseRoute.Location(
+ time = START_TIME,
+ latitude = 23.4,
+ longitude = -23.4,
+ altitude = Length.meters(10.0),
+ horizontalAccuracy = Length.meters(2.0),
+ verticalAccuracy = Length.meters(3.0)
+ )
)
)
)
@@ -1283,7 +1286,7 @@
as ExerciseSessionRecord
assertSdkRecord(sdkExerciseSession) {
- assertThat(exerciseRoute).isEqualTo(ExerciseRoute.NoData())
+ assertThat(exerciseRouteResult).isEqualTo(ExerciseRouteResult.NoData())
}
}
diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/contracts/ExerciseRouteRequestContract.kt b/health/connect/connect-client/src/main/java/androidx/health/connect/client/contracts/ExerciseRouteRequestContract.kt
index 6e8e74b..0e1e78f 100644
--- a/health/connect/connect-client/src/main/java/androidx/health/connect/client/contracts/ExerciseRouteRequestContract.kt
+++ b/health/connect/connect-client/src/main/java/androidx/health/connect/client/contracts/ExerciseRouteRequestContract.kt
@@ -32,9 +32,9 @@
*
* @sample androidx.health.connect.client.samples.ReadExerciseRoute
*/
-class ExerciseRouteRequestContract : ActivityResultContract<String, ExerciseRoute.Data?>() {
+class ExerciseRouteRequestContract : ActivityResultContract<String, ExerciseRoute?>() {
- private val delegate: ActivityResultContract<String, ExerciseRoute.Data?> =
+ private val delegate: ActivityResultContract<String, ExerciseRoute?> =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
RequestExerciseRouteUpsideDownCake()
} else {
@@ -42,7 +42,7 @@
}
/**
- * Creates an intent to request an [ExerciseRoute.Data]. It receives the exercise session id as
+ * Creates an intent to request an [ExerciseRoute]. It receives the exercise session id as
* [input].
*
* @param context the context
@@ -56,13 +56,13 @@
}
/**
- * Converts the activity result into [ExerciseRoute.Data], to return as output.
+ * Converts the activity result into [ExerciseRoute], to return as output.
*
* @return null if the user didn't grant access to the exercise route or if there's no exercise
* route for the session id passed on [createIntent].
* @see ActivityResultContract.parseResult
*/
- override fun parseResult(resultCode: Int, intent: Intent?): ExerciseRoute.Data? {
+ override fun parseResult(resultCode: Int, intent: Intent?): ExerciseRoute? {
return delegate.parseResult(resultCode, intent)
}
}
diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/HealthConnectClientImpl.kt b/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/HealthConnectClientImpl.kt
index 2fcac9a..8d6bfec 100644
--- a/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/HealthConnectClientImpl.kt
+++ b/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/HealthConnectClientImpl.kt
@@ -64,11 +64,16 @@
internal constructor(
private val delegate: HealthDataAsyncClient,
private val allPermissions: List<String> =
- HealthPermission.RECORD_TYPE_TO_PERMISSION.flatMap {
- listOf<String>(
- HealthPermission.WRITE_PERMISSION_PREFIX + it.value,
- HealthPermission.READ_PERMISSION_PREFIX + it.value
+ buildList() {
+ addAll(
+ HealthPermission.RECORD_TYPE_TO_PERMISSION.flatMap {
+ listOf<String>(
+ HealthPermission.WRITE_PERMISSION_PREFIX + it.value,
+ HealthPermission.READ_PERMISSION_PREFIX + it.value
+ )
+ }
)
+ add(HealthPermission.PERMISSION_WRITE_EXERCISE_ROUTE)
},
) : HealthConnectClient, PermissionController {
diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/converters/records/ProtoToRecordConverters.kt b/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/converters/records/ProtoToRecordConverters.kt
index dc60de8..a7f79299 100644
--- a/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/converters/records/ProtoToRecordConverters.kt
+++ b/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/converters/records/ProtoToRecordConverters.kt
@@ -35,6 +35,7 @@
import androidx.health.connect.client.records.DistanceRecord
import androidx.health.connect.client.records.ElevationGainedRecord
import androidx.health.connect.client.records.ExerciseRoute
+import androidx.health.connect.client.records.ExerciseRouteResult
import androidx.health.connect.client.records.ExerciseSessionRecord
import androidx.health.connect.client.records.FloorsClimbedRecord
import androidx.health.connect.client.records.HeartRateRecord
@@ -421,13 +422,13 @@
metadata = metadata,
segments = subTypeDataListsMap["segments"]?.toSegmentList() ?: emptyList(),
laps = subTypeDataListsMap["laps"]?.toLapList() ?: emptyList(),
- exerciseRoute =
+ exerciseRouteResult =
subTypeDataListsMap["route"]?.let {
- ExerciseRoute.Data(route = it.toLocationList())
+ ExerciseRouteResult.Data(ExerciseRoute(route = it.toLocationList()))
}
?: if (valuesMap["hasRoute"]?.booleanVal == true)
- ExerciseRoute.ConsentRequired()
- else ExerciseRoute.NoData(),
+ ExerciseRouteResult.ConsentRequired()
+ else ExerciseRouteResult.NoData(),
)
}
"Distance" ->
@@ -587,8 +588,8 @@
fun toExerciseRouteData(
protoWrapper: androidx.health.platform.client.exerciseroute.ExerciseRoute
-): ExerciseRoute.Data {
- return ExerciseRoute.Data(
+): ExerciseRoute {
+ return ExerciseRoute(
protoWrapper.proto.valuesList.map { value ->
ExerciseRoute.Location(
time = Instant.ofEpochMilli(value.startTimeMillis),
diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/converters/records/ProtoToRecordUtils.kt b/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/converters/records/ProtoToRecordUtils.kt
index f2fa0f4..168a416 100644
--- a/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/converters/records/ProtoToRecordUtils.kt
+++ b/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/converters/records/ProtoToRecordUtils.kt
@@ -119,17 +119,17 @@
startTime = Instant.ofEpochMilli(it.startTimeMillis),
endTime = Instant.ofEpochMilli(it.endTimeMillis),
stage = STAGE_TYPE_STRING_TO_INT_MAP[it.valuesMap["stage"]?.enumVal]
- ?: SleepSessionRecord.STAGE_TYPE_UNKNOWN
+ ?: SleepSessionRecord.STAGE_TYPE_UNKNOWN
)
}
}
+
internal fun DataProto.DataPoint.SubTypeDataList.toSegmentList(): List<ExerciseSegment> {
return valuesList.map {
ExerciseSegment(
startTime = Instant.ofEpochMilli(it.startTimeMillis),
endTime = Instant.ofEpochMilli(it.endTimeMillis),
- segmentType = (it.valuesMap["type"]?.longVal
- ?: EXERCISE_SEGMENT_TYPE_UNKNOWN).toInt(),
+ segmentType = (it.valuesMap["type"]?.longVal ?: EXERCISE_SEGMENT_TYPE_UNKNOWN).toInt(),
repetitions = it.valuesMap["reps"]?.longVal?.toInt() ?: 0
)
}
diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/converters/records/RecordToProtoConverters.kt b/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/converters/records/RecordToProtoConverters.kt
index e9e29d8..34127fc 100644
--- a/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/converters/records/RecordToProtoConverters.kt
+++ b/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/converters/records/RecordToProtoConverters.kt
@@ -34,7 +34,7 @@
import androidx.health.connect.client.records.CyclingPedalingCadenceRecord
import androidx.health.connect.client.records.DistanceRecord
import androidx.health.connect.client.records.ElevationGainedRecord
-import androidx.health.connect.client.records.ExerciseRoute
+import androidx.health.connect.client.records.ExerciseRouteResult
import androidx.health.connect.client.records.ExerciseSessionRecord
import androidx.health.connect.client.records.FloorsClimbedRecord
import androidx.health.connect.client.records.HeartRateRecord
@@ -289,7 +289,7 @@
is ExerciseSessionRecord ->
intervalProto()
.setDataType(protoDataType("ActivitySession"))
- .putValues("hasRoute", boolVal(exerciseRoute !is ExerciseRoute.NoData))
+ .putValues("hasRoute", boolVal(exerciseRouteResult !is ExerciseRouteResult.NoData))
.apply {
val exerciseType =
enumValFromInt(
@@ -316,11 +316,13 @@
.build()
)
}
- if (exerciseRoute is ExerciseRoute.Data) {
+ if (exerciseRouteResult is ExerciseRouteResult.Data) {
putSubTypeDataLists(
"route",
DataProto.DataPoint.SubTypeDataList.newBuilder()
- .addAllValues(exerciseRoute.route.map { it.toProto() })
+ .addAllValues(
+ exerciseRouteResult.exerciseRoute.route.map { it.toProto() }
+ )
.build()
)
}
diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/converters/records/RecordToProtoUtils.kt b/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/converters/records/RecordToProtoUtils.kt
index 8c1cbe2..3e5fa01 100644
--- a/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/converters/records/RecordToProtoUtils.kt
+++ b/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/converters/records/RecordToProtoUtils.kt
@@ -100,8 +100,10 @@
enumValFromInt(stage, SleepSessionRecord.STAGE_TYPE_INT_TO_STRING_MAP)?.let {
putValues("stage", it)
}
- }.build()
+ }
+ .build()
}
+
internal fun ExerciseSegment.toProto(): DataProto.SubTypeDataValue {
return DataProto.SubTypeDataValue.newBuilder()
.setStartTimeMillis(startTime.toEpochMilli())
diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/platform/records/RecordConverters.kt b/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/platform/records/RecordConverters.kt
index 4da86be..ad43fd6 100644
--- a/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/platform/records/RecordConverters.kt
+++ b/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/platform/records/RecordConverters.kt
@@ -36,6 +36,7 @@
import androidx.health.connect.client.records.ElevationGainedRecord
import androidx.health.connect.client.records.ExerciseLap
import androidx.health.connect.client.records.ExerciseRoute
+import androidx.health.connect.client.records.ExerciseRouteResult
import androidx.health.connect.client.records.ExerciseSegment
import androidx.health.connect.client.records.ExerciseSessionRecord
import androidx.health.connect.client.records.FloorsClimbedRecord
@@ -293,8 +294,9 @@
laps = laps.map { it.toSdkExerciseLap() }.sortedBy { it.startTime },
segments = segments.map { it.toSdkExerciseSegment() }.sortedBy { it.startTime },
metadata = metadata.toSdkMetadata(),
- exerciseRoute = route?.toSdkExerciseRouteData()
- ?: if (hasRoute()) ExerciseRoute.ConsentRequired() else ExerciseRoute.NoData(),
+ exerciseRouteResult = route?.let { ExerciseRouteResult.Data(it.toSdkExerciseRoute()) }
+ ?: if (hasRoute()) ExerciseRouteResult.ConsentRequired()
+ else ExerciseRouteResult.NoData(),
)
private fun PlatformFloorsClimbedRecord.toSdkFloorsClimbedRecord() =
@@ -708,8 +710,8 @@
title?.let { setTitle(it) }
setLaps(laps.map { it.toPlatformExerciseLap() })
setSegments(segments.map { it.toPlatformExerciseSegment() })
- if (exerciseRoute is ExerciseRoute.Data) {
- setRoute(exerciseRoute.toPlatformExerciseRoute())
+ if (exerciseRouteResult is ExerciseRouteResult.Data) {
+ setRoute(exerciseRouteResult.exerciseRoute.toPlatformExerciseRoute())
}
}
.build()
@@ -719,7 +721,7 @@
.apply { length?.let { setLength(it.toPlatformLength()) } }
.build()
-private fun ExerciseRoute.Data.toPlatformExerciseRoute() =
+private fun ExerciseRoute.toPlatformExerciseRoute() =
PlatformExerciseRoute(
route.map { location ->
PlatformExerciseRouteLocationBuilder(
@@ -1033,8 +1035,8 @@
private fun PlatformSleepSessionStage.toSdkSleepSessionStage() =
SleepSessionRecord.Stage(startTime, endTime, type.toSdkSleepStageType())
-internal fun PlatformExerciseRoute.toSdkExerciseRouteData() =
- ExerciseRoute.Data(
+internal fun PlatformExerciseRoute.toSdkExerciseRoute() =
+ ExerciseRoute(
routeLocations.map { value ->
ExerciseRoute.Location(
time = value.time,
diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/permission/RequestExerciseRouteInternal.kt b/health/connect/connect-client/src/main/java/androidx/health/connect/client/permission/RequestExerciseRouteInternal.kt
index 1fdba9c..b96640c 100644
--- a/health/connect/connect-client/src/main/java/androidx/health/connect/client/permission/RequestExerciseRouteInternal.kt
+++ b/health/connect/connect-client/src/main/java/androidx/health/connect/client/permission/RequestExerciseRouteInternal.kt
@@ -32,8 +32,7 @@
* @see androidx.activity.ComponentActivity.registerForActivityResult
*/
@RestrictTo(RestrictTo.Scope.LIBRARY)
-internal class RequestExerciseRouteInternal :
- ActivityResultContract<String, ExerciseRoute.Data?>() {
+internal class RequestExerciseRouteInternal : ActivityResultContract<String, ExerciseRoute?>() {
override fun createIntent(context: Context, input: String): Intent {
require(input.isNotEmpty()) { "Session identifier can't be empty" }
return Intent(HealthDataServiceConstants.ACTION_REQUEST_ROUTE).apply {
@@ -42,7 +41,7 @@
}
@Suppress("DEPRECATION") // getParcelableExtra
- override fun parseResult(resultCode: Int, intent: Intent?): ExerciseRoute.Data? {
+ override fun parseResult(resultCode: Int, intent: Intent?): ExerciseRoute? {
val route =
intent?.getParcelableExtra<androidx.health.platform.client.exerciseroute.ExerciseRoute>(
HealthDataServiceConstants.EXTRA_EXERCISE_ROUTE
diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/permission/platform/RequestExerciseRouteUpsideDownCake.kt b/health/connect/connect-client/src/main/java/androidx/health/connect/client/permission/platform/RequestExerciseRouteUpsideDownCake.kt
index f1a435a..c5ec226 100644
--- a/health/connect/connect-client/src/main/java/androidx/health/connect/client/permission/platform/RequestExerciseRouteUpsideDownCake.kt
+++ b/health/connect/connect-client/src/main/java/androidx/health/connect/client/permission/platform/RequestExerciseRouteUpsideDownCake.kt
@@ -24,7 +24,7 @@
import androidx.annotation.RestrictTo
import androidx.health.connect.client.HealthConnectClient
import androidx.health.connect.client.impl.platform.records.PlatformExerciseRoute
-import androidx.health.connect.client.impl.platform.records.toSdkExerciseRouteData
+import androidx.health.connect.client.impl.platform.records.toSdkExerciseRoute
import androidx.health.connect.client.records.ExerciseRoute
import androidx.health.platform.client.impl.logger.Logger
@@ -36,7 +36,7 @@
@RequiresApi(34)
@RestrictTo(RestrictTo.Scope.LIBRARY)
internal class RequestExerciseRouteUpsideDownCake :
- ActivityResultContract<String, ExerciseRoute.Data?>() {
+ ActivityResultContract<String, ExerciseRoute?>() {
override fun createIntent(context: Context, input: String): Intent {
require(input.isNotEmpty()) { "Session identifier can't be empty" }
return Intent(HealthConnectManager.ACTION_REQUEST_EXERCISE_ROUTE).apply {
@@ -44,7 +44,7 @@
}
}
- override fun parseResult(resultCode: Int, intent: Intent?): ExerciseRoute.Data? {
+ override fun parseResult(resultCode: Int, intent: Intent?): ExerciseRoute? {
val route =
intent?.getParcelableExtra(
HealthConnectManager.EXTRA_EXERCISE_ROUTE,
@@ -55,6 +55,6 @@
return null
}
Logger.debug(HealthConnectClient.HEALTH_CONNECT_CLIENT_TAG, "Returned a route.")
- return route.toSdkExerciseRouteData()
+ return route.toSdkExerciseRoute()
}
}
diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/records/ExerciseRoute.kt b/health/connect/connect-client/src/main/java/androidx/health/connect/client/records/ExerciseRoute.kt
index 6f36983..679eddb 100644
--- a/health/connect/connect-client/src/main/java/androidx/health/connect/client/records/ExerciseRoute.kt
+++ b/health/connect/connect-client/src/main/java/androidx/health/connect/client/records/ExerciseRoute.kt
@@ -19,63 +19,37 @@
import androidx.health.connect.client.units.Length
import java.time.Instant
-/** Captures a route associated with an exercise session a user does. */
-abstract class ExerciseRoute internal constructor() {
-
- /**
- * Class containing data of an exercise route.
- *
- * Contains a sequence of location points, with timestamps, which do not have to be in order.
- *
- * Location points contain a timestamp, longitude, latitude, and optionally altitude, horizontal
- * and vertical accuracy.
- */
- class Data constructor(val route: List<Location>) : ExerciseRoute() {
- init {
- val sortedRoute: List<Location> = route.sortedBy { it.time }
- for (i in 0 until sortedRoute.lastIndex) {
- require(sortedRoute[i].time.isBefore(sortedRoute[i + 1].time))
- }
- }
-
- internal fun isWithin(startTime: Instant, endTime: Instant): Boolean {
- val minTime = route.minBy { it.time }.time
- val maxTime = route.maxBy { it.time }.time
- return !minTime.isBefore(startTime) && maxTime.isBefore(endTime)
- }
-
- override fun equals(other: Any?): Boolean {
- if (this === other) return true
- if (other !is Data) return false
-
- return route == other.route
- }
-
- override fun hashCode(): Int {
- return route.hashCode()
+/**
+ * Captures a route associated with an exercise session a user does.
+ *
+ * Contains a sequence of location points, with timestamps, which do not have to be in order.
+ *
+ * Location points contain a timestamp, longitude, latitude, and optionally altitude, horizontal and
+ * vertical accuracy.
+ */
+class ExerciseRoute constructor(val route: List<Location>) {
+ init {
+ val sortedRoute: List<Location> = route.sortedBy { it.time }
+ for (i in 0 until sortedRoute.lastIndex) {
+ require(sortedRoute[i].time.isBefore(sortedRoute[i + 1].time))
}
}
- /** Class indicating that a permission hasn't been granted and a value couldn't be returned. */
- class ConsentRequired : ExerciseRoute() {
- override fun equals(other: Any?): Boolean {
- return other is ConsentRequired
- }
-
- override fun hashCode(): Int {
- return 0
- }
+ internal fun isWithin(startTime: Instant, endTime: Instant): Boolean {
+ val minTime = route.minBy { it.time }.time
+ val maxTime = route.maxBy { it.time }.time
+ return !minTime.isBefore(startTime) && maxTime.isBefore(endTime)
}
- /** Class indicating that there's no data to request permissions for. */
- class NoData : ExerciseRoute() {
- override fun equals(other: Any?): Boolean {
- return other is NoData
- }
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other !is ExerciseRoute) return false
- override fun hashCode(): Int {
- return 0
- }
+ return route == other.route
+ }
+
+ override fun hashCode(): Int {
+ return route.hashCode()
}
/**
@@ -88,7 +62,7 @@
* @param horizontalAccuracy in [Length] unit. Optional field. Valid range: non-negative
* numbers.
* @param verticalAccuracy in [Length] unit. Optional field. Valid range: non-negative numbers.
- * @see ExerciseRoute
+ * @see ExerciseRouteResult
*/
class Location(
val time: Instant,
diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/records/ExerciseRouteResult.kt b/health/connect/connect-client/src/main/java/androidx/health/connect/client/records/ExerciseRouteResult.kt
new file mode 100644
index 0000000..3978f5c
--- /dev/null
+++ b/health/connect/connect-client/src/main/java/androidx/health/connect/client/records/ExerciseRouteResult.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.health.connect.client.records
+
+/** Result of the route associated with an exercise session a user does. */
+abstract class ExerciseRouteResult internal constructor() {
+
+ /** Class containing data for an [ExerciseRoute]. */
+ class Data(val exerciseRoute: ExerciseRoute) : ExerciseRouteResult() {
+
+ override fun equals(other: Any?): Boolean {
+ if (other !is Data) {
+ return false
+ }
+ return exerciseRoute == other.exerciseRoute
+ }
+
+ override fun hashCode(): Int {
+ return 0
+ }
+ }
+
+ /** Class indicating that a permission hasn't been granted and a value couldn't be returned. */
+ class ConsentRequired : ExerciseRouteResult() {
+ override fun equals(other: Any?): Boolean {
+ return other is ConsentRequired
+ }
+
+ override fun hashCode(): Int {
+ return 0
+ }
+ }
+
+ /** Class indicating that there's no data to request permissions for. */
+ class NoData : ExerciseRouteResult() {
+ override fun equals(other: Any?): Boolean {
+ return other is NoData
+ }
+
+ override fun hashCode(): Int {
+ return 0
+ }
+ }
+}
diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/records/ExerciseSessionRecord.kt b/health/connect/connect-client/src/main/java/androidx/health/connect/client/records/ExerciseSessionRecord.kt
index 1a3747c..8a33653 100644
--- a/health/connect/connect-client/src/main/java/androidx/health/connect/client/records/ExerciseSessionRecord.kt
+++ b/health/connect/connect-client/src/main/java/androidx/health/connect/client/records/ExerciseSessionRecord.kt
@@ -59,8 +59,8 @@
*/
val laps: List<ExerciseLap> = emptyList(),
- /** [ExerciseRoute] [ExerciseRoute] of the session. */
- val exerciseRoute: ExerciseRoute = ExerciseRoute.NoData(),
+ /** [ExerciseRouteResult] [ExerciseRouteResult] of the session. */
+ val exerciseRouteResult: ExerciseRouteResult = ExerciseRouteResult.NoData(),
) : IntervalRecord {
@JvmOverloads
@@ -78,7 +78,7 @@
metadata: Metadata = Metadata.EMPTY,
segments: List<ExerciseSegment> = emptyList(),
laps: List<ExerciseLap> = emptyList(),
- exerciseRouteData: ExerciseRoute.Data? = null,
+ exerciseRoute: ExerciseRoute? = null,
) : this(
startTime,
startZoneOffset,
@@ -90,7 +90,7 @@
metadata,
segments,
laps,
- exerciseRouteData ?: ExerciseRoute.NoData()
+ exerciseRoute?.let { ExerciseRouteResult.Data(it) } ?: ExerciseRouteResult.NoData()
)
init {
@@ -130,8 +130,8 @@
"laps can not be out of parent time range."
}
}
- if (exerciseRoute is ExerciseRoute.Data) {
- require(exerciseRoute.isWithin(startTime, endTime)) {
+ if (exerciseRouteResult is ExerciseRouteResult.Data) {
+ require(exerciseRouteResult.exerciseRoute.isWithin(startTime, endTime)) {
"route can not be out of parent time range."
}
}
@@ -151,7 +151,7 @@
if (metadata != other.metadata) return false
if (segments != other.segments) return false
if (laps != other.laps) return false
- if (exerciseRoute != other.exerciseRoute) return false
+ if (exerciseRouteResult != other.exerciseRouteResult) return false
return true
}
@@ -164,7 +164,7 @@
result = 31 * result + endTime.hashCode()
result = 31 * result + (endZoneOffset?.hashCode() ?: 0)
result = 31 * result + metadata.hashCode()
- result = 31 * result + exerciseRoute.hashCode()
+ result = 31 * result + exerciseRouteResult.hashCode()
return result
}
diff --git a/health/connect/connect-client/src/test/java/androidx/health/connect/client/impl/HealthConnectClientImplTest.kt b/health/connect/connect-client/src/test/java/androidx/health/connect/client/impl/HealthConnectClientImplTest.kt
index 991beb3e..bc640cd 100644
--- a/health/connect/connect-client/src/test/java/androidx/health/connect/client/impl/HealthConnectClientImplTest.kt
+++ b/health/connect/connect-client/src/test/java/androidx/health/connect/client/impl/HealthConnectClientImplTest.kt
@@ -24,6 +24,7 @@
import android.os.Looper
import androidx.health.connect.client.changes.DeletionChange
import androidx.health.connect.client.changes.UpsertionChange
+import androidx.health.connect.client.permission.HealthPermission
import androidx.health.connect.client.permission.HealthPermission.Companion.getReadPermission
import androidx.health.connect.client.permission.HealthPermission.Companion.getWritePermission
import androidx.health.connect.client.records.ActiveCaloriesBurnedRecord
@@ -212,6 +213,20 @@
}
@Test
+ fun getGrantedPermissions_exerciseRoute() = runTest {
+ fakeAhpServiceStub.addGrantedPermission(
+ androidx.health.platform.client.permission.Permission(
+ PermissionProto.Permission.newBuilder()
+ .setPermission(HealthPermission.PERMISSION_WRITE_EXERCISE_ROUTE)
+ .build()
+ )
+ )
+ val response = testBlocking { healthConnectClient.getGrantedPermissions() }
+
+ assertThat(response).containsExactly(HealthPermission.PERMISSION_WRITE_EXERCISE_ROUTE)
+ }
+
+ @Test
fun insertRecords_steps() = runTest {
fakeAhpServiceStub.insertDataResponse = InsertDataResponse(listOf("0"))
val response = testBlocking {
diff --git a/health/connect/connect-client/src/test/java/androidx/health/connect/client/impl/converters/records/AllRecordsConverterTest.kt b/health/connect/connect-client/src/test/java/androidx/health/connect/client/impl/converters/records/AllRecordsConverterTest.kt
index ac86a98..2bd6f9b 100644
--- a/health/connect/connect-client/src/test/java/androidx/health/connect/client/impl/converters/records/AllRecordsConverterTest.kt
+++ b/health/connect/connect-client/src/test/java/androidx/health/connect/client/impl/converters/records/AllRecordsConverterTest.kt
@@ -643,7 +643,7 @@
)
),
exerciseRoute =
- ExerciseRoute.Data(
+ ExerciseRoute(
route =
listOf(
ExerciseRoute.Location(
@@ -676,7 +676,7 @@
startZoneOffset = null,
endTime = END_TIME,
endZoneOffset = null,
- exerciseRouteData = null,
+ exerciseRoute = null,
)
checkProtoAndRecordTypeNameMatch(data)
diff --git a/health/connect/connect-client/src/test/java/androidx/health/connect/client/permission/RequestExerciseRouteInternalTest.kt b/health/connect/connect-client/src/test/java/androidx/health/connect/client/permission/RequestExerciseRouteInternalTest.kt
index ffa47f9..b650485 100644
--- a/health/connect/connect-client/src/test/java/androidx/health/connect/client/permission/RequestExerciseRouteInternalTest.kt
+++ b/health/connect/connect-client/src/test/java/androidx/health/connect/client/permission/RequestExerciseRouteInternalTest.kt
@@ -73,7 +73,7 @@
)
)
val result = requestRouteContract.parseResult(0, intent)
- assertThat(result).isEqualTo(ExerciseRoute.Data(listOf()))
+ assertThat(result).isEqualTo(ExerciseRoute(listOf()))
}
@Test
@@ -114,7 +114,7 @@
val result = requestRouteContract.parseResult(0, intent)
assertThat(result)
.isEqualTo(
- ExerciseRoute.Data(
+ ExerciseRoute(
listOf(
ExerciseRoute.Location(
time = Instant.ofEpochMilli(1234L),
diff --git a/health/connect/connect-client/src/test/java/androidx/health/connect/client/records/ExerciseRouteTest.kt b/health/connect/connect-client/src/test/java/androidx/health/connect/client/records/ExerciseRouteTest.kt
index 46afdf5..35275bf 100644
--- a/health/connect/connect-client/src/test/java/androidx/health/connect/client/records/ExerciseRouteTest.kt
+++ b/health/connect/connect-client/src/test/java/androidx/health/connect/client/records/ExerciseRouteTest.kt
@@ -74,7 +74,7 @@
@Test
fun emptyRoute() {
- assertThat(ExerciseRoute.Data(listOf())).isEqualTo(ExerciseRoute.Data(listOf()))
+ assertThat(ExerciseRoute(listOf())).isEqualTo(ExerciseRoute(listOf()))
}
@Test
@@ -94,8 +94,8 @@
latitude = 34.8,
longitude = -34.8,
)
- assertThat(ExerciseRoute.Data(listOf(location1, location2)))
- .isEqualTo(ExerciseRoute.Data(listOf(location1, location2)))
+ assertThat(ExerciseRoute(listOf(location1, location2)))
+ .isEqualTo(ExerciseRoute(listOf(location1, location2)))
}
@Test
@@ -112,8 +112,6 @@
latitude = 34.8,
longitude = -34.8,
)
- assertFailsWith<IllegalArgumentException> {
- ExerciseRoute.Data(listOf(location1, location2))
- }
+ assertFailsWith<IllegalArgumentException> { ExerciseRoute(listOf(location1, location2)) }
}
}
diff --git a/health/connect/connect-client/src/test/java/androidx/health/connect/client/records/ExerciseSessionRecordTest.kt b/health/connect/connect-client/src/test/java/androidx/health/connect/client/records/ExerciseSessionRecordTest.kt
index 6adb8bb..73af184 100644
--- a/health/connect/connect-client/src/test/java/androidx/health/connect/client/records/ExerciseSessionRecordTest.kt
+++ b/health/connect/connect-client/src/test/java/androidx/health/connect/client/records/ExerciseSessionRecordTest.kt
@@ -62,7 +62,7 @@
)
),
exerciseRoute =
- ExerciseRoute.Data(
+ ExerciseRoute(
route =
listOf(
ExerciseRoute.Location(
@@ -103,7 +103,7 @@
)
),
exerciseRoute =
- ExerciseRoute.Data(
+ ExerciseRoute(
route =
listOf(
ExerciseRoute.Location(
@@ -131,7 +131,7 @@
exerciseType = ExerciseSessionRecord.EXERCISE_TYPE_EXERCISE_CLASS,
title = "title",
notes = "notes",
- exerciseRouteData = null,
+ exerciseRoute = null,
)
}
}
@@ -181,7 +181,7 @@
segmentType = ExerciseSegment.EXERCISE_SEGMENT_TYPE_BIKING
)
),
- exerciseRouteData = null,
+ exerciseRoute = null,
)
}
@@ -200,7 +200,7 @@
segmentType = ExerciseSegment.EXERCISE_SEGMENT_TYPE_BIKING
)
),
- exerciseRouteData = null,
+ exerciseRoute = null,
)
}
}
@@ -221,7 +221,7 @@
endTime = Instant.ofEpochMilli(1235L),
)
),
- exerciseRouteData = null,
+ exerciseRoute = null,
)
}
@@ -239,7 +239,7 @@
endTime = Instant.ofEpochMilli(1236L),
)
),
- exerciseRouteData = null,
+ exerciseRoute = null,
)
}
}
@@ -254,7 +254,7 @@
endZoneOffset = null,
exerciseType = EXERCISE_TYPE_BIKING,
exerciseRoute =
- ExerciseRoute.Data(
+ ExerciseRoute(
route =
listOf(
ExerciseRoute.Location(
@@ -275,7 +275,7 @@
endZoneOffset = null,
exerciseType = EXERCISE_TYPE_BIKING,
exerciseRoute =
- ExerciseRoute.Data(
+ ExerciseRoute(
route =
listOf(
ExerciseRoute.Location(
@@ -311,7 +311,7 @@
segmentType = ExerciseSegment.EXERCISE_SEGMENT_TYPE_BIKING
),
),
- exerciseRouteData = null,
+ exerciseRoute = null,
)
}
}
@@ -336,7 +336,7 @@
endTime = Instant.ofEpochMilli(1236L),
),
),
- exerciseRouteData = null,
+ exerciseRoute = null,
)
}
}
@@ -358,7 +358,7 @@
segmentType = ExerciseSegment.EXERCISE_SEGMENT_TYPE_PLANK
),
),
- exerciseRouteData = null,
+ exerciseRoute = null,
)
}
}
@@ -373,7 +373,7 @@
endZoneOffset = null,
exerciseType = EXERCISE_TYPE_BIKING,
exerciseRoute =
- ExerciseRoute.Data(
+ ExerciseRoute(
route =
listOf(
ExerciseRoute.Location(
@@ -384,18 +384,20 @@
)
),
)
- .exerciseRoute
+ .exerciseRouteResult
)
.isEqualTo(
- ExerciseRoute.Data(
- route =
- listOf(
- ExerciseRoute.Location(
- time = Instant.ofEpochMilli(1235L),
- latitude = 34.5,
- longitude = -34.5
+ ExerciseRouteResult.Data(
+ ExerciseRoute(
+ route =
+ listOf(
+ ExerciseRoute.Location(
+ time = Instant.ofEpochMilli(1235L),
+ latitude = 34.5,
+ longitude = -34.5
+ )
)
- )
+ )
)
)
assertThat(
@@ -405,10 +407,10 @@
endTime = Instant.ofEpochMilli(1236L),
endZoneOffset = null,
exerciseType = EXERCISE_TYPE_BIKING,
- exerciseRouteData = null
+ exerciseRoute = null
)
- .exerciseRoute
+ .exerciseRouteResult
)
- .isEqualTo(ExerciseRoute.NoData())
+ .isEqualTo(ExerciseRouteResult.NoData())
}
}
diff --git a/health/health-services-client/api/current.txt b/health/health-services-client/api/current.txt
index 92e1fab..aa45592 100644
--- a/health/health-services-client/api/current.txt
+++ b/health/health-services-client/api/current.txt
@@ -41,6 +41,7 @@
public interface ExerciseUpdateCallback {
method public void onAvailabilityChanged(androidx.health.services.client.data.DataType<?,?> dataType, androidx.health.services.client.data.Availability availability);
+ method public default void onExerciseEventReceived(androidx.health.services.client.data.ExerciseEvent event);
method public void onExerciseUpdateReceived(androidx.health.services.client.data.ExerciseUpdate update);
method public void onLapSummaryReceived(androidx.health.services.client.data.ExerciseLapSummary lapSummary);
method public void onRegistered();
@@ -331,12 +332,17 @@
}
public final class ExerciseConfig {
+ ctor public ExerciseConfig(androidx.health.services.client.data.ExerciseType exerciseType, java.util.Set<? extends androidx.health.services.client.data.DataType<?,?>> dataTypes, boolean isAutoPauseAndResumeEnabled, boolean isGpsEnabled);
+ ctor public ExerciseConfig(androidx.health.services.client.data.ExerciseType exerciseType, java.util.Set<? extends androidx.health.services.client.data.DataType<?,?>> dataTypes, boolean isAutoPauseAndResumeEnabled, boolean isGpsEnabled, optional java.util.List<? extends androidx.health.services.client.data.ExerciseGoal<?>> exerciseGoals);
+ ctor public ExerciseConfig(androidx.health.services.client.data.ExerciseType exerciseType, java.util.Set<? extends androidx.health.services.client.data.DataType<?,?>> dataTypes, boolean isAutoPauseAndResumeEnabled, boolean isGpsEnabled, optional java.util.List<? extends androidx.health.services.client.data.ExerciseGoal<?>> exerciseGoals, optional android.os.Bundle exerciseParams);
ctor public ExerciseConfig(androidx.health.services.client.data.ExerciseType exerciseType, java.util.Set<? extends androidx.health.services.client.data.DataType<?,?>> dataTypes, boolean isAutoPauseAndResumeEnabled, boolean isGpsEnabled, optional java.util.List<? extends androidx.health.services.client.data.ExerciseGoal<?>> exerciseGoals, optional android.os.Bundle exerciseParams, optional @FloatRange(from=0.0) float swimmingPoolLengthMeters);
ctor public ExerciseConfig(androidx.health.services.client.data.ExerciseType exerciseType, java.util.Set<? extends androidx.health.services.client.data.DataType<?,?>> dataTypes, boolean isAutoPauseAndResumeEnabled, boolean isGpsEnabled, optional java.util.List<? extends androidx.health.services.client.data.ExerciseGoal<?>> exerciseGoals, optional android.os.Bundle exerciseParams, optional @FloatRange(from=0.0) float swimmingPoolLengthMeters, optional androidx.health.services.client.data.ExerciseTypeConfig? exerciseTypeConfig);
ctor public ExerciseConfig(androidx.health.services.client.data.ExerciseType exerciseType, java.util.Set<? extends androidx.health.services.client.data.DataType<?,?>> dataTypes, boolean isAutoPauseAndResumeEnabled, boolean isGpsEnabled, optional java.util.List<? extends androidx.health.services.client.data.ExerciseGoal<?>> exerciseGoals, optional android.os.Bundle exerciseParams, optional @FloatRange(from=0.0) float swimmingPoolLengthMeters, optional androidx.health.services.client.data.ExerciseTypeConfig? exerciseTypeConfig, optional java.util.Set<androidx.health.services.client.data.BatchingMode> batchingModeOverrides);
+ ctor public ExerciseConfig(androidx.health.services.client.data.ExerciseType exerciseType, java.util.Set<? extends androidx.health.services.client.data.DataType<?,?>> dataTypes, boolean isAutoPauseAndResumeEnabled, boolean isGpsEnabled, optional java.util.List<? extends androidx.health.services.client.data.ExerciseGoal<?>> exerciseGoals, optional android.os.Bundle exerciseParams, optional @FloatRange(from=0.0) float swimmingPoolLengthMeters, optional androidx.health.services.client.data.ExerciseTypeConfig? exerciseTypeConfig, optional java.util.Set<androidx.health.services.client.data.BatchingMode> batchingModeOverrides, optional java.util.Set<? extends androidx.health.services.client.data.ExerciseEventType<?>> exerciseEventTypes);
method public static androidx.health.services.client.data.ExerciseConfig.Builder builder(androidx.health.services.client.data.ExerciseType exerciseType);
method public java.util.Set<androidx.health.services.client.data.BatchingMode> getBatchingModeOverrides();
method public java.util.Set<androidx.health.services.client.data.DataType<?,?>> getDataTypes();
+ method public java.util.Set<androidx.health.services.client.data.ExerciseEventType<?>> getExerciseEventTypes();
method public java.util.List<androidx.health.services.client.data.ExerciseGoal<?>> getExerciseGoals();
method public android.os.Bundle getExerciseParams();
method public androidx.health.services.client.data.ExerciseType getExerciseType();
@@ -346,6 +352,7 @@
method public boolean isGpsEnabled();
property public final java.util.Set<androidx.health.services.client.data.BatchingMode> batchingModeOverrides;
property public final java.util.Set<androidx.health.services.client.data.DataType<?,?>> dataTypes;
+ property public final java.util.Set<androidx.health.services.client.data.ExerciseEventType<?>> exerciseEventTypes;
property public final java.util.List<androidx.health.services.client.data.ExerciseGoal<?>> exerciseGoals;
property public final android.os.Bundle exerciseParams;
property public final androidx.health.services.client.data.ExerciseType exerciseType;
@@ -362,6 +369,7 @@
method public androidx.health.services.client.data.ExerciseConfig build();
method public androidx.health.services.client.data.ExerciseConfig.Builder setBatchingModeOverrides(java.util.Set<androidx.health.services.client.data.BatchingMode> batchingModeOverrides);
method public androidx.health.services.client.data.ExerciseConfig.Builder setDataTypes(java.util.Set<? extends androidx.health.services.client.data.DataType<?,?>> dataTypes);
+ method public androidx.health.services.client.data.ExerciseConfig.Builder setExerciseEventTypes(java.util.Set<? extends androidx.health.services.client.data.ExerciseEventType<?>> exerciseEventTypes);
method public androidx.health.services.client.data.ExerciseConfig.Builder setExerciseGoals(java.util.List<? extends androidx.health.services.client.data.ExerciseGoal<?>> exerciseGoals);
method public androidx.health.services.client.data.ExerciseConfig.Builder setExerciseParams(android.os.Bundle exerciseParams);
method public androidx.health.services.client.data.ExerciseConfig.Builder setExerciseTypeConfig(androidx.health.services.client.data.ExerciseTypeConfig? exerciseTypeConfig);
@@ -374,6 +382,31 @@
method public androidx.health.services.client.data.ExerciseConfig.Builder builder(androidx.health.services.client.data.ExerciseType exerciseType);
}
+ public abstract class ExerciseEvent {
+ field public static final androidx.health.services.client.data.ExerciseEvent.Companion Companion;
+ }
+
+ public static final class ExerciseEvent.Companion {
+ }
+
+ public abstract class ExerciseEventCapabilities {
+ method public abstract boolean isSupported();
+ property public abstract boolean isSupported;
+ field public static final androidx.health.services.client.data.ExerciseEventCapabilities.Companion Companion;
+ }
+
+ public static final class ExerciseEventCapabilities.Companion {
+ }
+
+ public final class ExerciseEventType<C extends androidx.health.services.client.data.ExerciseEventCapabilities> {
+ field public static final androidx.health.services.client.data.ExerciseEventType.Companion Companion;
+ field public static final androidx.health.services.client.data.ExerciseEventType<androidx.health.services.client.data.GolfShotEventCapabilities> GOLF_SHOT_EVENT;
+ field public static final androidx.health.services.client.data.ExerciseEventType<androidx.health.services.client.data.ExerciseEventCapabilities> UNKNOWN;
+ }
+
+ public static final class ExerciseEventType.Companion {
+ }
+
public final class ExerciseGoal<T extends java.lang.Number> implements android.os.Parcelable {
method public static <T extends java.lang.Number> androidx.health.services.client.data.ExerciseGoal<T> createMilestone(androidx.health.services.client.data.DataTypeCondition<T,androidx.health.services.client.data.AggregateDataType<T,?>> condition, T period);
method public static <T extends java.lang.Number> androidx.health.services.client.data.ExerciseGoal<T> createMilestoneGoalWithUpdatedThreshold(androidx.health.services.client.data.ExerciseGoal<T> goal, T newThreshold);
@@ -577,11 +610,16 @@
public final class ExerciseTypeCapabilities {
ctor public ExerciseTypeCapabilities(java.util.Set<? extends androidx.health.services.client.data.DataType<?,?>> supportedDataTypes, java.util.Map<androidx.health.services.client.data.AggregateDataType<?,?>,? extends java.util.Set<androidx.health.services.client.data.ComparisonType>> supportedGoals, java.util.Map<androidx.health.services.client.data.AggregateDataType<?,?>,? extends java.util.Set<androidx.health.services.client.data.ComparisonType>> supportedMilestones, boolean supportsAutoPauseAndResume);
+ ctor public ExerciseTypeCapabilities(java.util.Set<? extends androidx.health.services.client.data.DataType<?,?>> supportedDataTypes, java.util.Map<androidx.health.services.client.data.AggregateDataType<?,?>,? extends java.util.Set<androidx.health.services.client.data.ComparisonType>> supportedGoals, java.util.Map<androidx.health.services.client.data.AggregateDataType<?,?>,? extends java.util.Set<androidx.health.services.client.data.ComparisonType>> supportedMilestones, boolean supportsAutoPauseAndResume, optional java.util.Set<? extends androidx.health.services.client.data.ExerciseEventType<?>> supportedExerciseEvents);
+ ctor public ExerciseTypeCapabilities(java.util.Set<? extends androidx.health.services.client.data.DataType<?,?>> supportedDataTypes, java.util.Map<androidx.health.services.client.data.AggregateDataType<?,?>,? extends java.util.Set<androidx.health.services.client.data.ComparisonType>> supportedGoals, java.util.Map<androidx.health.services.client.data.AggregateDataType<?,?>,? extends java.util.Set<androidx.health.services.client.data.ComparisonType>> supportedMilestones, boolean supportsAutoPauseAndResume, optional java.util.Set<? extends androidx.health.services.client.data.ExerciseEventType<?>> supportedExerciseEvents, optional java.util.Map<androidx.health.services.client.data.ExerciseEventType<?>,? extends androidx.health.services.client.data.ExerciseEventCapabilities> exerciseEventCapabilities);
+ method public <C extends androidx.health.services.client.data.ExerciseEventCapabilities> C? getExerciseEventCapabilityDetails(androidx.health.services.client.data.ExerciseEventType<C> exerciseEventType);
method public java.util.Set<androidx.health.services.client.data.DataType<?,?>> getSupportedDataTypes();
+ method public java.util.Set<androidx.health.services.client.data.ExerciseEventType<?>> getSupportedExerciseEvents();
method public java.util.Map<androidx.health.services.client.data.AggregateDataType<?,?>,java.util.Set<androidx.health.services.client.data.ComparisonType>> getSupportedGoals();
method public java.util.Map<androidx.health.services.client.data.AggregateDataType<?,?>,java.util.Set<androidx.health.services.client.data.ComparisonType>> getSupportedMilestones();
method public boolean getSupportsAutoPauseAndResume();
property public final java.util.Set<androidx.health.services.client.data.DataType<?,?>> supportedDataTypes;
+ property public final java.util.Set<androidx.health.services.client.data.ExerciseEventType<?>> supportedExerciseEvents;
property public final java.util.Map<androidx.health.services.client.data.AggregateDataType<?,?>,java.util.Set<androidx.health.services.client.data.ComparisonType>> supportedGoals;
property public final java.util.Map<androidx.health.services.client.data.AggregateDataType<?,?>,java.util.Set<androidx.health.services.client.data.ComparisonType>> supportedMilestones;
property public final boolean supportsAutoPauseAndResume;
@@ -645,6 +683,33 @@
public static final class GolfExerciseTypeConfig.GolfShotTrackingPlaceInfo.Companion {
}
+ public final class GolfShotEvent extends androidx.health.services.client.data.ExerciseEvent {
+ ctor public GolfShotEvent(java.time.Duration durationSinceBoot, androidx.health.services.client.data.GolfShotEvent.GolfShotSwingType swingType);
+ method public java.time.Duration getDurationSinceBoot();
+ method public androidx.health.services.client.data.GolfShotEvent.GolfShotSwingType getSwingType();
+ property public final java.time.Duration durationSinceBoot;
+ property public final androidx.health.services.client.data.GolfShotEvent.GolfShotSwingType swingType;
+ }
+
+ public static final class GolfShotEvent.GolfShotSwingType {
+ field public static final androidx.health.services.client.data.GolfShotEvent.GolfShotSwingType.Companion Companion;
+ field public static final androidx.health.services.client.data.GolfShotEvent.GolfShotSwingType FULL;
+ field public static final androidx.health.services.client.data.GolfShotEvent.GolfShotSwingType PARTIAL;
+ field public static final androidx.health.services.client.data.GolfShotEvent.GolfShotSwingType PUTT;
+ field public static final androidx.health.services.client.data.GolfShotEvent.GolfShotSwingType UNKNOWN;
+ }
+
+ public static final class GolfShotEvent.GolfShotSwingType.Companion {
+ }
+
+ public final class GolfShotEventCapabilities extends androidx.health.services.client.data.ExerciseEventCapabilities {
+ ctor public GolfShotEventCapabilities(boolean isSupported, boolean isSwingTypeClassificationSupported);
+ method public boolean isSupported();
+ method public boolean isSwingTypeClassificationSupported();
+ property public boolean isSupported;
+ property public final boolean isSwingTypeClassificationSupported;
+ }
+
public final class HealthEvent {
ctor public HealthEvent(androidx.health.services.client.data.HealthEvent.Type type, java.time.Instant eventTime, androidx.health.services.client.data.DataPointContainer metrics);
method public java.time.Instant getEventTime();
diff --git a/health/health-services-client/api/restricted_current.txt b/health/health-services-client/api/restricted_current.txt
index 92e1fab..a74becf 100644
--- a/health/health-services-client/api/restricted_current.txt
+++ b/health/health-services-client/api/restricted_current.txt
@@ -41,6 +41,7 @@
public interface ExerciseUpdateCallback {
method public void onAvailabilityChanged(androidx.health.services.client.data.DataType<?,?> dataType, androidx.health.services.client.data.Availability availability);
+ method public default void onExerciseEventReceived(androidx.health.services.client.data.ExerciseEvent event);
method public void onExerciseUpdateReceived(androidx.health.services.client.data.ExerciseUpdate update);
method public void onLapSummaryReceived(androidx.health.services.client.data.ExerciseLapSummary lapSummary);
method public void onRegistered();
@@ -331,12 +332,17 @@
}
public final class ExerciseConfig {
+ ctor public ExerciseConfig(androidx.health.services.client.data.ExerciseType exerciseType, java.util.Set<? extends androidx.health.services.client.data.DataType<?,?>> dataTypes, boolean isAutoPauseAndResumeEnabled, boolean isGpsEnabled);
+ ctor public ExerciseConfig(androidx.health.services.client.data.ExerciseType exerciseType, java.util.Set<? extends androidx.health.services.client.data.DataType<?,?>> dataTypes, boolean isAutoPauseAndResumeEnabled, boolean isGpsEnabled, optional java.util.List<? extends androidx.health.services.client.data.ExerciseGoal<?>> exerciseGoals);
+ ctor public ExerciseConfig(androidx.health.services.client.data.ExerciseType exerciseType, java.util.Set<? extends androidx.health.services.client.data.DataType<?,?>> dataTypes, boolean isAutoPauseAndResumeEnabled, boolean isGpsEnabled, optional java.util.List<? extends androidx.health.services.client.data.ExerciseGoal<?>> exerciseGoals, optional android.os.Bundle exerciseParams);
ctor public ExerciseConfig(androidx.health.services.client.data.ExerciseType exerciseType, java.util.Set<? extends androidx.health.services.client.data.DataType<?,?>> dataTypes, boolean isAutoPauseAndResumeEnabled, boolean isGpsEnabled, optional java.util.List<? extends androidx.health.services.client.data.ExerciseGoal<?>> exerciseGoals, optional android.os.Bundle exerciseParams, optional @FloatRange(from=0.0) float swimmingPoolLengthMeters);
ctor public ExerciseConfig(androidx.health.services.client.data.ExerciseType exerciseType, java.util.Set<? extends androidx.health.services.client.data.DataType<?,?>> dataTypes, boolean isAutoPauseAndResumeEnabled, boolean isGpsEnabled, optional java.util.List<? extends androidx.health.services.client.data.ExerciseGoal<?>> exerciseGoals, optional android.os.Bundle exerciseParams, optional @FloatRange(from=0.0) float swimmingPoolLengthMeters, optional androidx.health.services.client.data.ExerciseTypeConfig? exerciseTypeConfig);
ctor public ExerciseConfig(androidx.health.services.client.data.ExerciseType exerciseType, java.util.Set<? extends androidx.health.services.client.data.DataType<?,?>> dataTypes, boolean isAutoPauseAndResumeEnabled, boolean isGpsEnabled, optional java.util.List<? extends androidx.health.services.client.data.ExerciseGoal<?>> exerciseGoals, optional android.os.Bundle exerciseParams, optional @FloatRange(from=0.0) float swimmingPoolLengthMeters, optional androidx.health.services.client.data.ExerciseTypeConfig? exerciseTypeConfig, optional java.util.Set<androidx.health.services.client.data.BatchingMode> batchingModeOverrides);
+ ctor public ExerciseConfig(androidx.health.services.client.data.ExerciseType exerciseType, java.util.Set<? extends androidx.health.services.client.data.DataType<?,?>> dataTypes, boolean isAutoPauseAndResumeEnabled, boolean isGpsEnabled, optional java.util.List<? extends androidx.health.services.client.data.ExerciseGoal<?>> exerciseGoals, optional android.os.Bundle exerciseParams, optional @FloatRange(from=0.0) float swimmingPoolLengthMeters, optional androidx.health.services.client.data.ExerciseTypeConfig? exerciseTypeConfig, optional java.util.Set<androidx.health.services.client.data.BatchingMode> batchingModeOverrides, optional java.util.Set<? extends androidx.health.services.client.data.ExerciseEventType<?>> exerciseEventTypes);
method public static androidx.health.services.client.data.ExerciseConfig.Builder builder(androidx.health.services.client.data.ExerciseType exerciseType);
method public java.util.Set<androidx.health.services.client.data.BatchingMode> getBatchingModeOverrides();
method public java.util.Set<androidx.health.services.client.data.DataType<?,?>> getDataTypes();
+ method public java.util.Set<androidx.health.services.client.data.ExerciseEventType<?>> getExerciseEventTypes();
method public java.util.List<androidx.health.services.client.data.ExerciseGoal<?>> getExerciseGoals();
method public android.os.Bundle getExerciseParams();
method public androidx.health.services.client.data.ExerciseType getExerciseType();
@@ -346,6 +352,7 @@
method public boolean isGpsEnabled();
property public final java.util.Set<androidx.health.services.client.data.BatchingMode> batchingModeOverrides;
property public final java.util.Set<androidx.health.services.client.data.DataType<?,?>> dataTypes;
+ property public final java.util.Set<androidx.health.services.client.data.ExerciseEventType<?>> exerciseEventTypes;
property public final java.util.List<androidx.health.services.client.data.ExerciseGoal<?>> exerciseGoals;
property public final android.os.Bundle exerciseParams;
property public final androidx.health.services.client.data.ExerciseType exerciseType;
@@ -362,6 +369,7 @@
method public androidx.health.services.client.data.ExerciseConfig build();
method public androidx.health.services.client.data.ExerciseConfig.Builder setBatchingModeOverrides(java.util.Set<androidx.health.services.client.data.BatchingMode> batchingModeOverrides);
method public androidx.health.services.client.data.ExerciseConfig.Builder setDataTypes(java.util.Set<? extends androidx.health.services.client.data.DataType<?,?>> dataTypes);
+ method public androidx.health.services.client.data.ExerciseConfig.Builder setExerciseEventTypes(java.util.Set<? extends androidx.health.services.client.data.ExerciseEventType<?>> exerciseEventTypes);
method public androidx.health.services.client.data.ExerciseConfig.Builder setExerciseGoals(java.util.List<? extends androidx.health.services.client.data.ExerciseGoal<?>> exerciseGoals);
method public androidx.health.services.client.data.ExerciseConfig.Builder setExerciseParams(android.os.Bundle exerciseParams);
method public androidx.health.services.client.data.ExerciseConfig.Builder setExerciseTypeConfig(androidx.health.services.client.data.ExerciseTypeConfig? exerciseTypeConfig);
@@ -374,6 +382,33 @@
method public androidx.health.services.client.data.ExerciseConfig.Builder builder(androidx.health.services.client.data.ExerciseType exerciseType);
}
+ public abstract class ExerciseEvent {
+ ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public ExerciseEvent();
+ field public static final androidx.health.services.client.data.ExerciseEvent.Companion Companion;
+ }
+
+ public static final class ExerciseEvent.Companion {
+ }
+
+ public abstract class ExerciseEventCapabilities {
+ ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public ExerciseEventCapabilities();
+ method public abstract boolean isSupported();
+ property public abstract boolean isSupported;
+ field public static final androidx.health.services.client.data.ExerciseEventCapabilities.Companion Companion;
+ }
+
+ public static final class ExerciseEventCapabilities.Companion {
+ }
+
+ public final class ExerciseEventType<C extends androidx.health.services.client.data.ExerciseEventCapabilities> {
+ field public static final androidx.health.services.client.data.ExerciseEventType.Companion Companion;
+ field public static final androidx.health.services.client.data.ExerciseEventType<androidx.health.services.client.data.GolfShotEventCapabilities> GOLF_SHOT_EVENT;
+ field public static final androidx.health.services.client.data.ExerciseEventType<androidx.health.services.client.data.ExerciseEventCapabilities> UNKNOWN;
+ }
+
+ public static final class ExerciseEventType.Companion {
+ }
+
public final class ExerciseGoal<T extends java.lang.Number> implements android.os.Parcelable {
method public static <T extends java.lang.Number> androidx.health.services.client.data.ExerciseGoal<T> createMilestone(androidx.health.services.client.data.DataTypeCondition<T,androidx.health.services.client.data.AggregateDataType<T,?>> condition, T period);
method public static <T extends java.lang.Number> androidx.health.services.client.data.ExerciseGoal<T> createMilestoneGoalWithUpdatedThreshold(androidx.health.services.client.data.ExerciseGoal<T> goal, T newThreshold);
@@ -577,11 +612,16 @@
public final class ExerciseTypeCapabilities {
ctor public ExerciseTypeCapabilities(java.util.Set<? extends androidx.health.services.client.data.DataType<?,?>> supportedDataTypes, java.util.Map<androidx.health.services.client.data.AggregateDataType<?,?>,? extends java.util.Set<androidx.health.services.client.data.ComparisonType>> supportedGoals, java.util.Map<androidx.health.services.client.data.AggregateDataType<?,?>,? extends java.util.Set<androidx.health.services.client.data.ComparisonType>> supportedMilestones, boolean supportsAutoPauseAndResume);
+ ctor public ExerciseTypeCapabilities(java.util.Set<? extends androidx.health.services.client.data.DataType<?,?>> supportedDataTypes, java.util.Map<androidx.health.services.client.data.AggregateDataType<?,?>,? extends java.util.Set<androidx.health.services.client.data.ComparisonType>> supportedGoals, java.util.Map<androidx.health.services.client.data.AggregateDataType<?,?>,? extends java.util.Set<androidx.health.services.client.data.ComparisonType>> supportedMilestones, boolean supportsAutoPauseAndResume, optional java.util.Set<? extends androidx.health.services.client.data.ExerciseEventType<?>> supportedExerciseEvents);
+ ctor public ExerciseTypeCapabilities(java.util.Set<? extends androidx.health.services.client.data.DataType<?,?>> supportedDataTypes, java.util.Map<androidx.health.services.client.data.AggregateDataType<?,?>,? extends java.util.Set<androidx.health.services.client.data.ComparisonType>> supportedGoals, java.util.Map<androidx.health.services.client.data.AggregateDataType<?,?>,? extends java.util.Set<androidx.health.services.client.data.ComparisonType>> supportedMilestones, boolean supportsAutoPauseAndResume, optional java.util.Set<? extends androidx.health.services.client.data.ExerciseEventType<?>> supportedExerciseEvents, optional java.util.Map<androidx.health.services.client.data.ExerciseEventType<?>,? extends androidx.health.services.client.data.ExerciseEventCapabilities> exerciseEventCapabilities);
+ method public <C extends androidx.health.services.client.data.ExerciseEventCapabilities> C? getExerciseEventCapabilityDetails(androidx.health.services.client.data.ExerciseEventType<C> exerciseEventType);
method public java.util.Set<androidx.health.services.client.data.DataType<?,?>> getSupportedDataTypes();
+ method public java.util.Set<androidx.health.services.client.data.ExerciseEventType<?>> getSupportedExerciseEvents();
method public java.util.Map<androidx.health.services.client.data.AggregateDataType<?,?>,java.util.Set<androidx.health.services.client.data.ComparisonType>> getSupportedGoals();
method public java.util.Map<androidx.health.services.client.data.AggregateDataType<?,?>,java.util.Set<androidx.health.services.client.data.ComparisonType>> getSupportedMilestones();
method public boolean getSupportsAutoPauseAndResume();
property public final java.util.Set<androidx.health.services.client.data.DataType<?,?>> supportedDataTypes;
+ property public final java.util.Set<androidx.health.services.client.data.ExerciseEventType<?>> supportedExerciseEvents;
property public final java.util.Map<androidx.health.services.client.data.AggregateDataType<?,?>,java.util.Set<androidx.health.services.client.data.ComparisonType>> supportedGoals;
property public final java.util.Map<androidx.health.services.client.data.AggregateDataType<?,?>,java.util.Set<androidx.health.services.client.data.ComparisonType>> supportedMilestones;
property public final boolean supportsAutoPauseAndResume;
@@ -645,6 +685,33 @@
public static final class GolfExerciseTypeConfig.GolfShotTrackingPlaceInfo.Companion {
}
+ public final class GolfShotEvent extends androidx.health.services.client.data.ExerciseEvent {
+ ctor public GolfShotEvent(java.time.Duration durationSinceBoot, androidx.health.services.client.data.GolfShotEvent.GolfShotSwingType swingType);
+ method public java.time.Duration getDurationSinceBoot();
+ method public androidx.health.services.client.data.GolfShotEvent.GolfShotSwingType getSwingType();
+ property public final java.time.Duration durationSinceBoot;
+ property public final androidx.health.services.client.data.GolfShotEvent.GolfShotSwingType swingType;
+ }
+
+ public static final class GolfShotEvent.GolfShotSwingType {
+ field public static final androidx.health.services.client.data.GolfShotEvent.GolfShotSwingType.Companion Companion;
+ field public static final androidx.health.services.client.data.GolfShotEvent.GolfShotSwingType FULL;
+ field public static final androidx.health.services.client.data.GolfShotEvent.GolfShotSwingType PARTIAL;
+ field public static final androidx.health.services.client.data.GolfShotEvent.GolfShotSwingType PUTT;
+ field public static final androidx.health.services.client.data.GolfShotEvent.GolfShotSwingType UNKNOWN;
+ }
+
+ public static final class GolfShotEvent.GolfShotSwingType.Companion {
+ }
+
+ public final class GolfShotEventCapabilities extends androidx.health.services.client.data.ExerciseEventCapabilities {
+ ctor public GolfShotEventCapabilities(boolean isSupported, boolean isSwingTypeClassificationSupported);
+ method public boolean isSupported();
+ method public boolean isSwingTypeClassificationSupported();
+ property public boolean isSupported;
+ property public final boolean isSwingTypeClassificationSupported;
+ }
+
public final class HealthEvent {
ctor public HealthEvent(androidx.health.services.client.data.HealthEvent.Type type, java.time.Instant eventTime, androidx.health.services.client.data.DataPointContainer metrics);
method public java.time.Instant getEventTime();
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/ExerciseUpdateCallback.kt b/health/health-services-client/src/main/java/androidx/health/services/client/ExerciseUpdateCallback.kt
index f2bc4c34..11aaf5d 100644
--- a/health/health-services-client/src/main/java/androidx/health/services/client/ExerciseUpdateCallback.kt
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/ExerciseUpdateCallback.kt
@@ -18,6 +18,7 @@
import androidx.health.services.client.data.Availability
import androidx.health.services.client.data.DataType
+import androidx.health.services.client.data.ExerciseEvent
import androidx.health.services.client.data.ExerciseLapSummary
import androidx.health.services.client.data.ExerciseState
import androidx.health.services.client.data.ExerciseUpdate
@@ -59,4 +60,10 @@
* @param availability the new [Availability] state
*/
public fun onAvailabilityChanged(dataType: DataType<*, *>, availability: Availability)
+
+ /**
+ * Called when an [ExerciseEvent] is emitted. May be called during any exercise state
+ * except for PREPARING or ENDED.
+ */
+ public fun onExerciseEventReceived(event: ExerciseEvent) {}
}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/BatchingMode.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/BatchingMode.kt
index fa9ea16..b61a875 100644
--- a/health/health-services-client/src/main/java/androidx/health/services/client/data/BatchingMode.kt
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/BatchingMode.kt
@@ -37,13 +37,15 @@
public companion object {
/**
- * Batching mode for receiving [DataType.HEART_RATE_BPM] updates with fast frequency.
+ * Deliver smaller and more frequent batches of [DataType.HEART_RATE_BPM] when the device is
+ * not interactive (e.g. screen is off).
*
- * Note: This mode will cause significantly increased power consumption compared to the
- * default batching mode, while still being more power efficient than streaming when in
- * non-interactive state. The exact power/performance tradeoff of this mode is device
- * implementation dependent and batched updates may be aligned with other wake ups but
- * target five second updates.
+ * This setting significantly increases power consumption, and is intended to be used by
+ * apps which need to send data to a separate device (e.g. a connected phone or TV) for
+ * real-time visualisation. It has no effect if the device is interactive.
+ *
+ * The exact power/performance tradeoff of this mode is device implementation dependent and
+ * batched updates may be aligned with other wake ups but target five second updates.
*/
@JvmField public val HEART_RATE_5_SECONDS: BatchingMode = BatchingMode(1)
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseConfig.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseConfig.kt
index 9110a8d..f417756 100644
--- a/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseConfig.kt
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseConfig.kt
@@ -42,9 +42,12 @@
* @property exerciseTypeConfig [ExerciseTypeConfig] containing attributes which may be
* modified after the exercise has started
* @property batchingModeOverrides [BatchingMode] overrides for this exercise
+ * @property exerciseEventTypes [ExerciseEventType]s which should be tracked for this exercise
*/
@Suppress("ParcelCreator")
-class ExerciseConfig(
+class ExerciseConfig
+@JvmOverloads
+constructor(
val exerciseType: ExerciseType,
val dataTypes: Set<DataType<*, *>>,
val isAutoPauseAndResumeEnabled: Boolean,
@@ -54,46 +57,8 @@
@FloatRange(from = 0.0) val swimmingPoolLengthMeters: Float = SWIMMING_POOL_LENGTH_UNSPECIFIED,
val exerciseTypeConfig: ExerciseTypeConfig? = null,
val batchingModeOverrides: Set<BatchingMode> = emptySet(),
+ val exerciseEventTypes: Set<ExerciseEventType<*>> = emptySet(),
) {
- constructor(
- exerciseType: ExerciseType,
- dataTypes: Set<DataType<*, *>>,
- isAutoPauseAndResumeEnabled: Boolean,
- isGpsEnabled: Boolean,
- exerciseGoals: List<ExerciseGoal<*>> = listOf(),
- exerciseParams: Bundle = Bundle(),
- @FloatRange(from = 0.0) swimmingPoolLengthMeters: Float = SWIMMING_POOL_LENGTH_UNSPECIFIED,
- exerciseTypeConfig: ExerciseTypeConfig? = null,
- ) : this(
- exerciseType,
- dataTypes,
- isAutoPauseAndResumeEnabled,
- isGpsEnabled,
- exerciseGoals,
- exerciseParams,
- swimmingPoolLengthMeters,
- exerciseTypeConfig,
- emptySet()
- )
-
- constructor(
- exerciseType: ExerciseType,
- dataTypes: Set<DataType<*, *>>,
- isAutoPauseAndResumeEnabled: Boolean,
- isGpsEnabled: Boolean,
- exerciseGoals: List<ExerciseGoal<*>> = listOf(),
- exerciseParams: Bundle = Bundle(),
- @FloatRange(from = 0.0) swimmingPoolLengthMeters: Float = SWIMMING_POOL_LENGTH_UNSPECIFIED,
- ) : this(
- exerciseType,
- dataTypes,
- isAutoPauseAndResumeEnabled,
- isGpsEnabled,
- exerciseGoals,
- exerciseParams,
- swimmingPoolLengthMeters,
- null
- )
internal constructor(
proto: DataProto.ExerciseConfig
@@ -114,6 +79,7 @@
ExerciseTypeConfig.fromProto(proto.exerciseTypeConfig)
} else null,
proto.batchingModeOverridesList.map { BatchingMode(it) }.toSet(),
+ proto.exerciseEventTypesList.map { ExerciseEventType.fromProto(it) }.toSet(),
)
init {
@@ -149,6 +115,7 @@
private var swimmingPoolLength: Float = SWIMMING_POOL_LENGTH_UNSPECIFIED
private var exerciseTypeConfig: ExerciseTypeConfig? = null
private var batchingModeOverrides: Set<BatchingMode> = emptySet()
+ private var exerciseEventTypes: Set<ExerciseEventType<*>> = emptySet()
/**
* Sets the requested [DataType]s that should be tracked during this exercise. If not
@@ -248,6 +215,16 @@
return this
}
+ /**
+ * Sets the [ExerciseEventType]s that should be tracked for this exercise.
+ *
+ * @param exerciseEventTypes the set of [ExerciseEventType]s to begin the exercise with
+ */
+ fun setExerciseEventTypes(exerciseEventTypes: Set<ExerciseEventType<*>>): Builder {
+ this.exerciseEventTypes = exerciseEventTypes
+ return this
+ }
+
/** Returns the built [ExerciseConfig]. */
fun build(): ExerciseConfig {
return ExerciseConfig(
@@ -260,6 +237,7 @@
swimmingPoolLength,
exerciseTypeConfig,
batchingModeOverrides,
+ exerciseEventTypes,
)
}
}
@@ -285,6 +263,7 @@
.setExerciseParams(BundlesUtil.toProto(exerciseParams))
.setSwimmingPoolLength(swimmingPoolLengthMeters)
.addAllBatchingModeOverrides(batchingModeOverrides.map { it.toProto() })
+ .addAllExerciseEventTypes(exerciseEventTypes.map { it.toProto() })
if (exerciseTypeConfig != null) {
builder.exerciseTypeConfig = exerciseTypeConfig.toProto()
}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseEvent.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseEvent.kt
new file mode 100644
index 0000000..6e6e28a
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseEvent.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.health.services.client.data
+
+import androidx.annotation.RestrictTo
+import androidx.annotation.RestrictTo.Scope
+import androidx.health.services.client.proto.DataProto
+import androidx.health.services.client.proto.DataProto.ExerciseEvent.ExerciseEventData.ExerciseEventDataCase
+import java.time.Duration
+
+/** Contains the latest exercise event for the current exercise. */
+public abstract class ExerciseEvent @RestrictTo(Scope.LIBRARY_GROUP) constructor() {
+ internal open fun toProto():
+ DataProto.ExerciseEvent = DataProto.ExerciseEvent.getDefaultInstance()
+
+ public companion object {
+ @JvmStatic
+ internal fun fromProto(proto: DataProto.ExerciseEvent): ExerciseEvent =
+ when (proto.exerciseEventData.exerciseEventDataCase) {
+ ExerciseEventDataCase.GOLF_SHOT_DATA ->
+ GolfShotEvent(
+ Duration.ofMillis(proto.exerciseEventData.golfShotData.durationFromBootMs),
+ GolfShotEvent.GolfShotSwingType.fromProto(
+ proto.exerciseEventData.golfShotData.golfShotSwingType)
+ )
+ else -> throw IllegalStateException("Exercise event not set on $proto")
+ }
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseEventCapabilities.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseEventCapabilities.kt
new file mode 100644
index 0000000..87a525c
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseEventCapabilities.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.health.services.client.data
+
+import androidx.annotation.RestrictTo
+import androidx.annotation.RestrictTo.Scope
+import androidx.health.services.client.proto.DataProto
+import androidx.health.services.client.proto.DataProto.ExerciseEventCapabilities.ExerciseEventCapabilitiesCase
+
+/** Contains the capabilities specific to the associated [ExerciseEvent]. */
+public abstract class ExerciseEventCapabilities @RestrictTo(Scope.LIBRARY_GROUP) constructor() {
+ /** Returns true if this [ExerciseEvent] is supported by the device and false otherwise. */
+ public abstract val isSupported: Boolean
+
+ internal open fun toProto(): DataProto.ExerciseEventCapabilities =
+ DataProto.ExerciseEventCapabilities.getDefaultInstance()
+
+ public companion object {
+ @JvmStatic
+ internal fun fromProto(proto: DataProto.ExerciseEventCapabilities): ExerciseEventCapabilities =
+ when (proto.exerciseEventCapabilitiesCase) {
+ ExerciseEventCapabilitiesCase.GOLF_SHOT_CAPABILITIES ->
+ GolfShotEventCapabilities(proto.golfShotCapabilities)
+ else -> throw IllegalStateException("Exercise event capabilities not set on $proto")
+ }
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseEventType.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseEventType.kt
new file mode 100644
index 0000000..35996a2
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseEventType.kt
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.health.services.client.data
+
+import androidx.annotation.RestrictTo
+import androidx.annotation.RestrictTo.Scope
+import androidx.health.services.client.proto.DataProto
+import androidx.health.services.client.proto.DataProto.ExerciseEventType.EXERCISE_EVENT_TYPE_GOLF_SHOT
+import androidx.health.services.client.proto.DataProto.ExerciseEventType.EXERCISE_EVENT_TYPE_UNKNOWN
+
+/**
+ * Type of exercise event which specifies the representations of [ExerciseEventCapabilities] for the
+ * event.
+ *
+ * Note: the exercise event type defines only the representation and data format of event types. It
+ * does not act as a form of delivery for the event data.
+ */
+public class ExerciseEventType<C : ExerciseEventCapabilities> @RestrictTo(RestrictTo.Scope.LIBRARY)
+public constructor(
+ private val id: Int
+) {
+ internal fun toProto(): DataProto.ExerciseEventType =
+ when (this) {
+ GOLF_SHOT_EVENT -> EXERCISE_EVENT_TYPE_GOLF_SHOT
+ else -> EXERCISE_EVENT_TYPE_UNKNOWN
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other !is ExerciseEventType<*>) return false
+
+ if (id != other.id) return false
+
+ return true
+ }
+
+ override fun hashCode(): Int = id
+
+ override fun toString(): String {
+ val name = when (id) {
+ 2 -> "GolfShotEvent"
+ else -> "UNKNOWN"
+ }
+ return "ExerciseEventType{ $name }"
+ }
+
+ public companion object {
+ /**
+ * An exercise event that can be used to notify the user a golf shot is detected by the device.
+ */
+ @JvmField
+ public val GOLF_SHOT_EVENT: ExerciseEventType<GolfShotEventCapabilities> =
+ ExerciseEventType<GolfShotEventCapabilities>(2)
+
+ /** An unknown event type. This should not be received. */
+ @JvmField
+ public val UNKNOWN: ExerciseEventType<ExerciseEventCapabilities> =
+ ExerciseEventType<ExerciseEventCapabilities>(0)
+
+ internal fun fromProto(proto: DataProto.ExerciseEventType): ExerciseEventType<*> =
+ when (proto) {
+ EXERCISE_EVENT_TYPE_GOLF_SHOT -> GOLF_SHOT_EVENT
+ EXERCISE_EVENT_TYPE_UNKNOWN -> UNKNOWN
+ }
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseTypeCapabilities.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseTypeCapabilities.kt
index 9c3dbc9..8f48a02 100644
--- a/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseTypeCapabilities.kt
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseTypeCapabilities.kt
@@ -22,7 +22,9 @@
/** Provides exercise specific capabilities data. */
@Suppress("ParcelCreator")
-public class ExerciseTypeCapabilities(
+public class ExerciseTypeCapabilities
+@JvmOverloads
+constructor(
/** Supported [DataType]s for a given exercise. */
public val supportedDataTypes: Set<DataType<*, *>>,
/** Map from supported goals [DataType]s to a set of compatible [ComparisonType]s. */
@@ -31,6 +33,11 @@
public val supportedMilestones: Map<AggregateDataType<*, *>, Set<ComparisonType>>,
/** Returns `true` if the given exercise supports auto pause and resume. */
public val supportsAutoPauseAndResume: Boolean,
+ /** Supported [ExerciseEvent]s for a given exercise. */
+ public val supportedExerciseEvents: Set<ExerciseEventType<*>> = emptySet(),
+ /** Map from [ExerciseEventType]s to their [ExerciseEventCapabilities]. */
+ internal val exerciseEventCapabilities: Map<ExerciseEventType<*>, ExerciseEventCapabilities> =
+ emptyMap(),
) {
internal constructor(
@@ -62,6 +69,16 @@
}
.toMap(),
supportsAutoPauseAndResume = proto.isAutoPauseAndResumeSupported,
+ supportedExerciseEvents =
+ proto.supportedExerciseEventsList
+ .map { ExerciseEventType.fromProto(it.exerciseEventType) }
+ .toSet(),
+ proto.supportedExerciseEventsList
+ .map { entry ->
+ ExerciseEventType.fromProto(entry.exerciseEventType) to
+ ExerciseEventCapabilities.fromProto(entry)
+ }
+ .toMap(),
)
internal val proto: DataProto.ExerciseTypeCapabilities =
@@ -88,8 +105,17 @@
.sortedBy { it.dataType.name } // Sorting to ensure equals() works
)
.setIsAutoPauseAndResumeSupported(supportsAutoPauseAndResume)
+ .addAllSupportedExerciseEvents(exerciseEventCapabilities.map { it.value.toProto() })
.build()
+ /** Returns the [ExerciseEventCapabilities] for a requested [ExerciseEventType]. */
+ public fun <C : ExerciseEventCapabilities> getExerciseEventCapabilityDetails(
+ exerciseEventType: ExerciseEventType<C>
+ ): C? {
+ @Suppress("UNCHECKED_CAST") // Map's keys' and values' types will match
+ return exerciseEventCapabilities[exerciseEventType] as C?
+ }
+
override fun toString(): String =
"ExerciseTypeCapabilities(" +
"supportedDataTypes=$supportedDataTypes, " +
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/GolfShotEvent.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/GolfShotEvent.kt
new file mode 100644
index 0000000..e2e05b6
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/GolfShotEvent.kt
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.health.services.client.data
+
+import androidx.health.services.client.proto.DataProto
+import java.time.Duration
+import java.util.Objects
+
+/** An [ExerciseEvent] that contains information about Golf Shot events for the current exercise. */
+public class GolfShotEvent(
+ val durationSinceBoot: Duration,
+ val swingType: GolfShotSwingType
+) : ExerciseEvent() {
+
+ internal constructor(
+ proto: DataProto.GolfShotData
+ ) : this(
+ Duration.ofMillis(proto.durationFromBootMs),
+ GolfShotSwingType.fromProto(proto.golfShotSwingType),
+ )
+
+ /** Golf Shot Swing Types. */
+ public class GolfShotSwingType private constructor(private val id: Int) {
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other !is GolfShotSwingType) return false
+ if (id != other.id) return false
+
+ return true
+ }
+ override fun hashCode(): Int = id
+
+ override fun toString(): String {
+ val name = when (id) {
+ 1 -> "PUTT"
+ 2 -> "PARTIAL"
+ 3 -> "FULL"
+ else -> "UNKNOWN"
+ }
+ return "GolfShotEvent{ $name }"
+ }
+
+ internal fun toProto(): DataProto.GolfShotSwingType =
+ DataProto.GolfShotSwingType.forNumber(id)
+ ?: DataProto.GolfShotSwingType.GOLF_SHOT_SWING_TYPE_UNKNOWN
+
+ companion object {
+ /** The swing type of the received golf shot is unknown. */
+ @JvmField val UNKNOWN: GolfShotSwingType = GolfShotSwingType(0)
+
+ /** The swing type of the received golf shot is putt. */
+ @JvmField val PUTT: GolfShotSwingType = GolfShotSwingType(1)
+
+ /** The swing type of the received golf shot is partial. */
+ @JvmField val PARTIAL: GolfShotSwingType = GolfShotSwingType(2)
+
+ /** The swing type of the received golf shot is full. */
+ @JvmField val FULL: GolfShotSwingType = GolfShotSwingType(3)
+
+ internal fun fromProto(proto: DataProto.GolfShotSwingType): GolfShotSwingType =
+ when (proto) {
+ DataProto.GolfShotSwingType.GOLF_SHOT_SWING_TYPE_PUTT -> PUTT
+ DataProto.GolfShotSwingType.GOLF_SHOT_SWING_TYPE_PARTIAL -> PARTIAL
+ DataProto.GolfShotSwingType.GOLF_SHOT_SWING_TYPE_FULL -> FULL
+ else -> UNKNOWN
+ }
+ }
+ }
+
+ internal override fun toProto(): DataProto.ExerciseEvent =
+ DataProto.ExerciseEvent.newBuilder()
+ .setExerciseEventData(
+ DataProto.ExerciseEvent.ExerciseEventData.newBuilder()
+ .setGolfShotData(
+ DataProto.GolfShotData.newBuilder()
+ .setDurationFromBootMs(durationSinceBoot.toMillis())
+ .setGolfShotSwingType(swingType.toProto()))
+ )
+ .build()
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other !is GolfShotEvent) return false
+
+ if (durationSinceBoot != other.durationSinceBoot) return false
+ if (swingType != other.swingType) return false
+
+ return true
+ }
+
+ override fun hashCode(): Int {
+ return Objects.hash(durationSinceBoot, swingType)
+ }
+
+ override fun toString(): String {
+ return "${this::class.simpleName}" +
+ "(durationSinceBoot=$durationSinceBoot, swingType=$swingType)"
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/GolfShotEventCapabilities.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/GolfShotEventCapabilities.kt
new file mode 100644
index 0000000..51d5bca
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/GolfShotEventCapabilities.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.health.services.client.data
+
+import androidx.annotation.RestrictTo
+import androidx.health.services.client.proto.DataProto
+import androidx.health.services.client.proto.DataProto.ExerciseEventCapabilities.GolfShotCapabilities
+import androidx.health.services.client.proto.DataProto.ExerciseEventType.EXERCISE_EVENT_TYPE_GOLF_SHOT
+import java.util.Objects
+
+/** Contains the Golf Shot capabilities specific to the associated [GolfShotEvent]. */
+public class GolfShotEventCapabilities(
+ override val isSupported: Boolean,
+ public val isSwingTypeClassificationSupported: Boolean,
+) : ExerciseEventCapabilities() {
+ internal constructor(
+ proto: DataProto.ExerciseEventCapabilities.GolfShotCapabilities
+ ) : this(proto.isSupported, proto.isSwingTypeClassificationSupported)
+
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ override fun toProto(): DataProto.ExerciseEventCapabilities =
+ DataProto.ExerciseEventCapabilities.newBuilder()
+ .setExerciseEventType(EXERCISE_EVENT_TYPE_GOLF_SHOT)
+ .setGolfShotCapabilities(
+ GolfShotCapabilities.newBuilder()
+ .setIsSupported(this.isSupported)
+ .setIsSwingTypeClassificationSupported(this.isSwingTypeClassificationSupported)
+ .build()
+ )
+ .build()
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other !is GolfShotEventCapabilities) return false
+ if (isSupported != other.isSupported) return false
+ if (isSwingTypeClassificationSupported != other.isSwingTypeClassificationSupported) return false
+
+ return true
+ }
+
+ override fun hashCode(): Int {
+ return Objects.hash(isSupported, isSwingTypeClassificationSupported)
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/ExerciseUpdateListenerStub.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ExerciseUpdateListenerStub.kt
index 74d6913..86832b4 100644
--- a/health/health-services-client/src/main/java/androidx/health/services/client/impl/ExerciseUpdateListenerStub.kt
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ExerciseUpdateListenerStub.kt
@@ -21,6 +21,7 @@
import androidx.health.services.client.ExerciseUpdateCallback
import androidx.health.services.client.data.Availability
import androidx.health.services.client.data.DataType
+import androidx.health.services.client.data.ExerciseEvent
import androidx.health.services.client.data.ExerciseLapSummary
import androidx.health.services.client.data.ExerciseUpdate
import androidx.health.services.client.impl.event.ExerciseUpdateListenerEvent
@@ -69,7 +70,13 @@
val availability = Availability.fromProto(proto.availabilityResponse.availability)
matchingDataTypes.forEach { listener.onAvailabilityChanged(it, availability) }
}
- null, EventCase.EVENT_NOT_SET -> Log.w(TAG, "Received unknown event ${proto.eventCase}")
+ EventCase.EXERCISE_EVENT_RESPONSE ->
+ listener
+ .onExerciseEventReceived(
+ ExerciseEvent.fromProto(
+ proto.exerciseEventResponse.exerciseEvent))
+ null,
+ EventCase.EVENT_NOT_SET -> Log.w(TAG, "Received unknown event ${proto.eventCase}")
}
}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/event/ExerciseUpdateListenerEvent.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/event/ExerciseUpdateListenerEvent.kt
index 7b4a169..919d2d5 100644
--- a/health/health-services-client/src/main/java/androidx/health/services/client/impl/event/ExerciseUpdateListenerEvent.kt
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/event/ExerciseUpdateListenerEvent.kt
@@ -3,6 +3,7 @@
import android.os.Parcelable
import androidx.health.services.client.data.ProtoParcelable
import androidx.health.services.client.impl.response.AvailabilityResponse
+import androidx.health.services.client.impl.response.ExerciseEventResponse
import androidx.health.services.client.impl.response.ExerciseLapSummaryResponse
import androidx.health.services.client.impl.response.ExerciseUpdateResponse
import androidx.health.services.client.proto.EventsProto.ExerciseUpdateListenerEvent as ListenerProto
@@ -40,5 +41,13 @@
ExerciseUpdateListenerEvent(
ListenerProto.newBuilder().setAvailabilityResponse(availability.proto).build()
)
+
+ @JvmStatic
+ public fun createExerciseEventUpdateEvent(
+ exerciseEvent: ExerciseEventResponse
+ ): ExerciseUpdateListenerEvent =
+ ExerciseUpdateListenerEvent(
+ ListenerProto.newBuilder().setExerciseEventResponse(exerciseEvent.proto).build()
+ )
}
}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/ExerciseEventResponse.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/ExerciseEventResponse.kt
new file mode 100644
index 0000000..23fd82b
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/ExerciseEventResponse.kt
@@ -0,0 +1,24 @@
+package androidx.health.services.client.impl.response
+
+import android.os.Parcelable
+import androidx.health.services.client.data.ExerciseEvent
+import androidx.health.services.client.data.ProtoParcelable
+import androidx.health.services.client.proto.ResponsesProto
+
+/** Response containing [ExerciseEvent] when it's sent. */
+internal class ExerciseEventResponse(public val exerciseEvent: ExerciseEvent) :
+ ProtoParcelable<ResponsesProto.ExerciseEventResponse>() {
+ override val proto: ResponsesProto.ExerciseEventResponse by lazy {
+ ResponsesProto.ExerciseEventResponse.newBuilder()
+ .setExerciseEvent(exerciseEvent.toProto())
+ .build()
+ }
+
+ public companion object {
+ @JvmField
+ public val CREATOR: Parcelable.Creator<ExerciseEventResponse> = newCreator { bytes ->
+ val proto = ResponsesProto.ExerciseEventResponse.parseFrom(bytes)
+ ExerciseEventResponse(ExerciseEvent.fromProto(proto.exerciseEvent))
+ }
+ }
+}
diff --git a/health/health-services-client/src/main/proto/data.proto b/health/health-services-client/src/main/proto/data.proto
index f754276..2a2e5ee 100644
--- a/health/health-services-client/src/main/proto/data.proto
+++ b/health/health-services-client/src/main/proto/data.proto
@@ -204,8 +204,9 @@
optional float swimming_pool_length = 8;
optional ExerciseTypeConfig exercise_type_config = 10;
repeated BatchingMode batching_mode_overrides = 11 [packed = true];
+ repeated ExerciseEventType exercise_event_types = 12 [packed = true];
reserved 9;
- reserved 12 to max; // Next ID
+ reserved 13 to max; // Next ID
}
message ExerciseInfo {
@@ -372,6 +373,56 @@
reserved 93 to max; // Next ID
}
+enum GolfShotSwingType {
+ GOLF_SHOT_SWING_TYPE_UNKNOWN = 0;
+ GOLF_SHOT_SWING_TYPE_PUTT = 1;
+ GOLF_SHOT_SWING_TYPE_PARTIAL = 2;
+ GOLF_SHOT_SWING_TYPE_FULL = 3;
+ reserved 4 to max; // Next ID
+}
+
+message GolfShotData {
+ optional int64 duration_from_boot_ms = 1;
+ optional GolfShotSwingType golf_shot_swing_type = 2;
+ reserved 3 to max; // Next ID
+}
+
+enum ExerciseEventType {
+ EXERCISE_EVENT_TYPE_UNKNOWN = 0;
+ EXERCISE_EVENT_TYPE_GOLF_SHOT = 2;
+ reserved 1;
+ reserved 3 to max; // Next ID
+}
+
+message ExerciseEvent {
+ message ExerciseEventData {
+ oneof exerciseEventData {
+ GolfShotData golf_shot_data = 2;
+ }
+ reserved 1;
+ reserved 3 to max; // Next ID
+ }
+
+ optional ExerciseEventData exercise_event_data = 1;
+ reserved 2 to max; // Next ID
+}
+
+message ExerciseEventCapabilities {
+ message GolfShotCapabilities {
+ optional bool is_supported = 1;
+ optional bool is_swing_type_classification_supported = 2;
+
+ reserved 3 to max; // Next ID
+ }
+
+ optional ExerciseEventType exercise_event_type = 1;
+ oneof exerciseEventCapabilities {
+ GolfShotCapabilities golf_shot_capabilities = 3;
+ }
+ reserved 2;
+ reserved 4 to max; // Next ID
+}
+
message ExerciseTypeCapabilities {
message SupportedGoalEntry {
optional DataType data_type = 1;
@@ -389,7 +440,9 @@
repeated SupportedMilestoneEntry supported_milestones = 3;
optional bool is_auto_pause_and_resume_supported = 4;
optional bool is_laps_supported = 5;
- reserved 6 to max; // Next ID
+ repeated ExerciseEventCapabilities supported_exercise_events = 7;
+ reserved 6;
+ reserved 8 to max; // Next ID
}
message ExerciseUpdate {
diff --git a/health/health-services-client/src/main/proto/events.proto b/health/health-services-client/src/main/proto/events.proto
index d8385ca..f621a8c 100644
--- a/health/health-services-client/src/main/proto/events.proto
+++ b/health/health-services-client/src/main/proto/events.proto
@@ -28,8 +28,9 @@
ExerciseUpdateResponse exercise_update_response = 1;
ExerciseLapSummaryResponse lap_summary_response = 2;
AvailabilityResponse availability_response = 3;
+ ExerciseEventResponse exercise_event_response = 4;
}
- reserved 4 to max; // Next ID
+ reserved 5 to max; // Next ID
}
message MeasureCallbackEvent {
diff --git a/health/health-services-client/src/main/proto/responses.proto b/health/health-services-client/src/main/proto/responses.proto
index eb72f92..6f38afa 100644
--- a/health/health-services-client/src/main/proto/responses.proto
+++ b/health/health-services-client/src/main/proto/responses.proto
@@ -39,6 +39,11 @@
reserved 2 to max; // Next ID
}
+message ExerciseEventResponse {
+ optional ExerciseEvent exercise_event = 1;
+ reserved 2 to max; // Next ID
+}
+
message ExerciseInfoResponse {
optional ExerciseInfo exercise_info = 1;
reserved 2 to max; // Next ID
diff --git a/health/health-services-client/src/test/java/androidx/health/services/client/data/ExerciseCapabilitiesTest.kt b/health/health-services-client/src/test/java/androidx/health/services/client/data/ExerciseCapabilitiesTest.kt
index 4a43798..fc24a36 100644
--- a/health/health-services-client/src/test/java/androidx/health/services/client/data/ExerciseCapabilitiesTest.kt
+++ b/health/health-services-client/src/test/java/androidx/health/services/client/data/ExerciseCapabilitiesTest.kt
@@ -31,8 +31,8 @@
EXERCISE_CAPABILITIES.getExerciseTypeCapabilities(
ExerciseType.WALKING
).supportedDataTypes
- ).isEqualTo(
- ImmutableSet.of(DataType.STEPS)
+ ).containsExactly(
+ DataType.STEPS
)
}
@@ -58,6 +58,19 @@
}
@Test
+ fun supportedExerciseEventForGolfExercise() {
+ assertThat(
+ EXERCISE_CAPABILITIES.getExerciseTypeCapabilities(
+ ExerciseType.GOLF).supportedExerciseEvents
+ ).isEqualTo(
+ EXERCISE_CAPABILITIES.typeToCapabilities.get(ExerciseType.GOLF)?.supportedExerciseEvents
+ )
+ assertThat(EXERCISE_CAPABILITIES.typeToCapabilities[ExerciseType.GOLF]
+ ?.getExerciseEventCapabilityDetails(ExerciseEventType.GOLF_SHOT_EVENT)
+ ?.isSwingTypeClassificationSupported).isTrue()
+ }
+
+ @Test
fun exercisesSupportingAutoResumeAndPause_returnCorrectSet() {
val supportsAutoPauseAndResume = ExerciseTypeCapabilities(
supportedDataTypes = ImmutableSet.of(),
@@ -93,6 +106,9 @@
assertThat(capabilities.autoPauseAndResumeEnabledExercises).containsExactlyElementsIn(
EXERCISE_CAPABILITIES.autoPauseAndResumeEnabledExercises
)
+ assertThat(capabilities.supportedBatchingModeOverrides).containsExactlyElementsIn(
+ EXERCISE_CAPABILITIES.supportedBatchingModeOverrides
+ )
}
@Test
@@ -106,6 +122,9 @@
assertThat(emptyCapabilities.autoPauseAndResumeEnabledExercises).containsExactlyElementsIn(
roundTripEmptyCapabilities.autoPauseAndResumeEnabledExercises
)
+ assertThat(emptyCapabilities.supportedBatchingModeOverrides).containsExactlyElementsIn(
+ roundTripEmptyCapabilities.supportedBatchingModeOverrides
+ )
}
companion object {
@@ -145,14 +164,29 @@
supportsAutoPauseAndResume = true,
)
+ private val GOLF_SHOT_EVENT_CAPABILITIES: GolfShotEventCapabilities =
+ GolfShotEventCapabilities(isSupported = true, isSwingTypeClassificationSupported = true)
+
+ private val GOLF_CAPABILITIES = ExerciseTypeCapabilities(
+ supportedDataTypes = emptySet(),
+ supportedGoals = emptyMap(),
+ supportedMilestones = emptyMap(),
+ supportsAutoPauseAndResume = true,
+ supportedExerciseEvents = setOf(ExerciseEventType.GOLF_SHOT_EVENT),
+ exerciseEventCapabilities =
+ ImmutableMap.of(ExerciseEventType.GOLF_SHOT_EVENT, GOLF_SHOT_EVENT_CAPABILITIES),
+ )
+
private val EXERCISE_TYPE_TO_EXERCISE_CAPABILITIES_MAPPING =
ImmutableMap.of(
ExerciseType.WALKING, WALKING_CAPABILITIES,
ExerciseType.RUNNING, RUNNING_CAPABILITIES,
- ExerciseType.SWIMMING_POOL, SWIMMING_CAPABILITIES
+ ExerciseType.SWIMMING_POOL, SWIMMING_CAPABILITIES,
+ ExerciseType.GOLF, GOLF_CAPABILITIES,
)
private val EXERCISE_CAPABILITIES: ExerciseCapabilities =
- ExerciseCapabilities(EXERCISE_TYPE_TO_EXERCISE_CAPABILITIES_MAPPING)
+ ExerciseCapabilities(EXERCISE_TYPE_TO_EXERCISE_CAPABILITIES_MAPPING,
+ ImmutableSet.of(BatchingMode.HEART_RATE_5_SECONDS))
}
}
diff --git a/health/health-services-client/src/test/java/androidx/health/services/client/data/ExerciseConfigTest.kt b/health/health-services-client/src/test/java/androidx/health/services/client/data/ExerciseConfigTest.kt
index da70783e..4a0aef3 100644
--- a/health/health-services-client/src/test/java/androidx/health/services/client/data/ExerciseConfigTest.kt
+++ b/health/health-services-client/src/test/java/androidx/health/services/client/data/ExerciseConfigTest.kt
@@ -48,6 +48,7 @@
.GolfShotTrackingPlaceInfo.GOLF_SHOT_TRACKING_PLACE_INFO_FAIRWAY
),
batchingModeOverrides = setOf(BatchingMode.HEART_RATE_5_SECONDS),
+ exerciseEventTypes = setOf(ExerciseEventType.GOLF_SHOT_EVENT),
).toProto()
val config = ExerciseConfig(proto)
@@ -69,6 +70,7 @@
).isEqualTo(GolfExerciseTypeConfig
.GolfShotTrackingPlaceInfo.GOLF_SHOT_TRACKING_PLACE_INFO_FAIRWAY)
assertThat(config.batchingModeOverrides).containsExactly(BatchingMode.HEART_RATE_5_SECONDS)
+ assertThat(config.exerciseEventTypes).containsExactly(ExerciseEventType.GOLF_SHOT_EVENT)
}
@Test
diff --git a/health/health-services-client/src/test/java/androidx/health/services/client/data/ExerciseEventCapabilitiesTest.kt b/health/health-services-client/src/test/java/androidx/health/services/client/data/ExerciseEventCapabilitiesTest.kt
new file mode 100644
index 0000000..7d4be53
--- /dev/null
+++ b/health/health-services-client/src/test/java/androidx/health/services/client/data/ExerciseEventCapabilitiesTest.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.health.services.client.data
+
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.RobolectricTestRunner
+
+@RunWith(RobolectricTestRunner::class)
+class ExerciseEventCapabilitiesTest {
+ @Test
+ fun golfShotEventCapabilities_roundTrip_returnsOriginal() {
+ val originalGolfShotCapabilities =
+ GolfShotEventCapabilities(
+ isSupported = true,
+ isSwingTypeClassificationSupported = true,
+ )
+
+ val proto = originalGolfShotCapabilities.toProto()
+ val capabilities = ExerciseEventCapabilities.fromProto(proto) as GolfShotEventCapabilities
+
+ assertThat(capabilities.isSupported).isTrue()
+ assertThat(capabilities.isSwingTypeClassificationSupported).isTrue()
+ assertThat(capabilities).isEqualTo(originalGolfShotCapabilities)
+ }
+}
diff --git a/health/health-services-client/src/test/java/androidx/health/services/client/data/ExerciseEventTest.kt b/health/health-services-client/src/test/java/androidx/health/services/client/data/ExerciseEventTest.kt
new file mode 100644
index 0000000..1d8610b
--- /dev/null
+++ b/health/health-services-client/src/test/java/androidx/health/services/client/data/ExerciseEventTest.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.health.services.client.data
+
+import com.google.common.truth.Truth.assertThat
+import java.time.Duration
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.RobolectricTestRunner
+
+@RunWith(RobolectricTestRunner::class)
+class ExerciseEventTest {
+ @Test
+ fun exerciseEventGolfShotRoundTrip() {
+ val originalGolfShotEvent =
+ GolfShotEvent(Duration.ofSeconds(1), GolfShotEvent.GolfShotSwingType.FULL)
+
+ val proto = originalGolfShotEvent.toProto()
+ val event = ExerciseEvent.fromProto(proto) as GolfShotEvent
+
+ assertThat(event.durationSinceBoot).isEqualTo(Duration.ofSeconds(1))
+ assertThat(event.swingType).isEqualTo(GolfShotEvent.GolfShotSwingType.FULL)
+ assertThat(event).isEqualTo(originalGolfShotEvent)
+ }
+}
diff --git a/health/health-services-client/src/test/java/androidx/health/services/client/data/ExerciseEventTypeTest.kt b/health/health-services-client/src/test/java/androidx/health/services/client/data/ExerciseEventTypeTest.kt
new file mode 100644
index 0000000..023cb9f
--- /dev/null
+++ b/health/health-services-client/src/test/java/androidx/health/services/client/data/ExerciseEventTypeTest.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.health.services.client.data
+
+import androidx.health.services.client.data.ExerciseEventType.Companion.GOLF_SHOT_EVENT
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.RobolectricTestRunner
+
+@RunWith(RobolectricTestRunner::class)
+class ExerciseEventTypeTest {
+ @Test
+ fun exerciseEventTypeGolfShotEventRoundTrip() {
+ val proto = GOLF_SHOT_EVENT.toProto()
+ val exerciseEventType = ExerciseEventType.fromProto(proto)
+
+ assertThat(exerciseEventType.toString()).isEqualTo("ExerciseEventType{ GolfShotEvent }")
+ assertThat(exerciseEventType).isEqualTo(GOLF_SHOT_EVENT)
+ }
+}
diff --git a/health/health-services-client/src/test/java/androidx/health/services/client/impl/ServiceBackedExerciseClientTest.kt b/health/health-services-client/src/test/java/androidx/health/services/client/impl/ServiceBackedExerciseClientTest.kt
index c918351..d0afedb 100644
--- a/health/health-services-client/src/test/java/androidx/health/services/client/impl/ServiceBackedExerciseClientTest.kt
+++ b/health/health-services-client/src/test/java/androidx/health/services/client/impl/ServiceBackedExerciseClientTest.kt
@@ -24,14 +24,19 @@
import androidx.health.services.client.data.Availability
import androidx.health.services.client.data.BatchingMode
import androidx.health.services.client.data.DataType
+import androidx.health.services.client.data.DataType.Companion.GOLF_SHOT_COUNT
import androidx.health.services.client.data.DataType.Companion.HEART_RATE_BPM
import androidx.health.services.client.data.DataType.Companion.HEART_RATE_BPM_STATS
import androidx.health.services.client.data.DataTypeAvailability.Companion.ACQUIRING
import androidx.health.services.client.data.ExerciseConfig
+import androidx.health.services.client.data.ExerciseEvent
+import androidx.health.services.client.data.ExerciseEventType
import androidx.health.services.client.data.ExerciseLapSummary
import androidx.health.services.client.data.ExerciseType
import androidx.health.services.client.data.ExerciseUpdate
import androidx.health.services.client.data.GolfExerciseTypeConfig
+import androidx.health.services.client.data.GolfShotEvent
+import androidx.health.services.client.data.GolfShotEvent.GolfShotSwingType
import androidx.health.services.client.data.WarmUpConfig
import androidx.health.services.client.impl.event.ExerciseUpdateListenerEvent
import androidx.health.services.client.impl.internal.IExerciseInfoCallback
@@ -47,8 +52,10 @@
import androidx.health.services.client.impl.request.UpdateExerciseTypeConfigRequest
import androidx.health.services.client.impl.response.AvailabilityResponse
import androidx.health.services.client.impl.response.ExerciseCapabilitiesResponse
+import androidx.health.services.client.impl.response.ExerciseEventResponse
import androidx.test.core.app.ApplicationProvider
import com.google.common.truth.Truth.assertThat
+import java.time.Duration
import kotlin.test.assertFailsWith
import org.junit.After
import org.junit.Before
@@ -205,6 +212,37 @@
}
@Test
+ fun withExerciseEventConfig_startExercise_receiveCorrectExerciseEventCallback() {
+ val exerciseConfig = ExerciseConfig(
+ ExerciseType.GOLF,
+ setOf(GOLF_SHOT_COUNT),
+ isAutoPauseAndResumeEnabled = false,
+ isGpsEnabled = false,
+ exerciseTypeConfig = GolfExerciseTypeConfig(
+ GolfExerciseTypeConfig
+ .GolfShotTrackingPlaceInfo.GOLF_SHOT_TRACKING_PLACE_INFO_PUTTING_GREEN
+ ),
+ exerciseEventTypes = setOf(ExerciseEventType.GOLF_SHOT_EVENT),
+ )
+ val golfShotEvent = ExerciseUpdateListenerEvent.createExerciseEventUpdateEvent(
+ ExerciseEventResponse(
+ GolfShotEvent(
+ Duration.ofMinutes(1), GolfShotSwingType.PUTT
+ )
+ )
+ )
+
+ client.setUpdateCallback(callback)
+ client.startExerciseAsync(exerciseConfig)
+ shadowOf(getMainLooper()).idle()
+ fakeService.listener!!.onExerciseUpdateListenerEvent(golfShotEvent)
+ shadowOf(getMainLooper()).idle()
+
+ assertThat(callback.exerciseEvents)
+ .contains(GolfShotEvent(Duration.ofMinutes(1), GolfShotSwingType.PUTT))
+ }
+
+ @Test
fun dataTypeInAvailabilityCallbackShouldMatchRequested_justSampleType_prepare() {
val warmUpConfig = WarmUpConfig(
ExerciseType.WALKING,
@@ -260,6 +298,7 @@
val registrationFailureThrowables = mutableListOf<Throwable>()
var onRegisteredCalls = 0
var onRegistrationFailedCalls = 0
+ var exerciseEvents = mutableSetOf<ExerciseEvent>()
override fun onRegistered() {
onRegisteredCalls++
@@ -277,6 +316,14 @@
override fun onAvailabilityChanged(dataType: DataType<*, *>, availability: Availability) {
availabilities[dataType] = availability
}
+
+ override fun onExerciseEventReceived(event: ExerciseEvent) {
+ when (event) {
+ is GolfShotEvent -> {
+ exerciseEvents.add(event)
+ }
+ }
+ }
}
class FakeServiceStub : IExerciseApiService.Stub() {
diff --git a/hilt/hilt-compiler/build.gradle b/hilt/hilt-compiler/build.gradle
index c25c109..aec41ba 100644
--- a/hilt/hilt-compiler/build.gradle
+++ b/hilt/hilt-compiler/build.gradle
@@ -16,6 +16,7 @@
import androidx.build.LibraryType
import androidx.build.SdkHelperKt
+import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
id("AndroidXPlugin")
@@ -31,18 +32,29 @@
kapt(libs.autoService)
compileOnly(libs.gradleIncapHelper)
kapt(libs.gradleIncapHelperProcessor)
- implementation(libs.autoCommon)
+ implementation(project(":room:room-compiler-processing"))
implementation(libs.javapoet)
+ implementation(libs.kspApi)
testImplementation(project(":hilt:hilt-common"))
+ testImplementation(project(":annotation:annotation"))
testImplementation(libs.junit)
testImplementation(libs.truth)
- testImplementation(libs.googleCompileTesting)
+ testImplementation(project(":room:room-compiler-processing-testing"))
testImplementation(libs.hiltCore)
testImplementationAarAsJar(project(":hilt:hilt-work"))
testImplementation(SdkHelperKt.getSdkDependency(project))
}
+tasks.withType(KotlinCompile).configureEach {
+ kotlinOptions {
+ freeCompilerArgs += [
+ "-opt-in=androidx.room.compiler.processing.ExperimentalProcessingApi",
+ "-opt-in=com.squareup.kotlinpoet.javapoet.KotlinPoetJavaPoetPreview"
+ ]
+ }
+}
+
tasks.withType(Test).configureEach {
// https://github.com/google/compile-testing/issues/222
it.jvmArgs "--add-opens=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED"
diff --git a/hilt/hilt-compiler/src/main/kotlin/androidx/hilt/AndroidXHiltKspProcessor.kt b/hilt/hilt-compiler/src/main/kotlin/androidx/hilt/AndroidXHiltKspProcessor.kt
new file mode 100644
index 0000000..7c6fef4
--- /dev/null
+++ b/hilt/hilt-compiler/src/main/kotlin/androidx/hilt/AndroidXHiltKspProcessor.kt
@@ -0,0 +1,35 @@
+/*
+ * 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.
+ */
+
+import androidx.hilt.work.WorkerStep
+import androidx.room.compiler.processing.ksp.KspBasicAnnotationProcessor
+import com.google.auto.service.AutoService
+import com.google.devtools.ksp.processing.SymbolProcessor
+import com.google.devtools.ksp.processing.SymbolProcessorEnvironment
+import com.google.devtools.ksp.processing.SymbolProcessorProvider
+
+class AndroidXHiltKspProcessor(
+ environment: SymbolProcessorEnvironment
+) : KspBasicAnnotationProcessor(environment) {
+ override fun processingSteps() = listOf(WorkerStep())
+
+ @AutoService(SymbolProcessorProvider::class)
+ class Provider : SymbolProcessorProvider {
+ override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor {
+ return AndroidXHiltKspProcessor(environment)
+ }
+ }
+}
diff --git a/hilt/hilt-compiler/src/main/kotlin/androidx/hilt/AndroidXHiltProcessor.kt b/hilt/hilt-compiler/src/main/kotlin/androidx/hilt/AndroidXHiltProcessor.kt
index 277140b..cf646af 100644
--- a/hilt/hilt-compiler/src/main/kotlin/androidx/hilt/AndroidXHiltProcessor.kt
+++ b/hilt/hilt-compiler/src/main/kotlin/androidx/hilt/AndroidXHiltProcessor.kt
@@ -17,13 +17,10 @@
package androidx.hilt
import androidx.hilt.work.WorkerStep
+import androidx.room.compiler.processing.javac.JavacBasicAnnotationProcessor
import com.google.auto.service.AutoService
-import javax.annotation.processing.AbstractProcessor
import javax.annotation.processing.Processor
-import javax.annotation.processing.RoundEnvironment
import javax.lang.model.SourceVersion
-import javax.lang.model.element.Element
-import javax.lang.model.element.TypeElement
import net.ltgt.gradle.incap.IncrementalAnnotationProcessor
import net.ltgt.gradle.incap.IncrementalAnnotationProcessorType.ISOLATING
@@ -32,32 +29,9 @@
*/
@AutoService(Processor::class)
@IncrementalAnnotationProcessor(ISOLATING)
-class AndroidXHiltProcessor : AbstractProcessor() {
+class AndroidXHiltProcessor : JavacBasicAnnotationProcessor() {
- override fun getSupportedAnnotationTypes() = setOf(
- ClassNames.HILT_WORKER.canonicalName()
- )
+ override fun processingSteps() = listOf(WorkerStep())
override fun getSupportedSourceVersion() = SourceVersion.latest()
-
- override fun process(
- annotations: MutableSet<out TypeElement>,
- roundEnv: RoundEnvironment
- ): Boolean {
- getSteps().forEach { step ->
- annotations.firstOrNull { it.qualifiedName.contentEquals(step.annotation()) }?.let {
- step.process(roundEnv.getElementsAnnotatedWith(it))
- }
- }
- return false
- }
-
- private fun getSteps() = listOf(
- WorkerStep(processingEnv)
- )
-
- interface Step {
- fun annotation(): String
- fun process(annotatedElements: Set<Element>)
- }
}
diff --git a/hilt/hilt-compiler/src/main/kotlin/androidx/hilt/assisted/AssistedFactoryGenerator.kt b/hilt/hilt-compiler/src/main/kotlin/androidx/hilt/assisted/AssistedFactoryGenerator.kt
deleted file mode 100644
index b452df0..0000000
--- a/hilt/hilt-compiler/src/main/kotlin/androidx/hilt/assisted/AssistedFactoryGenerator.kt
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- * 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.hilt.assisted
-
-import androidx.hilt.ClassNames
-import androidx.hilt.ext.L
-import androidx.hilt.ext.T
-import androidx.hilt.ext.W
-import androidx.hilt.ext.addGeneratedAnnotation
-import com.squareup.javapoet.ClassName
-import com.squareup.javapoet.CodeBlock
-import com.squareup.javapoet.FieldSpec
-import com.squareup.javapoet.JavaFile
-import com.squareup.javapoet.MethodSpec
-import com.squareup.javapoet.ParameterSpec
-import com.squareup.javapoet.ParameterizedTypeName
-import com.squareup.javapoet.TypeName
-import com.squareup.javapoet.TypeSpec
-import javax.annotation.processing.ProcessingEnvironment
-import javax.lang.model.element.Modifier
-import javax.lang.model.element.TypeElement
-import javax.lang.model.util.ElementFilter
-
-/**
- * Source generator for assisted factories.
- */
-internal class AssistedFactoryGenerator(
- private val processingEnv: ProcessingEnvironment,
- private val productClassName: ClassName,
- private val factoryClassName: ClassName,
- private val factorySuperTypeName: ParameterizedTypeName,
- private val originatingElement: TypeElement,
- private val dependencyRequests: List<DependencyRequest>
-) {
-
- fun generate() {
- val factoryTypeSpec = TypeSpec.classBuilder(factoryClassName)
- .addOriginatingElement(originatingElement)
- .addSuperinterface(factorySuperTypeName)
- .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
- .addGeneratedAnnotation(processingEnv.elementUtils, processingEnv.sourceVersion)
- .addFields(getFieldSpecs())
- .addMethod(getConstructorMethodSpec())
- .addMethod(getCreateMethodSpec())
- .build()
- JavaFile.builder(factoryClassName.packageName(), factoryTypeSpec)
- .build()
- .writeTo(processingEnv.filer)
- }
-
- private fun getFieldSpecs() = dependencyRequests
- .filterNot { it.isAssisted }
- .map { dependencyRequest ->
- val fieldTypeName = dependencyRequest.providerTypeName.withoutAnnotations()
- FieldSpec.builder(fieldTypeName, dependencyRequest.name)
- .addModifiers(Modifier.PRIVATE, Modifier.FINAL)
- .build()
- }
-
- private fun getConstructorMethodSpec() =
- MethodSpec.constructorBuilder()
- .addAnnotation(ClassNames.INJECT)
- .apply {
- dependencyRequests
- .filterNot { it.isAssisted }
- .forEach { dependencyRequest ->
- addParameter(dependencyRequest.providerTypeName, dependencyRequest.name)
- addStatement("this.$1N = $1N", dependencyRequest.name)
- }
- }
- .build()
-
- private fun getCreateMethodSpec(): MethodSpec {
- val factoryTypeElement =
- processingEnv.elementUtils.getTypeElement(factorySuperTypeName.rawType.canonicalName())
- val factoryMethod = ElementFilter.methodsIn(factoryTypeElement.enclosedElements).first()
- val parameterSpecs = factoryMethod.parameters.map { ParameterSpec.get(it) }
- val constructorArgs = dependencyRequests.map {
- val paramLiteral = when {
- it.isAssisted -> {
- factoryMethod.parameters.first { param ->
- TypeName.get(param.asType()) == it.type
- }.simpleName.toString()
- }
- it.isProvider -> it.name
- else -> "${it.name}.get()"
- }
- CodeBlock.of(L, paramLiteral)
- }
- return MethodSpec.methodBuilder(factoryMethod.simpleName.toString())
- .addAnnotation(Override::class.java)
- .addAnnotation(ClassNames.NON_NULL)
- .addModifiers(Modifier.PUBLIC)
- .returns(productClassName)
- .addParameters(parameterSpecs)
- .addStatement(
- "return new $T($L)",
- productClassName, CodeBlock.join(constructorArgs, ",$W")
- )
- .build()
- }
-}
diff --git a/hilt/hilt-compiler/src/main/kotlin/androidx/hilt/assisted/DependencyRequest.kt b/hilt/hilt-compiler/src/main/kotlin/androidx/hilt/assisted/DependencyRequest.kt
index 822c778..6130cc7c 100644
--- a/hilt/hilt-compiler/src/main/kotlin/androidx/hilt/assisted/DependencyRequest.kt
+++ b/hilt/hilt-compiler/src/main/kotlin/androidx/hilt/assisted/DependencyRequest.kt
@@ -17,11 +17,12 @@
package androidx.hilt.assisted
import androidx.hilt.ClassNames
-import androidx.hilt.ext.hasAnnotation
+import androidx.room.compiler.codegen.toJavaPoet
+import androidx.room.compiler.processing.XVariableElement
+import androidx.room.compiler.processing.toAnnotationSpec
import com.squareup.javapoet.AnnotationSpec
import com.squareup.javapoet.ParameterizedTypeName
import com.squareup.javapoet.TypeName
-import javax.lang.model.element.VariableElement
/**
* Data class that represents a binding request for an assisted injected type.
@@ -51,17 +52,16 @@
}
}
-internal fun VariableElement.toDependencyRequest(): DependencyRequest {
- val qualifier = annotationMirrors.find {
- it.annotationType.asElement().hasAnnotation("javax.inject.Qualifier")
- }?.let { AnnotationSpec.get(it) }
- val type = TypeName.get(asType())
+internal fun XVariableElement.toDependencyRequest(): DependencyRequest {
+ val qualifier = getAllAnnotations().find {
+ it.qualifiedName == "javax.inject.Qualifier"
+ }?.toAnnotationSpec(includeDefaultValues = false)
return DependencyRequest(
- name = simpleName.toString(),
- type = type,
+ name = this.name,
+ type = this.type.asTypeName().toJavaPoet(),
isAssisted = (
- hasAnnotation(ClassNames.ANDROIDX_ASSISTED.canonicalName()) ||
- hasAnnotation(ClassNames.ASSISTED.canonicalName())
+ this.hasAnnotation(ClassNames.ANDROIDX_ASSISTED) ||
+ this.hasAnnotation(ClassNames.ASSISTED)
) && qualifier == null,
qualifier = qualifier
)
diff --git a/hilt/hilt-compiler/src/main/kotlin/androidx/hilt/ext/annotationProcessing.kt b/hilt/hilt-compiler/src/main/kotlin/androidx/hilt/ext/annotationProcessing.kt
deleted file mode 100644
index 21827d2..0000000
--- a/hilt/hilt-compiler/src/main/kotlin/androidx/hilt/ext/annotationProcessing.kt
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * 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.hilt.ext
-
-import com.google.auto.common.MoreElements
-import javax.lang.model.element.Element
-import kotlin.reflect.KClass
-
-fun Element.hasAnnotation(clazz: KClass<out Annotation>) =
- MoreElements.isAnnotationPresent(this, clazz.java)
-
-fun Element.hasAnnotation(qName: String) = annotationMirrors.any {
- MoreElements.asType(it.annotationType.asElement()).qualifiedName.contentEquals(qName)
-}
diff --git a/hilt/hilt-compiler/src/main/kotlin/androidx/hilt/ext/javaPoet.kt b/hilt/hilt-compiler/src/main/kotlin/androidx/hilt/ext/javaPoet.kt
index d3fc1c2..9aae5b2 100644
--- a/hilt/hilt-compiler/src/main/kotlin/androidx/hilt/ext/javaPoet.kt
+++ b/hilt/hilt-compiler/src/main/kotlin/androidx/hilt/ext/javaPoet.kt
@@ -17,10 +17,10 @@
package androidx.hilt.ext
import androidx.hilt.AndroidXHiltProcessor
-import com.google.auto.common.GeneratedAnnotationSpecs
+import androidx.room.compiler.processing.XProcessingEnv
+import com.squareup.javapoet.AnnotationSpec
+import com.squareup.javapoet.ClassName
import com.squareup.javapoet.TypeSpec
-import javax.lang.model.SourceVersion
-import javax.lang.model.util.Elements
const val L = "\$L"
const val T = "\$T"
@@ -28,15 +28,13 @@
const val S = "\$S"
const val W = "\$W"
-internal fun TypeSpec.Builder.addGeneratedAnnotation(
- elements: Elements,
- sourceVersion: SourceVersion
-) = apply {
- GeneratedAnnotationSpecs.generatedAnnotationSpec(
- elements,
- sourceVersion,
- AndroidXHiltProcessor::class.java
- ).ifPresent { generatedAnnotation ->
- addAnnotation(generatedAnnotation)
+internal fun TypeSpec.Builder.addGeneratedAnnotation(env: XProcessingEnv) = apply {
+ env.findGeneratedAnnotation()?.let {
+ addAnnotation(
+ AnnotationSpec.builder(ClassName.bestGuess(it.asClassName().canonicalName))
+ .addMember("value", S, AndroidXHiltProcessor::class.java.canonicalName)
+ .build()
+
+ )
}
}
diff --git a/hilt/hilt-compiler/src/main/kotlin/androidx/hilt/work/WorkerElements.kt b/hilt/hilt-compiler/src/main/kotlin/androidx/hilt/work/WorkerElement.kt
similarity index 75%
rename from hilt/hilt-compiler/src/main/kotlin/androidx/hilt/work/WorkerElements.kt
rename to hilt/hilt-compiler/src/main/kotlin/androidx/hilt/work/WorkerElement.kt
index f62c1ee6..52d4ff4 100644
--- a/hilt/hilt-compiler/src/main/kotlin/androidx/hilt/work/WorkerElements.kt
+++ b/hilt/hilt-compiler/src/main/kotlin/androidx/hilt/work/WorkerElement.kt
@@ -18,23 +18,23 @@
import androidx.hilt.ClassNames
import androidx.hilt.assisted.toDependencyRequest
-import com.google.auto.common.MoreElements
+import androidx.room.compiler.codegen.toJavaPoet
+import androidx.room.compiler.processing.XConstructorElement
+import androidx.room.compiler.processing.XTypeElement
import com.squareup.javapoet.ClassName
import com.squareup.javapoet.ParameterizedTypeName
-import javax.lang.model.element.ExecutableElement
-import javax.lang.model.element.TypeElement
/**
* Data class that represents a Hilt injected Worker
*/
-internal data class WorkerElements(
- val typeElement: TypeElement,
- val constructorElement: ExecutableElement
+internal data class WorkerElement(
+ val typeElement: XTypeElement,
+ val constructorElement: XConstructorElement
) {
- val className = ClassName.get(typeElement)
+ val className = typeElement.asClassName().toJavaPoet()
val factoryClassName = ClassName.get(
- MoreElements.getPackage(typeElement).qualifiedName.toString(),
+ typeElement.packageName,
"${className.simpleNames().joinToString("_")}_AssistedFactory"
)
@@ -44,7 +44,7 @@
)
val moduleClassName = ClassName.get(
- MoreElements.getPackage(typeElement).qualifiedName.toString(),
+ typeElement.packageName,
"${className.simpleNames().joinToString("_")}_HiltModule"
)
diff --git a/hilt/hilt-compiler/src/main/kotlin/androidx/hilt/work/WorkerGenerator.kt b/hilt/hilt-compiler/src/main/kotlin/androidx/hilt/work/WorkerGenerator.kt
index 4052322..155f2e4 100644
--- a/hilt/hilt-compiler/src/main/kotlin/androidx/hilt/work/WorkerGenerator.kt
+++ b/hilt/hilt-compiler/src/main/kotlin/androidx/hilt/work/WorkerGenerator.kt
@@ -20,13 +20,15 @@
import androidx.hilt.ext.S
import androidx.hilt.ext.T
import androidx.hilt.ext.addGeneratedAnnotation
+import androidx.room.compiler.processing.XProcessingEnv
+import androidx.room.compiler.processing.addOriginatingElement
+import androidx.room.compiler.processing.writeTo
import com.squareup.javapoet.AnnotationSpec
import com.squareup.javapoet.JavaFile
import com.squareup.javapoet.MethodSpec
import com.squareup.javapoet.ParameterizedTypeName
import com.squareup.javapoet.TypeSpec
import com.squareup.javapoet.WildcardTypeName
-import javax.annotation.processing.ProcessingEnvironment
import javax.lang.model.element.Modifier
/**
@@ -52,13 +54,13 @@
* ```
*/
internal class WorkerGenerator(
- private val processingEnv: ProcessingEnvironment,
- private val injectedWorker: WorkerElements
+ private val processingEnv: XProcessingEnv,
+ private val injectedWorker: WorkerElement
) {
fun generate() {
val assistedFactoryTypeSpec = TypeSpec.interfaceBuilder(injectedWorker.factoryClassName)
.addOriginatingElement(injectedWorker.typeElement)
- .addGeneratedAnnotation(processingEnv.elementUtils, processingEnv.sourceVersion)
+ .addGeneratedAnnotation(processingEnv)
.addAnnotation(ClassNames.ASSISTED_FACTORY)
.addModifiers(Modifier.PUBLIC)
.addSuperinterface(injectedWorker.factorySuperTypeName)
@@ -69,7 +71,7 @@
val hiltModuleTypeSpec = TypeSpec.interfaceBuilder(injectedWorker.moduleClassName)
.addOriginatingElement(injectedWorker.typeElement)
- .addGeneratedAnnotation(processingEnv.elementUtils, processingEnv.sourceVersion)
+ .addGeneratedAnnotation(processingEnv)
.addAnnotation(ClassNames.MODULE)
.addAnnotation(
AnnotationSpec.builder(ClassNames.INSTALL_IN)
diff --git a/hilt/hilt-compiler/src/main/kotlin/androidx/hilt/work/WorkerStep.kt b/hilt/hilt-compiler/src/main/kotlin/androidx/hilt/work/WorkerStep.kt
index d06abf9..a02521a 100644
--- a/hilt/hilt-compiler/src/main/kotlin/androidx/hilt/work/WorkerStep.kt
+++ b/hilt/hilt-compiler/src/main/kotlin/androidx/hilt/work/WorkerStep.kt
@@ -16,99 +16,83 @@
package androidx.hilt.work
-import androidx.hilt.AndroidXHiltProcessor
import androidx.hilt.ClassNames
-import androidx.hilt.ext.hasAnnotation
-import com.google.auto.common.MoreElements
-import com.squareup.javapoet.TypeName
-import javax.annotation.processing.ProcessingEnvironment
-import javax.lang.model.element.Element
-import javax.lang.model.element.Modifier
-import javax.lang.model.element.NestingKind
-import javax.lang.model.element.TypeElement
-import javax.lang.model.util.ElementFilter
+import androidx.room.compiler.codegen.toJavaPoet
+import androidx.room.compiler.processing.XElement
+import androidx.room.compiler.processing.XProcessingEnv
+import androidx.room.compiler.processing.XProcessingStep
+import androidx.room.compiler.processing.XTypeElement
import javax.tools.Diagnostic
/**
* Processing step that generates code enabling assisted injection of Workers using Hilt.
*/
-class WorkerStep(
- private val processingEnv: ProcessingEnvironment
-) : AndroidXHiltProcessor.Step {
+class WorkerStep : XProcessingStep {
- private val elements = processingEnv.elementUtils
- private val types = processingEnv.typeUtils
- private val messager = processingEnv.messager
+ override fun annotations() = setOf(ClassNames.HILT_WORKER.canonicalName())
- override fun annotation() = ClassNames.HILT_WORKER.canonicalName()
-
- override fun process(annotatedElements: Set<Element>) {
- val parsedElements = mutableSetOf<TypeElement>()
- annotatedElements.forEach { element ->
- val typeElement = MoreElements.asType(element)
- if (parsedElements.add(typeElement)) {
- parse(typeElement)?.let { worker ->
- WorkerGenerator(
- processingEnv,
- worker
- ).generate()
- }
- }
- }
+ override fun process(
+ env: XProcessingEnv,
+ elementsByAnnotation: Map<String, Set<XElement>>,
+ isLastRound: Boolean
+ ): Set<XElement> {
+ elementsByAnnotation[ClassNames.HILT_WORKER.canonicalName()]
+ ?.filterIsInstance<XTypeElement>()
+ ?.mapNotNull { element -> parse(env, element) }
+ ?.forEach { worker -> WorkerGenerator(env, worker).generate() }
+ return emptySet()
}
- private fun parse(typeElement: TypeElement): WorkerElements? {
+ private fun parse(env: XProcessingEnv, workerTypeElement: XTypeElement): WorkerElement? {
var valid = true
- if (elements.getTypeElement(ClassNames.WORKER_ASSISTED_FACTORY.toString()) == null) {
- error(
+ if (env.findTypeElement(ClassNames.WORKER_ASSISTED_FACTORY) == null) {
+ env.error(
"To use @HiltWorker you must add the 'work' artifact. " +
"androidx.hilt:hilt-work:<version>"
)
valid = false
}
- if (!types.isSubtype(
- typeElement.asType(),
- elements.getTypeElement(ClassNames.LISTENABLE_WORKER.toString()).asType()
- )
- ) {
- error(
+ val workerType = workerTypeElement.type
+ val listenableWorkerType = env.requireType(ClassNames.LISTENABLE_WORKER)
+ if (!listenableWorkerType.isAssignableFrom(workerType)) {
+ env.error(
"@HiltWorker is only supported on types that subclass " +
- "${ClassNames.LISTENABLE_WORKER}."
+ "${ClassNames.LISTENABLE_WORKER}.",
+ workerTypeElement
)
valid = false
}
- val constructors = ElementFilter.constructorsIn(typeElement.enclosedElements).filter {
- if (it.hasAnnotation(ClassNames.INJECT.canonicalName())) {
- error(
+ val constructors = workerTypeElement.getConstructors().filter {
+ if (it.hasAnnotation(ClassNames.INJECT)) {
+ env.error(
"Worker constructor should be annotated with @AssistedInject instead of " +
- "@Inject."
+ "@Inject.",
+ it
)
valid = false
}
- it.hasAnnotation(ClassNames.ASSISTED_INJECT.canonicalName())
+ it.hasAnnotation(ClassNames.ASSISTED_INJECT)
}
if (constructors.size != 1) {
- error(
+ env.error(
"@HiltWorker annotated class should contain exactly one @AssistedInject " +
"annotated constructor.",
- typeElement
+ workerTypeElement
)
valid = false
}
- constructors.filter { it.modifiers.contains(Modifier.PRIVATE) }.forEach {
- error("@AssistedInject annotated constructors must not be private.", it)
+ constructors.filter { it.isPrivate() }.forEach {
+ env.error("@AssistedInject annotated constructors must not be private.", it)
valid = false
}
- if (typeElement.nestingKind == NestingKind.MEMBER &&
- !typeElement.modifiers.contains(Modifier.STATIC)
- ) {
- error(
+ if (workerTypeElement.isNested() && !workerTypeElement.isStatic()) {
+ env.error(
"@HiltWorker may only be used on inner classes if they are static.",
- typeElement
+ workerTypeElement
)
valid = false
}
@@ -119,35 +103,40 @@
var contextIndex = -1
var workerParametersIndex = -1
injectConstructor.parameters.forEachIndexed { index, param ->
- if (TypeName.get(param.asType()) == ClassNames.CONTEXT) {
- if (!param.hasAnnotation(ClassNames.ASSISTED.canonicalName())) {
- error("Missing @Assisted annotation in param '${param.simpleName}'.", param)
+ if (param.type.asTypeName().toJavaPoet() == ClassNames.CONTEXT) {
+ if (!param.hasAnnotation(ClassNames.ASSISTED)) {
+ env.error("Missing @Assisted annotation in param '${param.name}'.", param)
valid = false
}
contextIndex = index
}
- if (TypeName.get(param.asType()) == ClassNames.WORKER_PARAMETERS) {
- if (!param.hasAnnotation(ClassNames.ASSISTED.canonicalName())) {
- error("Missing @Assisted annotation in param '${param.simpleName}'.", param)
+ if (param.type.asTypeName().toJavaPoet() == ClassNames.WORKER_PARAMETERS) {
+ if (!param.hasAnnotation(ClassNames.ASSISTED)) {
+ env.error("Missing @Assisted annotation in param '${param.name}'.", param)
valid = false
}
workerParametersIndex = index
}
}
if (contextIndex > workerParametersIndex) {
- error(
+ env.error(
"The 'Context' parameter must be declared before the 'WorkerParameters' in the " +
"@AssistedInject constructor of a @HiltWorker annotated class.",
injectConstructor
)
+ valid = false
}
if (!valid) return null
- return WorkerElements(typeElement, injectConstructor)
+ return WorkerElement(workerTypeElement, injectConstructor)
}
- private fun error(message: String, element: Element? = null) {
- messager.printMessage(Diagnostic.Kind.ERROR, message, element)
+ private fun XProcessingEnv.error(message: String, element: XElement? = null) {
+ if (element != null) {
+ messager.printMessage(Diagnostic.Kind.ERROR, message, element)
+ } else {
+ messager.printMessage(Diagnostic.Kind.ERROR, message)
+ }
}
}
diff --git a/hilt/hilt-compiler/src/test/kotlin/androidx/hilt/testUtils.kt b/hilt/hilt-compiler/src/test/kotlin/androidx/hilt/ext/testUtils.kt
similarity index 68%
rename from hilt/hilt-compiler/src/test/kotlin/androidx/hilt/testUtils.kt
rename to hilt/hilt-compiler/src/test/kotlin/androidx/hilt/ext/testUtils.kt
index b24ad5e..722c714 100644
--- a/hilt/hilt-compiler/src/test/kotlin/androidx/hilt/testUtils.kt
+++ b/hilt/hilt-compiler/src/test/kotlin/androidx/hilt/ext/testUtils.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2020 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,13 +14,11 @@
* limitations under the License.
*/
-package androidx.hilt
+package androidx.hilt.ext
-import com.google.testing.compile.Compiler
-import com.google.testing.compile.Compiler.javac
-import com.google.testing.compile.JavaFileObjects
+import androidx.hilt.ClassNames
+import androidx.room.compiler.processing.util.Source
import java.io.File
-import javax.tools.JavaFileObject
val GENERATED_TYPE = try {
Class.forName("javax.annotation.processing.Generated")
@@ -55,11 +53,7 @@
}
}
-fun loadJavaSource(fileName: String, qName: String): JavaFileObject {
- val contents = File("src/test/data/sources/$fileName").readText(Charsets.UTF_8)
- return JavaFileObjects.forSourceString(qName, contents)
-}
-
-fun compiler(): Compiler = javac().withProcessors(AndroidXHiltProcessor())
-
-fun String.toJFO(qName: String) = JavaFileObjects.forSourceString(qName, this.trimIndent())
+fun loadJavaSource(fileName: String, qName: String) = Source.loadJavaSource(
+ file = File("src/test/data/sources/$fileName"),
+ qName = qName
+)
diff --git a/hilt/hilt-compiler/src/test/kotlin/androidx/hilt/work/WorkerGeneratorTest.kt b/hilt/hilt-compiler/src/test/kotlin/androidx/hilt/work/WorkerGeneratorTest.kt
index fc892a0..b9d87f4 100644
--- a/hilt/hilt-compiler/src/test/kotlin/androidx/hilt/work/WorkerGeneratorTest.kt
+++ b/hilt/hilt-compiler/src/test/kotlin/androidx/hilt/work/WorkerGeneratorTest.kt
@@ -16,12 +16,11 @@
package androidx.hilt.work
-import androidx.hilt.GENERATED_ANNOTATION
-import androidx.hilt.GENERATED_TYPE
-import androidx.hilt.Sources
-import androidx.hilt.compiler
-import androidx.hilt.toJFO
-import com.google.testing.compile.CompilationSubject.assertThat
+import androidx.hilt.ext.GENERATED_ANNOTATION
+import androidx.hilt.ext.GENERATED_TYPE
+import androidx.hilt.ext.Sources
+import androidx.room.compiler.processing.util.Source
+import androidx.room.compiler.processing.util.runProcessorTest
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
@@ -31,111 +30,128 @@
@Test
fun verifyAssistedFactory_mixedArgs() {
- val foo = """
- package androidx.hilt.work.test;
+ val foo = Source.java(
+ "androidx.hilt.work.test.Foo",
+ """
+ package androidx.hilt.work.test;
- public class Foo { }
- """.toJFO("androidx.hilt.work.test.Foo")
+ public class Foo { }
+ """
+ )
- val myWorker = """
- package androidx.hilt.work.test;
+ val myWorker = Source.java(
+ "androidx.hilt.work.test.MyWorker",
+ """
+ package androidx.hilt.work.test;
- import android.content.Context;
- import androidx.hilt.work.HiltWorker;
- import androidx.work.Worker;
- import androidx.work.WorkerParameters;
- import dagger.assisted.Assisted;
- import dagger.assisted.AssistedInject;
- import java.lang.String;
+ import android.content.Context;
+ import androidx.hilt.work.HiltWorker;
+ import androidx.work.Worker;
+ import androidx.work.WorkerParameters;
+ import dagger.assisted.Assisted;
+ import dagger.assisted.AssistedInject;
+ import java.lang.String;
- @HiltWorker
- class MyWorker extends Worker {
- @AssistedInject
- MyWorker(@Assisted Context context, @Assisted WorkerParameters params, String s,
- Foo f, long l) {
- super(context, params);
+ @HiltWorker
+ class MyWorker extends Worker {
+ @AssistedInject
+ MyWorker(@Assisted Context context, @Assisted WorkerParameters params, String s,
+ Foo f, long l) {
+ super(context, params);
+ }
}
- }
- """.toJFO("androidx.hilt.work.test.MyWorker")
+ """
+ )
- val expected = """
- package androidx.hilt.work.test;
+ val expected = Source.java(
+ "androidx.hilt.work.test.MyWorker_AssistedFactory",
+ """
+ package androidx.hilt.work.test;
- import androidx.hilt.work.WorkerAssistedFactory;
- import dagger.assisted.AssistedFactory;
- import $GENERATED_TYPE;
+ import androidx.hilt.work.WorkerAssistedFactory;
+ import dagger.assisted.AssistedFactory;
+ import $GENERATED_TYPE;
- $GENERATED_ANNOTATION
- @AssistedFactory
- public interface MyWorker_AssistedFactory extends WorkerAssistedFactory<MyWorker> {
- }
- """.toJFO("androidx.hilt.work.test.MyWorker_AssistedFactory")
+ $GENERATED_ANNOTATION
+ @AssistedFactory
+ public interface MyWorker_AssistedFactory extends WorkerAssistedFactory<MyWorker> {
+ }
+ """
+ )
- val compilation = compiler()
- .compile(
+ runProcessorTest(
+ sources = listOf(
foo, myWorker, Sources.LISTENABLE_WORKER, Sources.WORKER,
Sources.WORKER_PARAMETERS
- )
- assertThat(compilation).apply {
- succeeded()
- generatedSourceFile("androidx.hilt.work.test.MyWorker_AssistedFactory")
- .hasSourceEquivalentTo(expected)
+ ),
+ createProcessingSteps = { listOf(WorkerStep()) }
+ ) {
+ it.generatedSource(expected)
}
}
@Test
fun verifyMultibindModule() {
- val myWorker = """
- package androidx.hilt.work.test;
+ val myWorker = Source.java(
+ "androidx.hilt.work.test.MyWorker",
+ """
+ package androidx.hilt.work.test;
- import android.content.Context;
- import androidx.hilt.work.HiltWorker;
- import androidx.work.Worker;
- import androidx.work.WorkerParameters;
- import dagger.assisted.Assisted;
- import dagger.assisted.AssistedInject;
+ import android.content.Context;
+ import androidx.hilt.work.HiltWorker;
+ import androidx.work.Worker;
+ import androidx.work.WorkerParameters;
+ import dagger.assisted.Assisted;
+ import dagger.assisted.AssistedInject;
- @HiltWorker
- class MyWorker extends Worker {
- @AssistedInject
- MyWorker(@Assisted Context context, @Assisted WorkerParameters params) {
- super(context, params);
+ @HiltWorker
+ class MyWorker extends Worker {
+ @AssistedInject
+ MyWorker(@Assisted Context context, @Assisted WorkerParameters params) {
+ super(context, params);
+ }
}
- }
- """.toJFO("androidx.hilt.work.test.MyWorker")
+ """
+ )
- val expected = """
- package androidx.hilt.work.test;
+ val expected = Source.java(
+ "androidx.hilt.work.test.MyWorker_HiltModule",
+ """
+ package androidx.hilt.work.test;
- import androidx.hilt.work.WorkerAssistedFactory;
- import androidx.work.ListenableWorker;
- import dagger.Binds;
- import dagger.Module;
- import dagger.hilt.InstallIn;
- import dagger.hilt.codegen.OriginatingElement;
- import dagger.hilt.components.SingletonComponent;
- import dagger.multibindings.IntoMap;
- import dagger.multibindings.StringKey;
- import $GENERATED_TYPE;
+ import androidx.hilt.work.WorkerAssistedFactory;
+ import androidx.work.ListenableWorker;
+ import dagger.Binds;
+ import dagger.Module;
+ import dagger.hilt.InstallIn;
+ import dagger.hilt.codegen.OriginatingElement;
+ import dagger.hilt.components.SingletonComponent;
+ import dagger.multibindings.IntoMap;
+ import dagger.multibindings.StringKey;
+ import $GENERATED_TYPE;
- $GENERATED_ANNOTATION
- @Module
- @InstallIn(SingletonComponent.class)
- @OriginatingElement(topLevelClass = MyWorker.class)
- public interface MyWorker_HiltModule {
- @Binds
- @IntoMap
- @StringKey("androidx.hilt.work.test.MyWorker")
- WorkerAssistedFactory<? extends ListenableWorker> bind(MyWorker_AssistedFactory factory)
- }
- """.toJFO("androidx.hilt.work.test.MyWorker_HiltModule")
+ $GENERATED_ANNOTATION
+ @Module
+ @InstallIn(SingletonComponent.class)
+ @OriginatingElement(
+ topLevelClass = MyWorker.class
+ )
+ public interface MyWorker_HiltModule {
+ @Binds
+ @IntoMap
+ @StringKey("androidx.hilt.work.test.MyWorker")
+ WorkerAssistedFactory<? extends ListenableWorker> bind(MyWorker_AssistedFactory factory);
+ }
+ """
+ )
- val compilation = compiler()
- .compile(myWorker, Sources.LISTENABLE_WORKER, Sources.WORKER, Sources.WORKER_PARAMETERS)
- assertThat(compilation).apply {
- succeeded()
- generatedSourceFile("androidx.hilt.work.test.MyWorker_HiltModule")
- .hasSourceEquivalentTo(expected)
+ runProcessorTest(
+ sources = listOf(
+ myWorker, Sources.LISTENABLE_WORKER, Sources.WORKER, Sources.WORKER_PARAMETERS
+ ),
+ createProcessingSteps = { listOf(WorkerStep()) }
+ ) {
+ it.generatedSource(expected)
}
}
}
diff --git a/hilt/hilt-compiler/src/test/kotlin/androidx/hilt/work/WorkerStepTest.kt b/hilt/hilt-compiler/src/test/kotlin/androidx/hilt/work/WorkerStepTest.kt
index 5d7c63a..2c6022e 100644
--- a/hilt/hilt-compiler/src/test/kotlin/androidx/hilt/work/WorkerStepTest.kt
+++ b/hilt/hilt-compiler/src/test/kotlin/androidx/hilt/work/WorkerStepTest.kt
@@ -14,12 +14,14 @@
* limitations under the License.
*/
+@file:OptIn(ExperimentalProcessingApi::class)
+
package androidx.hilt.work
-import androidx.hilt.Sources
-import androidx.hilt.compiler
-import androidx.hilt.toJFO
-import com.google.testing.compile.CompilationSubject.assertThat
+import androidx.hilt.ext.Sources
+import androidx.room.compiler.processing.ExperimentalProcessingApi
+import androidx.room.compiler.processing.util.Source
+import androidx.room.compiler.processing.util.runProcessorTest
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
@@ -29,28 +31,33 @@
@Test
fun verifyEnclosingElementExtendsWorker() {
- val myWorker = """
- package androidx.hilt.work.test;
+ val myWorker = Source.java(
+ "androidx.hilt.work.test.MyWorker",
+ """
+ package androidx.hilt.work.test;
- import android.content.Context;
- import androidx.hilt.work.HiltWorker;
- import androidx.work.WorkerParameters;
- import dagger.assisted.Assisted;
- import dagger.assisted.AssistedInject;
+ import android.content.Context;
+ import androidx.hilt.work.HiltWorker;
+ import androidx.work.WorkerParameters;
+ import dagger.assisted.Assisted;
+ import dagger.assisted.AssistedInject;
- @HiltWorker
- class MyWorker {
- @AssistedInject
- MyWorker(@Assisted Context context, @Assisted WorkerParameters params) { }
- }
- """.toJFO("androidx.hilt.work.work.MyWorker")
+ @HiltWorker
+ class MyWorker {
+ @AssistedInject
+ MyWorker(@Assisted Context context, @Assisted WorkerParameters params) { }
+ }
+ """
+ )
- val compilation = compiler()
- .compile(myWorker, Sources.LISTENABLE_WORKER, Sources.WORKER, Sources.WORKER_PARAMETERS)
- assertThat(compilation).apply {
- failed()
- hadErrorCount(1)
- hadErrorContainingMatch(
+ runProcessorTest(
+ sources = listOf(
+ myWorker, Sources.LISTENABLE_WORKER, Sources.WORKER, Sources.WORKER_PARAMETERS
+ ),
+ createProcessingSteps = { listOf(WorkerStep()) }
+ ) {
+ it.hasErrorCount(1)
+ it.hasErrorContaining(
"@HiltWorker is only supported on types that subclass " +
"androidx.work.ListenableWorker."
)
@@ -59,37 +66,42 @@
@Test
fun verifySingleAnnotatedConstructor() {
- val myWorker = """
- package androidx.hilt.work.test;
+ val myWorker = Source.java(
+ "androidx.hilt.work.test.MyWorker",
+ """
+ package androidx.hilt.work.test;
- import android.content.Context;
- import androidx.hilt.work.HiltWorker;
- import androidx.work.Worker;
- import androidx.work.WorkerParameters;
- import dagger.assisted.Assisted;
- import dagger.assisted.AssistedInject;
- import java.lang.String;
+ import android.content.Context;
+ import androidx.hilt.work.HiltWorker;
+ import androidx.work.Worker;
+ import androidx.work.WorkerParameters;
+ import dagger.assisted.Assisted;
+ import dagger.assisted.AssistedInject;
+ import java.lang.String;
- @HiltWorker
- class MyWorker extends Worker {
- @AssistedInject
- MyWorker(@Assisted Context context, @Assisted WorkerParameters params) {
- super(context, params);
+ @HiltWorker
+ class MyWorker extends Worker {
+ @AssistedInject
+ MyWorker(@Assisted Context context, @Assisted WorkerParameters params) {
+ super(context, params);
+ }
+
+ @AssistedInject
+ MyWorker(Context context, WorkerParameters params, String s) {
+ super(context, params);
+ }
}
+ """
+ )
- @AssistedInject
- MyWorker(Context context, WorkerParameters params, String s) {
- super(context, params);
- }
- }
- """.toJFO("androidx.hilt.work.test.MyWorker")
-
- val compilation = compiler()
- .compile(myWorker, Sources.LISTENABLE_WORKER, Sources.WORKER, Sources.WORKER_PARAMETERS)
- assertThat(compilation).apply {
- failed()
- hadErrorCount(1)
- hadErrorContainingMatch(
+ runProcessorTest(
+ sources = listOf(
+ myWorker, Sources.LISTENABLE_WORKER, Sources.WORKER, Sources.WORKER_PARAMETERS
+ ),
+ createProcessingSteps = { listOf(WorkerStep()) }
+ ) {
+ it.hasErrorCount(1)
+ it.hasErrorContaining(
"@HiltWorker annotated class should contain exactly one @AssistedInject " +
"annotated constructor."
)
@@ -98,31 +110,36 @@
@Test
fun verifyNonPrivateConstructor() {
- val myWorker = """
- package androidx.hilt.work.test;
+ val myWorker = Source.java(
+ "androidx.hilt.work.test.MyWorker",
+ """
+ package androidx.hilt.work.test;
- import android.content.Context;
- import androidx.hilt.work.HiltWorker;
- import androidx.work.Worker;
- import androidx.work.WorkerParameters;
- import dagger.assisted.Assisted;
- import dagger.assisted.AssistedInject;
+ import android.content.Context;
+ import androidx.hilt.work.HiltWorker;
+ import androidx.work.Worker;
+ import androidx.work.WorkerParameters;
+ import dagger.assisted.Assisted;
+ import dagger.assisted.AssistedInject;
- @HiltWorker
- class MyWorker extends Worker {
- @AssistedInject
- private MyWorker(@Assisted Context context, @Assisted WorkerParameters params) {
- super(context, params);
+ @HiltWorker
+ class MyWorker extends Worker {
+ @AssistedInject
+ private MyWorker(@Assisted Context context, @Assisted WorkerParameters params) {
+ super(context, params);
+ }
}
- }
- """.toJFO("androidx.hilt.work.test.MyWorker")
+ """
+ )
- val compilation = compiler()
- .compile(myWorker, Sources.LISTENABLE_WORKER, Sources.WORKER, Sources.WORKER_PARAMETERS)
- assertThat(compilation).apply {
- failed()
- hadErrorCount(1)
- hadErrorContainingMatch(
+ runProcessorTest(
+ sources = listOf(
+ myWorker, Sources.LISTENABLE_WORKER, Sources.WORKER, Sources.WORKER_PARAMETERS
+ ),
+ createProcessingSteps = { listOf(WorkerStep()) }
+ ) {
+ it.hasErrorCount(1)
+ it.hasErrorContaining(
"@AssistedInject annotated constructors must not be private."
)
}
@@ -130,33 +147,38 @@
@Test
fun verifyInnerClassIsStatic() {
- val myWorker = """
- package androidx.hilt.work.test;
+ val myWorker = Source.java(
+ "androidx.hilt.work.test.Outer",
+ """
+ package androidx.hilt.work.test;
- import android.content.Context;
- import androidx.hilt.work.HiltWorker;
- import androidx.work.Worker;
- import androidx.work.WorkerParameters;
- import dagger.assisted.Assisted;
- import dagger.assisted.AssistedInject;
+ import android.content.Context;
+ import androidx.hilt.work.HiltWorker;
+ import androidx.work.Worker;
+ import androidx.work.WorkerParameters;
+ import dagger.assisted.Assisted;
+ import dagger.assisted.AssistedInject;
- class Outer {
- @HiltWorker
- class MyWorker extends Worker {
- @AssistedInject
- MyWorker(@Assisted Context context, @Assisted WorkerParameters params) {
- super(context, params);
+ class Outer {
+ @HiltWorker
+ class MyWorker extends Worker {
+ @AssistedInject
+ MyWorker(@Assisted Context context, @Assisted WorkerParameters params) {
+ super(context, params);
+ }
}
}
- }
- """.toJFO("androidx.hilt.work.test.Outer")
+ """
+ )
- val compilation = compiler()
- .compile(myWorker, Sources.LISTENABLE_WORKER, Sources.WORKER, Sources.WORKER_PARAMETERS)
- assertThat(compilation).apply {
- failed()
- hadErrorCount(1)
- hadErrorContainingMatch(
+ runProcessorTest(
+ sources = listOf(
+ myWorker, Sources.LISTENABLE_WORKER, Sources.WORKER, Sources.WORKER_PARAMETERS
+ ),
+ createProcessingSteps = { listOf(WorkerStep()) }
+ ) {
+ it.hasErrorCount(1)
+ it.hasErrorContaining(
"@HiltWorker may only be used on inner classes " +
"if they are static."
)
@@ -165,32 +187,37 @@
@Test
fun verifyConstructorAnnotation() {
- val myWorker = """
- package androidx.hilt.work.test;
+ val myWorker = Source.java(
+ "androidx.hilt.work.test.MyWorker",
+ """
+ package androidx.hilt.work.test;
- import android.content.Context;
- import androidx.hilt.work.HiltWorker;
- import androidx.work.Worker;
- import androidx.work.WorkerParameters;
- import dagger.assisted.Assisted;
- import dagger.assisted.AssistedInject;
- import java.lang.String;
- import javax.inject.Inject;
+ import android.content.Context;
+ import androidx.hilt.work.HiltWorker;
+ import androidx.work.Worker;
+ import androidx.work.WorkerParameters;
+ import dagger.assisted.Assisted;
+ import dagger.assisted.AssistedInject;
+ import java.lang.String;
+ import javax.inject.Inject;
- @HiltWorker
- class MyWorker extends Worker {
- @Inject
- MyWorker(@Assisted Context context, @Assisted WorkerParameters params) {
- super(context, params);
+ @HiltWorker
+ class MyWorker extends Worker {
+ @Inject
+ MyWorker(@Assisted Context context, @Assisted WorkerParameters params) {
+ super(context, params);
+ }
}
- }
- """.toJFO("androidx.hilt.work.test.MyWorker")
+ """
+ )
- val compilation = compiler()
- .compile(myWorker, Sources.LISTENABLE_WORKER, Sources.WORKER, Sources.WORKER_PARAMETERS)
- assertThat(compilation).apply {
- failed()
- hadErrorContainingMatch(
+ runProcessorTest(
+ sources = listOf(
+ myWorker, Sources.LISTENABLE_WORKER, Sources.WORKER, Sources.WORKER_PARAMETERS
+ ),
+ createProcessingSteps = { listOf(WorkerStep()) }
+ ) {
+ it.hasErrorContaining(
"Worker constructor should be annotated with @AssistedInject instead of @Inject."
)
}
@@ -198,31 +225,36 @@
@Test
fun verifyAssistedParamOrder() {
- val myWorker = """
- package androidx.hilt.work.test;
+ val myWorker = Source.java(
+ "androidx.hilt.work.test.MyWorker",
+ """
+ package androidx.hilt.work.test;
- import android.content.Context;
- import androidx.hilt.work.HiltWorker;
- import androidx.work.Worker;
- import androidx.work.WorkerParameters;
- import dagger.assisted.Assisted;
- import dagger.assisted.AssistedInject;
- import java.lang.String;
+ import android.content.Context;
+ import androidx.hilt.work.HiltWorker;
+ import androidx.work.Worker;
+ import androidx.work.WorkerParameters;
+ import dagger.assisted.Assisted;
+ import dagger.assisted.AssistedInject;
+ import java.lang.String;
- @HiltWorker
- class MyWorker extends Worker {
- @AssistedInject
- MyWorker(@Assisted WorkerParameters params, @Assisted Context context) {
- super(context, params);
+ @HiltWorker
+ class MyWorker extends Worker {
+ @AssistedInject
+ MyWorker(@Assisted WorkerParameters params, @Assisted Context context) {
+ super(context, params);
+ }
}
- }
- """.toJFO("androidx.hilt.work.test.MyWorker")
+ """
+ )
- val compilation = compiler()
- .compile(myWorker, Sources.LISTENABLE_WORKER, Sources.WORKER, Sources.WORKER_PARAMETERS)
- assertThat(compilation).apply {
- failed()
- hadErrorContainingMatch(
+ runProcessorTest(
+ sources = listOf(
+ myWorker, Sources.LISTENABLE_WORKER, Sources.WORKER, Sources.WORKER_PARAMETERS
+ ),
+ createProcessingSteps = { listOf(WorkerStep()) }
+ ) {
+ it.hasErrorContaining(
"The 'Context' parameter must be declared before the 'WorkerParameters' in the " +
"@AssistedInject constructor of a @HiltWorker annotated class.",
)
diff --git a/inspection/inspection-gradle-plugin/src/main/kotlin/androidx/inspection/gradle/AGPExtensions.kt b/inspection/inspection-gradle-plugin/src/main/kotlin/androidx/inspection/gradle/AGPExtensions.kt
index 6d6a8e6..520e21d7 100644
--- a/inspection/inspection-gradle-plugin/src/main/kotlin/androidx/inspection/gradle/AGPExtensions.kt
+++ b/inspection/inspection-gradle-plugin/src/main/kotlin/androidx/inspection/gradle/AGPExtensions.kt
@@ -20,6 +20,8 @@
import java.io.File
import java.util.Locale
import org.gradle.api.Project
+import org.gradle.api.file.Directory
+import org.gradle.api.provider.Provider
internal fun Variant.taskName(baseName: String) =
"$baseName${name.replaceFirstChar(Char::titlecase)}"
@@ -27,9 +29,8 @@
internal fun Project.taskWorkingDir(
variant: Variant,
baseName: String
-): File {
- val inspectionDir = File(project.buildDir, "androidx_inspection")
- return File(File(inspectionDir, baseName), variant.name)
+): Provider<Directory> {
+ return layout.buildDirectory.dir("androidx_inspection/$baseName/${variant.name}")
}
// Functions below will be removed once registerGenerateProguardDetectionFileTask is migrated
diff --git a/inspection/inspection-gradle-plugin/src/main/kotlin/androidx/inspection/gradle/DexInspectorTask.kt b/inspection/inspection-gradle-plugin/src/main/kotlin/androidx/inspection/gradle/DexInspectorTask.kt
index 7d95e6b..e8edc46e 100644
--- a/inspection/inspection-gradle-plugin/src/main/kotlin/androidx/inspection/gradle/DexInspectorTask.kt
+++ b/inspection/inspection-gradle-plugin/src/main/kotlin/androidx/inspection/gradle/DexInspectorTask.kt
@@ -128,7 +128,8 @@
): TaskProvider<Copy> {
return tasks.register(variant.taskName("unpackInspectorAAR"), Copy::class.java) {
it.from(zipTree(variant.artifacts.get(SingleArtifact.AAR)))
- it.destinationDir = taskWorkingDir(variant, "unpackedInspectorAAR")
+ // Remove .get().asFile once https://github.com/gradle/gradle/issues/25824 is fixed
+ it.destinationDir = taskWorkingDir(variant, "unpackedInspectorAAR").get().asFile
}
}
@@ -139,15 +140,14 @@
jar: TaskProvider<out Jar>
): TaskProvider<Zip> {
val name = jarName ?: "${project.name}.jar"
- val out = File(taskWorkingDir(variant, "dexedInspector"), name)
+ val output = taskWorkingDir(variant, "dexedInspector").map { it.file(name) }
val dex = tasks.register(variant.taskName("dexInspector"), DexInspectorTask::class.java) {
it.minSdkVersion = extension.defaultConfig.minSdk!!
it.setD8(extension.sdkDirectory, extension.buildToolsVersion)
it.setAndroidJar(extension.sdkDirectory, extension.compileSdkVersion!!)
it.jars.from(jar.get().archiveFile)
- it.outputFile.set(out)
- @Suppress("UnstableApiUsage")
+ it.outputFile.set(output)
it.compileClasspath.from(
variant.compileConfiguration.incoming.artifactView {
it.attributes {
@@ -163,11 +163,10 @@
return tasks.register(variant.taskName("assembleInspectorJar"), Zip::class.java) {
it.from(zipTree(jar.map { it.archiveFile }))
- it.from(zipTree(out))
+ it.from(dex.map { zipTree(it.outputFile) })
it.exclude("**/*.class")
it.archiveFileName.set(name)
it.destinationDirectory.set(taskWorkingDir(variant, "assembleInspectorJar"))
- it.dependsOn(dex)
it.includeEmptyDirs = false
}
}
diff --git a/kruth/kruth/api/current.txt b/kruth/kruth/api/current.txt
index 2208eb3..fda56c4 100644
--- a/kruth/kruth/api/current.txt
+++ b/kruth/kruth/api/current.txt
@@ -89,6 +89,8 @@
}
public final class MapSubject<K, V> extends androidx.kruth.Subject<java.util.Map<K,? extends V>> {
+ method public androidx.kruth.Ordered containsAtLeast(kotlin.Pair<? extends K,? extends V>... entries);
+ method public androidx.kruth.Ordered containsAtLeastEntriesIn(java.util.Map<K,? extends V> expectedMap);
method public androidx.kruth.Ordered containsExactly(kotlin.Pair<? extends K,? extends V>... entries);
method public androidx.kruth.Ordered containsExactlyEntriesIn(java.util.Map<K,? extends V> expectedMap);
method public void containsKey(Object? key);
@@ -161,6 +163,7 @@
}
public final class ThrowableSubject<T extends java.lang.Throwable> extends androidx.kruth.Subject<T> {
+ method public androidx.kruth.ThrowableSubject<java.lang.Throwable> hasCauseThat();
method public androidx.kruth.StringSubject hasMessageThat();
}
diff --git a/kruth/kruth/api/restricted_current.txt b/kruth/kruth/api/restricted_current.txt
index 380eaee..3e7a042 100644
--- a/kruth/kruth/api/restricted_current.txt
+++ b/kruth/kruth/api/restricted_current.txt
@@ -89,6 +89,8 @@
}
public final class MapSubject<K, V> extends androidx.kruth.Subject<java.util.Map<K,? extends V>> {
+ method public androidx.kruth.Ordered containsAtLeast(kotlin.Pair<? extends K,? extends V>... entries);
+ method public androidx.kruth.Ordered containsAtLeastEntriesIn(java.util.Map<K,? extends V> expectedMap);
method public androidx.kruth.Ordered containsExactly(kotlin.Pair<? extends K,? extends V>... entries);
method public androidx.kruth.Ordered containsExactlyEntriesIn(java.util.Map<K,? extends V> expectedMap);
method public void containsKey(Object? key);
@@ -162,6 +164,7 @@
}
public final class ThrowableSubject<T extends java.lang.Throwable> extends androidx.kruth.Subject<T> {
+ method public androidx.kruth.ThrowableSubject<java.lang.Throwable> hasCauseThat();
method public androidx.kruth.StringSubject hasMessageThat();
}
diff --git a/kruth/kruth/src/commonMain/kotlin/androidx/kruth/MapSubject.kt b/kruth/kruth/src/commonMain/kotlin/androidx/kruth/MapSubject.kt
index aaa165c..fa369ff 100644
--- a/kruth/kruth/src/commonMain/kotlin/androidx/kruth/MapSubject.kt
+++ b/kruth/kruth/src/commonMain/kotlin/androidx/kruth/MapSubject.kt
@@ -67,6 +67,18 @@
)
)
+ /**
+ * Fails if the map does not contain at least the given set of key/value pairs. The arguments
+ * must not contain duplicate keys.
+ */
+ fun containsAtLeast(vararg entries: Pair<K, V>): Ordered =
+ containsAtLeastEntriesIn(
+ accumulateMap(
+ functionName = "containsAtLeast",
+ entries = entries.toList(),
+ )
+ )
+
/** Fails if the map does not contain exactly the given set of entries in the given map. */
fun containsExactlyEntriesIn(expectedMap: Map<K, V>): Ordered {
requireNonNull(actual) { "Expected $expectedMap, but was null" }
@@ -84,6 +96,17 @@
return MapInOrder(expectedMap = expectedMap, allowUnexpected = false)
}
+ /** Fails if the map does not contain at least the given set of entries in the given map. */
+ fun containsAtLeastEntriesIn(expectedMap: Map<K, V>): Ordered {
+ if (expectedMap.isEmpty()) {
+ return NoopOrdered
+ }
+
+ containsEntriesInAnyOrder(expectedMap = expectedMap, allowUnexpected = true)
+
+ return MapInOrder(expectedMap = expectedMap, allowUnexpected = true)
+ }
+
private fun containsEntriesInAnyOrder(
expectedMap: Map<K, V>,
allowUnexpected: Boolean,
diff --git a/kruth/kruth/src/commonMain/kotlin/androidx/kruth/ThrowableSubject.kt b/kruth/kruth/src/commonMain/kotlin/androidx/kruth/ThrowableSubject.kt
index f613adf..a7d06c1 100644
--- a/kruth/kruth/src/commonMain/kotlin/androidx/kruth/ThrowableSubject.kt
+++ b/kruth/kruth/src/commonMain/kotlin/androidx/kruth/ThrowableSubject.kt
@@ -19,7 +19,7 @@
/**
* Propositions for [Throwable] subjects.
*/
-class ThrowableSubject<T : Throwable> internal constructor(
+class ThrowableSubject<out T : Throwable> internal constructor(
actual: T?,
private val metadata: FailureMetadata = FailureMetadata(),
) : Subject<T>(actual = actual, metadata = metadata) {
@@ -30,4 +30,17 @@
fun hasMessageThat(): StringSubject {
return StringSubject(actual = actual?.message, metadata = metadata)
}
+
+ /**
+ * Returns a new [ThrowableSubject] that supports assertions on this [Throwable]'s direct
+ * cause. This method can be invoked repeatedly (e.g.
+ * `assertThat(e).hasCauseThat().hasCauseThat()....` to assert on a particular indirect cause.
+ */
+ fun hasCauseThat(): ThrowableSubject<Throwable> {
+ if (actual == null) {
+ asserter.fail("Causal chain is not deep enough - add a .isNotNull() check?")
+ }
+
+ return ThrowableSubject(actual = actual.cause, metadata = metadata)
+ }
}
diff --git a/kruth/kruth/src/commonTest/kotlin/androidx/kruth/MapSubjectTest.kt b/kruth/kruth/src/commonTest/kotlin/androidx/kruth/MapSubjectTest.kt
index e1ddf10..f380fc7 100644
--- a/kruth/kruth/src/commonTest/kotlin/androidx/kruth/MapSubjectTest.kt
+++ b/kruth/kruth/src/commonTest/kotlin/androidx/kruth/MapSubjectTest.kt
@@ -220,6 +220,155 @@
}
@Test
+ fun containsAtLeastWithNullKey() {
+ val actual =
+ mapOf(
+ null to "value",
+ "unexpectedKey" to "unexpectedValue",
+ )
+
+ val expected = mapOf<String?, String>(null to "value")
+
+ assertThat(actual).containsAtLeast(null to "value")
+ assertThat(actual).containsAtLeast(null to "value").inOrder()
+ assertThat(actual).containsAtLeastEntriesIn(expected)
+ assertThat(actual).containsAtLeastEntriesIn(expected).inOrder()
+ }
+
+ @Test
+ fun containsAtLeastWithNullValue() {
+ val actual =
+ mapOf(
+ "key" to null,
+ "unexpectedKey" to "unexpectedValue",
+ )
+
+ val expected = mapOf<String, String?>("key" to null)
+
+ assertThat(actual).containsAtLeast("key" to null)
+ assertThat(actual).containsAtLeast("key" to null).inOrder()
+ assertThat(actual).containsAtLeastEntriesIn(expected)
+ assertThat(actual).containsAtLeastEntriesIn(expected).inOrder()
+ }
+
+ @Test
+ fun containsAtLeastEmpty() {
+ val actual = mapOf("key" to 1)
+ assertThat(actual).containsAtLeastEntriesIn(emptyMap())
+ assertThat(actual).containsAtLeastEntriesIn(emptyMap()).inOrder()
+ }
+
+ @Test
+ fun containsAtLeastOneEntry() {
+ val actual = mapOf("jan" to 1)
+ assertThat(actual).containsAtLeast("jan" to 1)
+ assertThat(actual).containsAtLeast("jan" to 1).inOrder()
+ assertThat(actual).containsAtLeastEntriesIn(actual)
+ assertThat(actual).containsAtLeastEntriesIn(actual).inOrder()
+ }
+
+ @Test
+ fun containsAtLeastMultipleEntries() {
+ val actual = mapOf("jan" to 1, "feb" to 2, "mar" to 3, "apr" to 4)
+ assertThat(actual).containsAtLeast("apr" to 4, "jan" to 1, "feb" to 2)
+ assertThat(actual).containsAtLeast("jan" to 1, "feb" to 2, "apr" to 4).inOrder()
+ assertThat(actual).containsAtLeastEntriesIn(mapOf("apr" to 4, "jan" to 1, "feb" to 2))
+ assertThat(actual).containsAtLeastEntriesIn(actual).inOrder()
+ }
+
+ @Test
+ fun containsAtLeastDuplicateKeys() {
+ val actual = mapOf("jan" to 1, "feb" to 2, "march" to 3)
+ try {
+ assertThat(actual).containsAtLeast("jan" to 1, "jan" to 2, "jan" to 3)
+ fail("Expected IllegalArgumentException")
+ } catch (expected: IllegalArgumentException) {
+ assertThat(expected)
+ .hasMessageThat()
+ .isEqualTo("Duplicate keys ([jan x 3]) cannot be passed to containsAtLeast().")
+ }
+ }
+
+ @Test
+ fun containsAtLeastMultipleDuplicateKeys() {
+ val actual = mapOf("jan" to 1, "feb" to 2, "march" to 3)
+ try {
+ assertThat(actual).containsAtLeast("jan" to 1, "jan" to 1, "feb" to 2, "feb" to 2)
+ fail("Expected IllegalArgumentException")
+ } catch (expected: IllegalArgumentException) {
+ assertThat(expected)
+ .hasMessageThat()
+ .isEqualTo(
+ "Duplicate keys ([jan x 2, feb x 2]) cannot be passed to containsAtLeast()."
+ )
+ }
+ }
+
+ @Test
+ fun containsAtLeastMissingKey() {
+ val actual = mapOf("jan" to 1, "feb" to 2)
+ assertFailsWith<AssertionError> {
+ assertThat(actual).containsAtLeast("jan" to 1, "march" to 3)
+ }
+ }
+
+ @Test
+ fun containsAtLeastWrongValue() {
+ val actual = mapOf("jan" to 1, "feb" to 2, "march" to 3)
+ assertFailsWith<AssertionError> {
+ assertThat(actual).containsAtLeast("jan" to 1, "march" to 33)
+ }
+ }
+
+ @Test
+ fun containsAtLeastWrongValueWithNull() {
+ // Test for https://github.com/google/truth/issues/468
+ val actual = mapOf<String, Int?>("jan" to 1, "feb" to 2, "march" to 3)
+ assertFailsWith<AssertionError> {
+ assertThat(actual).containsAtLeast("jan" to 1, "march" to null)
+ }
+ }
+
+ @Test
+ fun containsAtLeastExtraKeyAndMissingKeyAndWrongValue() {
+ val actual = mapOf("jan" to 1, "march" to 3)
+ assertFailsWith<AssertionError> {
+ assertThat(actual).containsAtLeast("march" to 33, "feb" to 2)
+ }
+ }
+
+ @Test
+ fun containsAtLeastNotInOrder() {
+ val actual = mapOf("jan" to 1, "feb" to 2, "march" to 3)
+ assertThat(actual).containsAtLeast("march" to 3, "feb" to 2)
+ assertFailsWith<AssertionError> {
+ assertThat(actual).containsAtLeast("march" to 3, "feb" to 2).inOrder()
+ }
+ }
+
+ @Test
+ fun containsAtLeastWrongValue_sameToStringForValues() {
+ assertFailsWith<AssertionError> {
+ assertThat(mapOf<String, Any>("jan" to 1L, "feb" to 2L, "mar" to 3L))
+ .containsAtLeast("jan" to 1, "feb" to 2)
+ }
+ }
+
+ @Test
+ fun containsAtLeastWrongValue_sameToStringForKeys() {
+ assertFailsWith<AssertionError> {
+ assertThat(mapOf(1L to "jan", 1 to "feb")).containsAtLeast(1 to "jan", 1L to "feb")
+ }
+ }
+
+ @Test
+ fun containsAtLeastExtraKeyAndMissingKey_failsWithSameToStringForKeys() {
+ assertFailsWith<AssertionError> {
+ assertThat(mapOf(1L to "jan", 2 to "feb")).containsAtLeast(1 to "jan", 2 to "feb")
+ }
+ }
+
+ @Test
fun isEmpty() {
assertThat(mapOf<Any, Any>()).isEmpty()
}
diff --git a/kruth/kruth/src/commonTest/kotlin/androidx/kruth/ThrowableSubjectTest.kt b/kruth/kruth/src/commonTest/kotlin/androidx/kruth/ThrowableSubjectTest.kt
index e14c7373..5c5df55 100644
--- a/kruth/kruth/src/commonTest/kotlin/androidx/kruth/ThrowableSubjectTest.kt
+++ b/kruth/kruth/src/commonTest/kotlin/androidx/kruth/ThrowableSubjectTest.kt
@@ -66,4 +66,60 @@
assertThat(npe).hasMessageThat().isEqualTo("message")
}
}
+
+ @Test
+ fun hasCauseThat_message() {
+ assertThat(Exception("foobar", IllegalStateException("barfoo")))
+ .hasCauseThat()
+ .hasMessageThat()
+ .isEqualTo("barfoo")
+ }
+
+ @Test
+ fun hasCauseThat_instanceOf() {
+ assertThat(Exception("foobar", IllegalStateException("barfoo")))
+ .hasCauseThat()
+ .isInstanceOf<IllegalStateException>()
+ }
+
+ @Test
+ fun hasCauseThat_null() {
+ assertThat(Exception("foobar")).hasCauseThat().isNull()
+ }
+
+ @Test
+ fun hasCauseThat_message_failure() {
+ val actual = Exception("foobar", IllegalStateException("barfoo"))
+ assertFailsWith<AssertionError> {
+ assertThat(actual).hasCauseThat().hasMessageThat().isEqualTo("message")
+ }
+ }
+
+ @Test
+ fun hasCauseThat_instanceOf_failure() {
+ val actual = Exception("foobar", IllegalStateException("barfoo"))
+ assertFailsWith<AssertionError> {
+ assertThat(actual).hasCauseThat().isInstanceOf<NullPointerException>()
+ }
+ }
+
+ @Test
+ fun hasCauseThat_tooDeep_failure() {
+ val actual = Exception("foobar")
+ assertFailsWith<AssertionError> {
+ assertThat(actual).hasCauseThat().hasCauseThat().isNull()
+ }
+ }
+
+ @Test
+ fun hasCauseThat_deepNull_failure() {
+ val actual = Exception("foobar", RuntimeException("barfoo", IllegalStateException("buzz")))
+ assertFailsWith<AssertionError> {
+ assertThat(actual)
+ .hasCauseThat()
+ .hasCauseThat()
+ .hasMessageThat()
+ .isEqualTo("message")
+ }
+ }
}
diff --git a/leanback/leanback/build.gradle b/leanback/leanback/build.gradle
index d22458a..018ffd4 100644
--- a/leanback/leanback/build.gradle
+++ b/leanback/leanback/build.gradle
@@ -25,7 +25,6 @@
// It can be removed if appcompat library is updated to a newer version
implementation("androidx.vectordrawable:vectordrawable-animated:1.1.0")
- androidTestImplementation(libs.kotlinStdlib)
androidTestImplementation(libs.testExtJunit)
androidTestImplementation(libs.testCore)
androidTestImplementation(libs.testRunner)
diff --git a/leanback/leanback/lint-baseline.xml b/leanback/leanback/lint-baseline.xml
index 667d89e..6b97ced 100644
--- a/leanback/leanback/lint-baseline.xml
+++ b/leanback/leanback/lint-baseline.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.0.0-beta03" type="baseline" client="gradle" dependencies="false" name="AGP (8.0.0-beta03)" variant="all" version="8.0.0-beta03">
+<issues format="6" by="lint 8.1.0-beta05" type="baseline" client="gradle" dependencies="false" name="AGP (8.1.0-beta05)" variant="all" version="8.1.0-beta05">
<issue
id="NewApi"
@@ -409,6 +409,15 @@
<issue
id="BanThreadSleep"
message="Uses Thread.sleep()"
+ errorLine1=" Thread.sleep(100);"
+ errorLine2=" ~~~~~">
+ <location
+ file="src/androidTest/java/androidx/leanback/widget/GridWidgetTest.java"/>
+ </issue>
+
+ <issue
+ id="BanThreadSleep"
+ message="Uses Thread.sleep()"
errorLine1=" Thread.sleep(80);"
errorLine2=" ~~~~~">
<location
diff --git a/leanback/leanback/src/androidTest/java/androidx/leanback/widget/GridWidgetTest.java b/leanback/leanback/src/androidTest/java/androidx/leanback/widget/GridWidgetTest.java
index 5110a5f..4ede5d5 100644
--- a/leanback/leanback/src/androidTest/java/androidx/leanback/widget/GridWidgetTest.java
+++ b/leanback/leanback/src/androidTest/java/androidx/leanback/widget/GridWidgetTest.java
@@ -39,6 +39,7 @@
import android.text.Selection;
import android.text.Spannable;
import android.util.DisplayMetrics;
+import android.util.Log;
import android.util.SparseArray;
import android.util.SparseIntArray;
import android.util.TypedValue;
@@ -83,6 +84,7 @@
@RunWith(AndroidJUnit4.class)
public class GridWidgetTest {
+ private static final String TAG = "GridWidgetTest";
private static final float DELTA = 1f;
private static final boolean HUMAN_DELAY = false;
private static final long WAIT_FOR_SCROLL_IDLE_TIMEOUT_MS = 60000;
@@ -92,6 +94,7 @@
@Rule
public final AnimationActivityTestRule<GridActivity> mActivityTestRule =
new AnimationActivityTestRule<GridActivity>(GridActivity.class, false, false);;
+
protected GridActivity mActivity;
protected BaseGridView mGridView;
protected GridLayoutManager mLayoutManager;
@@ -2137,6 +2140,132 @@
verifyBeginAligned();
}
+ @Ignore // This is a long running test that can take hours, improper for a presubmit check.
+ @Test
+ @AnimationTest
+ @SuppressWarnings("unchecked")
+ public void testRandomChangeAdapterAndScroll() throws Throwable {
+ // Randomly run adapter change and scrolling repeatedly, in order to capture a potential
+ // crash.
+ Intent intent = new Intent();
+ intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+ R.layout.horizontal_linear);
+ intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
+ intent.putExtra(GridActivity.EXTRA_ITEMS, new int[] {});
+ initActivity(intent);
+ mOrientation = BaseGridView.HORIZONTAL;
+ mNumRows = 1;
+
+ ArrayObjectAdapter adapter = new ArrayObjectAdapter(new TestPresenter());
+ ItemBridgeAdapter itemBridgeAdapter = new ItemBridgeAdapter();
+ itemBridgeAdapter.setAdapter(adapter);
+ mActivityTestRule.runOnUiThread(
+ new Runnable() {
+ @Override
+ public void run() {
+ // Setup a fixed size RV to match exactly the number of Views we want.
+ TestPresenter.setupHorizontalGridView(mGridView);
+ mGridView.setAdapter(itemBridgeAdapter);
+ }
+ }
+ );
+
+ for (int i = 0; i < 10000; i++) {
+ final List<TestPresenter.Item>[] itemsHolder = (List<TestPresenter.Item>[]) new List[1];
+ itemsHolder[0] = TestPresenter.generateItems(0, 20);
+ mActivityTestRule.runOnUiThread(
+ new Runnable() {
+ @Override
+ public void run() {
+ Log.d(TAG, "Setup initial items: " + itemsHolder[0].toString());
+ adapter.setItems(itemsHolder[0], TestPresenter.DIFF_CALLBACK);
+ }
+ }
+ );
+
+ for (int j = 0; j < 20; j++) {
+ // Either change adapter or scrolling
+ boolean changeAdapter = TestPresenter.randomBoolean();
+ if (changeAdapter) {
+ itemsHolder[0] = TestPresenter.randomChange(itemsHolder[0]);
+ mActivityTestRule.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ Log.d(TAG, "Random change items: " + itemsHolder[0].toString());
+ adapter.setItems(itemsHolder[0], TestPresenter.DIFF_CALLBACK);
+ }
+ });
+ } else {
+ mActivityTestRule.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ if (TestPresenter.randomBoolean()) {
+ Log.d(TAG, "Scrolling to first item");
+ mGridView.setSelectedPositionSmooth(0);
+ } else {
+ Log.d(TAG, "Scrolling to last item");
+ mGridView.setSelectedPositionSmooth(itemsHolder[0].size() - 1);
+ }
+ }
+ });
+ }
+ Thread.sleep(100);
+ }
+ }
+ }
+
+ @Ignore // wait b/292114537 to be fixed *and* update the recyclerview version in build.gradle.
+ @Test
+ @AnimationTest
+ @SuppressWarnings("unchecked")
+ public void testCrashOnRVChildHelperBug292114537() throws Throwable {
+ // see b/292114537
+ Intent intent = new Intent();
+ intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+ R.layout.horizontal_linear);
+ intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
+ intent.putExtra(GridActivity.EXTRA_ITEMS, new int[] {});
+ initActivity(intent);
+ mOrientation = BaseGridView.HORIZONTAL;
+ mNumRows = 1;
+
+ ArrayObjectAdapter adapter = new ArrayObjectAdapter(new TestPresenter());
+ ItemBridgeAdapter itemBridgeAdapter = new ItemBridgeAdapter();
+ itemBridgeAdapter.setAdapter(adapter);
+
+ mActivityTestRule.runOnUiThread(
+ new Runnable() {
+ @Override
+ public void run() {
+ // Setup a fixed size RV to match exactly the number of Views we want.
+ TestPresenter.setupHorizontalGridView(mGridView);
+ mGridView.setAdapter(itemBridgeAdapter);
+ mGridView.getItemAnimator().setAddDuration(1000);
+ mGridView.getItemAnimator().setMoveDuration(1000);
+ mGridView.getItemAnimator().setRemoveDuration(1000);
+ adapter.setItems(
+ TestPresenter.generateItems(new int[]{0, 1, 2, 3, 4, 5, 6, 7, 8}),
+ TestPresenter.DIFF_CALLBACK);
+ }
+ }
+ );
+ // showing 0 1(selected) 2 3 4 5(peek)
+ setSelectedPosition(1);
+
+ // swap 5 and 6, showing 0 1(selected) 2 3 4 6(slide in) 5(slide out)
+ mActivityTestRule.runOnUiThread(() ->
+ adapter.setItems(TestPresenter.generateItems(new int[]{0, 1, 2, 3, 4, 6, 5, 7, 8}),
+ TestPresenter.DIFF_CALLBACK));
+
+ // Wait a little bit for the ItemAnimation to be started
+ waitForItemAnimationStart();
+
+ // Scroll to 0 to remove "6", this can break ChildHelper and scroll to 2 may failed to find
+ // the View and crash.
+ setSelectedPosition(0);
+ setSelectedPosition(2);
+ }
+
@Test
public void testItemMovedHorizontalRtl() throws Throwable {
Intent intent = new Intent();
diff --git a/leanback/leanback/src/androidTest/java/androidx/leanback/widget/TestPresenter.java b/leanback/leanback/src/androidTest/java/androidx/leanback/widget/TestPresenter.java
new file mode 100644
index 0000000..4b30e10
--- /dev/null
+++ b/leanback/leanback/src/androidTest/java/androidx/leanback/widget/TestPresenter.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.leanback.widget;
+
+import android.graphics.Color;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.Random;
+
+final class TestPresenter extends Presenter {
+
+ static final class Item {
+ String mId;
+ String mDetail;
+
+ Item(String id, String detail) {
+ mId = id;
+ mDetail = detail;
+ }
+
+ @Override
+ public String toString() {
+ return mId + "- " + mDetail;
+ }
+ }
+
+ private static Random sRandom = new Random();
+
+ private static int sIdSeed = 100;
+
+ static Item generateItem(int id) {
+ Item item = new Item("" + id, "origin");
+ return item;
+ }
+
+ static List<Item> generateItems(int startId, int endId) {
+ List<Item> list = new ArrayList<>();
+ for (int i = startId; i < endId; i++) {
+ Item item = generateItem(i);
+ list.add(item);
+ }
+ return list;
+ }
+
+ static List<Item> generateItems(int[] ids) {
+ List<Item> list = new ArrayList<>();
+ for (int i : ids) {
+ Item item = new Item("" + i, "origin");
+ list.add(item);
+ }
+ return list;
+ }
+
+ static void setupHorizontalGridView(BaseGridView gridView) {
+ ViewGroup.LayoutParams layoutParams = gridView.getLayoutParams();
+ layoutParams.width = 1920;
+ layoutParams.height = 500;
+ gridView.setLayoutParams(layoutParams);
+ gridView.setPadding(116, 16, 116, 16);
+ gridView.setHorizontalSpacing(40);
+ gridView.setItemAlignmentOffsetPercent(0f);
+ gridView.setItemAlignmentOffset(0);
+ gridView.setWindowAlignmentOffsetPercent(0);
+ gridView.setWindowAlignmentOffset(116);
+ }
+
+ static boolean randomBoolean() {
+ return sRandom.nextInt(2) == 0;
+ }
+
+ static List<Item> randomChange(List<Item> items) {
+ List<Item> newList = new ArrayList<>();
+ // 70% old items are copied cover
+ for (int i = 0; i < items.size(); i++) {
+ if (sRandom.nextInt(10) < 7) {
+ newList.add(items.get(i));
+ }
+ }
+ // Randomly shift position <= 1/3 of the old items
+ int shiftCount = sRandom.nextInt(newList.size() / 3);
+ for (int i = 0; i < shiftCount; i++) {
+ int pos1 = sRandom.nextInt(newList.size());
+ Item item1 = newList.get(pos1);
+ int pos2 = sRandom.nextInt(newList.size());
+ Item item2 = newList.get(pos2);
+ newList.set(pos1, item2);
+ newList.set(pos2, item1);
+ }
+ // Insert new items into random positions
+ int newItemsCount = items.size() - newList.size();
+ for (int i = 0; i < newItemsCount; i++) {
+ Item item = generateItem(sIdSeed++);
+ newList.add(sRandom.nextInt(newList.size()), item);
+ }
+ return newList;
+ }
+
+ @NonNull
+ @Override
+ public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent) {
+ TextView tv = new TextView(parent.getContext());
+ tv.setFocusable(true);
+ tv.setFocusableInTouchMode(true);
+ tv.setLayoutParams(new ViewGroup.LayoutParams(393, 221));
+ tv.setBackgroundColor(Color.LTGRAY);
+ tv.setOnFocusChangeListener(new View.OnFocusChangeListener() {
+ @Override
+ public void onFocusChange(View v, boolean hasFocus) {
+ if (hasFocus) {
+ v.setBackgroundColor(Color.YELLOW);
+ } else {
+ v.setBackgroundColor(Color.LTGRAY);
+ }
+ }
+ });
+ return new ViewHolder(tv);
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull ViewHolder viewHolder, @Nullable Object item) {
+ ((TextView) viewHolder.view).setText(item.toString());
+ }
+
+ @Override
+ public void onUnbindViewHolder(@NonNull ViewHolder viewHolder) {
+ }
+
+ static final DiffCallback<Item> DIFF_CALLBACK =
+ new DiffCallback<Item>() {
+
+ @Override
+ public boolean areItemsTheSame(
+ @NonNull Item oldItem, @NonNull Item newItem) {
+ return Objects.equals(oldItem.mId, newItem.mId);
+ }
+
+ @Override
+ public boolean areContentsTheSame(
+ @NonNull Item oldItem,
+ @NonNull Item newItem) {
+ return Objects.equals(oldItem.mDetail, newItem.mDetail);
+ }
+ };
+}
diff --git a/libraryversions.toml b/libraryversions.toml
index 30e477f..a8392b0 100644
--- a/libraryversions.toml
+++ b/libraryversions.toml
@@ -20,7 +20,7 @@
CAR_APP = "1.4.0-alpha02"
COLLECTION = "1.3.0-alpha05"
COMPOSE = "1.6.0-alpha02"
-COMPOSE_COMPILER = "1.5.0"
+COMPOSE_COMPILER = "1.5.1"
COMPOSE_MATERIAL3 = "1.2.0-alpha04"
COMPOSE_MATERIAL3_ADAPTIVE = "1.0.0-alpha01"
COMPOSE_RUNTIME_TRACING = "1.0.0-alpha03"
@@ -54,12 +54,12 @@
DYNAMICANIMATION = "1.1.0-alpha04"
DYNAMICANIMATION_KTX = "1.0.0-alpha04"
EMOJI = "1.2.0-alpha03"
-EMOJI2 = "1.4.0-beta05"
+EMOJI2 = "1.4.0-rc01"
ENTERPRISE = "1.1.0-rc01"
EXIFINTERFACE = "1.4.0-alpha01"
FRAGMENT = "1.7.0-alpha02"
-FUTURES = "1.2.0-alpha01"
-GLANCE = "1.0.0-rc01"
+FUTURES = "1.2.0-alpha02"
+GLANCE = "1.1.0-alpha01"
GLANCE_PREVIEW = "1.0.0-alpha06"
GLANCE_TEMPLATE = "1.0.0-alpha06"
GLANCE_WEAR_TILES = "1.0.0-alpha06"
@@ -68,7 +68,7 @@
GRAPHICS_FILTERS = "1.0.0-alpha01"
GRAPHICS_SHAPES = "1.0.0-alpha03"
GRIDLAYOUT = "1.1.0-beta02"
-HEALTH_CONNECT = "1.1.0-alpha02"
+HEALTH_CONNECT = "1.1.0-alpha03"
HEALTH_SERVICES_CLIENT = "1.1.0-alpha01"
HEIFWRITER = "1.1.0-alpha02"
HILT = "1.1.0-alpha03"
@@ -92,7 +92,7 @@
MEDIA2 = "1.3.0-alpha01"
MEDIAROUTER = "1.6.0-beta01"
METRICS = "1.0.0-alpha05"
-NAVIGATION = "2.7.0-beta02"
+NAVIGATION = "2.7.0-rc01"
PAGING = "3.3.0-alpha01"
PALETTE = "1.1.0-alpha01"
PERCENTLAYOUT = "1.1.0-alpha01"
@@ -109,7 +109,7 @@
RECYCLERVIEW_SELECTION = "1.2.0-alpha02"
REMOTECALLBACK = "1.0.0-alpha02"
RESOURCEINSPECTION = "1.1.0-alpha01"
-ROOM = "2.6.0-alpha02"
+ROOM = "2.6.0-alpha03"
SAFEPARCEL = "1.0.0-alpha01"
SAVEDSTATE = "1.3.0-alpha01"
SECURITY = "1.1.0-alpha07"
@@ -123,7 +123,7 @@
SLICE_BUILDERS_KTX = "1.0.0-alpha08"
SLICE_REMOTECALLBACK = "1.0.0-alpha01"
SLIDINGPANELAYOUT = "1.3.0-alpha01"
-SQLITE = "2.4.0-alpha02"
+SQLITE = "2.4.0-alpha03"
SQLITE_INSPECTOR = "2.1.0-alpha01"
STABLE_AIDL = "1.0.0-alpha01"
STARTUP = "1.2.0-alpha03"
@@ -150,12 +150,12 @@
WEAR_INPUT_TESTING = "1.2.0-alpha03"
WEAR_ONGOING = "1.1.0-alpha01"
WEAR_PHONE_INTERACTIONS = "1.1.0-alpha04"
-WEAR_PROTOLAYOUT = "1.0.0-beta01"
+WEAR_PROTOLAYOUT = "1.0.0-rc01"
WEAR_REMOTE_INTERACTIONS = "1.1.0-alpha01"
-WEAR_TILES = "1.2.0-beta01"
+WEAR_TILES = "1.2.0-rc01"
WEAR_WATCHFACE = "1.2.0-alpha09"
WEBKIT = "1.8.0-beta01"
-WINDOW = "1.2.0-alpha03"
+WINDOW = "1.2.0-beta01"
WINDOW_EXTENSIONS = "1.2.0-beta01"
WINDOW_EXTENSIONS_CORE = "1.1.0-alpha01"
WINDOW_SIDECAR = "1.0.0-rc01"
diff --git a/lifecycle/lifecycle-common/src/test/java/androidx/lifecycle/observers/Base.java b/lifecycle/lifecycle-common/src/test/java/androidx/lifecycle/observers/Base.kt
similarity index 74%
rename from lifecycle/lifecycle-common/src/test/java/androidx/lifecycle/observers/Base.java
rename to lifecycle/lifecycle-common/src/test/java/androidx/lifecycle/observers/Base.kt
index fb21268..6118451 100644
--- a/lifecycle/lifecycle-common/src/test/java/androidx/lifecycle/observers/Base.java
+++ b/lifecycle/lifecycle-common/src/test/java/androidx/lifecycle/observers/Base.kt
@@ -14,15 +14,13 @@
* limitations under the License.
*/
-package androidx.lifecycle.observers;
+package androidx.lifecycle.observers
-import androidx.lifecycle.Lifecycle;
-import androidx.lifecycle.LifecycleObserver;
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleObserver
-@SuppressWarnings("deprecation")
-public class Base implements LifecycleObserver {
-
+open class Base : LifecycleObserver {
+ @Suppress("DEPRECATION")
@androidx.lifecycle.OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
- public void onCreate() {
- }
+ open fun onCreate() {}
}
diff --git a/lifecycle/lifecycle-common/src/test/java/androidx/lifecycle/observers/Base_LifecycleAdapter.java b/lifecycle/lifecycle-common/src/test/java/androidx/lifecycle/observers/Base_LifecycleAdapter.java
deleted file mode 100644
index d3eacca..0000000
--- a/lifecycle/lifecycle-common/src/test/java/androidx/lifecycle/observers/Base_LifecycleAdapter.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.lifecycle.observers;
-
-import androidx.annotation.NonNull;
-import androidx.lifecycle.GeneratedAdapter;
-import androidx.lifecycle.Lifecycle;
-import androidx.lifecycle.LifecycleOwner;
-import androidx.lifecycle.MethodCallsLogger;
-
-public class Base_LifecycleAdapter implements GeneratedAdapter {
-
- public Base_LifecycleAdapter(Base base) {
- }
-
- @Override
- public void callMethods(@NonNull LifecycleOwner source, @NonNull Lifecycle.Event event,
- boolean onAny, MethodCallsLogger logger) {
-
- }
-}
diff --git a/lifecycle/lifecycle-common/src/test/java/androidx/lifecycle/observers/Base_LifecycleAdapter.kt b/lifecycle/lifecycle-common/src/test/java/androidx/lifecycle/observers/Base_LifecycleAdapter.kt
new file mode 100644
index 0000000..d7c69e4
--- /dev/null
+++ b/lifecycle/lifecycle-common/src/test/java/androidx/lifecycle/observers/Base_LifecycleAdapter.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.lifecycle.observers
+
+import androidx.lifecycle.GeneratedAdapter
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.MethodCallsLogger
+
+@Suppress("UNUSED", "UNUSED_PARAMETER")
+class Base_LifecycleAdapter(base: Base) : GeneratedAdapter {
+ override fun callMethods(
+ source: LifecycleOwner,
+ event: Lifecycle.Event,
+ onAny: Boolean,
+ logger: MethodCallsLogger?
+ ) {}
+}
diff --git a/lifecycle/lifecycle-common/src/test/java/androidx/lifecycle/observers/DerivedSequence2.java b/lifecycle/lifecycle-common/src/test/java/androidx/lifecycle/observers/DerivedSequence2.kt
similarity index 77%
rename from lifecycle/lifecycle-common/src/test/java/androidx/lifecycle/observers/DerivedSequence2.java
rename to lifecycle/lifecycle-common/src/test/java/androidx/lifecycle/observers/DerivedSequence2.kt
index aeb87da..d9937cd 100644
--- a/lifecycle/lifecycle-common/src/test/java/androidx/lifecycle/observers/DerivedSequence2.java
+++ b/lifecycle/lifecycle-common/src/test/java/androidx/lifecycle/observers/DerivedSequence2.kt
@@ -14,15 +14,12 @@
* limitations under the License.
*/
-package androidx.lifecycle.observers;
+package androidx.lifecycle.observers
-import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.Lifecycle
-@SuppressWarnings("deprecation")
-public class DerivedSequence2 extends DerivedSequence1 {
-
+@Suppress("DEPRECATION")
+class DerivedSequence2 : DerivedSequence1() {
@androidx.lifecycle.OnLifecycleEvent(Lifecycle.Event.ON_STOP)
- void onStop() {
-
- }
+ fun onStop() {}
}
diff --git a/lifecycle/lifecycle-runtime/src/androidTest/java/androidx/lifecycle/ViewTreeLifecycleOwnerTest.java b/lifecycle/lifecycle-runtime/src/androidTest/java/androidx/lifecycle/ViewTreeLifecycleOwnerTest.java
deleted file mode 100644
index 2056c0f..0000000
--- a/lifecycle/lifecycle-runtime/src/androidTest/java/androidx/lifecycle/ViewTreeLifecycleOwnerTest.java
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * Copyright 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.lifecycle;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
-
-import android.content.Context;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.FrameLayout;
-
-import androidx.annotation.NonNull;
-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;
-
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class ViewTreeLifecycleOwnerTest {
- /**
- * Tests that a direct set/get on a single view survives a round trip
- */
- @Test
- public void setGetSameView() {
- final View v = new View(InstrumentationRegistry.getInstrumentation().getContext());
- final LifecycleOwner fakeOwner = new FakeLifecycleOwner();
-
- assertNull("initial LifecycleOwner expects null", ViewTreeLifecycleOwner.get(v));
-
- ViewTreeLifecycleOwner.set(v, fakeOwner);
-
- assertEquals("get the LifecycleOwner set directly", fakeOwner,
- ViewTreeLifecycleOwner.get(v));
- }
-
- /**
- * Tests that the owner set on a root of a subhierarchy is seen by both direct children
- * and other descendants
- */
- @Test
- public void getAncestorOwner() {
- final Context context = InstrumentationRegistry.getInstrumentation().getContext();
- final ViewGroup root = new FrameLayout(context);
- final ViewGroup parent = new FrameLayout(context);
- final View child = new View(context);
- root.addView(parent);
- parent.addView(child);
-
- assertNull("initial LifecycleOwner expects null", ViewTreeLifecycleOwner.get(child));
-
- final LifecycleOwner fakeOwner = new FakeLifecycleOwner();
- ViewTreeLifecycleOwner.set(root, fakeOwner);
-
- assertEquals("root sees owner", fakeOwner, ViewTreeLifecycleOwner.get(root));
- assertEquals("direct child sees owner", fakeOwner, ViewTreeLifecycleOwner.get(parent));
- assertEquals("grandchild sees owner", fakeOwner, ViewTreeLifecycleOwner.get(child));
- }
-
- /**
- * Tests that a new owner set between a root and a descendant is seen by the descendant
- * instead of the root value
- */
- @Test
- public void shadowedOwner() {
- final Context context = InstrumentationRegistry.getInstrumentation().getContext();
- final ViewGroup root = new FrameLayout(context);
- final ViewGroup parent = new FrameLayout(context);
- final View child = new View(context);
- root.addView(parent);
- parent.addView(child);
-
- assertNull("initial LifecycleOwner expects null", ViewTreeLifecycleOwner.get(child));
-
- final LifecycleOwner rootFakeOwner = new FakeLifecycleOwner();
- ViewTreeLifecycleOwner.set(root, rootFakeOwner);
-
- final LifecycleOwner parentFakeOwner = new FakeLifecycleOwner();
- ViewTreeLifecycleOwner.set(parent, parentFakeOwner);
-
- assertEquals("root sees owner", rootFakeOwner, ViewTreeLifecycleOwner.get(root));
- assertEquals("direct child sees owner", parentFakeOwner,
- ViewTreeLifecycleOwner.get(parent));
- assertEquals("grandchild sees owner", parentFakeOwner, ViewTreeLifecycleOwner.get(child));
- }
-
- static class FakeLifecycleOwner implements LifecycleOwner {
- @NonNull
- @Override
- public Lifecycle getLifecycle() {
- throw new UnsupportedOperationException("not a real LifecycleOwner");
- }
- }
-}
diff --git a/lifecycle/lifecycle-runtime/src/androidTest/java/androidx/lifecycle/ViewTreeLifecycleOwnerTest.kt b/lifecycle/lifecycle-runtime/src/androidTest/java/androidx/lifecycle/ViewTreeLifecycleOwnerTest.kt
new file mode 100644
index 0000000..3308c2b
--- /dev/null
+++ b/lifecycle/lifecycle-runtime/src/androidTest/java/androidx/lifecycle/ViewTreeLifecycleOwnerTest.kt
@@ -0,0 +1,129 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.lifecycle
+
+import android.view.View
+import android.widget.FrameLayout
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNull
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class ViewTreeLifecycleOwnerTest {
+ /**
+ * Tests that a direct set/get on a single view survives a round trip
+ */
+ @Test
+ fun setGetSameView() {
+ val v = View(getInstrumentation().context)
+ val fakeOwner = FakeLifecycleOwner()
+ assertNull("initial LifecycleOwner expects null", v.findViewTreeLifecycleOwner())
+ v.setViewTreeLifecycleOwner(fakeOwner)
+ assertEquals(
+ "get the LifecycleOwner set directly",
+ fakeOwner,
+ v.findViewTreeLifecycleOwner()
+ )
+ }
+
+ /**
+ * Tests that the owner set on a root of a sub-hierarchy is seen by both direct children
+ * and other descendants
+ */
+ @Test
+ fun getAncestorOwner() {
+ val context = getInstrumentation().context
+ val root = FrameLayout(context)
+ val parent = FrameLayout(context)
+ val child = View(context)
+ root.addView(parent)
+ parent.addView(child)
+ assertNull(
+ "initial LifecycleOwner expects null",
+ child.findViewTreeLifecycleOwner()
+ )
+ val fakeOwner = FakeLifecycleOwner()
+ root.setViewTreeLifecycleOwner(fakeOwner)
+ assertEquals(
+ "root sees owner",
+ fakeOwner,
+ root.findViewTreeLifecycleOwner()
+ )
+ assertEquals(
+ "direct child sees owner",
+ fakeOwner,
+ parent.findViewTreeLifecycleOwner()
+ )
+ assertEquals(
+ "grandchild sees owner",
+ fakeOwner,
+ child.findViewTreeLifecycleOwner()
+ )
+ }
+
+ /**
+ * Tests that a new owner set between a root and a descendant is seen by the descendant
+ * instead of the root value
+ */
+ @Test
+ fun shadowedOwner() {
+ val context = getInstrumentation().context
+ val root = FrameLayout(context)
+ val parent = FrameLayout(context)
+ val child = View(context)
+ root.addView(parent)
+ parent.addView(child)
+ assertNull(
+ "initial LifecycleOwner expects null",
+ child.findViewTreeLifecycleOwner()
+ )
+ val rootFakeOwner = FakeLifecycleOwner()
+ root.setViewTreeLifecycleOwner(rootFakeOwner)
+ val parentFakeOwner = FakeLifecycleOwner()
+ parent.setViewTreeLifecycleOwner(parentFakeOwner)
+ assertEquals(
+ "root sees owner",
+ rootFakeOwner,
+ root.findViewTreeLifecycleOwner()
+ )
+ assertEquals(
+ "direct child sees owner",
+ parentFakeOwner,
+ parent.findViewTreeLifecycleOwner()
+ )
+ assertEquals(
+ "grandchild sees owner",
+ parentFakeOwner,
+ child.findViewTreeLifecycleOwner()
+ )
+ }
+
+ internal class FakeLifecycleOwner : LifecycleOwner {
+ override val lifecycle = NoOpLifecycle()
+ }
+
+ internal class NoOpLifecycle : Lifecycle() {
+ override fun addObserver(observer: LifecycleObserver) {}
+ override fun removeObserver(observer: LifecycleObserver) {}
+ // use arbitrary State
+ override val currentState = State.RESUMED
+ }
+}
diff --git a/lifecycle/lifecycle-runtime/src/test/java/NoPackageObserver.java b/lifecycle/lifecycle-runtime/src/test/java/NoPackageObserver.kt
similarity index 68%
rename from lifecycle/lifecycle-runtime/src/test/java/NoPackageObserver.java
rename to lifecycle/lifecycle-runtime/src/test/java/NoPackageObserver.kt
index 60bc171..813087d 100644
--- a/lifecycle/lifecycle-runtime/src/test/java/NoPackageObserver.java
+++ b/lifecycle/lifecycle-runtime/src/test/java/NoPackageObserver.kt
@@ -14,13 +14,13 @@
* limitations under the License.
*/
-import static androidx.lifecycle.Lifecycle.Event.ON_CREATE;
+@file:Suppress("DEPRECATION")
-import androidx.lifecycle.LifecycleObserver;
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleObserver
+import androidx.lifecycle.OnLifecycleEvent
-@SuppressWarnings("deprecation")
-public class NoPackageObserver implements LifecycleObserver {
- @androidx.lifecycle.OnLifecycleEvent(ON_CREATE)
- void onCreate() {
- }
+open class NoPackageObserver : LifecycleObserver {
+ @OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
+ open fun onCreate() {}
}
diff --git a/lifecycle/lifecycle-runtime/src/test/java/NoPackageTest.java b/lifecycle/lifecycle-runtime/src/test/java/NoPackageTest.java
deleted file mode 100644
index 77ba93b..0000000
--- a/lifecycle/lifecycle-runtime/src/test/java/NoPackageTest.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import static androidx.lifecycle.Lifecycle.Event.ON_CREATE;
-
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import androidx.lifecycle.Lifecycle;
-import androidx.lifecycle.LifecycleOwner;
-import androidx.lifecycle.LifecycleRegistry;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-@RunWith(JUnit4.class)
-public class NoPackageTest {
-
- private LifecycleOwner mLifecycleOwner;
- private Lifecycle mLifecycle;
- private LifecycleRegistry mRegistry;
-
- @Before
- public void init() {
- mLifecycleOwner = mock(LifecycleOwner.class);
- mLifecycle = mock(Lifecycle.class);
- when(mLifecycleOwner.getLifecycle()).thenReturn(mLifecycle);
- mRegistry = LifecycleRegistry.createUnsafe(mLifecycleOwner);
- }
-
- @Test
- public void testNoPackage() {
- NoPackageObserver observer = mock(NoPackageObserver.class);
- mRegistry.addObserver(observer);
- mRegistry.handleLifecycleEvent(ON_CREATE);
- verify(observer).onCreate();
- }
-
-}
-
diff --git a/lifecycle/lifecycle-runtime/src/test/java/NoPackageTest.kt b/lifecycle/lifecycle-runtime/src/test/java/NoPackageTest.kt
new file mode 100644
index 0000000..9494d19
--- /dev/null
+++ b/lifecycle/lifecycle-runtime/src/test/java/NoPackageTest.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.LifecycleRegistry
+import androidx.lifecycle.LifecycleRegistry.Companion.createUnsafe
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
+
+@RunWith(JUnit4::class)
+class NoPackageTest {
+ private lateinit var lifecycleOwner: LifecycleOwner
+ private lateinit var lifecycle: Lifecycle
+ private lateinit var registry: LifecycleRegistry
+
+ @Before
+ fun init() {
+ lifecycleOwner = mock(LifecycleOwner::class.java)
+ lifecycle = mock(Lifecycle::class.java)
+ `when`(lifecycleOwner.lifecycle).thenReturn(lifecycle)
+ registry = createUnsafe(lifecycleOwner)
+ }
+
+ @Test
+ fun testNoPackage() {
+ val observer = mock(NoPackageObserver::class.java)
+ registry.addObserver(observer)
+ registry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE)
+ verify(observer).onCreate()
+ }
+}
diff --git a/lifecycle/lifecycle-service/src/androidTest/java/androidx/lifecycle/ServiceLifecycleTest.java b/lifecycle/lifecycle-service/src/androidTest/java/androidx/lifecycle/ServiceLifecycleTest.java
deleted file mode 100644
index 04055db..0000000
--- a/lifecycle/lifecycle-service/src/androidTest/java/androidx/lifecycle/ServiceLifecycleTest.java
+++ /dev/null
@@ -1,251 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.lifecycle;
-
-import static androidx.lifecycle.Lifecycle.Event.ON_CREATE;
-import static androidx.lifecycle.Lifecycle.Event.ON_DESTROY;
-import static androidx.lifecycle.Lifecycle.Event.ON_START;
-import static androidx.lifecycle.Lifecycle.Event.ON_STOP;
-
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.MatcherAssert.assertThat;
-
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.ServiceConnection;
-import android.os.IBinder;
-
-import androidx.lifecycle.Lifecycle.Event;
-import androidx.lifecycle.service.TestService;
-import androidx.localbroadcastmanager.content.LocalBroadcastManager;
-import androidx.test.core.app.ApplicationProvider;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.MediumTest;
-import androidx.test.platform.app.InstrumentationRegistry;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-
-@SuppressWarnings("deprecation")
-@MediumTest
-@RunWith(AndroidJUnit4.class)
-public class ServiceLifecycleTest {
-
- private static final int RETRY_NUMBER = 5;
- private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(1);
-
- private Intent mServiceIntent;
-
- private volatile List<Event> mLoggerEvents;
- private EventLogger mLogger;
-
- @Before
- public void setUp() {
- Context context = ApplicationProvider.getApplicationContext();
- mServiceIntent = new Intent(context, TestService.class);
- LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(context);
- IntentFilter intentFilter = new IntentFilter();
- intentFilter.addAction(TestService.ACTION_LOG_EVENT);
-
- // Overcautiousness: each EventLogger has its own events list, so one bad test won't spoil
- // others.
- mLoggerEvents = new ArrayList<>();
- mLogger = new EventLogger(mLoggerEvents);
- localBroadcastManager.registerReceiver(mLogger, intentFilter);
-
- }
-
- @After
- public void tearDown() {
- Context context = ApplicationProvider.getApplicationContext();
- LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(context);
- localBroadcastManager.unregisterReceiver(mLogger);
- mLogger = null;
- mLoggerEvents = null;
- }
-
- @Test
- public void testUnboundedService() throws TimeoutException, InterruptedException {
- Context context = ApplicationProvider.getApplicationContext();
- context.startService(mServiceIntent);
- awaitAndAssertEvents(ON_CREATE, ON_START);
- context.stopService(mServiceIntent);
- awaitAndAssertEvents(ON_CREATE, ON_START, ON_STOP, ON_DESTROY);
- }
-
- @Test
- public void testBoundedService() throws TimeoutException, InterruptedException {
- ServiceConnection connection = bindToService();
- awaitAndAssertEvents(ON_CREATE, ON_START);
- ApplicationProvider.getApplicationContext().unbindService(connection);
- awaitAndAssertEvents(ON_CREATE, ON_START, ON_STOP, ON_DESTROY);
- }
-
- @Test
- public void testStartBindUnbindStop() throws InterruptedException {
- Context context = ApplicationProvider.getApplicationContext();
- context.startService(mServiceIntent);
- awaitAndAssertEvents(ON_CREATE, ON_START);
-
- ServiceConnection connection = bindToService();
- // Precaution: give a chance to dispatch events
- InstrumentationRegistry.getInstrumentation().waitForIdleSync();
- // still the same events
- awaitAndAssertEvents(ON_CREATE, ON_START);
-
- context.unbindService(connection);
- // Precaution: give a chance to dispatch events
- InstrumentationRegistry.getInstrumentation().waitForIdleSync();
- // service is still started (stopServices/stopSelf weren't called)
- awaitAndAssertEvents(ON_CREATE, ON_START);
-
- context.stopService(mServiceIntent);
- awaitAndAssertEvents(ON_CREATE, ON_START, ON_STOP, ON_DESTROY);
- }
-
- @Test
- public void testStartBindStopUnbind() throws InterruptedException {
- Context context = ApplicationProvider.getApplicationContext();
- context.startService(mServiceIntent);
- awaitAndAssertEvents(ON_CREATE, ON_START);
-
- ServiceConnection connection = bindToService();
- // Precaution: give a chance to dispatch events
- InstrumentationRegistry.getInstrumentation().waitForIdleSync();
- // still the same events
- awaitAndAssertEvents(ON_CREATE, ON_START);
-
- context.stopService(mServiceIntent);
- // Precaution: give a chance to dispatch events
- InstrumentationRegistry.getInstrumentation().waitForIdleSync();
- // service is still bound
- awaitAndAssertEvents(ON_CREATE, ON_START);
-
- context.unbindService(connection);
- awaitAndAssertEvents(ON_CREATE, ON_START,
- ON_STOP, ON_DESTROY);
- }
-
- @Test
- public void testBindStartUnbindStop() throws InterruptedException {
- Context context = ApplicationProvider.getApplicationContext();
- ServiceConnection connection = bindToService();
- awaitAndAssertEvents(ON_CREATE, ON_START);
-
-
- context.startService(mServiceIntent);
- // Precaution: give a chance to dispatch events
- InstrumentationRegistry.getInstrumentation().waitForIdleSync();
- // still the same events
- awaitAndAssertEvents(ON_CREATE, ON_START);
-
- context.unbindService(connection);
- // Precaution: give a chance to dispatch events
- InstrumentationRegistry.getInstrumentation().waitForIdleSync();
- // service is still started (stopServices/stopSelf weren't called)
- awaitAndAssertEvents(ON_CREATE, ON_START);
-
- context.stopService(mServiceIntent);
- awaitAndAssertEvents(ON_CREATE, ON_START,
- ON_STOP, ON_DESTROY);
- }
-
- @Test
- public void testBindStartStopUnbind() throws InterruptedException {
- Context context = ApplicationProvider.getApplicationContext();
- ServiceConnection connection = bindToService();
- awaitAndAssertEvents(ON_CREATE, ON_START);
-
- context.startService(mServiceIntent);
- // Precaution: give a chance to dispatch events
- InstrumentationRegistry.getInstrumentation().waitForIdleSync();
- // still the same events
- awaitAndAssertEvents(ON_CREATE, ON_START);
-
- context.stopService(mServiceIntent);
- // Precaution: give a chance to dispatch events
- InstrumentationRegistry.getInstrumentation().waitForIdleSync();
- // service is still bound
- awaitAndAssertEvents(ON_CREATE, ON_START);
-
- context.unbindService(connection);
- awaitAndAssertEvents(ON_CREATE, ON_START,
- ON_STOP, ON_DESTROY);
- }
-
- // can't use ServiceTestRule because it proxies connection, so we can't use unbindService method
- private ServiceConnection bindToService() throws InterruptedException {
- Context context = ApplicationProvider.getApplicationContext();
- final CountDownLatch latch = new CountDownLatch(1);
- ServiceConnection connection = new ServiceConnection() {
- @Override
- public void onServiceConnected(ComponentName name, IBinder service) {
- latch.countDown();
- }
-
- @Override
- public void onServiceDisconnected(ComponentName name) {
-
- }
- };
-
- boolean success = context.bindService(mServiceIntent, connection, Context.BIND_AUTO_CREATE);
- assertThat(success, is(true));
- boolean awaited = latch.await(TIMEOUT, TimeUnit.MILLISECONDS);
- assertThat(awaited, is(true));
- return connection;
- }
-
- private void awaitAndAssertEvents(Event... events) throws InterruptedException {
- //noinspection SynchronizeOnNonFinalField
- synchronized (mLoggerEvents) {
- int retryCount = 0;
- while (mLoggerEvents.size() < events.length && retryCount++ < RETRY_NUMBER) {
- mLoggerEvents.wait(TIMEOUT);
- }
- assertThat(mLoggerEvents, is(Arrays.asList(events)));
- }
- }
-
- private static class EventLogger extends BroadcastReceiver {
- private final List<Event> mLoggerEvents;
-
- private EventLogger(List<Event> loggerEvents) {
- mLoggerEvents = loggerEvents;
- }
-
- @Override
- public void onReceive(Context context, Intent intent) {
- synchronized (mLoggerEvents) {
- mLoggerEvents.add((Event) intent.getSerializableExtra(TestService.EXTRA_KEY_EVENT));
- mLoggerEvents.notifyAll();
- }
- }
- }
-}
diff --git a/lifecycle/lifecycle-service/src/androidTest/java/androidx/lifecycle/ServiceLifecycleTest.kt b/lifecycle/lifecycle-service/src/androidTest/java/androidx/lifecycle/ServiceLifecycleTest.kt
new file mode 100644
index 0000000..439fdc0
--- /dev/null
+++ b/lifecycle/lifecycle-service/src/androidTest/java/androidx/lifecycle/ServiceLifecycleTest.kt
@@ -0,0 +1,230 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.lifecycle
+
+import android.content.BroadcastReceiver
+import android.content.ComponentName
+import android.content.Context
+import android.content.Context.BIND_AUTO_CREATE
+import android.content.Intent
+import android.content.IntentFilter
+import android.content.ServiceConnection
+import android.os.IBinder
+import androidx.lifecycle.Lifecycle.Event
+import androidx.lifecycle.Lifecycle.Event.ON_CREATE
+import androidx.lifecycle.Lifecycle.Event.ON_DESTROY
+import androidx.lifecycle.Lifecycle.Event.ON_START
+import androidx.lifecycle.Lifecycle.Event.ON_STOP
+import androidx.lifecycle.service.TestService
+import androidx.lifecycle.service.TestService.ACTION_LOG_EVENT
+import androidx.lifecycle.service.TestService.EXTRA_KEY_EVENT
+import androidx.localbroadcastmanager.content.LocalBroadcastManager.getInstance
+import androidx.test.core.app.ApplicationProvider.getApplicationContext
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit.MILLISECONDS
+import java.util.concurrent.TimeUnit.SECONDS
+import java.util.concurrent.TimeoutException
+import java.util.concurrent.locks.ReentrantLock
+import kotlin.concurrent.withLock
+import org.hamcrest.CoreMatchers.`is`
+import org.hamcrest.MatcherAssert.assertThat
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@Suppress("deprecation")
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+class ServiceLifecycleTest {
+ private lateinit var serviceIntent: Intent
+
+ @Volatile
+ private var loggerEvents = mutableListOf<Event?>()
+ private lateinit var logger: EventLogger
+
+ @Before
+ fun setUp() {
+ val context = getApplicationContext<Context>()
+ serviceIntent = Intent(context, TestService::class.java)
+ val localBroadcastManager = getInstance(context)
+ val intentFilter = IntentFilter()
+ intentFilter.addAction(ACTION_LOG_EVENT)
+
+ // Over-cautiousness: each EventLogger has its own events list, so one bad test won't spoil
+ // others.
+ loggerEvents = ArrayList()
+ logger = EventLogger(loggerEvents)
+ localBroadcastManager.registerReceiver(logger, intentFilter)
+ }
+
+ @After
+ fun tearDown() {
+ val context = getApplicationContext<Context>()
+ val localBroadcastManager = getInstance(context)
+ localBroadcastManager.unregisterReceiver(logger)
+ loggerEvents.clear()
+ }
+
+ @Test
+ @Throws(TimeoutException::class, InterruptedException::class)
+ fun testUnboundedService() {
+ val context = getApplicationContext<Context>()
+ context.startService(serviceIntent)
+ awaitAndAssertEvents(ON_CREATE, ON_START)
+ context.stopService(serviceIntent)
+ awaitAndAssertEvents(ON_CREATE, ON_START, ON_STOP, ON_DESTROY)
+ }
+
+ @Test
+ @Throws(TimeoutException::class, InterruptedException::class)
+ fun testBoundedService() {
+ val connection = bindToService()
+ awaitAndAssertEvents(ON_CREATE, ON_START)
+ getApplicationContext<Context>().unbindService(connection)
+ awaitAndAssertEvents(ON_CREATE, ON_START, ON_STOP, ON_DESTROY)
+ }
+
+ @Test
+ @Throws(InterruptedException::class)
+ fun testStartBindUnbindStop() {
+ val context = getApplicationContext<Context>()
+ context.startService(serviceIntent)
+ awaitAndAssertEvents(ON_CREATE, ON_START)
+ val connection = bindToService()
+ // Precaution: give a chance to dispatch events
+ getInstrumentation().waitForIdleSync()
+ // still the same events
+ awaitAndAssertEvents(ON_CREATE, ON_START)
+ context.unbindService(connection)
+ // Precaution: give a chance to dispatch events
+ getInstrumentation().waitForIdleSync()
+ // service is still started (stopServices/stopSelf weren't called)
+ awaitAndAssertEvents(ON_CREATE, ON_START)
+ context.stopService(serviceIntent)
+ awaitAndAssertEvents(ON_CREATE, ON_START, ON_STOP, ON_DESTROY)
+ }
+
+ @Test
+ @Throws(InterruptedException::class)
+ fun testStartBindStopUnbind() {
+ val context = getApplicationContext<Context>()
+ context.startService(serviceIntent)
+ awaitAndAssertEvents(ON_CREATE, ON_START)
+ val connection = bindToService()
+ // Precaution: give a chance to dispatch events
+ getInstrumentation().waitForIdleSync()
+ // still the same events
+ awaitAndAssertEvents(ON_CREATE, ON_START)
+ context.stopService(serviceIntent)
+ // Precaution: give a chance to dispatch events
+ getInstrumentation().waitForIdleSync()
+ // service is still bound
+ awaitAndAssertEvents(ON_CREATE, ON_START)
+ context.unbindService(connection)
+ awaitAndAssertEvents(ON_CREATE, ON_START, ON_STOP, ON_DESTROY)
+ }
+
+ @Test
+ @Throws(InterruptedException::class)
+ fun testBindStartUnbindStop() {
+ val context = getApplicationContext<Context>()
+ val connection = bindToService()
+ awaitAndAssertEvents(ON_CREATE, ON_START)
+ context.startService(serviceIntent)
+ // Precaution: give a chance to dispatch events
+ getInstrumentation().waitForIdleSync()
+ // still the same events
+ awaitAndAssertEvents(ON_CREATE, ON_START)
+ context.unbindService(connection)
+ // Precaution: give a chance to dispatch events
+ getInstrumentation().waitForIdleSync()
+ // service is still started (stopServices/stopSelf weren't called)
+ awaitAndAssertEvents(ON_CREATE, ON_START)
+ context.stopService(serviceIntent)
+ awaitAndAssertEvents(ON_CREATE, ON_START, ON_STOP, ON_DESTROY)
+ }
+
+ @Test
+ @Throws(InterruptedException::class)
+ fun testBindStartStopUnbind() {
+ val context = getApplicationContext<Context>()
+ val connection = bindToService()
+ awaitAndAssertEvents(ON_CREATE, ON_START)
+ context.startService(serviceIntent)
+ // Precaution: give a chance to dispatch events
+ getInstrumentation().waitForIdleSync()
+ // still the same events
+ awaitAndAssertEvents(ON_CREATE, ON_START)
+ context.stopService(serviceIntent)
+ // Precaution: give a chance to dispatch events
+ getInstrumentation().waitForIdleSync()
+ // service is still bound
+ awaitAndAssertEvents(ON_CREATE, ON_START)
+ context.unbindService(connection)
+ awaitAndAssertEvents(ON_CREATE, ON_START, ON_STOP, ON_DESTROY)
+ }
+
+ // can't use ServiceTestRule because it proxies connection, so we can't use unbindService method
+ @Throws(InterruptedException::class)
+ private fun bindToService(): ServiceConnection {
+ val context = getApplicationContext<Context>()
+ val latch = CountDownLatch(1)
+ val connection = object : ServiceConnection {
+ override fun onServiceConnected(name: ComponentName, service: IBinder) {
+ latch.countDown()
+ }
+
+ override fun onServiceDisconnected(name: ComponentName) {}
+ }
+ val success = context.bindService(serviceIntent, connection, BIND_AUTO_CREATE)
+ assertThat(success, `is`(true))
+ val awaited = latch.await(TIMEOUT, MILLISECONDS)
+ assertThat(awaited, `is`(true))
+ return connection
+ }
+
+ @Throws(InterruptedException::class)
+ private fun awaitAndAssertEvents(vararg events: Event) {
+ lock.withLock {
+ var retryCount = 0
+ while (loggerEvents.size < events.size && retryCount++ < RETRY_NUMBER) {
+ condition.await(TIMEOUT, SECONDS)
+ }
+ assertThat(loggerEvents, `is`(listOf(*events)))
+ }
+ }
+
+ private class EventLogger(private val loggerEvents: MutableList<Event?>) :
+ BroadcastReceiver() {
+ override fun onReceive(context: Context, intent: Intent) {
+ lock.withLock {
+ loggerEvents.add(intent.getSerializableExtra(EXTRA_KEY_EVENT) as Event)
+ condition.signalAll()
+ }
+ }
+ }
+
+ companion object {
+ private const val RETRY_NUMBER = 5
+ private val TIMEOUT = SECONDS.toMillis(1)
+ private val lock = ReentrantLock()
+ private val condition = lock.newCondition()
+ }
+}
diff --git a/media/media/build.gradle b/media/media/build.gradle
index 1523950..b1b873f 100644
--- a/media/media/build.gradle
+++ b/media/media/build.gradle
@@ -39,6 +39,10 @@
buildFeatures {
aidl = true
}
+ lint {
+ // Temporarily disabled due to flakiness (see b/291607684).
+ disable "RequireUnstableAidlAnnotation"
+ }
sourceSets {
main.java.srcDirs += [
]
diff --git a/media/media/lint-baseline.xml b/media/media/lint-baseline.xml
index 0d822ce..de43ef9 100644
--- a/media/media/lint-baseline.xml
+++ b/media/media/lint-baseline.xml
@@ -2,114 +2,6 @@
<issues format="6" by="lint 8.1.0-beta05" type="baseline" client="gradle" dependencies="false" name="AGP (8.1.0-beta05)" variant="all" version="8.1.0-beta05">
<issue
- id="PrereleaseSdkCoreDependency"
- message="Prelease SDK check isAtLeastU cannot be called as this project has a versioned dependency on androidx.core:core"
- errorLine1=" if (BuildCompat.isAtLeastU()) {"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/media/app/NotificationCompat.java"/>
- </issue>
-
- <issue
- id="PrereleaseSdkCoreDependency"
- message="Prelease SDK check isAtLeastU cannot be called as this project has a versioned dependency on androidx.core:core"
- errorLine1=" if (BuildCompat.isAtLeastU()) {"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/media/app/NotificationCompat.java"/>
- </issue>
-
- <issue
- id="RequireUnstableAidlAnnotation"
- message="Unstable AIDL files must be annotated with `@RequiresOptIn` marker"
- errorLine1="oneway interface IMediaControllerCallback {"
- errorLine2="^">
- <location
- file="src/main/aidl/android/support/v4/media/session/IMediaControllerCallback.aidl"/>
- </issue>
-
- <issue
- id="RequireUnstableAidlAnnotation"
- message="Unstable AIDL files must be annotated with `@RequiresOptIn` marker"
- errorLine1="interface IMediaSession {"
- errorLine2="^">
- <location
- file="src/main/aidl/android/support/v4/media/session/IMediaSession.aidl"/>
- </issue>
-
- <issue
- id="RequireUnstableAidlAnnotation"
- message="Unstable AIDL files must be annotated with `@RequiresOptIn` marker"
- errorLine1="@JavaOnlyStableParcelable parcelable MediaDescriptionCompat;"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/aidl/android/support/v4/media/MediaDescriptionCompat.aidl"/>
- </issue>
-
- <issue
- id="RequireUnstableAidlAnnotation"
- message="Unstable AIDL files must be annotated with `@RequiresOptIn` marker"
- errorLine1="@JavaOnlyStableParcelable parcelable MediaMetadataCompat;"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/aidl/android/support/v4/media/MediaMetadataCompat.aidl"/>
- </issue>
-
- <issue
- id="RequireUnstableAidlAnnotation"
- message="Unstable AIDL files must be annotated with `@RequiresOptIn` marker"
- errorLine1="@JavaOnlyStableParcelable parcelable MediaSessionCompat.Token;"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/aidl/android/support/v4/media/session/MediaSessionCompat.aidl"/>
- </issue>
-
- <issue
- id="RequireUnstableAidlAnnotation"
- message="Unstable AIDL files must be annotated with `@RequiresOptIn` marker"
- errorLine1="@JavaOnlyStableParcelable parcelable MediaSessionCompat.QueueItem;"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/aidl/android/support/v4/media/session/MediaSessionCompat.aidl"/>
- </issue>
-
- <issue
- id="RequireUnstableAidlAnnotation"
- message="Unstable AIDL files must be annotated with `@RequiresOptIn` marker"
- errorLine1="@JavaOnlyStableParcelable parcelable MediaSessionCompat.ResultReceiverWrapper;"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/aidl/android/support/v4/media/session/MediaSessionCompat.aidl"/>
- </issue>
-
- <issue
- id="RequireUnstableAidlAnnotation"
- message="Unstable AIDL files must be annotated with `@RequiresOptIn` marker"
- errorLine1="@JavaOnlyStableParcelable parcelable ParcelableVolumeInfo;"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/aidl/android/support/v4/media/session/ParcelableVolumeInfo.aidl"/>
- </issue>
-
- <issue
- id="RequireUnstableAidlAnnotation"
- message="Unstable AIDL files must be annotated with `@RequiresOptIn` marker"
- errorLine1="@JavaOnlyStableParcelable parcelable PlaybackStateCompat;"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/aidl/android/support/v4/media/session/PlaybackStateCompat.aidl"/>
- </issue>
-
- <issue
- id="RequireUnstableAidlAnnotation"
- message="Unstable AIDL files must be annotated with `@RequiresOptIn` marker"
- errorLine1="@JavaOnlyStableParcelable parcelable RatingCompat;"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/aidl/android/support/v4/media/RatingCompat.aidl"/>
- </issue>
-
- <issue
id="LambdaLast"
message="Functional interface parameters (such as parameter 1, "listener", in androidx.media.AudioFocusRequestCompat.Builder.setOnAudioFocusChangeListener) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions"
errorLine1=" @NonNull OnAudioFocusChangeListener listener, @NonNull Handler handler) {"
diff --git a/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSessionServiceTest.java b/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSessionServiceTest.java
index bdcfc8f..3ba9782 100644
--- a/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSessionServiceTest.java
+++ b/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSessionServiceTest.java
@@ -46,6 +46,7 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Test;
import java.util.ArrayList;
@@ -163,6 +164,7 @@
* can return different sessions for different controllers.
*/
@Test
+ @Ignore("Flaky: b/291281118")
public void onGetSession_returnsDifferentSessions() {
final List<SessionToken> tokens = new ArrayList<>();
TestServiceRegistry.getInstance().setOnGetSessionHandler(
diff --git a/mediarouter/mediarouter/src/androidTest/java/androidx/mediarouter/media/MediaRouter2TestActivity.java b/mediarouter/mediarouter/src/androidTest/java/androidx/mediarouter/media/MediaRouter2TestActivity.java
index 660fe42..74cd202 100644
--- a/mediarouter/mediarouter/src/androidTest/java/androidx/mediarouter/media/MediaRouter2TestActivity.java
+++ b/mediarouter/mediarouter/src/androidTest/java/androidx/mediarouter/media/MediaRouter2TestActivity.java
@@ -19,6 +19,7 @@
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
+import android.os.Build;
import android.os.Bundle;
import android.view.WindowManager;
@@ -44,8 +45,10 @@
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- setTurnScreenOn(true);
- setShowWhenLocked(true);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
+ setTurnScreenOn(true);
+ setShowWhenLocked(true);
+ }
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}
}
diff --git a/mediarouter/mediarouter/src/androidTest/java/androidx/mediarouter/media/MediaRouterInitializationTest.java b/mediarouter/mediarouter/src/androidTest/java/androidx/mediarouter/media/MediaRouterInitializationTest.java
index a1c7ea0..f455714 100644
--- a/mediarouter/mediarouter/src/androidTest/java/androidx/mediarouter/media/MediaRouterInitializationTest.java
+++ b/mediarouter/mediarouter/src/androidTest/java/androidx/mediarouter/media/MediaRouterInitializationTest.java
@@ -43,24 +43,23 @@
@Test
@MediumTest
public void testEmptyUserRoute() throws Exception {
- getInstrumentation().runOnMainSync(new Runnable() {
- @Override
- public void run() {
- final Context context = getApplicationContext();
- android.media.MediaRouter router =
- (android.media.MediaRouter) context.getSystemService(
- Context.MEDIA_ROUTER_SERVICE);
+ getInstrumentation()
+ .runOnMainSync(
+ () -> {
+ final Context context = getApplicationContext();
+ android.media.MediaRouter router =
+ (android.media.MediaRouter)
+ context.getSystemService(Context.MEDIA_ROUTER_SERVICE);
- // Add empty user route
- android.media.MediaRouter.RouteCategory category =
- router.createRouteCategory("", false);
- android.media.MediaRouter.UserRouteInfo routeInfo =
- router.createUserRoute(category);
- router.addUserRoute(routeInfo);
+ // Add empty user route
+ android.media.MediaRouter.RouteCategory category =
+ router.createRouteCategory("", false);
+ android.media.MediaRouter.UserRouteInfo routeInfo =
+ router.createUserRoute(category);
+ router.addUserRoute(routeInfo);
- MediaRouter mediaRouter = MediaRouter.getInstance(context);
- assertTrue(mediaRouter.getDefaultRoute() != null);
- }
- });
+ MediaRouter mediaRouter = MediaRouter.getInstance(context);
+ assertTrue(mediaRouter.getDefaultRoute() != null);
+ });
}
}
diff --git a/mediarouter/mediarouter/src/androidTest/java/androidx/mediarouter/media/MediaRouterTest.java b/mediarouter/mediarouter/src/androidTest/java/androidx/mediarouter/media/MediaRouterTest.java
index 0df2780..4f37053 100644
--- a/mediarouter/mediarouter/src/androidTest/java/androidx/mediarouter/media/MediaRouterTest.java
+++ b/mediarouter/mediarouter/src/androidTest/java/androidx/mediarouter/media/MediaRouterTest.java
@@ -72,26 +72,20 @@
@Before
public void setUp() throws Exception {
resetActiveAndPassiveScanCountDownLatches();
- getInstrumentation().runOnMainSync(new Runnable() {
- @Override
- public void run() {
- mContext = getApplicationContext();
- mRouter = MediaRouter.getInstance(mContext);
- mSession = new MediaSessionCompat(mContext, SESSION_TAG);
- mProvider = new MediaRouteProviderImpl(mContext);
- }
- });
+ getInstrumentation()
+ .runOnMainSync(
+ () -> {
+ mContext = getApplicationContext();
+ mRouter = MediaRouter.getInstance(mContext);
+ mSession = new MediaSessionCompat(mContext, SESSION_TAG);
+ mProvider = new MediaRouteProviderImpl(mContext);
+ });
}
@After
public void tearDown() throws Exception {
mSession.release();
- getInstrumentation().runOnMainSync(new Runnable() {
- @Override
- public void run() {
- MediaRouterTestHelper.resetMediaRouter();
- }
- });
+ getInstrumentation().runOnMainSync(() -> MediaRouterTestHelper.resetMediaRouter());
}
/**
@@ -101,13 +95,12 @@
@Test
@SmallTest
public void setMediaSessionCompat_receivesCallbacks() throws Exception {
- getInstrumentation().runOnMainSync(new Runnable() {
- @Override
- public void run() {
- mSession.setCallback(mSessionCallback);
- mRouter.setMediaSessionCompat(mSession);
- }
- });
+ getInstrumentation()
+ .runOnMainSync(
+ () -> {
+ mSession.setCallback(mSessionCallback);
+ mRouter.setMediaSessionCompat(mSession);
+ });
MediaControllerCompat controller = mSession.getController();
MediaControllerCompat.TransportControls controls = controller.getTransportControls();
@@ -205,14 +198,15 @@
// Add the provider and callback.
resetActiveAndPassiveScanCountDownLatches();
- getInstrumentation().runOnMainSync(new Runnable() {
- @Override
- public void run() {
- mRouter.addProvider(mProvider);
- mRouter.addCallback(selector, callback,
- MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN);
- }
- });
+ getInstrumentation()
+ .runOnMainSync(
+ () -> {
+ mRouter.addProvider(mProvider);
+ mRouter.addCallback(
+ selector,
+ callback,
+ MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN);
+ });
// Active scan should be true.
assertTrue(mActiveScanCountDownLatch.await(TIME_OUT_MS, TimeUnit.MILLISECONDS));
@@ -224,13 +218,13 @@
// Add the same callback again.
resetActiveAndPassiveScanCountDownLatches();
- getInstrumentation().runOnMainSync(new Runnable() {
- @Override
- public void run() {
- mRouter.addCallback(selector, callback,
- MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN);
- }
- });
+ getInstrumentation()
+ .runOnMainSync(
+ () ->
+ mRouter.addCallback(
+ selector,
+ callback,
+ MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN));
// Active scan should be true.
assertTrue(mActiveScanCountDownLatch.await(TIME_OUT_MS, TimeUnit.MILLISECONDS));
@@ -252,24 +246,25 @@
MediaRouterCallbackImpl callback2 = new MediaRouterCallbackImpl();
// Add the provider and the first callback.
- getInstrumentation().runOnMainSync(new Runnable() {
- @Override
- public void run() {
- mRouter.addProvider(mProvider);
- mRouter.addCallback(selector, callback1,
- MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN);
- }
- });
+ getInstrumentation()
+ .runOnMainSync(
+ () -> {
+ mRouter.addProvider(mProvider);
+ mRouter.addCallback(
+ selector,
+ callback1,
+ MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN);
+ });
// Wait for 5 seconds, add the second callback.
Thread.sleep(5000);
- getInstrumentation().runOnMainSync(new Runnable() {
- @Override
- public void run() {
- mRouter.addCallback(selector, callback2,
- MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN);
- }
- });
+ getInstrumentation()
+ .runOnMainSync(
+ () ->
+ mRouter.addCallback(
+ selector,
+ callback2,
+ MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN));
resetActiveAndPassiveScanCountDownLatches();
// Wait for active scan duration to nearly end, active scan flag should be true.
diff --git a/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRouter.java b/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRouter.java
index e3ecfdf..3091883 100644
--- a/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRouter.java
+++ b/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRouter.java
@@ -27,7 +27,6 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.IntentSender;
-import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Resources;
import android.net.Uri;
import android.os.Build;
@@ -100,8 +99,12 @@
static final String TAG = "MediaRouter";
static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
- @IntDef({UNSELECT_REASON_UNKNOWN, UNSELECT_REASON_DISCONNECTED, UNSELECT_REASON_STOPPED,
- UNSELECT_REASON_ROUTE_CHANGED})
+ @IntDef({
+ UNSELECT_REASON_UNKNOWN,
+ UNSELECT_REASON_DISCONNECTED,
+ UNSELECT_REASON_STOPPED,
+ UNSELECT_REASON_ROUTE_CHANGED
+ })
@Retention(RetentionPolicy.SOURCE)
@interface UnselectReason {}
@@ -149,14 +152,14 @@
final Context mContext;
final ArrayList<CallbackRecord> mCallbackRecords = new ArrayList<CallbackRecord>();
- @IntDef(flag = true,
+ @IntDef(
+ flag = true,
value = {
- CALLBACK_FLAG_PERFORM_ACTIVE_SCAN,
- CALLBACK_FLAG_REQUEST_DISCOVERY,
- CALLBACK_FLAG_UNFILTERED_EVENTS,
- CALLBACK_FLAG_FORCE_DISCOVERY
- }
- )
+ CALLBACK_FLAG_PERFORM_ACTIVE_SCAN,
+ CALLBACK_FLAG_REQUEST_DISCOVERY,
+ CALLBACK_FLAG_UNFILTERED_EVENTS,
+ CALLBACK_FLAG_FORCE_DISCOVERY
+ })
@Retention(RetentionPolicy.SOURCE)
private @interface CallbackFlags {}
@@ -313,7 +316,6 @@
* <li>{@link androidx.mediarouter.app.MediaRouteControllerDialog}
* <li>{@link androidx.mediarouter.app.MediaRouteDiscoveryFragment}
* </ul>
- *
*/
@RestrictTo(LIBRARY_GROUP)
public static void resetGlobalRouter() {
@@ -439,7 +441,6 @@
* <p>Must be called on the main thread.
*
* @return The selected route, which is guaranteed to never be null.
- *
* @see RouteInfo#getControlFilters
* @see RouteInfo#supportsControlCategory
* @see RouteInfo#supportsControlRequest
@@ -461,7 +462,6 @@
* @param selector The selector to match.
* @return The previously selected route if it matched the selector, otherwise the
* newly selected default route which is guaranteed to never be null.
- *
* @see MediaRouteSelector
* @see RouteInfo#matchesSelector
*/
@@ -578,26 +578,23 @@
/**
* Returns true if there is a route that matches the specified selector.
- * <p>
- * This method returns true if there are any available routes that match the
- * selector regardless of whether they are enabled or disabled. If the
- * {@link #AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE} flag is specified, then
- * the method will only consider non-default routes.
- * </p>
- * <p class="note">
- * On {@link ActivityManager#isLowRamDevice low-RAM devices} this method
- * will return true if it is possible to discover a matching route even if
- * discovery is not in progress or if no matching route has yet been found.
- * Use {@link #AVAILABILITY_FLAG_REQUIRE_MATCH} to require an actual match.
- * </p>
+ *
+ * <p>This method returns true if there are any available routes that match the selector
+ * regardless of whether they are enabled or disabled. If the {@link
+ * #AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE} flag is specified, then the method will only
+ * consider non-default routes.
+ *
+ * <p class="note">On {@link ActivityManager#isLowRamDevice low-RAM devices} this method will
+ * return true if it is possible to discover a matching route even if discovery is not in
+ * progress or if no matching route has yet been found. Use {@link
+ * #AVAILABILITY_FLAG_REQUIRE_MATCH} to require an actual match.
*
* <p>Must be called on the main thread.
*
* @param selector The selector to match.
- * @param flags Flags to control the determination of whether a route may be
- * available. May be zero or some combination of
- * {@link #AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE} and
- * {@link #AVAILABILITY_FLAG_REQUIRE_MATCH}.
+ * @param flags Flags to control the determination of whether a route may be available. May be
+ * zero or some combination of {@link #AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE} and {@link
+ * #AVAILABILITY_FLAG_REQUIRE_MATCH}.
* @return True if a matching route may be available.
*/
@MainThread
@@ -610,17 +607,16 @@
}
/**
- * Registers a callback to discover routes that match the selector and to receive
- * events when they change.
- * <p>
- * This is a convenience method that has the same effect as calling
- * {@link #addCallback(MediaRouteSelector, Callback, int)} without flags.
- * </p>
+ * Registers a callback to discover routes that match the selector and to receive events when
+ * they change.
+ *
+ * <p>This is a convenience method that has the same effect as calling {@link
+ * #addCallback(MediaRouteSelector, Callback, int)} without flags.
*
* <p>Must be called on the main thread.
*
- * @param selector A route selector that indicates the kinds of routes that the
- * callback would like to discover.
+ * @param selector A route selector that indicates the kinds of routes that the callback would
+ * like to discover.
* @param callback The callback to add.
* @see #removeCallback
*/
@@ -630,49 +626,46 @@
}
/**
- * Registers a callback to discover routes that match the selector and to receive
- * events when they change.
- * <p>
- * The selector describes the kinds of routes that the application wants to
- * discover. For example, if the application wants to use
- * live audio routes then it should include the
- * {@link MediaControlIntent#CATEGORY_LIVE_AUDIO live audio media control intent category}
- * in its selector when it adds a callback to the media router.
- * The selector may include any number of categories.
- * </p><p>
- * If the callback has already been registered, then the selector is added to
- * the set of selectors being monitored by the callback.
- * </p><p>
- * By default, the callback will only be invoked for events that affect routes
- * that match the specified selector. Event filtering may be disabled by specifying
- * the {@link #CALLBACK_FLAG_UNFILTERED_EVENTS} flag when the callback is registered.
- * </p><p>
- * Applications should use the {@link #isRouteAvailable} method to determine
- * whether is it possible to discover a route with the desired capabilities
- * and therefore whether the media route button should be shown to the user.
- * </p><p>
- * The {@link #CALLBACK_FLAG_REQUEST_DISCOVERY} flag should be used while the application
- * is in the foreground to request that passive discovery be performed if there are
- * sufficient resources to allow continuous passive discovery.
- * On {@link ActivityManager#isLowRamDevice low-RAM devices} this flag will be
- * ignored to conserve resources.
- * </p><p>
- * The {@link #CALLBACK_FLAG_FORCE_DISCOVERY} flag should be used when
- * passive discovery absolutely must be performed, even on low-RAM devices.
- * This flag has a significant performance impact on low-RAM devices
- * since it may cause many media route providers to be started simultaneously.
- * It is much better to use {@link #CALLBACK_FLAG_REQUEST_DISCOVERY} instead to avoid
- * performing passive discovery on these devices altogether.
- * </p><p>
- * The {@link #CALLBACK_FLAG_PERFORM_ACTIVE_SCAN} flag should be used when the
- * media route chooser dialog is showing to confirm the presence of available
- * routes that the user may connect to. This flag may use substantially more
- * power. Once active scan is requested, it will be effective for 30 seconds and will be
- * suppressed after the delay. If you need active scan after this duration, you have to add
- * your callback again with the {@link #CALLBACK_FLAG_PERFORM_ACTIVE_SCAN} flag.
- * </p>
+ * Registers a callback to discover routes that match the selector and to receive events when
+ * they change.
+ *
+ * <p>The selector describes the kinds of routes that the application wants to discover. For
+ * example, if the application wants to use live audio routes then it should include the {@link
+ * MediaControlIntent#CATEGORY_LIVE_AUDIO live audio media control intent category} in its
+ * selector when it adds a callback to the media router. The selector may include any number of
+ * categories.
+ *
+ * <p>If the callback has already been registered, then the selector is added to the set of
+ * selectors being monitored by the callback.
+ *
+ * <p>By default, the callback will only be invoked for events that affect routes that match the
+ * specified selector. Event filtering may be disabled by specifying the {@link
+ * #CALLBACK_FLAG_UNFILTERED_EVENTS} flag when the callback is registered.
+ *
+ * <p>Applications should use the {@link #isRouteAvailable} method to determine whether is it
+ * possible to discover a route with the desired capabilities and therefore whether the media
+ * route button should be shown to the user.
+ *
+ * <p>The {@link #CALLBACK_FLAG_REQUEST_DISCOVERY} flag should be used while the application is
+ * in the foreground to request that passive discovery be performed if there are sufficient
+ * resources to allow continuous passive discovery. On {@link ActivityManager#isLowRamDevice
+ * low-RAM devices} this flag will be ignored to conserve resources.
+ *
+ * <p>The {@link #CALLBACK_FLAG_FORCE_DISCOVERY} flag should be used when passive discovery
+ * absolutely must be performed, even on low-RAM devices. This flag has a significant
+ * performance impact on low-RAM devices since it may cause many media route providers to be
+ * started simultaneously. It is much better to use {@link #CALLBACK_FLAG_REQUEST_DISCOVERY}
+ * instead to avoid performing passive discovery on these devices altogether.
+ *
+ * <p>The {@link #CALLBACK_FLAG_PERFORM_ACTIVE_SCAN} flag should be used when the media route
+ * chooser dialog is showing to confirm the presence of available routes that the user may
+ * connect to. This flag may use substantially more power. Once active scan is requested, it
+ * will be effective for 30 seconds and will be suppressed after the delay. If you need active
+ * scan after this duration, you have to add your callback again with the {@link
+ * #CALLBACK_FLAG_PERFORM_ACTIVE_SCAN} flag.
*
* <h3>Example</h3>
+ *
* <pre>
* public class MyActivity extends Activity {
* private MediaRouter mRouter;
@@ -730,17 +723,18 @@
*
* <p>Must be called on the main thread.
*
- * @param selector A route selector that indicates the kinds of routes that the
- * callback would like to discover.
+ * @param selector A route selector that indicates the kinds of routes that the callback would
+ * like to discover.
* @param callback The callback to add.
- * @param flags Flags to control the behavior of the callback.
- * May be zero or a combination of {@link #CALLBACK_FLAG_PERFORM_ACTIVE_SCAN} and
- * {@link #CALLBACK_FLAG_UNFILTERED_EVENTS}.
+ * @param flags Flags to control the behavior of the callback. May be zero or a combination of
+ * {@link #CALLBACK_FLAG_PERFORM_ACTIVE_SCAN} and {@link #CALLBACK_FLAG_UNFILTERED_EVENTS}.
* @see #removeCallback
*/
// TODO: Change the usages of addCallback() for changing flags when setCallbackFlags() is added.
@MainThread
- public void addCallback(@NonNull MediaRouteSelector selector, @NonNull Callback callback,
+ public void addCallback(
+ @NonNull MediaRouteSelector selector,
+ @NonNull Callback callback,
@CallbackFlags int flags) {
if (selector == null) {
throw new IllegalArgumentException("selector must not be null");
@@ -846,7 +840,6 @@
* <p>Must be called on the main thread.
*
* @param providerInstance The media route provider instance to add.
- *
* @see MediaRouteProvider
* @see #removeCallback
*/
@@ -873,7 +866,6 @@
* <p>Must be called on the main thread.
*
* @param providerInstance The media route provider instance to remove.
- *
* @see MediaRouteProvider
* @see #addCallback
*/
@@ -913,7 +905,8 @@
if (DEBUG) {
Log.d(TAG, "addRemoteControlClient: " + remoteControlClient);
}
- getGlobalRouter().addRemoteControlClient(remoteControlClient);
+ getGlobalRouter()
+ .addRemoteControlClient((android.media.RemoteControlClient) remoteControlClient);
}
/**
@@ -921,8 +914,7 @@
*
* <p>Must be called on the main thread.
*
- * @param remoteControlClient The {@link android.media.RemoteControlClient}
- * to unregister.
+ * @param remoteControlClient The {@link android.media.RemoteControlClient} to unregister.
*/
@MainThread
public void removeRemoteControlClient(@NonNull Object remoteControlClient) {
@@ -934,7 +926,8 @@
if (DEBUG) {
Log.d(TAG, "removeRemoteControlClient: " + remoteControlClient);
}
- getGlobalRouter().removeRemoteControlClient(remoteControlClient);
+ getGlobalRouter()
+ .removeRemoteControlClient((android.media.RemoteControlClient) remoteControlClient);
}
/**
@@ -1063,8 +1056,6 @@
return getGlobalRouter().isMediaTransferEnabled();
}
- /**
- */
@RestrictTo(LIBRARY)
public static boolean isGroupVolumeUxEnabled() {
if (sGlobal == null) {
@@ -1126,8 +1117,11 @@
private List<RouteInfo> mMemberRoutes = new ArrayList<>();
private Map<String, DynamicRouteDescriptor> mDynamicGroupDescriptors;
- @IntDef({CONNECTION_STATE_DISCONNECTED, CONNECTION_STATE_CONNECTING,
- CONNECTION_STATE_CONNECTED})
+ @IntDef({
+ CONNECTION_STATE_DISCONNECTED,
+ CONNECTION_STATE_CONNECTING,
+ CONNECTION_STATE_CONNECTED
+ })
@Retention(RetentionPolicy.SOURCE)
private @interface ConnectionState {}
@@ -1153,7 +1147,7 @@
*/
public static final int CONNECTION_STATE_CONNECTED = 2;
- @IntDef({PLAYBACK_TYPE_LOCAL,PLAYBACK_TYPE_REMOTE})
+ @IntDef({PLAYBACK_TYPE_LOCAL, PLAYBACK_TYPE_REMOTE})
@Retention(RetentionPolicy.SOURCE)
private @interface PlaybackType {}
@@ -1282,7 +1276,7 @@
*/
public static final int DEVICE_TYPE_GROUP = 1000;
- @IntDef({PLAYBACK_VOLUME_FIXED,PLAYBACK_VOLUME_VARIABLE})
+ @IntDef({PLAYBACK_VOLUME_FIXED, PLAYBACK_VOLUME_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
private @interface PlaybackVolume {}
@@ -1428,7 +1422,6 @@
* <p>Must be called on the main thread.
*
* @return True if this route is currently selected.
- *
* @see MediaRouter#getSelectedRoute
*/
// Note: Only one representative route can return true. For instance:
@@ -1447,7 +1440,6 @@
* <p>Must be called on the main thread.
*
* @return True if this route is the default route.
- *
* @see MediaRouter#getDefaultRoute
*/
@MainThread
@@ -1462,7 +1454,6 @@
* <p>Must be called on the main thread.
*
* @return True if this route is a bluetooth route.
- *
* @see MediaRouter#getBluetoothRoute
*/
@MainThread
@@ -1490,7 +1481,6 @@
*
* @return A list of intent filters that specifies the media control intents that
* this route supports.
- *
* @see MediaControlIntent
* @see #supportsControlCategory
* @see #supportsControlRequest
@@ -1520,22 +1510,20 @@
}
/**
- * Returns true if the route supports the specified
- * {@link MediaControlIntent media control} category.
- * <p>
- * Media control categories describe the capabilities of this route
- * such as whether it supports live audio streaming or remote playback.
- * </p>
+ * Returns true if the route supports the specified {@link MediaControlIntent media control}
+ * category.
+ *
+ * <p>Media control categories describe the capabilities of this route such as whether it
+ * supports live audio streaming or remote playback.
*
* <p>Must be called on the main thread.
*
- * @param category A {@link MediaControlIntent media control} category
- * such as {@link MediaControlIntent#CATEGORY_LIVE_AUDIO},
- * {@link MediaControlIntent#CATEGORY_LIVE_VIDEO},
- * {@link MediaControlIntent#CATEGORY_REMOTE_PLAYBACK}, or a provider-defined
- * media control category.
+ * @param category A {@link MediaControlIntent media control} category such as {@link
+ * MediaControlIntent#CATEGORY_LIVE_AUDIO}, {@link
+ * MediaControlIntent#CATEGORY_LIVE_VIDEO}, {@link
+ * MediaControlIntent#CATEGORY_REMOTE_PLAYBACK}, or a provider-defined media control
+ * category.
* @return True if the route supports the specified intent category.
- *
* @see MediaControlIntent
* @see #getControlFilters
*/
@@ -1546,9 +1534,8 @@
}
checkCallingThread();
- int count = mControlFilters.size();
- for (int i = 0; i < count; i++) {
- if (mControlFilters.get(i).hasCategory(category)) {
+ for (IntentFilter intentFilter: mControlFilters) {
+ if (intentFilter.hasCategory(category)) {
return true;
}
}
@@ -1556,24 +1543,22 @@
}
/**
- * Returns true if the route supports the specified
- * {@link MediaControlIntent media control} category and action.
- * <p>
- * Media control actions describe specific requests that an application
- * can ask a route to perform.
- * </p>
+ * Returns true if the route supports the specified {@link MediaControlIntent media control}
+ * category and action.
+ *
+ * <p>Media control actions describe specific requests that an application can ask a route
+ * to perform.
*
* <p>Must be called on the main thread.
*
- * @param category A {@link MediaControlIntent media control} category
- * such as {@link MediaControlIntent#CATEGORY_LIVE_AUDIO},
- * {@link MediaControlIntent#CATEGORY_LIVE_VIDEO},
- * {@link MediaControlIntent#CATEGORY_REMOTE_PLAYBACK}, or a provider-defined
- * media control category.
- * @param action A {@link MediaControlIntent media control} action
- * such as {@link MediaControlIntent#ACTION_PLAY}.
+ * @param category A {@link MediaControlIntent media control} category such as {@link
+ * MediaControlIntent#CATEGORY_LIVE_AUDIO}, {@link
+ * MediaControlIntent#CATEGORY_LIVE_VIDEO}, {@link
+ * MediaControlIntent#CATEGORY_REMOTE_PLAYBACK}, or a provider-defined media control
+ * category.
+ * @param action A {@link MediaControlIntent media control} action such as {@link
+ * MediaControlIntent#ACTION_PLAY}.
* @return True if the route supports the specified intent action.
- *
* @see MediaControlIntent
* @see #getControlFilters
*/
@@ -1587,10 +1572,8 @@
}
checkCallingThread();
- int count = mControlFilters.size();
- for (int i = 0; i < count; i++) {
- IntentFilter filter = mControlFilters.get(i);
- if (filter.hasCategory(category) && filter.hasAction(action)) {
+ for (IntentFilter intentFilter: mControlFilters) {
+ if (intentFilter.hasCategory(category) && intentFilter.hasAction(action)) {
return true;
}
}
@@ -1609,7 +1592,6 @@
*
* @param intent A {@link MediaControlIntent media control intent}.
* @return True if the route can handle the specified intent.
- *
* @see MediaControlIntent
* @see #getControlFilters
*/
@@ -1621,9 +1603,8 @@
checkCallingThread();
ContentResolver contentResolver = getGlobalRouter().getContentResolver();
- int count = mControlFilters.size();
- for (int i = 0; i < count; i++) {
- if (mControlFilters.get(i).match(contentResolver, intent, true, TAG) >= 0) {
+ for (IntentFilter intentFilter: mControlFilters) {
+ if (intentFilter.match(contentResolver, intent, true, TAG) >= 0) {
return true;
}
}
@@ -1631,27 +1612,25 @@
}
/**
- * Sends a {@link MediaControlIntent media control} request to be performed
- * asynchronously by the route's destination.
- * <p>
- * Media control requests are used to request the route to perform
- * actions such as starting remote playback of a media item.
- * </p><p>
- * This function may only be called on a selected route. Control requests
- * sent to unselected routes will fail.
- * </p>
+ * Sends a {@link MediaControlIntent media control} request to be performed asynchronously
+ * by the route's destination.
+ *
+ * <p>Media control requests are used to request the route to perform actions such as
+ * starting remote playback of a media item.
+ *
+ * <p>This function may only be called on a selected route. Control requests sent to
+ * unselected routes will fail.
*
* <p>Must be called on the main thread.
*
* @param intent A {@link MediaControlIntent media control intent}.
- * @param callback A {@link ControlRequestCallback} to invoke with the result
- * of the request, or null if no result is required.
- *
+ * @param callback A {@link ControlRequestCallback} to invoke with the result of the
+ * request, or null if no result is required.
* @see MediaControlIntent
*/
@MainThread
- public void sendControlRequest(@NonNull Intent intent,
- @Nullable ControlRequestCallback callback) {
+ public void sendControlRequest(
+ @NonNull Intent intent, @Nullable ControlRequestCallback callback) {
if (intent == null) {
throw new IllegalArgumentException("intent must not be null");
}
@@ -1690,8 +1669,7 @@
return mDeviceType;
}
- /**
- */
+ /** */
@RestrictTo(LIBRARY)
public boolean isDefaultOrBluetooth() {
if (isDefault() || mDeviceType == DEVICE_TYPE_BLUETOOTH) {
@@ -1753,11 +1731,9 @@
}
/**
- * Gets whether this route supports disconnecting without interrupting
- * playback.
+ * Gets whether this route supports disconnecting without interrupting playback.
*
- * @return True if this route can disconnect without stopping playback,
- * false otherwise.
+ * @return True if this route can disconnect without stopping playback, false otherwise.
*/
public boolean canDisconnect() {
return mCanDisconnect;
@@ -1826,7 +1802,6 @@
*
* @return The preferred presentation display to use when this route is
* selected or null if none.
- *
* @see MediaControlIntent#CATEGORY_LIVE_VIDEO
* @see android.app.Presentation
*/
@@ -2172,6 +2147,7 @@
DynamicGroupState(DynamicRouteDescriptor descriptor) {
mDynamicDescriptor = descriptor;
}
+
/**
* Gets the selection state of the route when the {@link MediaRouteProvider} of the
* route supports
@@ -2186,22 +2162,16 @@
: DynamicRouteDescriptor.UNSELECTED;
}
- /**
- */
@RestrictTo(LIBRARY)
public boolean isUnselectable() {
return mDynamicDescriptor == null || mDynamicDescriptor.isUnselectable();
}
- /**
- */
@RestrictTo(LIBRARY)
public boolean isGroupable() {
return mDynamicDescriptor != null && mDynamicDescriptor.isGroupable();
}
- /**
- */
@RestrictTo(LIBRARY)
public boolean isTransferable() {
return mDynamicDescriptor != null && mDynamicDescriptor.isTransferable();
@@ -2294,10 +2264,9 @@
}
RouteInfo findRouteByDescriptorId(String id) {
- final int count = mRoutes.size();
- for (int i = 0; i < count; i++) {
- if (mRoutes.get(i).mDescriptorId.equals(id)) {
- return mRoutes.get(i);
+ for (RouteInfo route: mRoutes) {
+ if (route.mDescriptorId.equals(id)) {
+ return route;
}
}
return null;
@@ -2335,80 +2304,82 @@
* @deprecated Use {@link #onRouteSelected(MediaRouter, RouteInfo, int)} instead.
*/
@Deprecated
- public void onRouteSelected(@NonNull MediaRouter router, @NonNull RouteInfo route) {
- }
+ public void onRouteSelected(@NonNull MediaRouter router, @NonNull RouteInfo route) {}
/**
* Called when the supplied media route becomes selected as the active route.
- * <p>
- * The reason provided will be one of the following:
+ *
+ * <p>The reason provided will be one of the following:
+ *
* <ul>
- * <li>{@link MediaRouter#UNSELECT_REASON_UNKNOWN}</li>
- * <li>{@link MediaRouter#UNSELECT_REASON_DISCONNECTED}</li>
- * <li>{@link MediaRouter#UNSELECT_REASON_STOPPED}</li>
- * <li>{@link MediaRouter#UNSELECT_REASON_ROUTE_CHANGED}</li>
+ * <li>{@link MediaRouter#UNSELECT_REASON_UNKNOWN}
+ * <li>{@link MediaRouter#UNSELECT_REASON_DISCONNECTED}
+ * <li>{@link MediaRouter#UNSELECT_REASON_STOPPED}
+ * <li>{@link MediaRouter#UNSELECT_REASON_ROUTE_CHANGED}
* </ul>
*
* @param router The media router reporting the event.
* @param route The route that has been selected.
* @param reason The reason for unselecting the previous route.
*/
- public void onRouteSelected(@NonNull MediaRouter router, @NonNull RouteInfo route,
- @UnselectReason int reason) {
+ public void onRouteSelected(
+ @NonNull MediaRouter router, @NonNull RouteInfo route, @UnselectReason int reason) {
onRouteSelected(router, route);
}
- //TODO: Revise the comment when we have a feature that enables dynamic grouping on pre-R
+ // TODO: Revise the comment when we have a feature that enables dynamic grouping on pre-R
// devices.
+
/**
- * Called when the supplied media route becomes selected as the active route, which
- * may be different from the route requested by {@link #selectRoute(RouteInfo)}.
- * That can happen when {@link MediaTransferReceiver media transfer feature} is enabled.
- * The default implementation calls {@link #onRouteSelected(MediaRouter, RouteInfo, int)}
- * with the actually selected route.
+ * Called when the supplied media route becomes selected as the active route, which may be
+ * different from the route requested by {@link #selectRoute(RouteInfo)}. That can happen
+ * when {@link MediaTransferReceiver media transfer feature} is enabled. The default
+ * implementation calls {@link #onRouteSelected(MediaRouter, RouteInfo, int)} with the
+ * actually selected route.
*
* @param router The media router reporting the event.
* @param selectedRoute The route that has been selected.
* @param reason The reason for unselecting the previous route.
* @param requestedRoute The route that was requested to be selected.
*/
- public void onRouteSelected(@NonNull MediaRouter router,
- @NonNull RouteInfo selectedRoute, @UnselectReason int reason,
+ public void onRouteSelected(
+ @NonNull MediaRouter router,
+ @NonNull RouteInfo selectedRoute,
+ @UnselectReason int reason,
@NonNull RouteInfo requestedRoute) {
onRouteSelected(router, selectedRoute, reason);
}
/**
- * Called when the supplied media route becomes unselected as the active route.
- * For detailed reason, override {@link #onRouteUnselected(MediaRouter, RouteInfo, int)}
- * instead.
+ * Called when the supplied media route becomes unselected as the active route. For detailed
+ * reason, override {@link #onRouteUnselected(MediaRouter, RouteInfo, int)} instead.
*
* @param router The media router reporting the event.
* @param route The route that has been unselected.
* @deprecated Use {@link #onRouteUnselected(MediaRouter, RouteInfo, int)} instead.
*/
@Deprecated
- public void onRouteUnselected(@NonNull MediaRouter router, @NonNull RouteInfo route) {
- }
+ public void onRouteUnselected(@NonNull MediaRouter router, @NonNull RouteInfo route) {}
/**
- * Called when the supplied media route becomes unselected as the active route.
- * The default implementation calls {@link #onRouteUnselected}.
- * <p>
- * The reason provided will be one of the following:
+ * Called when the supplied media route becomes unselected as the active route. The default
+ * implementation calls {@link #onRouteUnselected}.
+ *
+ * <p>The reason provided will be one of the following:
+ *
* <ul>
- * <li>{@link MediaRouter#UNSELECT_REASON_UNKNOWN}</li>
- * <li>{@link MediaRouter#UNSELECT_REASON_DISCONNECTED}</li>
- * <li>{@link MediaRouter#UNSELECT_REASON_STOPPED}</li>
- * <li>{@link MediaRouter#UNSELECT_REASON_ROUTE_CHANGED}</li>
+ * <li>{@link MediaRouter#UNSELECT_REASON_UNKNOWN}
+ * <li>{@link MediaRouter#UNSELECT_REASON_DISCONNECTED}
+ * <li>{@link MediaRouter#UNSELECT_REASON_STOPPED}
+ * <li>{@link MediaRouter#UNSELECT_REASON_ROUTE_CHANGED}
* </ul>
*
* @param router The media router reporting the event.
* @param route The route that has been unselected.
* @param reason The reason for unselecting the route.
*/
- public void onRouteUnselected(@NonNull MediaRouter router, @NonNull RouteInfo route,
- @UnselectReason int reason) {
+ public void onRouteUnselected(
+ @NonNull MediaRouter router, @NonNull RouteInfo route, @UnselectReason int reason) {
onRouteUnselected(router, route);
}
@@ -2418,8 +2389,7 @@
* @param router The media router reporting the event.
* @param route The route that has become available for use.
*/
- public void onRouteAdded(@NonNull MediaRouter router, @NonNull RouteInfo route) {
- }
+ public void onRouteAdded(@NonNull MediaRouter router, @NonNull RouteInfo route) {}
/**
* Called when a media route has been removed.
@@ -2427,8 +2397,7 @@
* @param router The media router reporting the event.
* @param route The route that has been removed from availability.
*/
- public void onRouteRemoved(@NonNull MediaRouter router, @NonNull RouteInfo route) {
- }
+ public void onRouteRemoved(@NonNull MediaRouter router, @NonNull RouteInfo route) {}
/**
* Called when a property of the indicated media route has changed.
@@ -2436,8 +2405,7 @@
* @param router The media router reporting the event.
* @param route The route that was changed.
*/
- public void onRouteChanged(@NonNull MediaRouter router, @NonNull RouteInfo route) {
- }
+ public void onRouteChanged(@NonNull MediaRouter router, @NonNull RouteInfo route) {}
/**
* Called when a media route's volume changes.
@@ -2445,24 +2413,20 @@
* @param router The media router reporting the event.
* @param route The route whose volume changed.
*/
- public void onRouteVolumeChanged(@NonNull MediaRouter router, @NonNull RouteInfo route) {
- }
+ public void onRouteVolumeChanged(@NonNull MediaRouter router, @NonNull RouteInfo route) {}
/**
* Called when a media route's presentation display changes.
- * <p>
- * This method is called whenever the route's presentation display becomes
- * available, is removed or has changes to some of its properties (such as its size).
- * </p>
+ *
+ * <p>This method is called whenever the route's presentation display becomes available, is
+ * removed or has changes to some of its properties (such as its size).
*
* @param router The media router reporting the event.
* @param route The route whose presentation display changed.
- *
* @see RouteInfo#getPresentationDisplay()
*/
- public void onRoutePresentationDisplayChanged(@NonNull MediaRouter router,
- @NonNull RouteInfo route) {
- }
+ public void onRoutePresentationDisplayChanged(
+ @NonNull MediaRouter router, @NonNull RouteInfo route) {}
/**
* Called when a media route provider has been added.
@@ -2470,8 +2434,7 @@
* @param router The media router reporting the event.
* @param provider The provider that has become available for use.
*/
- public void onProviderAdded(@NonNull MediaRouter router, @NonNull ProviderInfo provider) {
- }
+ public void onProviderAdded(@NonNull MediaRouter router, @NonNull ProviderInfo provider) {}
/**
* Called when a media route provider has been removed.
@@ -2479,8 +2442,8 @@
* @param router The media router reporting the event.
* @param provider The provider that has been removed from availability.
*/
- public void onProviderRemoved(@NonNull MediaRouter router, @NonNull ProviderInfo provider) {
- }
+ public void onProviderRemoved(
+ @NonNull MediaRouter router, @NonNull ProviderInfo provider) {}
/**
* Called when a property of the indicated media route provider has changed.
@@ -2488,15 +2451,13 @@
* @param router The media router reporting the event.
* @param provider The provider that was changed.
*/
- public void onProviderChanged(@NonNull MediaRouter router, @NonNull ProviderInfo provider) {
- }
+ public void onProviderChanged(
+ @NonNull MediaRouter router, @NonNull ProviderInfo provider) {}
- /**
- */
+ /** */
@RestrictTo(LIBRARY)
- public void onRouterParamsChanged(@NonNull MediaRouter router,
- @Nullable MediaRouterParams params) {
- }
+ public void onRouterParamsChanged(
+ @NonNull MediaRouter router, @Nullable MediaRouterParams params) {}
}
/**
@@ -2507,28 +2468,27 @@
public interface OnPrepareTransferListener {
/**
* Implement this to handle transfer seamlessly.
- * <p>
- * Setting the listener will defer stopping the previous route, from which you may
- * get the media status to resume media seamlessly on the new route.
- * When the transfer is prepared, set the returned future to stop media being played
- * on the previous route and release resources.
- * This method is called on the main thread.
- * <p>
- * {@link Callback#onRouteUnselected(MediaRouter, RouteInfo, int)} and
- * {@link Callback#onRouteSelected(MediaRouter, RouteInfo, int)} are called after
- * the future is done.
+ *
+ * <p>Setting the listener will defer stopping the previous route, from which you may get
+ * the media status to resume media seamlessly on the new route. When the transfer is
+ * prepared, set the returned future to stop media being played on the previous route and
+ * release resources. This method is called on the main thread.
+ *
+ * <p>{@link Callback#onRouteUnselected(MediaRouter, RouteInfo, int)} and {@link
+ * Callback#onRouteSelected(MediaRouter, RouteInfo, int)} are called after the future is
+ * done.
*
* @param fromRoute The route that is about to be unselected.
* @param toRoute The route that is about to be selected.
- * @return A {@link ListenableFuture} whose completion indicates that the
- * transfer is prepared or {@code null} to indicate that no preparation is needed.
- * If a future is returned, until the future is completed,
- * the media continues to be played on the previous route.
+ * @return A {@link ListenableFuture} whose completion indicates that the transfer is
+ * prepared or {@code null} to indicate that no preparation is needed. If a future is
+ * returned, until the future is completed, the media continues to be played on the
+ * previous route.
*/
@MainThread
@Nullable
- ListenableFuture<Void> onPrepareTransfer(@NonNull RouteInfo fromRoute,
- @NonNull RouteInfo toRoute);
+ ListenableFuture<Void> onPrepareTransfer(
+ @NonNull RouteInfo fromRoute, @NonNull RouteInfo toRoute);
}
/**
@@ -2540,22 +2500,20 @@
/**
* Called when a media control request succeeds.
*
- * @param data Result data, or null if none.
- * Contents depend on the {@link MediaControlIntent media control action}.
+ * @param data Result data, or null if none. Contents depend on the {@link
+ * MediaControlIntent media control action}.
*/
- public void onResult(@Nullable Bundle data) {
- }
+ public void onResult(@Nullable Bundle data) {}
/**
* Called when a media control request fails.
*
- * @param error A localized error message which may be shown to the user, or null
- * if the cause of the error is unclear.
- * @param data Error data, or null if none.
- * Contents depend on the {@link MediaControlIntent media control action}.
+ * @param error A localized error message which may be shown to the user, or null if the
+ * cause of the error is unclear.
+ * @param data Error data, or null if none. Contents depend on the {@link MediaControlIntent
+ * media control action}.
*/
- public void onError(@Nullable String error, @Nullable Bundle data) {
- }
+ public void onError(@Nullable String error, @Nullable Bundle data) {}
}
private static final class CallbackRecord {
@@ -2603,7 +2561,7 @@
implements SystemMediaRouteProvider.SyncCallback,
RegisteredMediaRouteProviderWatcher.Callback {
final Context mApplicationContext;
- boolean mIsInitialized;
+ private boolean mIsInitialized;
SystemMediaRouteProvider mSystemProvider;
@VisibleForTesting
@@ -2641,31 +2599,32 @@
private int mCallbackCount;
OnPrepareTransferListener mOnPrepareTransferListener;
PrepareTransferNotifier mTransferNotifier;
- RouteInfo mTransferredRoute;
- RouteController mTransferredRouteController;
-
private MediaSessionRecord mMediaSession;
MediaSessionCompat mRccMediaSession;
private MediaSessionCompat mCompatSession;
private final MediaSessionCompat.OnActiveChangeListener mSessionActiveListener =
new MediaSessionCompat.OnActiveChangeListener() {
- @Override
- public void onActiveChanged() {
- if(mRccMediaSession != null) {
- if (mRccMediaSession.isActive()) {
- addRemoteControlClient(mRccMediaSession.getRemoteControlClient());
- } else {
- removeRemoteControlClient(mRccMediaSession.getRemoteControlClient());
+ @Override
+ public void onActiveChanged() {
+ if (mRccMediaSession != null) {
+ android.media.RemoteControlClient remoteControlClient =
+ (android.media.RemoteControlClient)
+ mRccMediaSession.getRemoteControlClient();
+ if (mRccMediaSession.isActive()) {
+ addRemoteControlClient(remoteControlClient);
+ } else {
+ removeRemoteControlClient(remoteControlClient);
+ }
+ }
}
- }
- }
- };
+ };
GlobalMediaRouter(Context applicationContext) {
mApplicationContext = applicationContext;
- mLowRam = ActivityManagerCompat.isLowRamDevice(
- (ActivityManager)applicationContext.getSystemService(
- Context.ACTIVITY_SERVICE));
+ mLowRam =
+ ActivityManagerCompat.isLowRamDevice(
+ (ActivityManager)
+ applicationContext.getSystemService(Context.ACTIVITY_SERVICE));
}
@SuppressLint({"NewApi", "SyntheticAccessor"})
@@ -2697,13 +2656,8 @@
private void start() {
// Using lambda would break some apps.
- mActiveScanThrottlingHelper = new MediaRouterActiveScanThrottlingHelper(
- new Runnable() {
- @Override
- public void run() {
- updateDiscoveryRequest();
- }
- });
+ mActiveScanThrottlingHelper =
+ new MediaRouterActiveScanThrottlingHelper(this::updateDiscoveryRequest);
addProvider(mSystemProvider, /* treatRouteDescriptorIdsAsUnique= */ true);
if (mMr2Provider != null) {
addProvider(mMr2Provider, /* treatRouteDescriptorIdsAsUnique= */ true);
@@ -2748,7 +2702,7 @@
}
}
router = new MediaRouter(context);
- mRouters.add(new WeakReference<MediaRouter>(router));
+ mRouters.add(new WeakReference<>(router));
return router;
}
@@ -2756,18 +2710,6 @@
return mApplicationContext.getContentResolver();
}
- public Context getProviderContext(String packageName) {
- if (packageName.equals(SystemMediaRouteProvider.PACKAGE_NAME)) {
- return mApplicationContext;
- }
- try {
- return mApplicationContext.createPackageContext(
- packageName, Context.CONTEXT_RESTRICTED);
- } catch (NameNotFoundException ex) {
- return null;
- }
- }
-
public Display getDisplay(int displayId) {
if (mDisplayManager == null) {
mDisplayManager = DisplayManagerCompat.getInstance(mApplicationContext);
@@ -2882,7 +2824,8 @@
return mProviders;
}
- @NonNull RouteInfo getDefaultRoute() {
+ @NonNull
+ RouteInfo getDefaultRoute() {
if (mDefaultRoute == null) {
// This should never happen once the media router has been fully
// initialized but it is good to check for the error in case there
@@ -2897,7 +2840,8 @@
return mBluetoothRoute;
}
- @NonNull RouteInfo getSelectedRoute() {
+ @NonNull
+ RouteInfo getSelectedRoute() {
if (mSelectedRoute == null) {
// This should never happen once the media router has been fully
// initialized but it is good to check for the error in case there
@@ -3056,7 +3000,7 @@
boolean activeScan =
mActiveScanThrottlingHelper
- .finalizeActiveScanAndScheduleSuppressActiveScanRunnable();
+ .finalizeActiveScanAndScheduleSuppressActiveScanRunnable();
mCallbackCount = callbackCount;
MediaRouteSelector selector = discover ? builder.build() : MediaRouteSelector.EMPTY;
@@ -3092,9 +3036,8 @@
}
// Notify providers.
- final int providerCount = mProviders.size();
- for (int i = 0; i < providerCount; i++) {
- MediaRouteProvider provider = mProviders.get(i).mProviderInstance;
+ for (ProviderInfo providerInfo: mProviders) {
+ MediaRouteProvider provider = providerInfo.mProviderInstance;
if (provider == mMr2Provider) {
// MediaRoute2Provider is handled by updateMr2ProviderDiscoveryRequest().
continue;
@@ -3150,11 +3093,11 @@
return mRouterParams.isTransferToLocalEnabled();
}
- /**
- */
+ /** */
@RestrictTo(RestrictTo.Scope.LIBRARY)
public boolean isGroupVolumeUxEnabled() {
- return mRouterParams == null || mRouterParams.mExtras == null
+ return mRouterParams == null
+ || mRouterParams.mExtras == null
|| mRouterParams.mExtras.getBoolean(
MediaRouterParams.ENABLE_GROUP_VOLUME_UX, true);
}
@@ -3210,7 +3153,7 @@
if (mSelectedRouteController == controller) {
selectRoute(chooseFallbackRoute(), UNSELECT_REASON_STOPPED);
}
- //TODO: Maybe release a member route controller if the given controller is a member of
+ // TODO: Maybe release a member route controller if the given controller is a member of
// the selected route.
}
@@ -3224,10 +3167,9 @@
}
private ProviderInfo findProviderInfo(MediaRouteProvider providerInstance) {
- final int count = mProviders.size();
- for (int i = 0; i < count; i++) {
- if (mProviders.get(i).mProviderInstance == providerInstance) {
- return mProviders.get(i);
+ for (ProviderInfo providerInfo: mProviders) {
+ if (providerInfo.mProviderInstance == providerInstance) {
+ return providerInfo;
}
}
return null;
@@ -3488,8 +3430,7 @@
private boolean isSystemDefaultRoute(RouteInfo route) {
return route.getProviderInstance() == mSystemProvider
- && route.mDescriptorId.equals(
- SystemMediaRouteProvider.DEFAULT_ROUTE_ID);
+ && route.mDescriptorId.equals(SystemMediaRouteProvider.DEFAULT_ROUTE_ID);
}
void selectRouteInternal(@NonNull RouteInfo route,
@@ -3565,11 +3506,18 @@
if (mSelectedRoute == null) {
mSelectedRoute = route;
mSelectedRouteController = routeController;
- mCallbackHandler.post(GlobalMediaRouter.CallbackHandler.MSG_ROUTE_SELECTED,
- new Pair<>(null, route), unselectReason);
+ mCallbackHandler.post(
+ GlobalMediaRouter.CallbackHandler.MSG_ROUTE_SELECTED,
+ new Pair<>(null, route),
+ unselectReason);
} else {
- notifyTransfer(this, route, routeController, unselectReason,
- /*requestedRoute=*/null, /*memberRoutes=*/null);
+ notifyTransfer(
+ this,
+ route,
+ routeController,
+ unselectReason,
+ /* requestedRoute= */ null,
+ /* memberRoutes= */ null);
}
}
@@ -3682,7 +3630,7 @@
}
}
- public void addRemoteControlClient(Object rcc) {
+ public void addRemoteControlClient(android.media.RemoteControlClient rcc) {
int index = findRemoteControlClientRecord(rcc);
if (index < 0) {
RemoteControlClientRecord record = new RemoteControlClientRecord(rcc);
@@ -3690,7 +3638,7 @@
}
}
- public void removeRemoteControlClient(Object rcc) {
+ public void removeRemoteControlClient(android.media.RemoteControlClient rcc) {
int index = findRemoteControlClientRecord(rcc);
if (index >= 0) {
RemoteControlClientRecord record = mRemoteControlClients.remove(index);
@@ -3708,14 +3656,18 @@
setMediaSessionRecord(session != null ? new MediaSessionRecord(session) : null);
} else {
if (mRccMediaSession != null) {
- removeRemoteControlClient(mRccMediaSession.getRemoteControlClient());
+ removeRemoteControlClient(
+ (android.media.RemoteControlClient)
+ mRccMediaSession.getRemoteControlClient());
mRccMediaSession.removeOnActiveChangeListener(mSessionActiveListener);
}
mRccMediaSession = session;
if (session != null) {
session.addOnActiveChangeListener(mSessionActiveListener);
if (session.isActive()) {
- addRemoteControlClient(session.getRemoteControlClient());
+ addRemoteControlClient(
+ (android.media.RemoteControlClient)
+ session.getRemoteControlClient());
}
}
}
@@ -3740,7 +3692,7 @@
return null;
}
- private int findRemoteControlClientRecord(Object rcc) {
+ private int findRemoteControlClientRecord(android.media.RemoteControlClient rcc) {
final int count = mRemoteControlClients.size();
for (int i = 0; i < count; i++) {
RemoteControlClientRecord record = mRemoteControlClients.get(i);
@@ -3767,10 +3719,8 @@
mPlaybackInfo.volumeControlId = null;
}
- final int count = mRemoteControlClients.size();
- for (int i = 0; i < count; i++) {
- RemoteControlClientRecord record = mRemoteControlClients.get(i);
- record.updatePlaybackInfo();
+ for (RemoteControlClientRecord remoteControlClientRecord: mRemoteControlClients) {
+ remoteControlClientRecord.updatePlaybackInfo();
}
if (mMediaSession != null) {
if (mSelectedRoute == getDefaultRoute()
@@ -3933,13 +3883,13 @@
private final RemoteControlClientCompat mRccCompat;
private boolean mDisconnected;
- public RemoteControlClientRecord(Object rcc) {
+ RemoteControlClientRecord(android.media.RemoteControlClient rcc) {
mRccCompat = RemoteControlClientCompat.obtain(mApplicationContext, rcc);
mRccCompat.setVolumeCallback(this);
updatePlaybackInfo();
}
- public Object getRemoteControlClient() {
+ public android.media.RemoteControlClient getRemoteControlClient() {
return mRccCompat.getRemoteControlClient();
}
@@ -4084,10 +4034,12 @@
case MSG_TYPE_ROUTE: {
final RouteInfo route =
(what == MSG_ROUTE_ANOTHER_SELECTED || what == MSG_ROUTE_SELECTED)
- ? ((Pair<RouteInfo, RouteInfo>) obj).second : (RouteInfo) obj;
+ ? ((Pair<RouteInfo, RouteInfo>) obj).second
+ : (RouteInfo) obj;
final RouteInfo optionalRoute =
(what == MSG_ROUTE_ANOTHER_SELECTED || what == MSG_ROUTE_SELECTED)
- ? ((Pair<RouteInfo, RouteInfo>) obj).first : null;
+ ? ((Pair<RouteInfo, RouteInfo>) obj).first
+ : null;
if (route == null || !record.filterRouteEvent(
route, what, optionalRoute, arg)) {
break;
@@ -4121,7 +4073,7 @@
break;
}
case MSG_TYPE_PROVIDER: {
- final ProviderInfo provider = (ProviderInfo)obj;
+ final ProviderInfo provider = (ProviderInfo) obj;
switch (what) {
case MSG_PROVIDER_ADDED:
callback.onProviderAdded(router, provider);
diff --git a/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRouterJellybean.java b/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRouterJellybean.java
index 667f9f4..0494663 100644
--- a/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRouterJellybean.java
+++ b/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRouterJellybean.java
@@ -57,98 +57,110 @@
return out;
}
- public static Object getSelectedRoute(android.media.MediaRouter router, int type) {
+ public static MediaRouter.RouteInfo getSelectedRoute(android.media.MediaRouter router,
+ int type) {
return router.getSelectedRoute(type);
}
- public static void selectRoute(android.media.MediaRouter router, int types, Object routeObj) {
- router.selectRoute(types, (android.media.MediaRouter.RouteInfo) routeObj);
+ public static void selectRoute(android.media.MediaRouter router, int types,
+ android.media.MediaRouter.RouteInfo route) {
+ router.selectRoute(types, route);
}
public static void addCallback(android.media.MediaRouter router, int types,
- Object callbackObj) {
- router.addCallback(types, (android.media.MediaRouter.Callback) callbackObj);
+ android.media.MediaRouter.Callback callback) {
+ router.addCallback(types, callback);
}
- public static void removeCallback(android.media.MediaRouter router, Object callbackObj) {
- router.removeCallback((android.media.MediaRouter.Callback) callbackObj);
+ public static void removeCallback(android.media.MediaRouter router,
+ android.media.MediaRouter.Callback callback) {
+ router.removeCallback(callback);
}
- public static Object createRouteCategory(android.media.MediaRouter router,
+ public static android.media.MediaRouter.RouteCategory createRouteCategory(
+ android.media.MediaRouter router,
String name, boolean isGroupable) {
return router.createRouteCategory(name, isGroupable);
}
- public static Object createUserRoute(android.media.MediaRouter router, Object categoryObj) {
- return router.createUserRoute((android.media.MediaRouter.RouteCategory) categoryObj);
+ public static MediaRouter.UserRouteInfo createUserRoute(android.media.MediaRouter router,
+ android.media.MediaRouter.RouteCategory category) {
+ return router.createUserRoute(category);
}
- public static void addUserRoute(android.media.MediaRouter router, Object routeObj) {
- router.addUserRoute((android.media.MediaRouter.UserRouteInfo) routeObj);
+ public static void addUserRoute(android.media.MediaRouter router,
+ android.media.MediaRouter.UserRouteInfo route) {
+ router.addUserRoute(route);
}
- public static void removeUserRoute(android.media.MediaRouter router, Object routeObj) {
+ public static void removeUserRoute(android.media.MediaRouter router,
+ android.media.MediaRouter.UserRouteInfo route) {
try {
- router.removeUserRoute((android.media.MediaRouter.UserRouteInfo) routeObj);
+ router.removeUserRoute(route);
} catch (IllegalArgumentException e) {
// Work around for https://issuetracker.google.com/issues/202931542.
Log.w(TAG, "Failed to remove user route", e);
}
}
- public static Object createCallback(Callback callback) {
+ public static CallbackProxy<Callback> createCallback(Callback callback) {
return new CallbackProxy<>(callback);
}
- public static Object createVolumeCallback(VolumeCallback callback) {
+ public static VolumeCallbackProxy<VolumeCallback> createVolumeCallback(
+ VolumeCallback callback) {
return new VolumeCallbackProxy<>(callback);
}
public static final class RouteInfo {
@NonNull
- public static CharSequence getName(@NonNull Object routeObj, @NonNull Context context) {
- return ((android.media.MediaRouter.RouteInfo) routeObj).getName(context);
+ public static CharSequence getName(@NonNull android.media.MediaRouter.RouteInfo route,
+ @NonNull Context context) {
+ return route.getName(context);
}
- public static int getSupportedTypes(@NonNull Object routeObj) {
- return ((android.media.MediaRouter.RouteInfo) routeObj).getSupportedTypes();
+ public static int getSupportedTypes(@NonNull android.media.MediaRouter.RouteInfo route) {
+ return route.getSupportedTypes();
}
- public static int getPlaybackType(@NonNull Object routeObj) {
- return ((android.media.MediaRouter.RouteInfo) routeObj).getPlaybackType();
+ public static int getPlaybackType(@NonNull android.media.MediaRouter.RouteInfo route) {
+ return route.getPlaybackType();
}
- public static int getPlaybackStream(@NonNull Object routeObj) {
- return ((android.media.MediaRouter.RouteInfo) routeObj).getPlaybackStream();
+ public static int getPlaybackStream(@NonNull android.media.MediaRouter.RouteInfo route) {
+ return route.getPlaybackStream();
}
- public static int getVolume(@NonNull Object routeObj) {
- return ((android.media.MediaRouter.RouteInfo) routeObj).getVolume();
+ public static int getVolume(@NonNull android.media.MediaRouter.RouteInfo route) {
+ return route.getVolume();
}
- public static int getVolumeMax(@NonNull Object routeObj) {
- return ((android.media.MediaRouter.RouteInfo) routeObj).getVolumeMax();
+ public static int getVolumeMax(@NonNull android.media.MediaRouter.RouteInfo route) {
+ return route.getVolumeMax();
}
- public static int getVolumeHandling(@NonNull Object routeObj) {
- return ((android.media.MediaRouter.RouteInfo) routeObj).getVolumeHandling();
+ public static int getVolumeHandling(@NonNull android.media.MediaRouter.RouteInfo route) {
+ return route.getVolumeHandling();
}
@Nullable
- public static Object getTag(@NonNull Object routeObj) {
- return ((android.media.MediaRouter.RouteInfo) routeObj).getTag();
+ public static Object getTag(@NonNull android.media.MediaRouter.RouteInfo route) {
+ return route.getTag();
}
- public static void setTag(@NonNull Object routeObj, @Nullable Object tag) {
- ((android.media.MediaRouter.RouteInfo) routeObj).setTag(tag);
+ public static void setTag(@NonNull android.media.MediaRouter.RouteInfo route,
+ @Nullable Object tag) {
+ route.setTag(tag);
}
- public static void requestSetVolume(@NonNull Object routeObj, int volume) {
- ((android.media.MediaRouter.RouteInfo) routeObj).requestSetVolume(volume);
+ public static void requestSetVolume(@NonNull android.media.MediaRouter.RouteInfo route,
+ int volume) {
+ route.requestSetVolume(volume);
}
- public static void requestUpdateVolume(@NonNull Object routeObj, int direction) {
- ((android.media.MediaRouter.RouteInfo) routeObj).requestUpdateVolume(direction);
+ public static void requestUpdateVolume(
+ @NonNull android.media.MediaRouter.RouteInfo route, int direction) {
+ route.requestUpdateVolume(direction);
}
private RouteInfo() {
@@ -156,40 +168,45 @@
}
public static final class UserRouteInfo {
- public static void setName(@NonNull Object routeObj, @NonNull CharSequence name) {
- ((android.media.MediaRouter.UserRouteInfo) routeObj).setName(name);
+ public static void setName(@NonNull android.media.MediaRouter.UserRouteInfo route,
+ @NonNull CharSequence name) {
+ route.setName(name);
}
- public static void setPlaybackType(@NonNull Object routeObj, int type) {
- ((android.media.MediaRouter.UserRouteInfo) routeObj).setPlaybackType(type);
+ public static void setPlaybackType(
+ @NonNull android.media.MediaRouter.UserRouteInfo route, int type) {
+ route.setPlaybackType(type);
}
- public static void setPlaybackStream(@NonNull Object routeObj, int stream) {
- ((android.media.MediaRouter.UserRouteInfo) routeObj).setPlaybackStream(stream);
+ public static void setPlaybackStream(
+ @NonNull android.media.MediaRouter.UserRouteInfo route, int stream) {
+ route.setPlaybackStream(stream);
}
- public static void setVolume(@NonNull Object routeObj, int volume) {
- ((android.media.MediaRouter.UserRouteInfo) routeObj).setVolume(volume);
+ public static void setVolume(@NonNull android.media.MediaRouter.UserRouteInfo route,
+ int volume) {
+ route.setVolume(volume);
}
- public static void setVolumeMax(@NonNull Object routeObj, int volumeMax) {
- ((android.media.MediaRouter.UserRouteInfo) routeObj).setVolumeMax(volumeMax);
+ public static void setVolumeMax(@NonNull android.media.MediaRouter.UserRouteInfo route,
+ int volumeMax) {
+ route.setVolumeMax(volumeMax);
}
- public static void setVolumeHandling(@NonNull Object routeObj, int volumeHandling) {
- ((android.media.MediaRouter.UserRouteInfo) routeObj).setVolumeHandling(volumeHandling);
+ public static void setVolumeHandling(
+ @NonNull android.media.MediaRouter.UserRouteInfo route, int volumeHandling) {
+ route.setVolumeHandling(volumeHandling);
}
- public static void setVolumeCallback(@NonNull Object routeObj,
- @NonNull Object volumeCallbackObj) {
- ((android.media.MediaRouter.UserRouteInfo) routeObj).setVolumeCallback(
- (android.media.MediaRouter.VolumeCallback) volumeCallbackObj);
+ public static void setVolumeCallback(@NonNull android.media.MediaRouter.UserRouteInfo route,
+ @NonNull android.media.MediaRouter.VolumeCallback volumeCallback) {
+ route.setVolumeCallback(volumeCallback);
}
- public static void setRemoteControlClient(@NonNull Object routeObj,
- @Nullable Object rccObj) {
- ((android.media.MediaRouter.UserRouteInfo) routeObj).setRemoteControlClient(
- (android.media.RemoteControlClient) rccObj);
+ public static void setRemoteControlClient(
+ @NonNull android.media.MediaRouter.UserRouteInfo route,
+ @Nullable android.media.RemoteControlClient rcc) {
+ route.setRemoteControlClient(rcc);
}
private UserRouteInfo() {
@@ -197,27 +214,30 @@
}
public interface Callback {
- void onRouteSelected(int type, @NonNull Object routeObj);
+ void onRouteSelected(int type, @NonNull android.media.MediaRouter.RouteInfo route);
- void onRouteUnselected(int type, @NonNull Object routeObj);
+ void onRouteUnselected(int type, @NonNull android.media.MediaRouter.RouteInfo route);
- void onRouteAdded(@NonNull Object routeObj);
+ void onRouteAdded(@NonNull android.media.MediaRouter.RouteInfo route);
- void onRouteRemoved(@NonNull Object routeObj);
+ void onRouteRemoved(@NonNull android.media.MediaRouter.RouteInfo route);
- void onRouteChanged(@NonNull Object routeObj);
+ void onRouteChanged(@NonNull android.media.MediaRouter.RouteInfo route);
- void onRouteGrouped(@NonNull Object routeObj, @NonNull Object groupObj, int index);
+ void onRouteGrouped(@NonNull android.media.MediaRouter.RouteInfo route,
+ @NonNull android.media.MediaRouter.RouteGroup group, int index);
- void onRouteUngrouped(@NonNull Object routeObj, @NonNull Object groupObj);
+ void onRouteUngrouped(@NonNull android.media.MediaRouter.RouteInfo route,
+ @NonNull android.media.MediaRouter.RouteGroup group);
- void onRouteVolumeChanged(@NonNull Object routeObj);
+ void onRouteVolumeChanged(@NonNull android.media.MediaRouter.RouteInfo route);
}
public interface VolumeCallback {
- void onVolumeSetRequest(@NonNull Object routeObj, int volume);
+ void onVolumeSetRequest(@NonNull android.media.MediaRouter.RouteInfo route, int volume);
- void onVolumeUpdateRequest(@NonNull Object routeObj, int direction);
+ void onVolumeUpdateRequest(@NonNull android.media.MediaRouter.RouteInfo route,
+ int direction);
}
/**
@@ -243,10 +263,7 @@
// check is happening in the class' constructor and in SystemMediaRouteProvider#obtain
@SuppressLint("BanUncheckedReflection")
public void selectRoute(@NonNull android.media.MediaRouter router, int types,
- @NonNull Object routeObj) {
- android.media.MediaRouter.RouteInfo route =
- (android.media.MediaRouter.RouteInfo) routeObj;
-
+ @NonNull android.media.MediaRouter.RouteInfo route) {
int routeTypes = route.getSupportedTypes();
if ((routeTypes & ROUTE_TYPE_USER) == 0) {
// Handle non-user routes.
@@ -383,8 +400,7 @@
}
@Override
- public void onVolumeSetRequest(android.media.MediaRouter.RouteInfo route,
- int volume) {
+ public void onVolumeSetRequest(android.media.MediaRouter.RouteInfo route, int volume) {
mCallback.onVolumeSetRequest(route, volume);
}
diff --git a/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRouterJellybeanMr1.java b/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRouterJellybeanMr1.java
index 6dc606d..0d6642f 100644
--- a/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRouterJellybeanMr1.java
+++ b/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRouterJellybeanMr1.java
@@ -36,21 +36,22 @@
final class MediaRouterJellybeanMr1 {
private static final String TAG = "MediaRouterJellybeanMr1";
- public static Object createCallback(Callback callback) {
- return new CallbackProxy<Callback>(callback);
+ public static CallbackProxy<Callback> createCallback(Callback callback) {
+ return new CallbackProxy<>(callback);
}
public static final class RouteInfo {
- public static boolean isEnabled(@NonNull Object routeObj) {
- return ((android.media.MediaRouter.RouteInfo) routeObj).isEnabled();
+ public static boolean isEnabled(@NonNull android.media.MediaRouter.RouteInfo route) {
+ return route.isEnabled();
}
@Nullable
- public static Display getPresentationDisplay(@NonNull Object routeObj) {
+ public static Display getPresentationDisplay(
+ @NonNull android.media.MediaRouter.RouteInfo route) {
// android.media.MediaRouter.RouteInfo.getPresentationDisplay() was
// added in API 17. However, some factory releases of JB MR1 missed it.
try {
- return ((android.media.MediaRouter.RouteInfo) routeObj).getPresentationDisplay();
+ return route.getPresentationDisplay();
} catch (NoSuchMethodError ex) {
Log.w(TAG, "Cannot get presentation display for the route.", ex);
}
@@ -62,7 +63,7 @@
}
public interface Callback extends MediaRouterJellybean.Callback {
- void onRoutePresentationDisplayChanged(@NonNull Object routeObj);
+ void onRoutePresentationDisplayChanged(@NonNull android.media.MediaRouter.RouteInfo route);
}
/**
@@ -175,10 +176,7 @@
// code: the reflection is used for a specific Android version and the real Android API
// check is happening in the class' constructor and in SystemMediaRouteProvider#obtain
@SuppressLint("BanUncheckedReflection")
- public boolean isConnecting(@NonNull Object routeObj) {
- android.media.MediaRouter.RouteInfo route =
- (android.media.MediaRouter.RouteInfo) routeObj;
-
+ public boolean isConnecting(@NonNull android.media.MediaRouter.RouteInfo route) {
if (mGetStatusCodeMethod != null) {
try {
int statusCode = (Integer) mGetStatusCodeMethod.invoke(route);
diff --git a/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/RemoteControlClientCompat.java b/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/RemoteControlClientCompat.java
index 41ec5b6..4033297 100644
--- a/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/RemoteControlClientCompat.java
+++ b/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/RemoteControlClientCompat.java
@@ -33,22 +33,23 @@
*/
abstract class RemoteControlClientCompat {
protected final Context mContext;
- protected final Object mRcc;
+ protected final android.media.RemoteControlClient mRcc;
protected VolumeCallback mVolumeCallback;
- protected RemoteControlClientCompat(Context context, Object rcc) {
+ protected RemoteControlClientCompat(Context context, android.media.RemoteControlClient rcc) {
mContext = context;
mRcc = rcc;
}
- public static RemoteControlClientCompat obtain(Context context, Object rcc) {
+ public static RemoteControlClientCompat obtain(
+ Context context, android.media.RemoteControlClient rcc) {
if (Build.VERSION.SDK_INT >= 16) {
return new JellybeanImpl(context, rcc);
}
return new LegacyImpl(context, rcc);
}
- public Object getRemoteControlClient() {
+ public android.media.RemoteControlClient getRemoteControlClient() {
return mRcc;
}
@@ -101,7 +102,7 @@
* Called when the volume for the route should be set to the given value.
*
* @param volume An integer indicating the new volume value that should be used,
- * always between 0 and the value set by {@link PlaybackInfo#volumeMax}.
+ * always between 0 and the value set by {@link PlaybackInfo#volumeMax}.
*/
void onVolumeSetRequest(int volume);
}
@@ -111,7 +112,7 @@
* Does nothing.
*/
static class LegacyImpl extends RemoteControlClientCompat {
- public LegacyImpl(Context context, Object rcc) {
+ LegacyImpl(Context context, android.media.RemoteControlClient rcc) {
super(context, rcc);
}
}
@@ -127,39 +128,39 @@
@RequiresApi(16)
static class JellybeanImpl extends RemoteControlClientCompat {
private final android.media.MediaRouter mRouter;
- private final Object mUserRouteCategoryObj;
- private final Object mUserRouteObj;
+ private final android.media.MediaRouter.RouteCategory mUserRouteCategory;
+ private final android.media.MediaRouter.UserRouteInfo mUserRoute;
private boolean mRegistered;
- public JellybeanImpl(Context context, Object rcc) {
+ JellybeanImpl(Context context, android.media.RemoteControlClient rcc) {
super(context, rcc);
mRouter = MediaRouterJellybean.getMediaRouter(context);
- mUserRouteCategoryObj = MediaRouterJellybean.createRouteCategory(
+ mUserRouteCategory = MediaRouterJellybean.createRouteCategory(
mRouter, "", false);
- mUserRouteObj = MediaRouterJellybean.createUserRoute(
- mRouter, mUserRouteCategoryObj);
+ mUserRoute = MediaRouterJellybean.createUserRoute(
+ mRouter, mUserRouteCategory);
}
@Override
public void setPlaybackInfo(PlaybackInfo info) {
MediaRouterJellybean.UserRouteInfo.setVolume(
- mUserRouteObj, info.volume);
+ mUserRoute, info.volume);
MediaRouterJellybean.UserRouteInfo.setVolumeMax(
- mUserRouteObj, info.volumeMax);
+ mUserRoute, info.volumeMax);
MediaRouterJellybean.UserRouteInfo.setVolumeHandling(
- mUserRouteObj, info.volumeHandling);
+ mUserRoute, info.volumeHandling);
MediaRouterJellybean.UserRouteInfo.setPlaybackStream(
- mUserRouteObj, info.playbackStream);
+ mUserRoute, info.playbackStream);
MediaRouterJellybean.UserRouteInfo.setPlaybackType(
- mUserRouteObj, info.playbackType);
+ mUserRoute, info.playbackType);
if (!mRegistered) {
mRegistered = true;
- MediaRouterJellybean.UserRouteInfo.setVolumeCallback(mUserRouteObj,
+ MediaRouterJellybean.UserRouteInfo.setVolumeCallback(mUserRoute,
MediaRouterJellybean.createVolumeCallback(
new VolumeCallbackWrapper(this)));
- MediaRouterJellybean.UserRouteInfo.setRemoteControlClient(mUserRouteObj, mRcc);
+ MediaRouterJellybean.UserRouteInfo.setRemoteControlClient(mUserRoute, mRcc);
}
}
@@ -172,11 +173,12 @@
private final WeakReference<JellybeanImpl> mImplWeak;
public VolumeCallbackWrapper(JellybeanImpl impl) {
- mImplWeak = new WeakReference<JellybeanImpl>(impl);
+ mImplWeak = new WeakReference<>(impl);
}
@Override
- public void onVolumeUpdateRequest(@NonNull Object routeObj, int direction) {
+ public void onVolumeUpdateRequest(@NonNull android.media.MediaRouter.RouteInfo route,
+ int direction) {
JellybeanImpl impl = mImplWeak.get();
if (impl != null && impl.mVolumeCallback != null) {
impl.mVolumeCallback.onVolumeUpdateRequest(direction);
@@ -184,7 +186,8 @@
}
@Override
- public void onVolumeSetRequest(@NonNull Object routeObj, int volume) {
+ public void onVolumeSetRequest(@NonNull android.media.MediaRouter.RouteInfo route,
+ int volume) {
JellybeanImpl impl = mImplWeak.get();
if (impl != null && impl.mVolumeCallback != null) {
impl.mVolumeCallback.onVolumeSetRequest(volume);
diff --git a/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/SystemMediaRouteProvider.java b/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/SystemMediaRouteProvider.java
index d2349e6..d8925fb 100644
--- a/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/SystemMediaRouteProvider.java
+++ b/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/SystemMediaRouteProvider.java
@@ -237,9 +237,9 @@
private final SyncCallback mSyncCallback;
protected final android.media.MediaRouter mRouter;
- protected final Object mCallbackObj;
- protected final Object mVolumeCallbackObj;
- protected final Object mUserRouteCategoryObj;
+ protected final android.media.MediaRouter.Callback mCallback;
+ protected final android.media.MediaRouter.VolumeCallback mVolumeCallback;
+ protected final android.media.MediaRouter.RouteCategory mUserRouteCategory;
protected int mRouteTypes;
protected boolean mActiveScan;
protected boolean mCallbackRegistered;
@@ -262,11 +262,11 @@
super(context);
mSyncCallback = syncCallback;
mRouter = MediaRouterJellybean.getMediaRouter(context);
- mCallbackObj = createCallbackObj();
- mVolumeCallbackObj = createVolumeCallbackObj();
+ mCallback = createCallback();
+ mVolumeCallback = createVolumeCallback();
Resources r = context.getResources();
- mUserRouteCategoryObj = MediaRouterJellybean.createRouteCategory(
+ mUserRouteCategory = MediaRouterJellybean.createRouteCategory(
mRouter, r.getString(R.string.mr_user_route_category_name), false);
updateSystemRoutes();
@@ -277,7 +277,7 @@
int index = findSystemRouteRecordByDescriptorId(routeId);
if (index >= 0) {
SystemRouteRecord record = mSystemRouteRecords.get(index);
- return new SystemRouteController(record.mRouteObj);
+ return new SystemRouteController(record.mRoute);
}
return null;
}
@@ -311,8 +311,8 @@
}
@Override
- public void onRouteAdded(@NonNull Object routeObj) {
- if (addSystemRouteNoPublish(routeObj)) {
+ public void onRouteAdded(@NonNull android.media.MediaRouter.RouteInfo route) {
+ if (addSystemRouteNoPublish(route)) {
publishRoutes();
}
}
@@ -320,19 +320,19 @@
private void updateSystemRoutes() {
updateCallback();
boolean changed = false;
- for (Object routeObj : MediaRouterJellybean.getRoutes(mRouter)) {
- changed |= addSystemRouteNoPublish(routeObj);
+ for (android.media.MediaRouter.RouteInfo route : MediaRouterJellybean.getRoutes(
+ mRouter)) {
+ changed |= addSystemRouteNoPublish(route);
}
if (changed) {
publishRoutes();
}
}
- private boolean addSystemRouteNoPublish(Object routeObj) {
- if (getUserRouteRecord(routeObj) == null
- && findSystemRouteRecord(routeObj) < 0) {
- String id = assignRouteId(routeObj);
- SystemRouteRecord record = new SystemRouteRecord(routeObj, id);
+ private boolean addSystemRouteNoPublish(android.media.MediaRouter.RouteInfo route) {
+ if (getUserRouteRecord(route) == null && findSystemRouteRecord(route) < 0) {
+ String id = assignRouteId(route);
+ SystemRouteRecord record = new SystemRouteRecord(route, id);
updateSystemRouteDescriptor(record);
mSystemRouteRecords.add(record);
return true;
@@ -340,13 +340,13 @@
return false;
}
- private String assignRouteId(Object routeObj) {
+ private String assignRouteId(android.media.MediaRouter.RouteInfo route) {
// TODO: The framework media router should supply a unique route id that
// we can use here. For now we use a hash of the route name and take care
// to dedupe it.
- boolean isDefault = (getDefaultRoute() == routeObj);
+ boolean isDefault = (getDefaultRoute() == route);
String id = isDefault ? DEFAULT_ROUTE_ID :
- String.format(Locale.US, "ROUTE_%08x", getRouteName(routeObj).hashCode());
+ String.format(Locale.US, "ROUTE_%08x", getRouteName(route).hashCode());
if (findSystemRouteRecordByDescriptorId(id) < 0) {
return id;
}
@@ -359,9 +359,9 @@
}
@Override
- public void onRouteRemoved(@NonNull Object routeObj) {
- if (getUserRouteRecord(routeObj) == null) {
- int index = findSystemRouteRecord(routeObj);
+ public void onRouteRemoved(@NonNull android.media.MediaRouter.RouteInfo route) {
+ if (getUserRouteRecord(route) == null) {
+ int index = findSystemRouteRecord(route);
if (index >= 0) {
mSystemRouteRecords.remove(index);
publishRoutes();
@@ -370,9 +370,9 @@
}
@Override
- public void onRouteChanged(@NonNull Object routeObj) {
- if (getUserRouteRecord(routeObj) == null) {
- int index = findSystemRouteRecord(routeObj);
+ public void onRouteChanged(@NonNull android.media.MediaRouter.RouteInfo route) {
+ if (getUserRouteRecord(route) == null) {
+ int index = findSystemRouteRecord(route);
if (index >= 0) {
SystemRouteRecord record = mSystemRouteRecords.get(index);
updateSystemRouteDescriptor(record);
@@ -382,12 +382,12 @@
}
@Override
- public void onRouteVolumeChanged(@NonNull Object routeObj) {
- if (getUserRouteRecord(routeObj) == null) {
- int index = findSystemRouteRecord(routeObj);
+ public void onRouteVolumeChanged(@NonNull android.media.MediaRouter.RouteInfo route) {
+ if (getUserRouteRecord(route) == null) {
+ int index = findSystemRouteRecord(route);
if (index >= 0) {
SystemRouteRecord record = mSystemRouteRecords.get(index);
- int newVolume = MediaRouterJellybean.RouteInfo.getVolume(routeObj);
+ int newVolume = MediaRouterJellybean.RouteInfo.getVolume(route);
if (newVolume != record.mRouteDescriptor.getVolume()) {
record.mRouteDescriptor =
new MediaRouteDescriptor.Builder(record.mRouteDescriptor)
@@ -400,21 +400,22 @@
}
@Override
- public void onRouteSelected(int type, @NonNull Object routeObj) {
- if (routeObj != MediaRouterJellybean.getSelectedRoute(mRouter,
+ public void onRouteSelected(int type,
+ @NonNull android.media.MediaRouter.RouteInfo route) {
+ if (route != MediaRouterJellybean.getSelectedRoute(mRouter,
MediaRouterJellybean.ALL_ROUTE_TYPES)) {
// The currently selected route has already changed so this callback
// is stale. Drop it to prevent getting into sync loops.
return;
}
- UserRouteRecord userRouteRecord = getUserRouteRecord(routeObj);
+ UserRouteRecord userRouteRecord = getUserRouteRecord(route);
if (userRouteRecord != null) {
userRouteRecord.mRoute.select();
} else {
// Select the route if it already exists in the compat media router.
// If not, we will select it instead when the route is added.
- int index = findSystemRouteRecord(routeObj);
+ int index = findSystemRouteRecord(route);
if (index >= 0) {
SystemRouteRecord record = mSystemRouteRecords.get(index);
mSyncCallback.onSystemRouteSelectedByDescriptorId(record.mRouteDescriptorId);
@@ -423,32 +424,37 @@
}
@Override
- public void onRouteUnselected(int type, @NonNull Object routeObj) {
+ public void onRouteUnselected(int type,
+ @NonNull android.media.MediaRouter.RouteInfo route) {
// Nothing to do when a route is unselected.
// We only need to handle when a route is selected.
}
@Override
- public void onRouteGrouped(@NonNull Object routeObj, @NonNull Object groupObj, int index) {
+ public void onRouteGrouped(@NonNull android.media.MediaRouter.RouteInfo route,
+ @NonNull android.media.MediaRouter.RouteGroup group, int index) {
// Route grouping is deprecated and no longer supported.
}
@Override
- public void onRouteUngrouped(@NonNull Object routeObj, @NonNull Object groupObj) {
+ public void onRouteUngrouped(@NonNull android.media.MediaRouter.RouteInfo route,
+ @NonNull android.media.MediaRouter.RouteGroup group) {
// Route grouping is deprecated and no longer supported.
}
@Override
- public void onVolumeSetRequest(@NonNull Object routeObj, int volume) {
- UserRouteRecord record = getUserRouteRecord(routeObj);
+ public void onVolumeSetRequest(@NonNull android.media.MediaRouter.RouteInfo route,
+ int volume) {
+ UserRouteRecord record = getUserRouteRecord(route);
if (record != null) {
record.mRoute.requestSetVolume(volume);
}
}
@Override
- public void onVolumeUpdateRequest(@NonNull Object routeObj, int direction) {
- UserRouteRecord record = getUserRouteRecord(routeObj);
+ public void onVolumeUpdateRequest(@NonNull android.media.MediaRouter.RouteInfo route,
+ int direction) {
+ UserRouteRecord record = getUserRouteRecord(route);
if (record != null) {
record.mRoute.requestUpdateVolume(direction);
}
@@ -457,20 +463,21 @@
@Override
public void onSyncRouteAdded(MediaRouter.RouteInfo route) {
if (route.getProviderInstance() != this) {
- Object routeObj = MediaRouterJellybean.createUserRoute(
- mRouter, mUserRouteCategoryObj);
- UserRouteRecord record = new UserRouteRecord(route, routeObj);
- MediaRouterJellybean.RouteInfo.setTag(routeObj, record);
- MediaRouterJellybean.UserRouteInfo.setVolumeCallback(routeObj, mVolumeCallbackObj);
+ android.media.MediaRouter.UserRouteInfo userRoute =
+ MediaRouterJellybean.createUserRoute(mRouter, mUserRouteCategory);
+ UserRouteRecord record = new UserRouteRecord(route, userRoute);
+ MediaRouterJellybean.RouteInfo.setTag(userRoute, record);
+ MediaRouterJellybean.UserRouteInfo.setVolumeCallback(userRoute, mVolumeCallback);
updateUserRouteProperties(record);
mUserRouteRecords.add(record);
- MediaRouterJellybean.addUserRoute(mRouter, routeObj);
+ MediaRouterJellybean.addUserRoute(mRouter, userRoute);
} else {
// If the newly added route is the counterpart of the currently selected
// route in the framework media router then ensure it is selected in
// the compat media router.
- Object routeObj = MediaRouterJellybean.getSelectedRoute(
- mRouter, MediaRouterJellybean.ALL_ROUTE_TYPES);
+ android.media.MediaRouter.RouteInfo routeObj =
+ MediaRouterJellybean.getSelectedRoute(
+ mRouter, MediaRouterJellybean.ALL_ROUTE_TYPES);
int index = findSystemRouteRecord(routeObj);
if (index >= 0) {
SystemRouteRecord record = mSystemRouteRecords.get(index);
@@ -487,9 +494,9 @@
int index = findUserRouteRecord(route);
if (index >= 0) {
UserRouteRecord record = mUserRouteRecords.remove(index);
- MediaRouterJellybean.RouteInfo.setTag(record.mRouteObj, null);
- MediaRouterJellybean.UserRouteInfo.setVolumeCallback(record.mRouteObj, null);
- MediaRouterJellybean.removeUserRoute(mRouter, record.mRouteObj);
+ MediaRouterJellybean.RouteInfo.setTag(record.mUserRoute, null);
+ MediaRouterJellybean.UserRouteInfo.setVolumeCallback(record.mUserRoute, null);
+ MediaRouterJellybean.removeUserRoute(mRouter, record.mUserRoute);
}
}
}
@@ -517,13 +524,13 @@
int index = findUserRouteRecord(route);
if (index >= 0) {
UserRouteRecord record = mUserRouteRecords.get(index);
- selectRoute(record.mRouteObj);
+ selectRoute(record.mUserRoute);
}
} else {
int index = findSystemRouteRecordByDescriptorId(route.getDescriptorId());
if (index >= 0) {
SystemRouteRecord record = mSystemRouteRecords.get(index);
- selectRoute(record.mRouteObj);
+ selectRoute(record.mRoute);
}
}
}
@@ -539,10 +546,10 @@
setDescriptor(builder.build());
}
- protected int findSystemRouteRecord(Object routeObj) {
+ protected int findSystemRouteRecord(android.media.MediaRouter.RouteInfo route) {
final int count = mSystemRouteRecords.size();
for (int i = 0; i < count; i++) {
- if (mSystemRouteRecords.get(i).mRouteObj == routeObj) {
+ if (mSystemRouteRecords.get(i).mRoute == route) {
return i;
}
}
@@ -569,8 +576,8 @@
return -1;
}
- protected UserRouteRecord getUserRouteRecord(Object routeObj) {
- Object tag = MediaRouterJellybean.RouteInfo.getTag(routeObj);
+ protected UserRouteRecord getUserRouteRecord(android.media.MediaRouter.RouteInfo route) {
+ Object tag = MediaRouterJellybean.RouteInfo.getTag(route);
return tag instanceof UserRouteRecord ? (UserRouteRecord) tag : null;
}
@@ -578,24 +585,24 @@
// We must always recreate the route descriptor when making any changes
// because they are intended to be immutable once published.
MediaRouteDescriptor.Builder builder = new MediaRouteDescriptor.Builder(
- record.mRouteDescriptorId, getRouteName(record.mRouteObj));
+ record.mRouteDescriptorId, getRouteName(record.mRoute));
onBuildSystemRouteDescriptor(record, builder);
record.mRouteDescriptor = builder.build();
}
- protected String getRouteName(Object routeObj) {
+ protected String getRouteName(android.media.MediaRouter.RouteInfo route) {
// Routes should not have null names but it may happen for badly configured
// user routes. We tolerate this by using an empty name string here but
// such unnamed routes will be discarded by the media router upstream
// (with a log message so we can track down the problem).
- CharSequence name = MediaRouterJellybean.RouteInfo.getName(routeObj, getContext());
+ CharSequence name = MediaRouterJellybean.RouteInfo.getName(route, getContext());
return name != null ? name.toString() : "";
}
protected void onBuildSystemRouteDescriptor(SystemRouteRecord record,
MediaRouteDescriptor.Builder builder) {
int supportedTypes = MediaRouterJellybean.RouteInfo.getSupportedTypes(
- record.mRouteObj);
+ record.mRoute);
if ((supportedTypes & MediaRouterJellybean.ROUTE_TYPE_LIVE_AUDIO) != 0) {
builder.addControlFilters(LIVE_AUDIO_CONTROL_FILTERS);
}
@@ -604,58 +611,58 @@
}
builder.setPlaybackType(
- MediaRouterJellybean.RouteInfo.getPlaybackType(record.mRouteObj));
+ MediaRouterJellybean.RouteInfo.getPlaybackType(record.mRoute));
builder.setPlaybackStream(
- MediaRouterJellybean.RouteInfo.getPlaybackStream(record.mRouteObj));
+ MediaRouterJellybean.RouteInfo.getPlaybackStream(record.mRoute));
builder.setVolume(
- MediaRouterJellybean.RouteInfo.getVolume(record.mRouteObj));
+ MediaRouterJellybean.RouteInfo.getVolume(record.mRoute));
builder.setVolumeMax(
- MediaRouterJellybean.RouteInfo.getVolumeMax(record.mRouteObj));
+ MediaRouterJellybean.RouteInfo.getVolumeMax(record.mRoute));
builder.setVolumeHandling(
- MediaRouterJellybean.RouteInfo.getVolumeHandling(record.mRouteObj));
+ MediaRouterJellybean.RouteInfo.getVolumeHandling(record.mRoute));
}
protected void updateUserRouteProperties(UserRouteRecord record) {
MediaRouterJellybean.UserRouteInfo.setName(
- record.mRouteObj, record.mRoute.getName());
+ record.mUserRoute, record.mRoute.getName());
MediaRouterJellybean.UserRouteInfo.setPlaybackType(
- record.mRouteObj, record.mRoute.getPlaybackType());
+ record.mUserRoute, record.mRoute.getPlaybackType());
MediaRouterJellybean.UserRouteInfo.setPlaybackStream(
- record.mRouteObj, record.mRoute.getPlaybackStream());
+ record.mUserRoute, record.mRoute.getPlaybackStream());
MediaRouterJellybean.UserRouteInfo.setVolume(
- record.mRouteObj, record.mRoute.getVolume());
+ record.mUserRoute, record.mRoute.getVolume());
MediaRouterJellybean.UserRouteInfo.setVolumeMax(
- record.mRouteObj, record.mRoute.getVolumeMax());
+ record.mUserRoute, record.mRoute.getVolumeMax());
MediaRouterJellybean.UserRouteInfo.setVolumeHandling(
- record.mRouteObj, record.mRoute.getVolumeHandling());
+ record.mUserRoute, record.mRoute.getVolumeHandling());
}
protected void updateCallback() {
if (mCallbackRegistered) {
mCallbackRegistered = false;
- MediaRouterJellybean.removeCallback(mRouter, mCallbackObj);
+ MediaRouterJellybean.removeCallback(mRouter, mCallback);
}
if (mRouteTypes != 0) {
mCallbackRegistered = true;
- MediaRouterJellybean.addCallback(mRouter, mRouteTypes, mCallbackObj);
+ MediaRouterJellybean.addCallback(mRouter, mRouteTypes, mCallback);
}
}
- protected Object createCallbackObj() {
+ protected android.media.MediaRouter.Callback createCallback() {
return MediaRouterJellybean.createCallback(this);
}
- protected Object createVolumeCallbackObj() {
+ protected android.media.MediaRouter.VolumeCallback createVolumeCallback() {
return MediaRouterJellybean.createVolumeCallback(this);
}
- protected void selectRoute(Object routeObj) {
+ protected void selectRoute(android.media.MediaRouter.RouteInfo route) {
if (mSelectRouteWorkaround == null) {
mSelectRouteWorkaround = new MediaRouterJellybean.SelectRouteWorkaround();
}
mSelectRouteWorkaround.selectRoute(mRouter,
- MediaRouterJellybean.ALL_ROUTE_TYPES, routeObj);
+ MediaRouterJellybean.ALL_ROUTE_TYPES, route);
}
protected Object getDefaultRoute() {
@@ -670,12 +677,12 @@
* and published by this route provider to the support library media router.
*/
protected static final class SystemRouteRecord {
- public final Object mRouteObj;
+ public final android.media.MediaRouter.RouteInfo mRoute;
public final String mRouteDescriptorId;
public MediaRouteDescriptor mRouteDescriptor; // assigned immediately after creation
- public SystemRouteRecord(Object routeObj, String id) {
- mRouteObj = routeObj;
+ public SystemRouteRecord(android.media.MediaRouter.RouteInfo route, String id) {
+ mRoute = route;
mRouteDescriptorId = id;
}
}
@@ -686,29 +693,30 @@
*/
protected static final class UserRouteRecord {
public final MediaRouter.RouteInfo mRoute;
- public final Object mRouteObj;
+ public final android.media.MediaRouter.UserRouteInfo mUserRoute;
- public UserRouteRecord(MediaRouter.RouteInfo route, Object routeObj) {
+ public UserRouteRecord(MediaRouter.RouteInfo route,
+ android.media.MediaRouter.UserRouteInfo userRoute) {
mRoute = route;
- mRouteObj = routeObj;
+ mUserRoute = userRoute;
}
}
protected static final class SystemRouteController extends RouteController {
- private final Object mRouteObj;
+ private final android.media.MediaRouter.RouteInfo mRoute;
- public SystemRouteController(Object routeObj) {
- mRouteObj = routeObj;
+ public SystemRouteController(android.media.MediaRouter.RouteInfo route) {
+ mRoute = route;
}
@Override
public void onSetVolume(int volume) {
- MediaRouterJellybean.RouteInfo.requestSetVolume(mRouteObj, volume);
+ MediaRouterJellybean.RouteInfo.requestSetVolume(mRoute, volume);
}
@Override
public void onUpdateVolume(int delta) {
- MediaRouterJellybean.RouteInfo.requestUpdateVolume(mRouteObj, delta);
+ MediaRouterJellybean.RouteInfo.requestUpdateVolume(mRoute, delta);
}
}
}
@@ -727,12 +735,13 @@
}
@Override
- public void onRoutePresentationDisplayChanged(@NonNull Object routeObj) {
- int index = findSystemRouteRecord(routeObj);
+ public void onRoutePresentationDisplayChanged(
+ @NonNull android.media.MediaRouter.RouteInfo route) {
+ int index = findSystemRouteRecord(route);
if (index >= 0) {
SystemRouteRecord record = mSystemRouteRecords.get(index);
Display newPresentationDisplay =
- MediaRouterJellybeanMr1.RouteInfo.getPresentationDisplay(routeObj);
+ MediaRouterJellybeanMr1.RouteInfo.getPresentationDisplay(route);
int newPresentationDisplayId = (newPresentationDisplay != null
? newPresentationDisplay.getDisplayId() : -1);
if (newPresentationDisplayId
@@ -751,7 +760,7 @@
MediaRouteDescriptor.Builder builder) {
super.onBuildSystemRouteDescriptor(record, builder);
- if (!MediaRouterJellybeanMr1.RouteInfo.isEnabled(record.mRouteObj)) {
+ if (!MediaRouterJellybeanMr1.RouteInfo.isEnabled(record.mRoute)) {
builder.setEnabled(false);
}
@@ -760,7 +769,7 @@
}
Display presentationDisplay =
- MediaRouterJellybeanMr1.RouteInfo.getPresentationDisplay(record.mRouteObj);
+ MediaRouterJellybeanMr1.RouteInfo.getPresentationDisplay(record.mRoute);
if (presentationDisplay != null) {
builder.setPresentationDisplayId(presentationDisplay.getDisplayId());
}
@@ -778,7 +787,7 @@
}
@Override
- protected Object createCallbackObj() {
+ protected android.media.MediaRouter.Callback createCallback() {
return MediaRouterJellybeanMr1.createCallback(this);
}
@@ -786,7 +795,7 @@
if (mIsConnectingWorkaround == null) {
mIsConnectingWorkaround = new MediaRouterJellybeanMr1.IsConnectingWorkaround();
}
- return mIsConnectingWorkaround.isConnecting(record.mRouteObj);
+ return mIsConnectingWorkaround.isConnecting(record.mRoute);
}
}
@@ -805,8 +814,7 @@
MediaRouteDescriptor.Builder builder) {
super.onBuildSystemRouteDescriptor(record, builder);
- CharSequence description =
- ((android.media.MediaRouter.RouteInfo) record.mRouteObj).getDescription();
+ CharSequence description = record.mRoute.getDescription();
if (description != null) {
builder.setDescription(description.toString());
}
@@ -814,14 +822,13 @@
@DoNotInline
@Override
- protected void selectRoute(Object routeObj) {
- MediaRouterJellybean.selectRoute(mRouter,
- MediaRouterJellybean.ALL_ROUTE_TYPES, routeObj);
+ protected void selectRoute(android.media.MediaRouter.RouteInfo route) {
+ MediaRouterJellybean.selectRoute(mRouter, MediaRouterJellybean.ALL_ROUTE_TYPES, route);
}
@DoNotInline
@Override
- protected Object getDefaultRoute() {
+ protected android.media.MediaRouter.RouteInfo getDefaultRoute() {
return mRouter.getDefaultRoute();
}
@@ -829,28 +836,26 @@
@Override
protected void updateUserRouteProperties(UserRouteRecord record) {
super.updateUserRouteProperties(record);
- ((android.media.MediaRouter.UserRouteInfo) record.mRouteObj).setDescription(
- record.mRoute.getDescription());
+ record.mUserRoute.setDescription(record.mRoute.getDescription());
}
@DoNotInline
@Override
protected void updateCallback() {
if (mCallbackRegistered) {
- MediaRouterJellybean.removeCallback(mRouter, mCallbackObj);
+ MediaRouterJellybean.removeCallback(mRouter, mCallback);
}
mCallbackRegistered = true;
int flags = MediaRouter.CALLBACK_FLAG_UNFILTERED_EVENTS
| (mActiveScan ? MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN : 0);
- mRouter.addCallback(mRouteTypes,
- (android.media.MediaRouter.Callback) mCallbackObj, flags);
+ mRouter.addCallback(mRouteTypes, mCallback, flags);
}
@DoNotInline
@Override
protected boolean isConnecting(SystemRouteRecord record) {
- return ((android.media.MediaRouter.RouteInfo) record.mRouteObj).isConnecting();
+ return record.mRoute.isConnecting();
}
}
@@ -869,8 +874,7 @@
protected void onBuildSystemRouteDescriptor(SystemRouteRecord record,
MediaRouteDescriptor.Builder builder) {
super.onBuildSystemRouteDescriptor(record, builder);
- builder.setDeviceType(
- ((android.media.MediaRouter.RouteInfo) record.mRouteObj).getDeviceType());
+ builder.setDeviceType(record.mRoute.getDeviceType());
}
}
}
diff --git a/navigation/navigation-compose/build.gradle b/navigation/navigation-compose/build.gradle
index ebc4b00..0ecac1e 100644
--- a/navigation/navigation-compose/build.gradle
+++ b/navigation/navigation-compose/build.gradle
@@ -27,12 +27,12 @@
dependencies {
implementation(libs.kotlinStdlib)
- implementation("androidx.compose.foundation:foundation-layout:1.5.0-beta03")
+ implementation("androidx.compose.foundation:foundation-layout:1.5.0-rc01")
api("androidx.activity:activity-compose:1.7.0")
- api("androidx.compose.animation:animation:1.5.0-beta03")
- api("androidx.compose.runtime:runtime:1.5.0-beta03")
- api("androidx.compose.runtime:runtime-saveable:1.5.0-beta03")
- api("androidx.compose.ui:ui:1.5.0-beta03")
+ api("androidx.compose.animation:animation:1.5.0-rc01")
+ api("androidx.compose.runtime:runtime:1.5.0-rc01")
+ api("androidx.compose.runtime:runtime-saveable:1.5.0-rc01")
+ api("androidx.compose.ui:ui:1.5.0-rc01")
api("androidx.lifecycle:lifecycle-viewmodel-compose:2.6.1")
api(projectOrArtifact(":navigation:navigation-runtime-ktx"))
diff --git a/navigation/navigation-safe-args-generator/build.gradle b/navigation/navigation-safe-args-generator/build.gradle
index c63d57d..4a019c6 100644
--- a/navigation/navigation-safe-args-generator/build.gradle
+++ b/navigation/navigation-safe-args-generator/build.gradle
@@ -24,6 +24,14 @@
androidx.configureAarAsJarForConfiguration("testImplementation")
+// android.jar and xmlpull has the same classes, but android.jar has stubs instead of real
+// implementation, so we remove org.xmlpull.* from android.jar used for tests
+tasks.register("strippedAndroidJar", Jar).configure {
+ it.from(zipTree(SdkHelperKt.getSdkDependency(project).getFiles().first()))
+ it.exclude("org/xmlpull/**")
+ it.archiveFileName.set("stripped-android.jar")
+}
+
dependencies {
implementation(libs.xpp3)
implementation(libs.xmlpull)
@@ -37,24 +45,16 @@
testImplementation(projectOrArtifact(":room:room-compiler-processing-testing"), {
exclude group: "androidx.room", module: "room-compiler-processing"
})
- testImplementation(SdkHelperKt.getSdkDependency(project))
testImplementationAarAsJar(project(":navigation:navigation-common"))
testImplementationAarAsJar(project(":lifecycle:lifecycle-viewmodel-savedstate"))
+ testImplementation(tasks.named("strippedAndroidJar").map { it.outputs.files })
}
-tasks.findByName("test").doFirst {
- // android.jar and xmlpull has the same classes, but android.jar has stubs instead of real
- // implementation, so we move android.jar to end of classpath
- def classpath = it.classpath.getFiles()
- def androidJar = classpath.find { it.name == "android.jar" }
- it.classpath = files(classpath.minus(androidJar).plus(androidJar))
-}
tasks.withType(Test).configureEach {
// https://github.com/google/compile-testing/issues/222
it.jvmArgs "--add-opens=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED"
}
-
androidx {
name = "Navigation TypeSafe Arguments Generator"
type = LibraryType.OTHER_CODE_PROCESSOR
diff --git a/navigation/navigation-safe-args-generator/src/main/kotlin/androidx/navigation/safe/args/generator/kotlin/KotlinNavWriter.kt b/navigation/navigation-safe-args-generator/src/main/kotlin/androidx/navigation/safe/args/generator/kotlin/KotlinNavWriter.kt
index e2a61cc..b1021c3 100644
--- a/navigation/navigation-safe-args-generator/src/main/kotlin/androidx/navigation/safe/args/generator/kotlin/KotlinNavWriter.kt
+++ b/navigation/navigation-safe-args-generator/src/main/kotlin/androidx/navigation/safe/args/generator/kotlin/KotlinNavWriter.kt
@@ -128,11 +128,16 @@
val className = ClassName("", action.id.javaIdentifier.toCamelCase())
val actionIdPropSpec =
- PropertySpec.builder("actionId", Int::class, KModifier.OVERRIDE)
+ PropertySpec.builder("actionId", Int::class, KModifier.PUBLIC, KModifier.OVERRIDE)
.initializer("%L", action.id.accessor()).build()
val argumentsPropSpec =
- PropertySpec.builder("arguments", BUNDLE_CLASSNAME, KModifier.OVERRIDE)
+ PropertySpec.builder(
+ "arguments",
+ BUNDLE_CLASSNAME,
+ KModifier.PUBLIC,
+ KModifier.OVERRIDE
+ )
.getter(
FunSpec.getterBuilder().apply {
if (action.args.any { it.type is ObjectType }) {
diff --git a/paging/paging-common/lint-baseline.xml b/paging/paging-common/lint-baseline.xml
deleted file mode 100644
index 034ebe2..0000000
--- a/paging/paging-common/lint-baseline.xml
+++ /dev/null
@@ -1,40 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.1.0-beta02" type="baseline" client="gradle" dependencies="false" name="AGP (8.1.0-beta02)" variant="all" version="8.1.0-beta02">
-
- <issue
- id="BanThreadSleep"
- message="Uses Thread.sleep()"
- errorLine1=" Thread.sleep(1000)"
- errorLine2=" ~~~~~">
- <location
- file="src/test/kotlin/androidx/paging/PagedListTest.kt"/>
- </issue>
-
- <issue
- id="BanThreadSleep"
- message="Uses Thread.sleep()"
- errorLine1=" Thread.sleep(100)"
- errorLine2=" ~~~~~">
- <location
- file="src/test/kotlin/androidx/paging/SingleRunnerTest.kt"/>
- </issue>
-
- <issue
- id="SupportAnnotationUsage"
- message="Did you mean `@get:VisibleForTesting`? Without `get:` this annotates the constructor parameter itself instead of the associated getter."
- errorLine1=" @VisibleForTesting"
- errorLine2=" ~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/kotlin/androidx/paging/PageFetcher.kt"/>
- </issue>
-
- <issue
- id="SupportAnnotationUsage"
- message="Did you mean `@get:RestrictTo`? Without `get:` this annotates the constructor parameter itself instead of the associated getter."
- errorLine1=" @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/kotlin/androidx/paging/PagedList.kt"/>
- </issue>
-
-</issues>
diff --git a/paging/paging-compose/lint-baseline.xml b/paging/paging-compose/lint-baseline.xml
deleted file mode 100644
index 2612a94..0000000
--- a/paging/paging-compose/lint-baseline.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.1.0-beta02" type="baseline" client="gradle" dependencies="false" name="AGP (8.1.0-beta02)" variant="all" version="8.1.0-beta02">
-
- <issue
- id="PrimitiveInLambda"
- message="Use a functional interface instead of lambda syntax for lambdas with primitive values in return type Function1<Integer, Object> of 'itemKey'."
- errorLine1="): (index: Int) -> Any {"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/paging/compose/LazyFoundationExtensions.kt"/>
- </issue>
-
- <issue
- id="PrimitiveInLambda"
- message="Use a functional interface instead of lambda syntax for lambdas with primitive values in return type Function1<Integer, Object> of 'itemContentType'."
- errorLine1="): (index: Int) -> Any? {"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/paging/compose/LazyFoundationExtensions.kt"/>
- </issue>
-
-</issues>
diff --git a/paging/samples/lint-baseline.xml b/paging/samples/lint-baseline.xml
deleted file mode 100644
index 0a6dfe4..0000000
--- a/paging/samples/lint-baseline.xml
+++ /dev/null
@@ -1,85 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.1.0-beta05" type="baseline" client="gradle" dependencies="true" name="AGP (8.1.0-beta05)" variant="all" version="8.1.0-beta05">
-
- <issue
- id="ObsoleteSampledAnnotation"
- message="androidx.paging.samples.cachedInSample is annotated with @Sampled, but is not linked to from a @sample tag."
- errorLine1="fun cachedInSample() {"
- errorLine2=" ~~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/paging/samples/CachedInSample.kt"/>
- </issue>
-
- <issue
- id="ObsoleteSampledAnnotation"
- message="androidx.paging.samples.insertSeparatorsSample is annotated with @Sampled, but is not linked to from a @sample tag."
- errorLine1="fun insertSeparatorsSample() {"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/paging/samples/InsertSeparatorsSample.kt"/>
- </issue>
-
- <issue
- id="ObsoleteSampledAnnotation"
- message="androidx.paging.samples.insertSeparatorsUiModelSample is annotated with @Sampled, but is not linked to from a @sample tag."
- errorLine1="fun insertSeparatorsUiModelSample() {"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/paging/samples/InsertSeparatorsUiModelSample.kt"/>
- </issue>
-
- <issue
- id="ObsoleteSampledAnnotation"
- message="androidx.paging.samples.itemKeyedPagingSourceSample is annotated with @Sampled, but is not linked to from a @sample tag."
- errorLine1="fun itemKeyedPagingSourceSample() {"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/paging/samples/PagingSourceSample.kt"/>
- </issue>
-
- <issue
- id="ObsoleteSampledAnnotation"
- message="androidx.paging.samples.pageKeyedPagingSourceSample is annotated with @Sampled, but is not linked to from a @sample tag."
- errorLine1="fun pageKeyedPagingSourceSample() {"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/paging/samples/PagingSourceSample.kt"/>
- </issue>
-
- <issue
- id="ObsoleteSampledAnnotation"
- message="androidx.paging.samples.pageKeyedPage is annotated with @Sampled, but is not linked to from a @sample tag."
- errorLine1="fun pageKeyedPage() {"
- errorLine2=" ~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/paging/samples/PagingSourceSample.kt"/>
- </issue>
-
- <issue
- id="ObsoleteSampledAnnotation"
- message="androidx.paging.samples.pageIndexedPage is annotated with @Sampled, but is not linked to from a @sample tag."
- errorLine1="fun pageIndexedPage() {"
- errorLine2=" ~~~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/paging/samples/PagingSourceSample.kt"/>
- </issue>
-
- <issue
- id="ObsoleteSampledAnnotation"
- message="androidx.paging.samples.remoteMediatorItemKeyedSample is annotated with @Sampled, but is not linked to from a @sample tag."
- errorLine1="fun remoteMediatorItemKeyedSample() {"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/paging/samples/RemoteMediatorSample.kt"/>
- </issue>
-
- <issue
- id="ObsoleteSampledAnnotation"
- message="androidx.paging.samples.remoteMediatorPageKeyedSample is annotated with @Sampled, but is not linked to from a @sample tag."
- errorLine1="fun remoteMediatorPageKeyedSample() {"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/paging/samples/RemoteMediatorSample.kt"/>
- </issue>
-
-</issues>
diff --git a/playground-common/playground.properties b/playground-common/playground.properties
index 02a04e61..866f0cb9 100644
--- a/playground-common/playground.properties
+++ b/playground-common/playground.properties
@@ -25,6 +25,6 @@
kotlin.code.style=official
# Disable docs
androidx.enableDocumentation=false
-androidx.playground.snapshotBuildId=10339410
+androidx.playground.snapshotBuildId=10533165
androidx.playground.metalavaBuildId=10499661
androidx.studio.type=playground
diff --git a/privacysandbox/ads/ads-adservices-java/api/current.txt b/privacysandbox/ads/ads-adservices-java/api/current.txt
index 26eea8b..18004f9 100644
--- a/privacysandbox/ads/ads-adservices-java/api/current.txt
+++ b/privacysandbox/ads/ads-adservices-java/api/current.txt
@@ -64,6 +64,7 @@
method public static final androidx.privacysandbox.ads.adservices.java.measurement.MeasurementManagerFutures? from(android.content.Context context);
method @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_ATTRIBUTION) public abstract com.google.common.util.concurrent.ListenableFuture<java.lang.Integer> getMeasurementApiStatusAsync();
method @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_ATTRIBUTION) public abstract com.google.common.util.concurrent.ListenableFuture<kotlin.Unit> registerSourceAsync(android.net.Uri attributionSource, android.view.InputEvent? inputEvent);
+ method @SuppressCompatibility @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_ATTRIBUTION) @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.RegisterSourceOptIn public abstract com.google.common.util.concurrent.ListenableFuture<kotlin.Unit> registerSourceAsync(androidx.privacysandbox.ads.adservices.measurement.SourceRegistrationRequest request);
method @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_ATTRIBUTION) public abstract com.google.common.util.concurrent.ListenableFuture<kotlin.Unit> registerTriggerAsync(android.net.Uri trigger);
method @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_ATTRIBUTION) public abstract com.google.common.util.concurrent.ListenableFuture<kotlin.Unit> registerWebSourceAsync(androidx.privacysandbox.ads.adservices.measurement.WebSourceRegistrationRequest request);
method @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_ATTRIBUTION) public abstract com.google.common.util.concurrent.ListenableFuture<kotlin.Unit> registerWebTriggerAsync(androidx.privacysandbox.ads.adservices.measurement.WebTriggerRegistrationRequest request);
diff --git a/privacysandbox/ads/ads-adservices-java/api/restricted_current.txt b/privacysandbox/ads/ads-adservices-java/api/restricted_current.txt
index 26eea8b..18004f9 100644
--- a/privacysandbox/ads/ads-adservices-java/api/restricted_current.txt
+++ b/privacysandbox/ads/ads-adservices-java/api/restricted_current.txt
@@ -64,6 +64,7 @@
method public static final androidx.privacysandbox.ads.adservices.java.measurement.MeasurementManagerFutures? from(android.content.Context context);
method @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_ATTRIBUTION) public abstract com.google.common.util.concurrent.ListenableFuture<java.lang.Integer> getMeasurementApiStatusAsync();
method @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_ATTRIBUTION) public abstract com.google.common.util.concurrent.ListenableFuture<kotlin.Unit> registerSourceAsync(android.net.Uri attributionSource, android.view.InputEvent? inputEvent);
+ method @SuppressCompatibility @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_ATTRIBUTION) @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.RegisterSourceOptIn public abstract com.google.common.util.concurrent.ListenableFuture<kotlin.Unit> registerSourceAsync(androidx.privacysandbox.ads.adservices.measurement.SourceRegistrationRequest request);
method @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_ATTRIBUTION) public abstract com.google.common.util.concurrent.ListenableFuture<kotlin.Unit> registerTriggerAsync(android.net.Uri trigger);
method @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_ATTRIBUTION) public abstract com.google.common.util.concurrent.ListenableFuture<kotlin.Unit> registerWebSourceAsync(androidx.privacysandbox.ads.adservices.measurement.WebSourceRegistrationRequest request);
method @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_ATTRIBUTION) public abstract com.google.common.util.concurrent.ListenableFuture<kotlin.Unit> registerWebTriggerAsync(androidx.privacysandbox.ads.adservices.measurement.WebTriggerRegistrationRequest request);
diff --git a/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/endtoend/measurement/MeasurementManagerTest.java b/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/endtoend/measurement/MeasurementManagerTest.java
index 6a657ad4..7bc4ea9 100644
--- a/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/endtoend/measurement/MeasurementManagerTest.java
+++ b/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/endtoend/measurement/MeasurementManagerTest.java
@@ -26,6 +26,7 @@
import androidx.privacysandbox.ads.adservices.java.endtoend.TestUtil;
import androidx.privacysandbox.ads.adservices.java.measurement.MeasurementManagerFutures;
import androidx.privacysandbox.ads.adservices.measurement.DeletionRequest;
+import androidx.privacysandbox.ads.adservices.measurement.SourceRegistrationRequest;
import androidx.privacysandbox.ads.adservices.measurement.WebSourceParams;
import androidx.privacysandbox.ads.adservices.measurement.WebSourceRegistrationRequest;
import androidx.privacysandbox.ads.adservices.measurement.WebTriggerParams;
@@ -107,6 +108,19 @@
}
@Test
+ public void testRegisterAppSources_NoServerSetup_NoErrors() throws Exception {
+ // Skip the test if SDK extension 5 is not present.
+ Assume.assumeTrue(AdServicesInfo.INSTANCE.version() >= 5);
+
+ SourceRegistrationRequest request =
+ new SourceRegistrationRequest.Builder(
+ Collections.singletonList(SOURCE_REGISTRATION_URI))
+ .build();
+ assertThat(mMeasurementManager.registerSourceAsync(request).get())
+ .isNotNull();
+ }
+
+ @Test
public void testRegisterTrigger_NoServerSetup_NoErrors() throws Exception {
// Skip the test if SDK extension 5 is not present.
Assume.assumeTrue(AdServicesInfo.INSTANCE.version() >= 5);
diff --git a/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/measurement/MeasurementManagerFuturesTest.kt b/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/measurement/MeasurementManagerFuturesTest.kt
index 6e1752b..0106e76 100644
--- a/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/measurement/MeasurementManagerFuturesTest.kt
+++ b/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/measurement/MeasurementManagerFuturesTest.kt
@@ -24,8 +24,10 @@
import android.os.ext.SdkExtensions
import android.view.InputEvent
import androidx.annotation.RequiresExtension
+import androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures
import androidx.privacysandbox.ads.adservices.java.measurement.MeasurementManagerFutures.Companion.from
import androidx.privacysandbox.ads.adservices.measurement.DeletionRequest
+import androidx.privacysandbox.ads.adservices.measurement.SourceRegistrationRequest
import androidx.privacysandbox.ads.adservices.measurement.WebSourceParams
import androidx.privacysandbox.ads.adservices.measurement.WebSourceRegistrationRequest
import androidx.privacysandbox.ads.adservices.measurement.WebTriggerParams
@@ -36,13 +38,23 @@
import androidx.test.filters.SmallTest
import com.google.common.truth.Truth.assertThat
import java.time.Instant
+import java.util.concurrent.ExecutionException
+import java.util.concurrent.Executor
import kotlin.test.assertNotEquals
+import kotlin.test.assertTrue
+import kotlin.test.fail
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.withContext
import org.junit.Assume
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mockito.atLeastOnce
+import org.mockito.Mockito.atMost
import org.mockito.Mockito.doAnswer
import org.mockito.Mockito.mock
import org.mockito.Mockito.spy
@@ -278,6 +290,112 @@
assertThat(result.get() == state)
}
+ @ExperimentalFeatures.RegisterSourceOptIn
+ @Test
+ @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 5)
+ fun testRegisterSourceAsync_allSuccess() {
+ val sdkExtVersion = SdkExtensions.getExtensionVersion(SdkExtensions.AD_SERVICES)
+
+ Assume.assumeTrue("minSdkVersion = API 33 ext 5", sdkExtVersion >= 5)
+ val inputEvent = mock(InputEvent::class.java)
+ val measurementManager = mockMeasurementManager(mContext)
+ val managerCompat = from(mContext)
+ val successCallback = { args: InvocationOnMock ->
+ assertNotEquals(Looper.myLooper(), Looper.getMainLooper())
+ val receiver = args.getArgument<OutcomeReceiver<Any, Exception>>(3)
+ receiver.onResult(Object())
+ null
+ }
+ doAnswer(successCallback).`when`(measurementManager)
+ .registerSource(any(), any(), any(), any())
+
+ // Actually invoke the compat code.
+ val request = SourceRegistrationRequest.Builder(listOf(uri1, uri2))
+ .setInputEvent(inputEvent)
+ .build()
+ managerCompat!!.registerSourceAsync(request).get()
+
+ // Verify that the compat code was invoked correctly.
+ verify(measurementManager).registerSource(
+ eq(uri1),
+ eq(inputEvent),
+ any(Executor::class.java),
+ any())
+ verify(measurementManager).registerSource(
+ eq(uri2),
+ eq(inputEvent),
+ any(Executor::class.java),
+ any())
+ }
+
+ @ExperimentalFeatures.RegisterSourceOptIn
+ @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 5)
+ @Test
+ fun testRegisterSource_15thOf20Fails_atLeast15thExecutes() {
+ val sdkExtVersion = SdkExtensions.getExtensionVersion(SdkExtensions.AD_SERVICES)
+
+ Assume.assumeTrue("minSdkVersion = API 33 ext 5", sdkExtVersion >= 5)
+ val measurementManager = mockMeasurementManager(mContext)
+ val mockInputEvent = mock(InputEvent::class.java)
+ val managerCompat = from(mContext)
+ val successCallback = { args: InvocationOnMock ->
+ val receiver = args.getArgument<OutcomeReceiver<Any, Exception>>(3)
+ receiver.onResult(Object())
+ null
+ }
+
+ val errorMessage = "some error occurred"
+ val errorCallback = { args: InvocationOnMock ->
+ val receiver = args.getArgument<OutcomeReceiver<Any, Exception>>(3)
+ receiver.onError(IllegalArgumentException(errorMessage))
+ null
+ }
+
+ val uris = (1..20).map { i ->
+ val uri = Uri.parse("www.uri$i.com")
+ if (i == 15) {
+ doAnswer(errorCallback).`when`(measurementManager)
+ .registerSource(eq(uri), any(), any(), any())
+ } else {
+ doAnswer(successCallback).`when`(measurementManager)
+ .registerSource(eq(uri), any(), any(), any())
+ }
+ uri
+ }.toList()
+
+ val request = SourceRegistrationRequest(uris, mockInputEvent)
+
+ // Actually invoke the compat code.
+ runBlocking {
+ try {
+ withContext(Dispatchers.Main) {
+ managerCompat!!.registerSourceAsync(request).get()
+ }
+ fail("Expected failure.")
+ } catch (e: ExecutionException) {
+ assertTrue(e.cause!! is IllegalArgumentException)
+ assertThat(e.cause!!.message).isEqualTo(errorMessage)
+ }
+ }
+
+ // Verify that the compat code was invoked correctly.
+ // registerSource gets called 1-20 times. We cannot predict the exact number because
+ // uri15 would crash asynchronously. Other uris may succeed and those threads on default
+ // dispatcher won't crash.
+ verify(measurementManager, atLeastOnce()).registerSource(
+ any(),
+ eq(mockInputEvent),
+ any(),
+ any()
+ )
+ verify(measurementManager, atMost(20)).registerSource(
+ any(),
+ eq(mockInputEvent),
+ any(),
+ any()
+ )
+ }
+
@SdkSuppress(minSdkVersion = 30)
@RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 5)
companion object {
diff --git a/privacysandbox/ads/ads-adservices-java/src/main/java/androidx/privacysandbox/ads/adservices/java/measurement/MeasurementManagerFutures.kt b/privacysandbox/ads/ads-adservices-java/src/main/java/androidx/privacysandbox/ads/adservices/java/measurement/MeasurementManagerFutures.kt
index b04eb21..e6c5487 100644
--- a/privacysandbox/ads/ads-adservices-java/src/main/java/androidx/privacysandbox/ads/adservices/java/measurement/MeasurementManagerFutures.kt
+++ b/privacysandbox/ads/ads-adservices-java/src/main/java/androidx/privacysandbox/ads/adservices/java/measurement/MeasurementManagerFutures.kt
@@ -22,10 +22,12 @@
import android.view.InputEvent
import androidx.annotation.DoNotInline
import androidx.annotation.RequiresPermission
+import androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures
import androidx.privacysandbox.ads.adservices.java.internal.asListenableFuture
import androidx.privacysandbox.ads.adservices.measurement.DeletionRequest
import androidx.privacysandbox.ads.adservices.measurement.MeasurementManager
import androidx.privacysandbox.ads.adservices.measurement.MeasurementManager.Companion.obtain
+import androidx.privacysandbox.ads.adservices.measurement.SourceRegistrationRequest
import androidx.privacysandbox.ads.adservices.measurement.WebSourceRegistrationRequest
import androidx.privacysandbox.ads.adservices.measurement.WebTriggerRegistrationRequest
import com.google.common.util.concurrent.ListenableFuture
@@ -75,6 +77,19 @@
abstract fun registerTriggerAsync(trigger: Uri): ListenableFuture<Unit>
/**
+ * Register attribution sources(click or view). This API will not process any redirects, all
+ * registration URLs should be supplied with the request.
+ *
+ * @param request source registration request
+ */
+ @ExperimentalFeatures.RegisterSourceOptIn
+ @SuppressWarnings("MissingNullability")
+ @RequiresPermission(AdServicesPermissions.ACCESS_ADSERVICES_ATTRIBUTION)
+ abstract fun registerSourceAsync(
+ request: SourceRegistrationRequest
+ ): ListenableFuture<Unit>
+
+ /**
* Register an attribution source(click or view) from web context. This API will not process any
* redirects, all registration URLs should be supplied with the request. At least one of
* appDestination or webDestination parameters are required to be provided.
@@ -135,6 +150,17 @@
}
@DoNotInline
+ @ExperimentalFeatures.RegisterSourceOptIn
+ @RequiresPermission(AdServicesPermissions.ACCESS_ADSERVICES_ATTRIBUTION)
+ override fun registerSourceAsync(
+ request: SourceRegistrationRequest
+ ): ListenableFuture<Unit> {
+ return CoroutineScope(Dispatchers.Default).async {
+ mMeasurementManager.registerSource(request)
+ }.asListenableFuture()
+ }
+
+ @DoNotInline
@RequiresPermission(AdServicesPermissions.ACCESS_ADSERVICES_ATTRIBUTION)
override fun registerTriggerAsync(trigger: Uri): ListenableFuture<Unit> {
return CoroutineScope(Dispatchers.Default).async {
diff --git a/privacysandbox/ads/ads-adservices/api/current.txt b/privacysandbox/ads/ads-adservices/api/current.txt
index 839dec6..6a4905b 100644
--- a/privacysandbox/ads/ads-adservices/api/current.txt
+++ b/privacysandbox/ads/ads-adservices/api/current.txt
@@ -126,6 +126,12 @@
property public final String identifier;
}
+ public sealed interface ExperimentalFeatures {
+ }
+
+ @SuppressCompatibility @kotlin.RequiresOptIn(message="This API is experimental.", level=kotlin.RequiresOptIn.Level.WARNING) public static @interface ExperimentalFeatures.RegisterSourceOptIn {
+ }
+
}
package androidx.privacysandbox.ads.adservices.customaudience {
@@ -249,6 +255,7 @@
method @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_ATTRIBUTION) public abstract suspend Object? getMeasurementApiStatus(kotlin.coroutines.Continuation<? super java.lang.Integer>);
method public static final androidx.privacysandbox.ads.adservices.measurement.MeasurementManager? obtain(android.content.Context context);
method @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_ATTRIBUTION) public abstract suspend Object? registerSource(android.net.Uri attributionSource, android.view.InputEvent? inputEvent, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+ method @SuppressCompatibility @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_ATTRIBUTION) @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.RegisterSourceOptIn public abstract suspend Object? registerSource(androidx.privacysandbox.ads.adservices.measurement.SourceRegistrationRequest request, kotlin.coroutines.Continuation<? super kotlin.Unit>);
method @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_ATTRIBUTION) public abstract suspend Object? registerTrigger(android.net.Uri trigger, kotlin.coroutines.Continuation<? super kotlin.Unit>);
method @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_ATTRIBUTION) public abstract suspend Object? registerWebSource(androidx.privacysandbox.ads.adservices.measurement.WebSourceRegistrationRequest request, kotlin.coroutines.Continuation<? super kotlin.Unit>);
method @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_ATTRIBUTION) public abstract suspend Object? registerWebTrigger(androidx.privacysandbox.ads.adservices.measurement.WebTriggerRegistrationRequest request, kotlin.coroutines.Continuation<? super kotlin.Unit>);
@@ -261,6 +268,20 @@
method public androidx.privacysandbox.ads.adservices.measurement.MeasurementManager? obtain(android.content.Context context);
}
+ @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.RegisterSourceOptIn public final class SourceRegistrationRequest {
+ ctor public SourceRegistrationRequest(java.util.List<? extends android.net.Uri> registrationUris, optional android.view.InputEvent? inputEvent);
+ method public android.view.InputEvent? getInputEvent();
+ method public java.util.List<android.net.Uri> getRegistrationUris();
+ property public final android.view.InputEvent? inputEvent;
+ property public final java.util.List<android.net.Uri> registrationUris;
+ }
+
+ public static final class SourceRegistrationRequest.Builder {
+ ctor public SourceRegistrationRequest.Builder(java.util.List<? extends android.net.Uri> registrationUris);
+ method public androidx.privacysandbox.ads.adservices.measurement.SourceRegistrationRequest build();
+ method public androidx.privacysandbox.ads.adservices.measurement.SourceRegistrationRequest.Builder setInputEvent(android.view.InputEvent inputEvent);
+ }
+
@RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public final class WebSourceParams {
ctor public WebSourceParams(android.net.Uri registrationUri, boolean debugKeyAllowed);
method public boolean getDebugKeyAllowed();
diff --git a/privacysandbox/ads/ads-adservices/api/restricted_current.txt b/privacysandbox/ads/ads-adservices/api/restricted_current.txt
index 839dec6..6a4905b 100644
--- a/privacysandbox/ads/ads-adservices/api/restricted_current.txt
+++ b/privacysandbox/ads/ads-adservices/api/restricted_current.txt
@@ -126,6 +126,12 @@
property public final String identifier;
}
+ public sealed interface ExperimentalFeatures {
+ }
+
+ @SuppressCompatibility @kotlin.RequiresOptIn(message="This API is experimental.", level=kotlin.RequiresOptIn.Level.WARNING) public static @interface ExperimentalFeatures.RegisterSourceOptIn {
+ }
+
}
package androidx.privacysandbox.ads.adservices.customaudience {
@@ -249,6 +255,7 @@
method @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_ATTRIBUTION) public abstract suspend Object? getMeasurementApiStatus(kotlin.coroutines.Continuation<? super java.lang.Integer>);
method public static final androidx.privacysandbox.ads.adservices.measurement.MeasurementManager? obtain(android.content.Context context);
method @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_ATTRIBUTION) public abstract suspend Object? registerSource(android.net.Uri attributionSource, android.view.InputEvent? inputEvent, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+ method @SuppressCompatibility @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_ATTRIBUTION) @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.RegisterSourceOptIn public abstract suspend Object? registerSource(androidx.privacysandbox.ads.adservices.measurement.SourceRegistrationRequest request, kotlin.coroutines.Continuation<? super kotlin.Unit>);
method @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_ATTRIBUTION) public abstract suspend Object? registerTrigger(android.net.Uri trigger, kotlin.coroutines.Continuation<? super kotlin.Unit>);
method @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_ATTRIBUTION) public abstract suspend Object? registerWebSource(androidx.privacysandbox.ads.adservices.measurement.WebSourceRegistrationRequest request, kotlin.coroutines.Continuation<? super kotlin.Unit>);
method @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_ATTRIBUTION) public abstract suspend Object? registerWebTrigger(androidx.privacysandbox.ads.adservices.measurement.WebTriggerRegistrationRequest request, kotlin.coroutines.Continuation<? super kotlin.Unit>);
@@ -261,6 +268,20 @@
method public androidx.privacysandbox.ads.adservices.measurement.MeasurementManager? obtain(android.content.Context context);
}
+ @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.RegisterSourceOptIn public final class SourceRegistrationRequest {
+ ctor public SourceRegistrationRequest(java.util.List<? extends android.net.Uri> registrationUris, optional android.view.InputEvent? inputEvent);
+ method public android.view.InputEvent? getInputEvent();
+ method public java.util.List<android.net.Uri> getRegistrationUris();
+ property public final android.view.InputEvent? inputEvent;
+ property public final java.util.List<android.net.Uri> registrationUris;
+ }
+
+ public static final class SourceRegistrationRequest.Builder {
+ ctor public SourceRegistrationRequest.Builder(java.util.List<? extends android.net.Uri> registrationUris);
+ method public androidx.privacysandbox.ads.adservices.measurement.SourceRegistrationRequest build();
+ method public androidx.privacysandbox.ads.adservices.measurement.SourceRegistrationRequest.Builder setInputEvent(android.view.InputEvent inputEvent);
+ }
+
@RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public final class WebSourceParams {
ctor public WebSourceParams(android.net.Uri registrationUri, boolean debugKeyAllowed);
method public boolean getDebugKeyAllowed();
diff --git a/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/measurement/MeasurementManagerTest.kt b/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/measurement/MeasurementManagerTest.kt
index 191e2d8..1e0a826 100644
--- a/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/measurement/MeasurementManagerTest.kt
+++ b/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/measurement/MeasurementManagerTest.kt
@@ -23,13 +23,16 @@
import android.os.ext.SdkExtensions
import android.view.InputEvent
import androidx.annotation.RequiresExtension
+import androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures
import androidx.privacysandbox.ads.adservices.measurement.MeasurementManager.Companion.obtain
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SdkSuppress
import androidx.test.filters.SmallTest
+import androidx.testutils.fail
import com.google.common.truth.Truth.assertThat
import java.time.Instant
+import kotlin.IllegalArgumentException
import kotlinx.coroutines.runBlocking
import org.junit.Assume
import org.junit.Before
@@ -37,9 +40,12 @@
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.eq
import org.mockito.Mockito.doAnswer
import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
import org.mockito.Mockito.spy
+import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
import org.mockito.invocation.InvocationOnMock
@@ -211,6 +217,105 @@
assertThat(!actualRequest.sourceParams[0].isDebugKeyAllowed)
}
+ @ExperimentalFeatures.RegisterSourceOptIn
+ @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 5)
+ @Test
+ fun testRegisterSource_allSuccess() {
+ val sdkExtVersion = SdkExtensions.getExtensionVersion(SdkExtensions.AD_SERVICES)
+
+ Assume.assumeTrue("minSdkVersion = API 33 ext 5", sdkExtVersion >= 5)
+ val measurementManager = mockMeasurementManager(mContext)
+ val mockInputEvent = mock(InputEvent::class.java)
+ val managerCompat = obtain(mContext)
+ val successCallback = { args: InvocationOnMock ->
+ val receiver = args.getArgument<OutcomeReceiver<Any, Exception>>(3)
+ receiver.onResult(Object())
+ null
+ }
+ doAnswer(successCallback).`when`(measurementManager)
+ .registerSource(any(), any(), any(), any())
+
+ val request = SourceRegistrationRequest(listOf(uri1, uri2), mockInputEvent)
+
+ // Actually invoke the compat code.
+ runBlocking {
+ managerCompat!!.registerSource(request)
+ }
+
+ // Verify that the compat code was invoked correctly.
+ verify(measurementManager, times(2)).registerSource(
+ any(),
+ eq(mockInputEvent),
+ any(),
+ any()
+ )
+ }
+
+ @ExperimentalFeatures.RegisterSourceOptIn
+ @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 5)
+ @Test
+ fun testRegisterSource_15thOf20Fails_remaining5DoNotExecute() {
+ val sdkExtVersion = SdkExtensions.getExtensionVersion(SdkExtensions.AD_SERVICES)
+
+ Assume.assumeTrue("minSdkVersion = API 33 ext 5", sdkExtVersion >= 5)
+ val measurementManager = mockMeasurementManager(mContext)
+ val mockInputEvent = mock(InputEvent::class.java)
+ val managerCompat = obtain(mContext)
+ val successCallback = { args: InvocationOnMock ->
+ val receiver = args.getArgument<OutcomeReceiver<Any, Exception>>(3)
+ receiver.onResult(Object())
+ null
+ }
+
+ val errorMessage = "some error occurred"
+ val errorCallback = { args: InvocationOnMock ->
+ val receiver = args.getArgument<OutcomeReceiver<Any, Exception>>(3)
+ receiver.onError(IllegalArgumentException(errorMessage))
+ null
+ }
+ val uris = (0..20).map { i ->
+ val uri = Uri.parse("www.uri$i.com")
+ if (i == 15) {
+ doAnswer(errorCallback).`when`(measurementManager)
+ .registerSource(eq(uri), any(), any(), any())
+ } else {
+ doAnswer(successCallback).`when`(measurementManager)
+ .registerSource(eq(uri), any(), any(), any())
+ }
+ uri
+ }.toList()
+
+ val request = SourceRegistrationRequest(uris, mockInputEvent)
+
+ // Actually invoke the compat code.
+ runBlocking {
+ try {
+ managerCompat!!.registerSource(request)
+ fail("Expected failure.")
+ } catch (e: IllegalArgumentException) {
+ assertThat(e.message).isEqualTo(errorMessage)
+ }
+ }
+
+ // Verify that the compat code was invoked correctly.
+ (0..15).forEach { i ->
+ verify(measurementManager).registerSource(
+ eq(Uri.parse("www.uri$i.com")),
+ eq(mockInputEvent),
+ any(),
+ any()
+ )
+ }
+ (16..20).forEach { i ->
+ verify(measurementManager, never()).registerSource(
+ eq(Uri.parse("www.uri$i.com")),
+ eq(mockInputEvent),
+ any(),
+ any()
+ )
+ }
+ }
+
@Test
@RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 5)
fun testRegisterWebTrigger() {
diff --git a/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/measurement/SourceRegistrationRequestTest.kt b/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/measurement/SourceRegistrationRequestTest.kt
new file mode 100644
index 0000000..cff885a
--- /dev/null
+++ b/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/measurement/SourceRegistrationRequestTest.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.privacysandbox.ads.adservices.measurement
+
+import android.net.Uri
+import android.view.InputEvent
+import androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SdkSuppress
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.mock
+
+@ExperimentalFeatures.RegisterSourceOptIn
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@SdkSuppress(minSdkVersion = 33)
+class SourceRegistrationRequestTest {
+ @Test
+ fun testToString() {
+ val result = "AppSourcesRegistrationRequest { RegistrationUris=" +
+ "[[www.abc.com, www.xyz.com]], InputEvent=null }"
+
+ val uri1 = Uri.parse("www.abc.com")
+ val uri2 = Uri.parse("www.xyz.com")
+ val params = listOf(uri1, uri2)
+ val request = SourceRegistrationRequest.Builder(params).build()
+ Truth.assertThat(request.toString()).isEqualTo(result)
+ }
+
+ @Test
+ fun testEquals() {
+ val uri1 = Uri.parse("www.abc.com")
+ val uri2 = Uri.parse("www.xyz.com")
+ val params = listOf(uri1, uri2)
+ val inputEvent = mock(InputEvent::class.java)
+ val request1 = SourceRegistrationRequest.Builder(params)
+ .setInputEvent(inputEvent)
+ .build()
+ val request2 = SourceRegistrationRequest(params, inputEvent)
+ val request3 = SourceRegistrationRequest.Builder(params).build()
+
+ Truth.assertThat(request1 == request2).isTrue()
+ Truth.assertThat(request1 != request3).isTrue()
+ }
+}
diff --git a/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/topics/GetTopicsRequestTest.kt b/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/topics/GetTopicsRequestTest.kt
index e76c23e..70cd0c4 100644
--- a/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/topics/GetTopicsRequestTest.kt
+++ b/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/topics/GetTopicsRequestTest.kt
@@ -18,6 +18,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import androidx.testutils.assertThrows
import com.google.common.truth.Truth
import org.junit.Test
import org.junit.runner.RunWith
@@ -41,4 +42,30 @@
// Verify equality.
Truth.assertThat(request == request2).isTrue()
}
+
+ @Test
+ fun testToString_emptySdkName() {
+ val result = "GetTopicsRequest: adsSdkName=, shouldRecordObservation=true"
+ val request = GetTopicsRequest("", true)
+ Truth.assertThat(request.toString()).isEqualTo(result)
+
+ // Verify Builder.
+ val request2 = GetTopicsRequest.Builder()
+ .setShouldRecordObservation(true)
+ .build()
+ Truth.assertThat(request.toString()).isEqualTo(result)
+
+ // Verify equality.
+ Truth.assertThat(request == request2).isTrue()
+ }
+
+ @Test
+ fun testBuilder_setEmptyAdsSdkName_throwsError() {
+ assertThrows(IllegalStateException::class.java) {
+ GetTopicsRequest.Builder()
+ .setAdsSdkName("")
+ .setShouldRecordObservation(true)
+ .build()
+ }
+ }
}
diff --git a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/common/ExperimentalFeatures.kt b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/common/ExperimentalFeatures.kt
new file mode 100644
index 0000000..e1935b3
--- /dev/null
+++ b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/common/ExperimentalFeatures.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.privacysandbox.ads.adservices.common
+
+/**
+ * Contains AdServices experimental feature opt-in anntations.
+ */
+sealed interface ExperimentalFeatures {
+ /**
+ * Clients should use it when they want to use [MeasurementManager#registerSource] API.
+ */
+ @RequiresOptIn("This API is experimental.", RequiresOptIn.Level.WARNING)
+ annotation class RegisterSourceOptIn
+}
diff --git a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/measurement/MeasurementManager.kt b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/measurement/MeasurementManager.kt
index e316ab0..de62bd2 100644
--- a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/measurement/MeasurementManager.kt
+++ b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/measurement/MeasurementManager.kt
@@ -27,7 +27,10 @@
import androidx.annotation.RequiresExtension
import androidx.annotation.RequiresPermission
import androidx.core.os.asOutcomeReceiver
+import androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures
import androidx.privacysandbox.ads.adservices.internal.AdServicesInfo
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.launch
import kotlinx.coroutines.suspendCancellableCoroutine
/**
@@ -82,6 +85,16 @@
abstract suspend fun registerWebTrigger(request: WebTriggerRegistrationRequest)
/**
+ * Register an attribution source(click or view) context. This API will not process any
+ * redirects, all registration URLs should be supplied with the request.
+ *
+ * @param request source registration request
+ */
+ @RequiresPermission(ACCESS_ADSERVICES_ATTRIBUTION)
+ @ExperimentalFeatures.RegisterSourceOptIn
+ abstract suspend fun registerSource(request: SourceRegistrationRequest)
+
+ /**
* Get Measurement API status.
*
* The call returns an integer value (see [MEASUREMENT_API_STATE_DISABLED] and
@@ -160,6 +173,26 @@
}
}
+ @DoNotInline
+ @ExperimentalFeatures.RegisterSourceOptIn
+ @RequiresPermission(ACCESS_ADSERVICES_ATTRIBUTION)
+ override suspend fun registerSource(
+ request: SourceRegistrationRequest
+ ): Unit = coroutineScope {
+ request.registrationUris.forEach { uri ->
+ launch {
+ suspendCancellableCoroutine<Any> { continuation ->
+ mMeasurementManager.registerSource(
+ uri,
+ request.inputEvent,
+ Runnable::run,
+ continuation.asOutcomeReceiver()
+ )
+ }
+ }
+ }
+ }
+
private fun convertWebSourceRequest(
request: WebSourceRegistrationRequest
): android.adservices.measurement.WebSourceRegistrationRequest {
diff --git a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/measurement/SourceRegistrationRequest.kt b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/measurement/SourceRegistrationRequest.kt
new file mode 100644
index 0000000..7bda8bf
--- /dev/null
+++ b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/measurement/SourceRegistrationRequest.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.privacysandbox.ads.adservices.measurement
+
+import android.net.Uri
+import android.view.InputEvent
+import androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures
+
+/**
+ * Class to hold input to measurement source registration calls from web context.
+ *
+ * @param registrationUris [List] of Registration [Uri]s to fetch sources.
+ * @param inputEvent User Interaction [InputEvent] used by the AttributionReporting API to
+ * distinguish clicks from views.
+ */
+@ExperimentalFeatures.RegisterSourceOptIn
+class SourceRegistrationRequest constructor(
+ val registrationUris: List<Uri>,
+ val inputEvent: InputEvent? = null
+ ) {
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other !is SourceRegistrationRequest) return false
+ return this.registrationUris == other.registrationUris &&
+ this.inputEvent == other.inputEvent
+ }
+
+ override fun hashCode(): Int {
+ var hash = registrationUris.hashCode()
+ if (inputEvent != null) {
+ hash = 31 * hash + inputEvent.hashCode()
+ }
+ return hash
+ }
+
+ override fun toString(): String {
+ val vals = "RegistrationUris=[$registrationUris], InputEvent=$inputEvent"
+ return "AppSourcesRegistrationRequest { $vals }"
+ }
+
+ /**
+ * Builder for [SourceRegistrationRequest].
+ *
+ * @param registrationUris source registration request [Uri]
+ */
+ class Builder(
+ private val registrationUris: List<Uri>
+ ) {
+ private var inputEvent: InputEvent? = null
+
+ /**
+ * Setter for input event.
+ *
+ * @param inputEvent User Interaction InputEvent used by the AttributionReporting API to
+ * distinguish clicks from views.
+ * @return builder
+ */
+ fun setInputEvent(inputEvent: InputEvent): Builder = apply {
+ this.inputEvent = inputEvent
+ }
+
+ /** Pre-validates parameters and builds [SourceRegistrationRequest]. */
+ fun build(): SourceRegistrationRequest {
+ return SourceRegistrationRequest(
+ registrationUris,
+ inputEvent
+ )
+ }
+ }
+}
diff --git a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/topics/GetTopicsRequest.kt b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/topics/GetTopicsRequest.kt
index d8f4a81..6dede0c 100644
--- a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/topics/GetTopicsRequest.kt
+++ b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/topics/GetTopicsRequest.kt
@@ -64,7 +64,10 @@
*
* @param adsSdkName the Ads Sdk Name.
*/
- fun setAdsSdkName(adsSdkName: String): Builder = apply { this.adsSdkName = adsSdkName }
+ fun setAdsSdkName(adsSdkName: String): Builder = apply {
+ check(adsSdkName.isNotEmpty()) { "adsSdkName must be set" }
+ this.adsSdkName = adsSdkName
+ }
/**
* Set the Record Observation.
@@ -80,7 +83,6 @@
/** Builds a [GetTopicsRequest] instance. */
fun build(): GetTopicsRequest {
- check(adsSdkName.isNotEmpty()) { "adsSdkName must be set" }
return GetTopicsRequest(adsSdkName, shouldRecordObservation)
}
}
diff --git a/privacysandbox/tools/tools-apicompiler/src/main/java/androidx/privacysandbox/tools/apicompiler/generator/AbstractSdkProviderGenerator.kt b/privacysandbox/tools/tools-apicompiler/src/main/java/androidx/privacysandbox/tools/apicompiler/generator/AbstractSdkProviderGenerator.kt
index 540f146..21bb582 100644
--- a/privacysandbox/tools/tools-apicompiler/src/main/java/androidx/privacysandbox/tools/apicompiler/generator/AbstractSdkProviderGenerator.kt
+++ b/privacysandbox/tools/tools-apicompiler/src/main/java/androidx/privacysandbox/tools/apicompiler/generator/AbstractSdkProviderGenerator.kt
@@ -61,7 +61,7 @@
private fun generateGetViewFunction(): FunSpec {
return FunSpec.builder("getView").build {
- addModifiers(KModifier.OVERRIDE)
+ addModifiers(KModifier.PUBLIC, KModifier.OVERRIDE)
addParameter("windowContext", contextClass)
addParameter("params", bundleClass)
addParameter("width", Int::class)
diff --git a/privacysandbox/tools/tools-apicompiler/src/main/java/androidx/privacysandbox/tools/apicompiler/generator/Api33SdkProviderGenerator.kt b/privacysandbox/tools/tools-apicompiler/src/main/java/androidx/privacysandbox/tools/apicompiler/generator/Api33SdkProviderGenerator.kt
index 5639123..f4c9aef 100644
--- a/privacysandbox/tools/tools-apicompiler/src/main/java/androidx/privacysandbox/tools/apicompiler/generator/Api33SdkProviderGenerator.kt
+++ b/privacysandbox/tools/tools-apicompiler/src/main/java/androidx/privacysandbox/tools/apicompiler/generator/Api33SdkProviderGenerator.kt
@@ -40,7 +40,7 @@
ClassName("android.app.sdksandbox", "SandboxedSdkProvider")
override fun generateOnLoadSdkFunction() = FunSpec.builder("onLoadSdk").build {
- addModifiers(KModifier.OVERRIDE)
+ addModifiers(KModifier.PUBLIC, KModifier.OVERRIDE)
addParameter("params", bundleClass)
returns(sandboxedSdkClass)
diff --git a/privacysandbox/tools/tools-apicompiler/src/main/java/androidx/privacysandbox/tools/apicompiler/generator/CompatSdkProviderGenerator.kt b/privacysandbox/tools/tools-apicompiler/src/main/java/androidx/privacysandbox/tools/apicompiler/generator/CompatSdkProviderGenerator.kt
index 5053460f..14bd699 100644
--- a/privacysandbox/tools/tools-apicompiler/src/main/java/androidx/privacysandbox/tools/apicompiler/generator/CompatSdkProviderGenerator.kt
+++ b/privacysandbox/tools/tools-apicompiler/src/main/java/androidx/privacysandbox/tools/apicompiler/generator/CompatSdkProviderGenerator.kt
@@ -40,7 +40,7 @@
ClassName("androidx.privacysandbox.sdkruntime.core", "SandboxedSdkProviderCompat")
override fun generateOnLoadSdkFunction() = FunSpec.builder("onLoadSdk").build {
- addModifiers(KModifier.OVERRIDE)
+ addModifiers(KModifier.PUBLIC, KModifier.OVERRIDE)
addParameter("params", bundleClass)
returns(sandboxedSdkCompatClass)
diff --git a/privacysandbox/tools/tools-apicompiler/src/test/test-data/fullfeaturedsdk/output/com/mysdk/MyCallbackClientProxy.kt b/privacysandbox/tools/tools-apicompiler/src/test/test-data/fullfeaturedsdk/output/com/mysdk/MyCallbackClientProxy.kt
index b471257..af49258 100644
--- a/privacysandbox/tools/tools-apicompiler/src/test/test-data/fullfeaturedsdk/output/com/mysdk/MyCallbackClientProxy.kt
+++ b/privacysandbox/tools/tools-apicompiler/src/test/test-data/fullfeaturedsdk/output/com/mysdk/MyCallbackClientProxy.kt
@@ -7,19 +7,19 @@
public val remote: IMyCallback,
public val context: Context,
) : MyCallback {
- public override fun onComplete(response: Response): Unit {
+ public override fun onComplete(response: Response) {
remote.onComplete(ResponseConverter(context).toParcelable(response))
}
- public override fun onClick(x: Int, y: Int): Unit {
+ public override fun onClick(x: Int, y: Int) {
remote.onClick(x, y)
}
- public override fun onCompleteInterface(myInterface: MyInterface): Unit {
+ public override fun onCompleteInterface(myInterface: MyInterface) {
remote.onCompleteInterface(MyInterfaceStubDelegate(myInterface, context))
}
- public override fun onCompleteUiInterface(myUiInterface: MyUiInterface): Unit {
+ public override fun onCompleteUiInterface(myUiInterface: MyUiInterface) {
remote.onCompleteUiInterface(IMyUiInterfaceCoreLibInfoAndBinderWrapperConverter.toParcelable(myUiInterface.toCoreLibInfo(context),
MyUiInterfaceStubDelegate(myUiInterface, context)))
}
diff --git a/privacysandbox/tools/tools-apicompiler/src/test/test-data/fullfeaturedsdk/output/com/mysdk/MyInterfaceStubDelegate.kt b/privacysandbox/tools/tools-apicompiler/src/test/test-data/fullfeaturedsdk/output/com/mysdk/MyInterfaceStubDelegate.kt
index 0f0d2f7..223d580 100644
--- a/privacysandbox/tools/tools-apicompiler/src/test/test-data/fullfeaturedsdk/output/com/mysdk/MyInterfaceStubDelegate.kt
+++ b/privacysandbox/tools/tools-apicompiler/src/test/test-data/fullfeaturedsdk/output/com/mysdk/MyInterfaceStubDelegate.kt
@@ -1,10 +1,8 @@
package com.mysdk
import android.content.Context
-import com.mysdk.PrivacySandboxThrowableParcelConverter
import com.mysdk.PrivacySandboxThrowableParcelConverter.toThrowableParcel
import kotlin.Int
-import kotlin.Unit
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@@ -16,7 +14,7 @@
private val coroutineScope: CoroutineScope = CoroutineScope(Dispatchers.Main)
public override fun doSomething(request: ParcelableRequest,
- transactionCallback: IResponseTransactionCallback): Unit {
+ transactionCallback: IResponseTransactionCallback) {
val job = coroutineScope.launch {
try {
val result = delegate.doSomething(RequestConverter(context).fromParcelable(request))
@@ -31,7 +29,7 @@
}
public override fun getMyInterface(input: IMyInterface,
- transactionCallback: IMyInterfaceTransactionCallback): Unit {
+ transactionCallback: IMyInterfaceTransactionCallback) {
val job = coroutineScope.launch {
try {
val result = delegate.getMyInterface((input as MyInterfaceStubDelegate).delegate)
@@ -46,7 +44,7 @@
}
public override fun getMySecondInterface(input: IMySecondInterface,
- transactionCallback: IMySecondInterfaceTransactionCallback): Unit {
+ transactionCallback: IMySecondInterfaceTransactionCallback) {
val job = coroutineScope.launch {
try {
val result = delegate.getMySecondInterface((input as
@@ -61,7 +59,7 @@
transactionCallback.onCancellable(cancellationSignal)
}
- public override fun doMoreStuff(x: Int): Unit {
+ public override fun doMoreStuff(x: Int) {
coroutineScope.launch {
delegate.doMoreStuff(x)
}
diff --git a/privacysandbox/tools/tools-apicompiler/src/test/test-data/fullfeaturedsdk/output/com/mysdk/MySdkStubDelegate.kt b/privacysandbox/tools/tools-apicompiler/src/test/test-data/fullfeaturedsdk/output/com/mysdk/MySdkStubDelegate.kt
index b1f2995..3f376f2 100644
--- a/privacysandbox/tools/tools-apicompiler/src/test/test-data/fullfeaturedsdk/output/com/mysdk/MySdkStubDelegate.kt
+++ b/privacysandbox/tools/tools-apicompiler/src/test/test-data/fullfeaturedsdk/output/com/mysdk/MySdkStubDelegate.kt
@@ -3,11 +3,9 @@
import android.content.Context
import android.os.Bundle
import androidx.privacysandbox.ui.provider.toCoreLibInfo
-import com.mysdk.PrivacySandboxThrowableParcelConverter
import com.mysdk.PrivacySandboxThrowableParcelConverter.toThrowableParcel
import kotlin.Int
import kotlin.IntArray
-import kotlin.Unit
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@@ -22,7 +20,7 @@
x: Int,
y: Int,
transactionCallback: IStringTransactionCallback,
- ): Unit {
+ ) {
val job = coroutineScope.launch {
try {
val result = delegate.doStuff(x, y)
@@ -37,7 +35,7 @@
}
public override fun handleRequest(request: ParcelableRequest,
- transactionCallback: IResponseTransactionCallback): Unit {
+ transactionCallback: IResponseTransactionCallback) {
val job = coroutineScope.launch {
try {
val result = delegate.handleRequest(RequestConverter(context).fromParcelable(request))
@@ -52,7 +50,7 @@
}
public override fun logRequest(request: ParcelableRequest,
- transactionCallback: IUnitTransactionCallback): Unit {
+ transactionCallback: IUnitTransactionCallback) {
val job = coroutineScope.launch {
try {
delegate.logRequest(RequestConverter(context).fromParcelable(request))
@@ -66,20 +64,20 @@
transactionCallback.onCancellable(cancellationSignal)
}
- public override fun setListener(listener: IMyCallback): Unit {
+ public override fun setListener(listener: IMyCallback) {
coroutineScope.launch {
delegate.setListener(MyCallbackClientProxy(listener, context))
}
}
- public override fun doMoreStuff(): Unit {
+ public override fun doMoreStuff() {
coroutineScope.launch {
delegate.doMoreStuff()
}
}
public override fun getMyInterface(input: IMyInterface,
- transactionCallback: IMyInterfaceTransactionCallback): Unit {
+ transactionCallback: IMyInterfaceTransactionCallback) {
val job = coroutineScope.launch {
try {
val result = delegate.getMyInterface((input as MyInterfaceStubDelegate).delegate)
@@ -93,7 +91,7 @@
transactionCallback.onCancellable(cancellationSignal)
}
- public override fun mutateMySecondInterface(input: IMySecondInterface): Unit {
+ public override fun mutateMySecondInterface(input: IMySecondInterface) {
coroutineScope.launch {
delegate.mutateMySecondInterface((input as MySecondInterfaceStubDelegate).delegate)
}
@@ -103,7 +101,7 @@
x: IntArray,
y: IntArray,
transactionCallback: IListStringTransactionCallback,
- ): Unit {
+ ) {
val job = coroutineScope.launch {
try {
val result = delegate.handleNullablePrimitives(x.firstOrNull(), y.firstOrNull())
@@ -118,7 +116,7 @@
}
public override fun handleNullableValues(maybeRequest: ParcelableRequest?,
- transactionCallback: IResponseTransactionCallback): Unit {
+ transactionCallback: IResponseTransactionCallback) {
val job = coroutineScope.launch {
try {
val result = delegate.handleNullableValues(maybeRequest?.let { notNullValue ->
@@ -135,7 +133,7 @@
}
public override fun handleNullableInterfaces(maybeCallback: IMyCallback?,
- transactionCallback: IMyInterfaceTransactionCallback): Unit {
+ transactionCallback: IMyInterfaceTransactionCallback) {
val job = coroutineScope.launch {
try {
val result = delegate.handleNullableInterfaces(maybeCallback?.let { notNullValue ->
@@ -151,8 +149,7 @@
transactionCallback.onCancellable(cancellationSignal)
}
- public override fun returnUiInterface(transactionCallback: IMyUiInterfaceTransactionCallback):
- Unit {
+ public override fun returnUiInterface(transactionCallback: IMyUiInterfaceTransactionCallback) {
val job = coroutineScope.launch {
try {
val result = delegate.returnUiInterface()
@@ -167,22 +164,20 @@
transactionCallback.onCancellable(cancellationSignal)
}
- public override fun acceptUiInterfaceParam(input: IMyUiInterfaceCoreLibInfoAndBinderWrapper):
- Unit {
+ public override fun acceptUiInterfaceParam(input: IMyUiInterfaceCoreLibInfoAndBinderWrapper) {
coroutineScope.launch {
delegate.acceptUiInterfaceParam((input.binder as MyUiInterfaceStubDelegate).delegate)
}
}
- public override fun acceptSdkActivityLauncherParam(activityLauncher: Bundle): Unit {
+ public override fun acceptSdkActivityLauncherParam(activityLauncher: Bundle) {
coroutineScope.launch {
delegate.acceptSdkActivityLauncherParam(SdkActivityLauncherAndBinderWrapper(activityLauncher))
}
}
public override
- fun returnSdkActivityLauncher(transactionCallback: ISdkActivityLauncherTransactionCallback):
- Unit {
+ fun returnSdkActivityLauncher(transactionCallback: ISdkActivityLauncherTransactionCallback) {
val job = coroutineScope.launch {
try {
val result = delegate.returnSdkActivityLauncher()
diff --git a/privacysandbox/tools/tools-apicompiler/src/test/test-data/fullfeaturedsdk/output/com/mysdk/MySecondInterfaceStubDelegate.kt b/privacysandbox/tools/tools-apicompiler/src/test/test-data/fullfeaturedsdk/output/com/mysdk/MySecondInterfaceStubDelegate.kt
index 817f83a..af61158b 100644
--- a/privacysandbox/tools/tools-apicompiler/src/test/test-data/fullfeaturedsdk/output/com/mysdk/MySecondInterfaceStubDelegate.kt
+++ b/privacysandbox/tools/tools-apicompiler/src/test/test-data/fullfeaturedsdk/output/com/mysdk/MySecondInterfaceStubDelegate.kt
@@ -1,7 +1,6 @@
package com.mysdk
import android.content.Context
-import com.mysdk.PrivacySandboxThrowableParcelConverter
import com.mysdk.PrivacySandboxThrowableParcelConverter.toThrowableParcel
import kotlin.Array
import kotlin.BooleanArray
@@ -11,7 +10,6 @@
import kotlin.IntArray
import kotlin.LongArray
import kotlin.String
-import kotlin.Unit
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@@ -22,8 +20,7 @@
) : IMySecondInterface.Stub() {
private val coroutineScope: CoroutineScope = CoroutineScope(Dispatchers.Main)
- public override fun doIntStuff(x: IntArray, transactionCallback: IListIntTransactionCallback):
- Unit {
+ public override fun doIntStuff(x: IntArray, transactionCallback: IListIntTransactionCallback) {
val job = coroutineScope.launch {
try {
val result = delegate.doIntStuff(x.toList())
@@ -37,8 +34,7 @@
transactionCallback.onCancellable(cancellationSignal)
}
- public override fun doCharStuff(x: CharArray, transactionCallback: IListCharTransactionCallback):
- Unit {
+ public override fun doCharStuff(x: CharArray, transactionCallback: IListCharTransactionCallback) {
val job = coroutineScope.launch {
try {
val result = delegate.doCharStuff(x.toList())
@@ -53,7 +49,7 @@
}
public override fun doFloatStuff(x: FloatArray,
- transactionCallback: IListFloatTransactionCallback): Unit {
+ transactionCallback: IListFloatTransactionCallback) {
val job = coroutineScope.launch {
try {
val result = delegate.doFloatStuff(x.toList())
@@ -67,8 +63,7 @@
transactionCallback.onCancellable(cancellationSignal)
}
- public override fun doLongStuff(x: LongArray, transactionCallback: IListLongTransactionCallback):
- Unit {
+ public override fun doLongStuff(x: LongArray, transactionCallback: IListLongTransactionCallback) {
val job = coroutineScope.launch {
try {
val result = delegate.doLongStuff(x.toList())
@@ -83,7 +78,7 @@
}
public override fun doDoubleStuff(x: DoubleArray,
- transactionCallback: IListDoubleTransactionCallback): Unit {
+ transactionCallback: IListDoubleTransactionCallback) {
val job = coroutineScope.launch {
try {
val result = delegate.doDoubleStuff(x.toList())
@@ -98,7 +93,7 @@
}
public override fun doBooleanStuff(x: BooleanArray,
- transactionCallback: IListBooleanTransactionCallback): Unit {
+ transactionCallback: IListBooleanTransactionCallback) {
val job = coroutineScope.launch {
try {
val result = delegate.doBooleanStuff(x.toList())
@@ -112,8 +107,8 @@
transactionCallback.onCancellable(cancellationSignal)
}
- public override fun doShortStuff(x: IntArray, transactionCallback: IListShortTransactionCallback):
- Unit {
+ public override fun doShortStuff(x: IntArray,
+ transactionCallback: IListShortTransactionCallback) {
val job = coroutineScope.launch {
try {
val result = delegate.doShortStuff(x.map { it.toShort() }.toList())
@@ -128,7 +123,7 @@
}
public override fun doStringStuff(x: Array<String>,
- transactionCallback: IListStringTransactionCallback): Unit {
+ transactionCallback: IListStringTransactionCallback) {
val job = coroutineScope.launch {
try {
val result = delegate.doStringStuff(x.toList())
@@ -143,7 +138,7 @@
}
public override fun doValueStuff(x: Array<ParcelableRequest>,
- transactionCallback: IListResponseTransactionCallback): Unit {
+ transactionCallback: IListResponseTransactionCallback) {
val job = coroutineScope.launch {
try {
val result = delegate.doValueStuff(x.map { RequestConverter(context).fromParcelable(it)
diff --git a/privacysandbox/tools/tools-apicompiler/src/test/test-data/fullfeaturedsdk/output/com/mysdk/MyUiInterfaceStubDelegate.kt b/privacysandbox/tools/tools-apicompiler/src/test/test-data/fullfeaturedsdk/output/com/mysdk/MyUiInterfaceStubDelegate.kt
index b234751..747598f 100644
--- a/privacysandbox/tools/tools-apicompiler/src/test/test-data/fullfeaturedsdk/output/com/mysdk/MyUiInterfaceStubDelegate.kt
+++ b/privacysandbox/tools/tools-apicompiler/src/test/test-data/fullfeaturedsdk/output/com/mysdk/MyUiInterfaceStubDelegate.kt
@@ -2,7 +2,6 @@
import android.content.Context
import kotlin.Int
-import kotlin.Unit
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@@ -13,7 +12,7 @@
) : IMyUiInterface.Stub() {
private val coroutineScope: CoroutineScope = CoroutineScope(Dispatchers.Main)
- public override fun doSomethingForUi(x: Int, y: Int): Unit {
+ public override fun doSomethingForUi(x: Int, y: Int) {
coroutineScope.launch {
delegate.doSomethingForUi(x, y)
}
diff --git a/privacysandbox/tools/tools-apicompiler/src/test/test-data/fullfeaturedsdk/output/com/mysdk/TransportCancellationCallback.kt b/privacysandbox/tools/tools-apicompiler/src/test/test-data/fullfeaturedsdk/output/com/mysdk/TransportCancellationCallback.kt
index 287dc52..ba1815c 100644
--- a/privacysandbox/tools/tools-apicompiler/src/test/test-data/fullfeaturedsdk/output/com/mysdk/TransportCancellationCallback.kt
+++ b/privacysandbox/tools/tools-apicompiler/src/test/test-data/fullfeaturedsdk/output/com/mysdk/TransportCancellationCallback.kt
@@ -8,7 +8,7 @@
) : ICancellationSignal.Stub() {
private val hasCancelled: AtomicBoolean = AtomicBoolean(false)
- public override fun cancel(): Unit {
+ public override fun cancel() {
if (hasCancelled.compareAndSet(false, true)) {
onCancel()
}
diff --git a/privacysandbox/tools/tools-apicompiler/src/test/test-data/sdkwithpackages/output/com/myotherpackage/MyOtherPackageInterfaceStubDelegate.kt b/privacysandbox/tools/tools-apicompiler/src/test/test-data/sdkwithpackages/output/com/myotherpackage/MyOtherPackageInterfaceStubDelegate.kt
index 594b8e3..ad1f00a 100644
--- a/privacysandbox/tools/tools-apicompiler/src/test/test-data/sdkwithpackages/output/com/myotherpackage/MyOtherPackageInterfaceStubDelegate.kt
+++ b/privacysandbox/tools/tools-apicompiler/src/test/test-data/sdkwithpackages/output/com/myotherpackage/MyOtherPackageInterfaceStubDelegate.kt
@@ -5,7 +5,6 @@
import com.mysdk.PrivacySandboxThrowableParcelConverter.toThrowableParcel
import com.mysdk.TransportCancellationCallback
import kotlin.Int
-import kotlin.Unit
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@@ -16,14 +15,14 @@
) : IMyOtherPackageInterface.Stub() {
private val coroutineScope: CoroutineScope = CoroutineScope(Dispatchers.Main)
- public override fun doStuff(x: Int): Unit {
+ public override fun doStuff(x: Int) {
coroutineScope.launch {
delegate.doStuff(x)
}
}
public override fun useDataClass(x: ParcelableMyOtherPackageDataClass,
- transactionCallback: IUnitTransactionCallback): Unit {
+ transactionCallback: IUnitTransactionCallback) {
val job = coroutineScope.launch {
try {
delegate.useDataClass(MyOtherPackageDataClassConverter(context).fromParcelable(x))
diff --git a/privacysandbox/tools/tools-apicompiler/src/test/test-data/sdkwithpackages/output/com/mysdk/MyMainPackageInterfaceStubDelegate.kt b/privacysandbox/tools/tools-apicompiler/src/test/test-data/sdkwithpackages/output/com/mysdk/MyMainPackageInterfaceStubDelegate.kt
index 661bf8c..c5bf8e7 100644
--- a/privacysandbox/tools/tools-apicompiler/src/test/test-data/sdkwithpackages/output/com/mysdk/MyMainPackageInterfaceStubDelegate.kt
+++ b/privacysandbox/tools/tools-apicompiler/src/test/test-data/sdkwithpackages/output/com/mysdk/MyMainPackageInterfaceStubDelegate.kt
@@ -3,10 +3,8 @@
import android.content.Context
import com.myotherpackage.MyOtherPackageDataClassConverter
import com.myotherpackage.ParcelableMyOtherPackageDataClass
-import com.mysdk.PrivacySandboxThrowableParcelConverter
import com.mysdk.PrivacySandboxThrowableParcelConverter.toThrowableParcel
import kotlin.IntArray
-import kotlin.Unit
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@@ -17,8 +15,7 @@
) : IMyMainPackageInterface.Stub() {
private val coroutineScope: CoroutineScope = CoroutineScope(Dispatchers.Main)
- public override fun doIntStuff(x: IntArray, transactionCallback: IListIntTransactionCallback):
- Unit {
+ public override fun doIntStuff(x: IntArray, transactionCallback: IListIntTransactionCallback) {
val job = coroutineScope.launch {
try {
val result = delegate.doIntStuff(x.toList())
@@ -33,7 +30,7 @@
}
public override fun useDataClass(x: ParcelableMyOtherPackageDataClass,
- transactionCallback: IMyOtherPackageDataClassTransactionCallback): Unit {
+ transactionCallback: IMyOtherPackageDataClassTransactionCallback) {
val job = coroutineScope.launch {
try {
val result =
diff --git a/privacysandbox/tools/tools-apicompiler/src/test/test-data/sdkwithpackages/output/com/mysdk/MySdkStubDelegate.kt b/privacysandbox/tools/tools-apicompiler/src/test/test-data/sdkwithpackages/output/com/mysdk/MySdkStubDelegate.kt
index b9c2fb5..d95fdd0 100644
--- a/privacysandbox/tools/tools-apicompiler/src/test/test-data/sdkwithpackages/output/com/mysdk/MySdkStubDelegate.kt
+++ b/privacysandbox/tools/tools-apicompiler/src/test/test-data/sdkwithpackages/output/com/mysdk/MySdkStubDelegate.kt
@@ -2,10 +2,8 @@
import android.content.Context
import com.myotherpackage.MyOtherPackageInterfaceStubDelegate
-import com.mysdk.PrivacySandboxThrowableParcelConverter
import com.mysdk.PrivacySandboxThrowableParcelConverter.toThrowableParcel
import kotlin.Int
-import kotlin.Unit
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@@ -20,7 +18,7 @@
x: Int,
y: Int,
transactionCallback: IStringTransactionCallback,
- ): Unit {
+ ) {
val job = coroutineScope.launch {
try {
val result = delegate.doStuff(x, y)
@@ -35,7 +33,7 @@
}
public override
- fun getMyInterface(transactionCallback: IMyMainPackageInterfaceTransactionCallback): Unit {
+ fun getMyInterface(transactionCallback: IMyMainPackageInterfaceTransactionCallback) {
val job = coroutineScope.launch {
try {
val result = delegate.getMyInterface()
@@ -50,8 +48,7 @@
}
public override
- fun getMyOtherPackageInterface(transactionCallback: IMyOtherPackageInterfaceTransactionCallback):
- Unit {
+ fun getMyOtherPackageInterface(transactionCallback: IMyOtherPackageInterfaceTransactionCallback) {
val job = coroutineScope.launch {
try {
val result = delegate.getMyOtherPackageInterface()
diff --git a/privacysandbox/tools/tools-apicompiler/src/test/test-data/sdkwithpackages/output/com/mysdk/TransportCancellationCallback.kt b/privacysandbox/tools/tools-apicompiler/src/test/test-data/sdkwithpackages/output/com/mysdk/TransportCancellationCallback.kt
index 287dc52..ba1815c 100644
--- a/privacysandbox/tools/tools-apicompiler/src/test/test-data/sdkwithpackages/output/com/mysdk/TransportCancellationCallback.kt
+++ b/privacysandbox/tools/tools-apicompiler/src/test/test-data/sdkwithpackages/output/com/mysdk/TransportCancellationCallback.kt
@@ -8,7 +8,7 @@
) : ICancellationSignal.Stub() {
private val hasCancelled: AtomicBoolean = AtomicBoolean(false)
- public override fun cancel(): Unit {
+ public override fun cancel() {
if (hasCancelled.compareAndSet(false, true)) {
onCancel()
}
diff --git a/privacysandbox/tools/tools-apicompiler/src/test/test-data/withoutruntimelibrarysdk/output/com/mysdk/TransportCancellationCallback.kt b/privacysandbox/tools/tools-apicompiler/src/test/test-data/withoutruntimelibrarysdk/output/com/mysdk/TransportCancellationCallback.kt
index 287dc52..ba1815c 100644
--- a/privacysandbox/tools/tools-apicompiler/src/test/test-data/withoutruntimelibrarysdk/output/com/mysdk/TransportCancellationCallback.kt
+++ b/privacysandbox/tools/tools-apicompiler/src/test/test-data/withoutruntimelibrarysdk/output/com/mysdk/TransportCancellationCallback.kt
@@ -8,7 +8,7 @@
) : ICancellationSignal.Stub() {
private val hasCancelled: AtomicBoolean = AtomicBoolean(false)
- public override fun cancel(): Unit {
+ public override fun cancel() {
if (hasCancelled.compareAndSet(false, true)) {
onCancel()
}
diff --git a/privacysandbox/tools/tools-apicompiler/src/test/test-data/withoutruntimelibrarysdk/output/com/mysdk/WithoutRuntimeLibrarySdkStubDelegate.kt b/privacysandbox/tools/tools-apicompiler/src/test/test-data/withoutruntimelibrarysdk/output/com/mysdk/WithoutRuntimeLibrarySdkStubDelegate.kt
index c46719e..a814b72 100644
--- a/privacysandbox/tools/tools-apicompiler/src/test/test-data/withoutruntimelibrarysdk/output/com/mysdk/WithoutRuntimeLibrarySdkStubDelegate.kt
+++ b/privacysandbox/tools/tools-apicompiler/src/test/test-data/withoutruntimelibrarysdk/output/com/mysdk/WithoutRuntimeLibrarySdkStubDelegate.kt
@@ -3,7 +3,6 @@
import android.content.Context
import com.mysdk.PrivacySandboxThrowableParcelConverter.toThrowableParcel
import kotlin.Int
-import kotlin.Unit
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@@ -18,7 +17,7 @@
x: Int,
y: Int,
transactionCallback: IStringTransactionCallback,
- ): Unit {
+ ) {
val job = coroutineScope.launch {
try {
val result = delegate.doStuff(x, y)
diff --git a/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/output/com/sdkwithcallbacks/MyInterface.kt b/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/output/com/sdkwithcallbacks/MyInterface.kt
index 0474314..0b68d85 100644
--- a/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/output/com/sdkwithcallbacks/MyInterface.kt
+++ b/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/output/com/sdkwithcallbacks/MyInterface.kt
@@ -1,5 +1,5 @@
package com.sdkwithcallbacks
public interface MyInterface {
- public fun doStuff(): Unit
+ public fun doStuff()
}
diff --git a/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/output/com/sdkwithcallbacks/MyInterfaceClientProxy.kt b/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/output/com/sdkwithcallbacks/MyInterfaceClientProxy.kt
index f325ee4..c11a869 100644
--- a/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/output/com/sdkwithcallbacks/MyInterfaceClientProxy.kt
+++ b/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/output/com/sdkwithcallbacks/MyInterfaceClientProxy.kt
@@ -3,7 +3,7 @@
public class MyInterfaceClientProxy(
public val remote: IMyInterface,
) : MyInterface {
- public override fun doStuff(): Unit {
+ public override fun doStuff() {
remote.doStuff()
}
}
diff --git a/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/output/com/sdkwithcallbacks/MyUiInterface.kt b/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/output/com/sdkwithcallbacks/MyUiInterface.kt
index 2975910..b337c45 100644
--- a/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/output/com/sdkwithcallbacks/MyUiInterface.kt
+++ b/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/output/com/sdkwithcallbacks/MyUiInterface.kt
@@ -3,5 +3,5 @@
import androidx.privacysandbox.ui.core.SandboxedUiAdapter
public interface MyUiInterface : SandboxedUiAdapter {
- public fun doUiStuff(): Unit
+ public fun doUiStuff()
}
diff --git a/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/output/com/sdkwithcallbacks/MyUiInterfaceClientProxy.kt b/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/output/com/sdkwithcallbacks/MyUiInterfaceClientProxy.kt
index 6aa0aae..4d8eeb9 100644
--- a/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/output/com/sdkwithcallbacks/MyUiInterfaceClientProxy.kt
+++ b/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/output/com/sdkwithcallbacks/MyUiInterfaceClientProxy.kt
@@ -15,7 +15,7 @@
public val sandboxedUiAdapter: SandboxedUiAdapter =
SandboxedUiAdapterFactory.createFromCoreLibInfo(coreLibInfo)
- public override fun doUiStuff(): Unit {
+ public override fun doUiStuff() {
remote.doUiStuff()
}
@@ -27,7 +27,7 @@
isZOrderOnTop: Boolean,
clientExecutor: Executor,
client: SandboxedUiAdapter.SessionClient,
- ): Unit {
+ ) {
sandboxedUiAdapter.openSession(context, windowInputToken, initialWidth, initialHeight,
isZOrderOnTop, clientExecutor, client)
}
diff --git a/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/output/com/sdkwithcallbacks/SdkCallback.kt b/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/output/com/sdkwithcallbacks/SdkCallback.kt
index ec21831..040ec55 100644
--- a/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/output/com/sdkwithcallbacks/SdkCallback.kt
+++ b/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/output/com/sdkwithcallbacks/SdkCallback.kt
@@ -3,13 +3,13 @@
import androidx.privacysandbox.ui.core.SdkActivityLauncher
public interface SdkCallback {
- public fun onCompleteInterface(myInterface: MyInterface): Unit
+ public fun onCompleteInterface(myInterface: MyInterface)
- public fun onEmptyEvent(): Unit
+ public fun onEmptyEvent()
- public fun onPrimitivesReceived(x: Int, y: Int): Unit
+ public fun onPrimitivesReceived(x: Int, y: Int)
- public fun onSdkActivityLauncherReceived(myLauncher: SdkActivityLauncher): Unit
+ public fun onSdkActivityLauncherReceived(myLauncher: SdkActivityLauncher)
- public fun onValueReceived(response: Response): Unit
+ public fun onValueReceived(response: Response)
}
diff --git a/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/output/com/sdkwithcallbacks/SdkCallbackStubDelegate.kt b/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/output/com/sdkwithcallbacks/SdkCallbackStubDelegate.kt
index 3bc4ed5..f98dafb 100644
--- a/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/output/com/sdkwithcallbacks/SdkCallbackStubDelegate.kt
+++ b/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/output/com/sdkwithcallbacks/SdkCallbackStubDelegate.kt
@@ -4,7 +4,6 @@
import com.sdkwithcallbacks.ResponseConverter.fromParcelable
import com.sdkwithcallbacks.SdkActivityLauncherConverter.getLocalOrProxyLauncher
import kotlin.Int
-import kotlin.Unit
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@@ -14,31 +13,31 @@
) : ISdkCallback.Stub() {
private val coroutineScope: CoroutineScope = CoroutineScope(Dispatchers.Main)
- public override fun onCompleteInterface(myInterface: IMyInterface): Unit {
+ public override fun onCompleteInterface(myInterface: IMyInterface) {
coroutineScope.launch {
delegate.onCompleteInterface(MyInterfaceClientProxy(myInterface))
}
}
- public override fun onEmptyEvent(): Unit {
+ public override fun onEmptyEvent() {
coroutineScope.launch {
delegate.onEmptyEvent()
}
}
- public override fun onPrimitivesReceived(x: Int, y: Int): Unit {
+ public override fun onPrimitivesReceived(x: Int, y: Int) {
coroutineScope.launch {
delegate.onPrimitivesReceived(x, y)
}
}
- public override fun onSdkActivityLauncherReceived(myLauncher: Bundle): Unit {
+ public override fun onSdkActivityLauncherReceived(myLauncher: Bundle) {
coroutineScope.launch {
delegate.onSdkActivityLauncherReceived(getLocalOrProxyLauncher(myLauncher))
}
}
- public override fun onValueReceived(response: ParcelableResponse): Unit {
+ public override fun onValueReceived(response: ParcelableResponse) {
coroutineScope.launch {
delegate.onValueReceived(fromParcelable(response))
}
diff --git a/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/output/com/sdkwithcallbacks/SdkService.kt b/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/output/com/sdkwithcallbacks/SdkService.kt
index 510bd89..3a427d3 100644
--- a/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/output/com/sdkwithcallbacks/SdkService.kt
+++ b/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/output/com/sdkwithcallbacks/SdkService.kt
@@ -1,5 +1,5 @@
package com.sdkwithcallbacks
public interface SdkService {
- public fun registerCallback(callback: SdkCallback): Unit
+ public fun registerCallback(callback: SdkCallback)
}
diff --git a/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/output/com/sdkwithcallbacks/SdkServiceClientProxy.kt b/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/output/com/sdkwithcallbacks/SdkServiceClientProxy.kt
index 83a8df2..d69a96b 100644
--- a/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/output/com/sdkwithcallbacks/SdkServiceClientProxy.kt
+++ b/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/output/com/sdkwithcallbacks/SdkServiceClientProxy.kt
@@ -3,7 +3,7 @@
public class SdkServiceClientProxy(
public val remote: ISdkService,
) : SdkService {
- public override fun registerCallback(callback: SdkCallback): Unit {
+ public override fun registerCallback(callback: SdkCallback) {
remote.registerCallback(SdkCallbackStubDelegate(callback))
}
}
diff --git a/privacysandbox/tools/tools-apigenerator/src/test/test-data/interfaces/output/com/sdk/MyInterface.kt b/privacysandbox/tools/tools-apigenerator/src/test/test-data/interfaces/output/com/sdk/MyInterface.kt
index cc0fb50..61b2aa6 100644
--- a/privacysandbox/tools/tools-apigenerator/src/test/test-data/interfaces/output/com/sdk/MyInterface.kt
+++ b/privacysandbox/tools/tools-apigenerator/src/test/test-data/interfaces/output/com/sdk/MyInterface.kt
@@ -5,9 +5,9 @@
public interface MyInterface {
public suspend fun add(x: Int, y: Int): Int
- public fun doSomething(firstInterface: MyInterface, secondInterface: MySecondInterface): Unit
+ public fun doSomething(firstInterface: MyInterface, secondInterface: MySecondInterface)
- public fun doSomethingWithNullableInterface(maybeInterface: MySecondInterface?): Unit
+ public fun doSomethingWithNullableInterface(maybeInterface: MySecondInterface?)
- public fun doSomethingWithSdkActivityLauncher(launcher: SdkActivityLauncher): Unit
+ public fun doSomethingWithSdkActivityLauncher(launcher: SdkActivityLauncher)
}
diff --git a/privacysandbox/tools/tools-apigenerator/src/test/test-data/interfaces/output/com/sdk/MyInterfaceClientProxy.kt b/privacysandbox/tools/tools-apigenerator/src/test/test-data/interfaces/output/com/sdk/MyInterfaceClientProxy.kt
index 33c8b84..ad696066 100644
--- a/privacysandbox/tools/tools-apigenerator/src/test/test-data/interfaces/output/com/sdk/MyInterfaceClientProxy.kt
+++ b/privacysandbox/tools/tools-apigenerator/src/test/test-data/interfaces/output/com/sdk/MyInterfaceClientProxy.kt
@@ -32,19 +32,19 @@
}
public override fun doSomething(firstInterface: MyInterface,
- secondInterface: MySecondInterface): Unit {
+ secondInterface: MySecondInterface) {
remote.doSomething((firstInterface as MyInterfaceClientProxy).remote,
IMySecondInterfaceCoreLibInfoAndBinderWrapperConverter.toParcelable((secondInterface
as MySecondInterfaceClientProxy).coreLibInfo, secondInterface.remote))
}
- public override fun doSomethingWithNullableInterface(maybeInterface: MySecondInterface?): Unit {
+ public override fun doSomethingWithNullableInterface(maybeInterface: MySecondInterface?) {
remote.doSomethingWithNullableInterface(maybeInterface?.let { notNullValue ->
IMySecondInterfaceCoreLibInfoAndBinderWrapperConverter.toParcelable((notNullValue as
MySecondInterfaceClientProxy).coreLibInfo, notNullValue.remote) })
}
- public override fun doSomethingWithSdkActivityLauncher(launcher: SdkActivityLauncher): Unit {
+ public override fun doSomethingWithSdkActivityLauncher(launcher: SdkActivityLauncher) {
remote.doSomethingWithSdkActivityLauncher(toBinder(launcher))
}
}
diff --git a/privacysandbox/tools/tools-apigenerator/src/test/test-data/interfaces/output/com/sdk/MySdkClientProxy.kt b/privacysandbox/tools/tools-apigenerator/src/test/test-data/interfaces/output/com/sdk/MySdkClientProxy.kt
index 578aa60..fcb3541 100644
--- a/privacysandbox/tools/tools-apigenerator/src/test/test-data/interfaces/output/com/sdk/MySdkClientProxy.kt
+++ b/privacysandbox/tools/tools-apigenerator/src/test/test-data/interfaces/output/com/sdk/MySdkClientProxy.kt
@@ -1,6 +1,5 @@
package com.sdk
-import com.sdk.PrivacySandboxThrowableParcelConverter
import com.sdk.PrivacySandboxThrowableParcelConverter.fromThrowableParcel
import kotlin.coroutines.resumeWithException
import kotlinx.coroutines.suspendCancellableCoroutine
diff --git a/privacysandbox/tools/tools-apigenerator/src/test/test-data/interfaces/output/com/sdk/MySecondInterface.kt b/privacysandbox/tools/tools-apigenerator/src/test/test-data/interfaces/output/com/sdk/MySecondInterface.kt
index 0505e08..36f87f5 100644
--- a/privacysandbox/tools/tools-apigenerator/src/test/test-data/interfaces/output/com/sdk/MySecondInterface.kt
+++ b/privacysandbox/tools/tools-apigenerator/src/test/test-data/interfaces/output/com/sdk/MySecondInterface.kt
@@ -3,5 +3,5 @@
import androidx.privacysandbox.ui.core.SandboxedUiAdapter
public interface MySecondInterface : SandboxedUiAdapter {
- public fun doStuff(): Unit
+ public fun doStuff()
}
diff --git a/privacysandbox/tools/tools-apigenerator/src/test/test-data/interfaces/output/com/sdk/MySecondInterfaceClientProxy.kt b/privacysandbox/tools/tools-apigenerator/src/test/test-data/interfaces/output/com/sdk/MySecondInterfaceClientProxy.kt
index 48db338..d7f90e3 100644
--- a/privacysandbox/tools/tools-apigenerator/src/test/test-data/interfaces/output/com/sdk/MySecondInterfaceClientProxy.kt
+++ b/privacysandbox/tools/tools-apigenerator/src/test/test-data/interfaces/output/com/sdk/MySecondInterfaceClientProxy.kt
@@ -15,7 +15,7 @@
public val sandboxedUiAdapter: SandboxedUiAdapter =
SandboxedUiAdapterFactory.createFromCoreLibInfo(coreLibInfo)
- public override fun doStuff(): Unit {
+ public override fun doStuff() {
remote.doStuff()
}
@@ -27,7 +27,7 @@
isZOrderOnTop: Boolean,
clientExecutor: Executor,
client: SandboxedUiAdapter.SessionClient,
- ): Unit {
+ ) {
sandboxedUiAdapter.openSession(context, windowInputToken, initialWidth, initialHeight,
isZOrderOnTop, clientExecutor, client)
}
diff --git a/privacysandbox/tools/tools-apigenerator/src/test/test-data/primitives/output/com/mysdk/TestSandboxSdk.kt b/privacysandbox/tools/tools-apigenerator/src/test/test-data/primitives/output/com/mysdk/TestSandboxSdk.kt
index 912155e..cb65c54 100644
--- a/privacysandbox/tools/tools-apigenerator/src/test/test-data/primitives/output/com/mysdk/TestSandboxSdk.kt
+++ b/privacysandbox/tools/tools-apigenerator/src/test/test-data/primitives/output/com/mysdk/TestSandboxSdk.kt
@@ -7,19 +7,19 @@
third: Long,
): Boolean
- public fun echoBoolean(input: Boolean): Unit
+ public fun echoBoolean(input: Boolean)
- public fun echoChar(input: Char): Unit
+ public fun echoChar(input: Char)
- public fun echoDouble(input: Double): Unit
+ public fun echoDouble(input: Double)
- public fun echoFloat(input: Float): Unit
+ public fun echoFloat(input: Float)
- public fun echoInt(input: Int): Unit
+ public fun echoInt(input: Int)
- public fun echoLong(input: Long): Unit
+ public fun echoLong(input: Long)
- public fun echoString(input: String): Unit
+ public fun echoString(input: String)
public suspend fun processBooleanList(x: List<Boolean>): List<Boolean>
@@ -39,13 +39,13 @@
public suspend fun processStringList(x: List<String>): List<String>
- public fun receiveAndReturnNothing(): Unit
+ public fun receiveAndReturnNothing()
- public suspend fun receiveAndReturnNothingAsync(): Unit
+ public suspend fun receiveAndReturnNothingAsync()
public fun receiveMultipleArguments(
first: Int,
second: String,
third: Long,
- ): Unit
+ )
}
diff --git a/privacysandbox/tools/tools-apigenerator/src/test/test-data/primitives/output/com/mysdk/TestSandboxSdkClientProxy.kt b/privacysandbox/tools/tools-apigenerator/src/test/test-data/primitives/output/com/mysdk/TestSandboxSdkClientProxy.kt
index 329c0df..bc02194 100644
--- a/privacysandbox/tools/tools-apigenerator/src/test/test-data/primitives/output/com/mysdk/TestSandboxSdkClientProxy.kt
+++ b/privacysandbox/tools/tools-apigenerator/src/test/test-data/primitives/output/com/mysdk/TestSandboxSdkClientProxy.kt
@@ -1,6 +1,5 @@
package com.mysdk
-import com.mysdk.PrivacySandboxThrowableParcelConverter
import com.mysdk.PrivacySandboxThrowableParcelConverter.fromThrowableParcel
import kotlin.coroutines.resumeWithException
import kotlinx.coroutines.suspendCancellableCoroutine
@@ -34,31 +33,31 @@
}
}
- public override fun echoBoolean(input: Boolean): Unit {
+ public override fun echoBoolean(input: Boolean) {
remote.echoBoolean(input)
}
- public override fun echoChar(input: Char): Unit {
+ public override fun echoChar(input: Char) {
remote.echoChar(input)
}
- public override fun echoDouble(input: Double): Unit {
+ public override fun echoDouble(input: Double) {
remote.echoDouble(input)
}
- public override fun echoFloat(input: Float): Unit {
+ public override fun echoFloat(input: Float) {
remote.echoFloat(input)
}
- public override fun echoInt(input: Int): Unit {
+ public override fun echoInt(input: Int) {
remote.echoInt(input)
}
- public override fun echoLong(input: Long): Unit {
+ public override fun echoLong(input: Long) {
remote.echoLong(input)
}
- public override fun echoString(input: String): Unit {
+ public override fun echoString(input: String) {
remote.echoString(input)
}
@@ -269,7 +268,7 @@
}
}
- public override fun receiveAndReturnNothing(): Unit {
+ public override fun receiveAndReturnNothing() {
remote.receiveAndReturnNothing()
}
@@ -299,7 +298,7 @@
first: Int,
second: String,
third: Long,
- ): Unit {
+ ) {
remote.receiveMultipleArguments(first, second, third)
}
}
diff --git a/privacysandbox/tools/tools-apigenerator/src/test/test-data/values/output/com/sdkwithvalues/MyInterface.kt b/privacysandbox/tools/tools-apigenerator/src/test/test-data/values/output/com/sdkwithvalues/MyInterface.kt
index b7db6ef..a8ebad1 100644
--- a/privacysandbox/tools/tools-apigenerator/src/test/test-data/values/output/com/sdkwithvalues/MyInterface.kt
+++ b/privacysandbox/tools/tools-apigenerator/src/test/test-data/values/output/com/sdkwithvalues/MyInterface.kt
@@ -1,5 +1,5 @@
package com.sdkwithvalues
public interface MyInterface {
- public fun doStuff(): Unit
+ public fun doStuff()
}
diff --git a/privacysandbox/tools/tools-apigenerator/src/test/test-data/values/output/com/sdkwithvalues/MyInterfaceClientProxy.kt b/privacysandbox/tools/tools-apigenerator/src/test/test-data/values/output/com/sdkwithvalues/MyInterfaceClientProxy.kt
index 2fd68e4..781d720 100644
--- a/privacysandbox/tools/tools-apigenerator/src/test/test-data/values/output/com/sdkwithvalues/MyInterfaceClientProxy.kt
+++ b/privacysandbox/tools/tools-apigenerator/src/test/test-data/values/output/com/sdkwithvalues/MyInterfaceClientProxy.kt
@@ -3,7 +3,7 @@
public class MyInterfaceClientProxy(
public val remote: IMyInterface,
) : MyInterface {
- public override fun doStuff(): Unit {
+ public override fun doStuff() {
remote.doStuff()
}
}
diff --git a/privacysandbox/tools/tools-apigenerator/src/test/test-data/values/output/com/sdkwithvalues/MyUiInterface.kt b/privacysandbox/tools/tools-apigenerator/src/test/test-data/values/output/com/sdkwithvalues/MyUiInterface.kt
index 65cbc2b..4bf3412 100644
--- a/privacysandbox/tools/tools-apigenerator/src/test/test-data/values/output/com/sdkwithvalues/MyUiInterface.kt
+++ b/privacysandbox/tools/tools-apigenerator/src/test/test-data/values/output/com/sdkwithvalues/MyUiInterface.kt
@@ -3,5 +3,5 @@
import androidx.privacysandbox.ui.core.SandboxedUiAdapter
public interface MyUiInterface : SandboxedUiAdapter {
- public fun doUiStuff(): Unit
+ public fun doUiStuff()
}
diff --git a/privacysandbox/tools/tools-apigenerator/src/test/test-data/values/output/com/sdkwithvalues/MyUiInterfaceClientProxy.kt b/privacysandbox/tools/tools-apigenerator/src/test/test-data/values/output/com/sdkwithvalues/MyUiInterfaceClientProxy.kt
index bd43324..86709a8 100644
--- a/privacysandbox/tools/tools-apigenerator/src/test/test-data/values/output/com/sdkwithvalues/MyUiInterfaceClientProxy.kt
+++ b/privacysandbox/tools/tools-apigenerator/src/test/test-data/values/output/com/sdkwithvalues/MyUiInterfaceClientProxy.kt
@@ -15,7 +15,7 @@
public val sandboxedUiAdapter: SandboxedUiAdapter =
SandboxedUiAdapterFactory.createFromCoreLibInfo(coreLibInfo)
- public override fun doUiStuff(): Unit {
+ public override fun doUiStuff() {
remote.doUiStuff()
}
@@ -27,7 +27,7 @@
isZOrderOnTop: Boolean,
clientExecutor: Executor,
client: SandboxedUiAdapter.SessionClient,
- ): Unit {
+ ) {
sandboxedUiAdapter.openSession(context, windowInputToken, initialWidth, initialHeight,
isZOrderOnTop, clientExecutor, client)
}
diff --git a/privacysandbox/tools/tools-apigenerator/src/test/test-data/values/output/com/sdkwithvalues/SdkInterfaceClientProxy.kt b/privacysandbox/tools/tools-apigenerator/src/test/test-data/values/output/com/sdkwithvalues/SdkInterfaceClientProxy.kt
index def4b57..6505a91 100644
--- a/privacysandbox/tools/tools-apigenerator/src/test/test-data/values/output/com/sdkwithvalues/SdkInterfaceClientProxy.kt
+++ b/privacysandbox/tools/tools-apigenerator/src/test/test-data/values/output/com/sdkwithvalues/SdkInterfaceClientProxy.kt
@@ -1,10 +1,7 @@
package com.sdkwithvalues
-import com.sdkwithvalues.PrivacySandboxThrowableParcelConverter
import com.sdkwithvalues.PrivacySandboxThrowableParcelConverter.fromThrowableParcel
-import com.sdkwithvalues.SdkRequestConverter
import com.sdkwithvalues.SdkRequestConverter.toParcelable
-import com.sdkwithvalues.SdkResponseConverter
import com.sdkwithvalues.SdkResponseConverter.fromParcelable
import kotlin.coroutines.resumeWithException
import kotlinx.coroutines.suspendCancellableCoroutine
diff --git a/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/generator/ClientProxyTypeGenerator.kt b/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/generator/ClientProxyTypeGenerator.kt
index c562ae2..04c8db3 100644
--- a/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/generator/ClientProxyTypeGenerator.kt
+++ b/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/generator/ClientProxyTypeGenerator.kt
@@ -107,6 +107,7 @@
private fun toSuspendFunSpec(method: Method) =
FunSpec.builder(method.name).build {
+ addModifiers(KModifier.PUBLIC)
addModifiers(KModifier.OVERRIDE)
addModifiers(KModifier.SUSPEND)
addParameters(method.parameters.map { it.poetSpec() })
@@ -128,14 +129,14 @@
private fun toNonSuspendFunSpec(method: Method) =
FunSpec.builder(method.name).build {
- addModifiers(KModifier.OVERRIDE)
+ addModifiers(KModifier.PUBLIC, KModifier.OVERRIDE)
addParameters(method.parameters.map { it.poetSpec() })
addCode(generateRemoteCall(method))
}
private fun generateOpenSession() = FunSpec.builder("openSession").build {
- addModifiers(KModifier.OVERRIDE)
+ addModifiers(KModifier.PUBLIC, KModifier.OVERRIDE)
addParameters(
listOf(
ParameterSpec(contextPropertyName, contextClass),
diff --git a/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/generator/PrivacySandboxCancellationExceptionFileGenerator.kt b/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/generator/PrivacySandboxCancellationExceptionFileGenerator.kt
index dadcb7d5..a53dbe9 100644
--- a/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/generator/PrivacySandboxCancellationExceptionFileGenerator.kt
+++ b/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/generator/PrivacySandboxCancellationExceptionFileGenerator.kt
@@ -36,11 +36,11 @@
PropertySpec.builder(
"message",
String::class.asTypeName().copy(nullable = true),
- ).addModifiers(KModifier.OVERRIDE).build(),
+ ).addModifiers(KModifier.PUBLIC, KModifier.OVERRIDE).build(),
PropertySpec.builder(
"cause",
Throwable::class.asTypeName().copy(nullable = true),
- ).addModifiers(KModifier.OVERRIDE).build()
+ ).addModifiers(KModifier.PUBLIC, KModifier.OVERRIDE).build()
)
)
}
diff --git a/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/generator/PrivacySandboxExceptionFileGenerator.kt b/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/generator/PrivacySandboxExceptionFileGenerator.kt
index fdb9242..c93a34f 100644
--- a/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/generator/PrivacySandboxExceptionFileGenerator.kt
+++ b/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/generator/PrivacySandboxExceptionFileGenerator.kt
@@ -36,11 +36,11 @@
PropertySpec.builder(
"message",
String::class.asTypeName().copy(nullable = true),
- ).addModifiers(KModifier.OVERRIDE).build(),
+ ).addModifiers(KModifier.PUBLIC, KModifier.OVERRIDE).build(),
PropertySpec.builder(
"cause",
Throwable::class.asTypeName().copy(nullable = true),
- ).addModifiers(KModifier.OVERRIDE).build()
+ ).addModifiers(KModifier.PUBLIC, KModifier.OVERRIDE).build()
)
)
}
diff --git a/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/generator/StubDelegatesGenerator.kt b/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/generator/StubDelegatesGenerator.kt
index 85f2dfa..7082174 100644
--- a/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/generator/StubDelegatesGenerator.kt
+++ b/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/generator/StubDelegatesGenerator.kt
@@ -102,7 +102,7 @@
private fun toSuspendFunSpec(method: Method): FunSpec {
return FunSpec.builder(method.name).build {
- addModifiers(KModifier.OVERRIDE)
+ addModifiers(KModifier.PUBLIC, KModifier.OVERRIDE)
addParameters(getParameters(method))
addCode {
addControlFlow(
@@ -145,7 +145,7 @@
}
private fun toNonSuspendFunSpec(method: Method) = FunSpec.builder(method.name).build {
- addModifiers(KModifier.OVERRIDE)
+ addModifiers(KModifier.PUBLIC, KModifier.OVERRIDE)
addParameters(getParameters(method))
addCode(CodeBlock.builder().build {
addControlFlow("%L.%M", coroutineScopePropertyName, launchMethod) {
diff --git a/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/generator/TransportCancellationGenerator.kt b/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/generator/TransportCancellationGenerator.kt
index 8920889..cf8251b 100644
--- a/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/generator/TransportCancellationGenerator.kt
+++ b/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/generator/TransportCancellationGenerator.kt
@@ -54,7 +54,7 @@
).initializer("%T(false)", atomicBooleanClass).build()
)
addFunction(FunSpec.builder("cancel").build {
- addModifiers(KModifier.OVERRIDE)
+ addModifiers(KModifier.PUBLIC, KModifier.OVERRIDE)
addCode {
addControlFlow("if (hasCancelled.compareAndSet(false, true))") {
addStatement("onCancel()")
diff --git a/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/LinearLayoutManagerRemoveShownItemsTest.kt b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/LinearLayoutManagerRemoveShownItemsTest.kt
index 3d907a4..68cd698 100644
--- a/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/LinearLayoutManagerRemoveShownItemsTest.kt
+++ b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/LinearLayoutManagerRemoveShownItemsTest.kt
@@ -90,10 +90,18 @@
mLayoutManager.waitForLayout(2)
// .. then the views after the removed view are moved into the gap
- assertEquals(expectedResultingAdapterPosition, llm.findFirstVisibleItemPosition())
- assertEquals(expectedResultingAdapterPosition, llm.findFirstCompletelyVisibleItemPosition())
- assertEquals(expectedResultingAdapterPosition, llm.findLastCompletelyVisibleItemPosition())
- assertEquals(expectedResultingAdapterPosition, llm.findLastVisibleItemPosition())
+ mActivityRule.runOnUiThread {
+ assertEquals(expectedResultingAdapterPosition, llm.findFirstVisibleItemPosition())
+ assertEquals(
+ expectedResultingAdapterPosition,
+ llm.findFirstCompletelyVisibleItemPosition()
+ )
+ assertEquals(
+ expectedResultingAdapterPosition,
+ llm.findLastCompletelyVisibleItemPosition()
+ )
+ assertEquals(expectedResultingAdapterPosition, llm.findLastVisibleItemPosition())
+ }
}
/**
@@ -136,13 +144,18 @@
mLayoutManager.waitForLayout(2)
// .. then the views after the removed view are moved into the gap
- assertEquals(expectedResultingAdapterPosition, llm.findFirstVisibleItemPosition())
- assertEquals(expectedResultingAdapterPosition, llm.findFirstCompletelyVisibleItemPosition())
- assertEquals(
- expectedResultingAdapterPosition + 1,
- llm.findLastCompletelyVisibleItemPosition()
- )
- assertEquals(expectedResultingAdapterPosition + 1, llm.findLastVisibleItemPosition())
+ mActivityRule.runOnUiThread {
+ assertEquals(expectedResultingAdapterPosition, llm.findFirstVisibleItemPosition())
+ assertEquals(
+ expectedResultingAdapterPosition,
+ llm.findFirstCompletelyVisibleItemPosition()
+ )
+ assertEquals(
+ expectedResultingAdapterPosition + 1,
+ llm.findLastCompletelyVisibleItemPosition()
+ )
+ assertEquals(expectedResultingAdapterPosition + 1, llm.findLastVisibleItemPosition())
+ }
}
/**
@@ -183,13 +196,21 @@
mLayoutManager.waitForLayout(2)
// .. then the views after the removed view are moved into the gap
- assertEquals(expectedResultingAdapterPosition, llm.findFirstVisibleItemPosition())
- assertEquals(expectedResultingAdapterPosition, llm.findFirstCompletelyVisibleItemPosition())
- assertEquals(expectedResultingAdapterPosition, llm.findLastCompletelyVisibleItemPosition())
- assertEquals(expectedResultingAdapterPosition, llm.findLastVisibleItemPosition())
+ mActivityRule.runOnUiThread {
+ assertEquals(expectedResultingAdapterPosition, llm.findFirstVisibleItemPosition())
+ assertEquals(
+ expectedResultingAdapterPosition,
+ llm.findFirstCompletelyVisibleItemPosition()
+ )
+ assertEquals(
+ expectedResultingAdapterPosition,
+ llm.findLastCompletelyVisibleItemPosition()
+ )
+ assertEquals(expectedResultingAdapterPosition, llm.findLastVisibleItemPosition())
+ }
}
- private inner class MyLayoutManager internal constructor(context: Context, val itemSize: Int) :
+ private inner class MyLayoutManager(context: Context, val itemSize: Int) :
WrappedLinearLayoutManager(context, config.mOrientation, config.mReverseLayout) {
override fun calculateExtraLayoutSpace(
diff --git a/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/RecyclerViewLayoutTest.java b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/RecyclerViewLayoutTest.java
index b8aafcc..1bdee58 100644
--- a/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/RecyclerViewLayoutTest.java
+++ b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/RecyclerViewLayoutTest.java
@@ -1897,12 +1897,13 @@
any(int[].class), eq(ViewCompat.TYPE_TOUCH));
verify(nsp, atLeastOnce()).onStopNestedScroll(eq(mRecyclerView), eq(ViewCompat.TYPE_TOUCH));
- // Verify that the non-touch events were dispatched by the fling settle
- verify(nsp, times(1)).onStartNestedScroll(eq(mRecyclerView), eq(mRecyclerView),
+ // Verify that no non-touch events were dispatched by the fling settle, because this is a
+ // drag, not a fling
+ verify(nsp, never()).onStartNestedScroll(eq(mRecyclerView), eq(mRecyclerView),
eq(ViewCompat.SCROLL_AXIS_VERTICAL), eq(ViewCompat.TYPE_NON_TOUCH));
- verify(nsp, times(1)).onNestedScrollAccepted(eq(mRecyclerView), eq(mRecyclerView),
+ verify(nsp, never()).onNestedScrollAccepted(eq(mRecyclerView), eq(mRecyclerView),
eq(ViewCompat.SCROLL_AXIS_VERTICAL), eq(ViewCompat.TYPE_NON_TOUCH));
- verify(nsp, atLeastOnce()).onNestedPreScroll(eq(mRecyclerView), anyInt(), anyInt(),
+ verify(nsp, never()).onNestedPreScroll(eq(mRecyclerView), anyInt(), anyInt(),
any(int[].class), eq(ViewCompat.TYPE_NON_TOUCH));
}
@@ -1923,12 +1924,13 @@
any(int[].class), eq(ViewCompat.TYPE_TOUCH));
verify(nsp, atLeastOnce()).onStopNestedScroll(eq(mRecyclerView), eq(ViewCompat.TYPE_TOUCH));
- // Verify that the non-touch events were dispatched by the fling settle
- verify(nsp, times(1)).onStartNestedScroll(eq(mRecyclerView), eq(mRecyclerView),
+ // Verify that no non-touch events were dispatched by the fling settle, because this is a
+ // drag, not a fling
+ verify(nsp, never()).onStartNestedScroll(eq(mRecyclerView), eq(mRecyclerView),
eq(ViewCompat.SCROLL_AXIS_HORIZONTAL), eq(ViewCompat.TYPE_NON_TOUCH));
- verify(nsp, times(1)).onNestedScrollAccepted(eq(mRecyclerView), eq(mRecyclerView),
+ verify(nsp, never()).onNestedScrollAccepted(eq(mRecyclerView), eq(mRecyclerView),
eq(ViewCompat.SCROLL_AXIS_HORIZONTAL), eq(ViewCompat.TYPE_NON_TOUCH));
- verify(nsp, atLeastOnce()).onNestedPreScroll(eq(mRecyclerView), anyInt(), anyInt(),
+ verify(nsp, never()).onNestedPreScroll(eq(mRecyclerView), anyInt(), anyInt(),
any(int[].class), eq(ViewCompat.TYPE_NON_TOUCH));
}
diff --git a/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/RecyclerViewSmoothScrollToPositionTest.kt b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/RecyclerViewSmoothScrollToPositionTest.kt
index c3cab50..6a10b3d 100644
--- a/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/RecyclerViewSmoothScrollToPositionTest.kt
+++ b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/RecyclerViewSmoothScrollToPositionTest.kt
@@ -159,7 +159,9 @@
calledOnStop.await(30, TimeUnit.SECONDS)
)
Assert.assertNotNull(
- "smoothScrollToPosition should succeed",
+ "smoothScrollToPosition should succeed " +
+ "(first visible item: " + layoutManager.findFirstVisibleItemPosition() +
+ ", last visible item: " + layoutManager.findLastVisibleItemPosition() + ")",
recyclerView.findViewHolderForLayoutPosition(targetPosition)
)
}
diff --git a/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/TouchUtils.java b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/TouchUtils.java
index 64f84a8..1b886a6 100644
--- a/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/TouchUtils.java
+++ b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/TouchUtils.java
@@ -319,6 +319,11 @@
event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_MOVE, x, y, 0);
inst.sendPointerSync(event);
}
+
+ // Dwell at the end, because this is a drag, not a fling
+ eventTime += 500;
+ event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_MOVE, x, y, 0);
+ inst.sendPointerSync(event);
eventTime++;
event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_UP, x, y, 0);
inst.sendPointerSync(event);
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/dao/MusicDao.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/dao/MusicDao.kt
index a7ef64d..5753602 100644
--- a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/dao/MusicDao.kt
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/dao/MusicDao.kt
@@ -20,6 +20,7 @@
import androidx.collection.SparseArrayCompat
import androidx.lifecycle.LiveData
import androidx.room.Dao
+import androidx.room.Delete
import androidx.room.Insert
import androidx.room.MapInfo
import androidx.room.Query
@@ -32,6 +33,9 @@
import androidx.room.integration.kotlintestapp.vo.Artist
import androidx.room.integration.kotlintestapp.vo.Image
import androidx.room.integration.kotlintestapp.vo.ImageFormat
+import androidx.room.integration.kotlintestapp.vo.Playlist
+import androidx.room.integration.kotlintestapp.vo.PlaylistSongXRef
+import androidx.room.integration.kotlintestapp.vo.PlaylistWithSongs
import androidx.room.integration.kotlintestapp.vo.ReleasedAlbum
import androidx.room.integration.kotlintestapp.vo.Song
import androidx.sqlite.db.SupportSQLiteQuery
@@ -41,6 +45,7 @@
import io.reactivex.Flowable
import java.nio.ByteBuffer
import java.util.Date
+import kotlinx.coroutines.flow.Flow
@JvmDefaultWithCompatibility
@Dao
@@ -53,6 +58,16 @@
@Insert
fun addAlbums(vararg albums: Album)
+
+ @Insert
+ fun addPlaylists(vararg playlists: Playlist)
+
+ @Insert
+ fun addPlaylistSongRelations(vararg relations: PlaylistSongXRef)
+
+ @Delete
+ fun removePlaylistSongRelations(vararg relations: PlaylistSongXRef)
+
@Insert
fun addImages(vararg images: Image)
@@ -355,4 +370,8 @@
@MapInfo(keyColumn = "mImageYear", valueColumn = "mTitle")
@RewriteQueriesToDropUnusedColumns
fun getNestedMapWithMapInfoKeyAndValue(): Map<Long, Map<Artist, Map<Album, List<String>>>>
+
+ @Transaction
+ @Query("SELECT * FROM Playlist WHERE mPlaylistId = :id")
+ fun getPlaylistsWithSongsFlow(id: Int): Flow<PlaylistWithSongs>
}
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/FlowQueryTest.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/FlowQueryTest.kt
index 4a23ca5..02d20ce 100644
--- a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/FlowQueryTest.kt
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/FlowQueryTest.kt
@@ -18,6 +18,10 @@
import androidx.kruth.assertThat
import androidx.room.integration.kotlintestapp.vo.Book
+import androidx.room.integration.kotlintestapp.vo.Playlist
+import androidx.room.integration.kotlintestapp.vo.PlaylistSongXRef
+import androidx.room.integration.kotlintestapp.vo.PlaylistWithSongs
+import androidx.room.integration.kotlintestapp.vo.Song
import androidx.room.withTransaction
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
@@ -30,7 +34,6 @@
import kotlinx.coroutines.cancelAndJoin
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.buffer
-import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.produceIn
import kotlinx.coroutines.flow.take
@@ -318,4 +321,73 @@
job.cancelAndJoin()
}
+
+ @Test
+ fun playlistSongs_async_update(): Unit = runBlocking {
+ val musicDao = database.musicDao()
+ val song1 = Song(
+ 1,
+ "I Know Places",
+ "Taylor Swift",
+ "1989",
+ 195,
+ 2014
+ );
+ val song2 = Song(
+ 2,
+ "Blank Space",
+ "Taylor Swift",
+ "1989",
+ 241,
+ 2014
+ )
+ musicDao.addSongs(song1, song2)
+ musicDao.addPlaylists(
+ Playlist(1),
+ Playlist(2)
+ )
+ musicDao.addPlaylistSongRelations(PlaylistSongXRef(1, 1))
+
+ val latches = Array(3) { CountDownLatch(1) }
+ val results = mutableListOf<PlaylistWithSongs>()
+ var collectCall = 0
+ val job = async(Dispatchers.IO) {
+ musicDao.getPlaylistsWithSongsFlow(1).collect {
+ if (collectCall >= latches.size) {
+ fail("Should have only collected 3 results.")
+ }
+ results.add(it)
+ latches[collectCall].countDown()
+ collectCall++
+ }
+ }
+
+ latches[0].await()
+ assertThat(results.size).isEqualTo(1)
+ results[0].let { playlist ->
+ assertThat(playlist.songs.size).isEqualTo(1)
+ assertThat(playlist.songs[0]).isEqualTo(song1)
+ }
+
+ musicDao.addPlaylistSongRelations(PlaylistSongXRef(1, 2))
+
+ latches[1].await()
+ assertThat(results.size).isEqualTo(2)
+ results[1].let { playlist ->
+ assertThat(playlist.songs.size).isEqualTo(2)
+ assertThat(playlist.songs[0]).isEqualTo(song1)
+ assertThat(playlist.songs[1]).isEqualTo(song2)
+ }
+
+ musicDao.removePlaylistSongRelations(PlaylistSongXRef(1, 2))
+
+ latches[2].await()
+ assertThat(results.size).isEqualTo(3)
+ results[2].let { playlist ->
+ assertThat(playlist.songs.size).isEqualTo(1)
+ assertThat(playlist.songs[0]).isEqualTo(song1)
+ }
+
+ job.cancelAndJoin()
+ }
}
diff --git a/room/room-compiler-processing-testing/build.gradle b/room/room-compiler-processing-testing/build.gradle
index e789fcd..5b2da1b 100644
--- a/room/room-compiler-processing-testing/build.gradle
+++ b/room/room-compiler-processing-testing/build.gradle
@@ -30,7 +30,6 @@
implementation(libs.kotlinStdlibJdk8) // KSP defines older version as dependency, force update.
implementation(libs.ksp)
implementation(libs.googleCompileTesting)
- implementation(project(":kruth:kruth"))
// specify these to match the kotlin compiler version in AndroidX rather than what KSP or KCT
// uses
implementation(libs.kotlinCompilerEmbeddable)
diff --git a/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/CompilationResultSubject.kt b/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/CompilationResultSubject.kt
index e38371a..6642422 100644
--- a/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/CompilationResultSubject.kt
+++ b/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/CompilationResultSubject.kt
@@ -16,16 +16,18 @@
package androidx.room.compiler.processing.util
-import androidx.kruth.FailureMetadata
-import androidx.kruth.StringSubject
-import androidx.kruth.Subject
-import androidx.kruth.assertAbout
-import androidx.kruth.assertThat
import androidx.room.compiler.processing.ExperimentalProcessingApi
import androidx.room.compiler.processing.SyntheticJavacProcessor
import androidx.room.compiler.processing.SyntheticProcessor
import androidx.room.compiler.processing.util.compiler.TestCompilationResult
import androidx.room.compiler.processing.util.runner.CompilationTestRunner
+import com.google.common.truth.Fact.fact
+import com.google.common.truth.Fact.simpleFact
+import com.google.common.truth.FailureMetadata
+import com.google.common.truth.StringSubject
+import com.google.common.truth.Subject
+import com.google.common.truth.Subject.Factory
+import com.google.common.truth.Truth
import com.google.testing.compile.Compilation
import java.util.regex.Pattern
import javax.tools.Diagnostic
@@ -124,9 +126,8 @@
class CompilationResultSubject internal constructor(
failureMetadata: FailureMetadata,
val compilationResult: CompilationResult,
-) : Subject<CompilationResult>(
- actual = compilationResult,
- metadata = failureMetadata,
+) : Subject<CompilationResultSubject, CompilationResult>(
+ failureMetadata, compilationResult
) {
/**
* set to true if any assertion on the subject requires it to fail (e.g. looking for errors)
@@ -149,7 +150,9 @@
fun hasRawOutputContaining(expected: String) = apply {
val found = compilationResult.rawOutput().contains(expected)
if (!found) {
- failWithActual("Did not find $expected in the output.")
+ failWithActual(
+ simpleFact("Did not find $expected in the output.")
+ )
}
}
@@ -171,7 +174,9 @@
private fun hasDiagnosticCount(kind: Diagnostic.Kind, expected: Int) = apply {
val actual = compilationResult.diagnosticsOfKind(kind).size
if (actual != expected) {
- failWithActual("expected $expected $kind messages, found $actual")
+ failWithActual(
+ simpleFact("expected $expected $kind messages, found $actual")
+ )
}
}
/**
@@ -326,7 +331,9 @@
fun hasError() = apply {
shouldSucceed = false
if (compilationResult.diagnosticsOfKind(Diagnostic.Kind.ERROR).isEmpty()) {
- failWithActual("expected at least one failure message")
+ failWithActual(
+ simpleFact("expected at least one failure message")
+ )
}
}
@@ -337,9 +344,12 @@
*/
fun generatedSourceFileWithPath(relativePath: String): StringSubject {
val match = findGeneratedSource(relativePath)
- ?: failWithActual("Didn't generate file with path: $relativePath")
-
- return assertThat(match.contents)
+ if (match == null) {
+ failWithActual(
+ simpleFact("Didn't generate file with path: $relativePath")
+ )
+ }
+ return Truth.assertThat(match!!.contents)
}
private fun findGeneratedSource(relativePath: String) = compilationResult.generatedSources
@@ -358,14 +368,20 @@
fun generatedSource(source: Source) = apply {
val match = compilationResult.generatedSources.firstOrNull {
it.relativePath == source.relativePath
- } ?: failWithActual("Didn't generate $source")
+ }
+ if (match == null) {
+ failWithActual(
+ simpleFact("Didn't generate $source")
+ )
+ return@apply
+ }
val mismatch = source.findMismatch(match)
if (mismatch != null) {
failWithActual(
- "Generated code does not match expected",
- "mismatch: $mismatch",
- "expected: ${source.contents}",
- "actual: ${match.contents}",
+ simpleFact("Generated code does not match expected"),
+ fact("mismatch", mismatch),
+ fact("expected", source.contents),
+ fact("actual", match.contents),
)
}
}
@@ -377,8 +393,10 @@
internal fun assertCompilationResult() {
if (compilationResult.successfulCompilation != shouldSucceed) {
failWithActual(
- "expected compilation result to be: $shouldSucceed but was " +
- "${compilationResult.successfulCompilation}"
+ simpleFact(
+ "expected compilation result to be: $shouldSucceed but was " +
+ "${compilationResult.successfulCompilation}"
+ )
)
}
}
@@ -389,7 +407,9 @@
*/
internal fun assertAllExpectedRoundsAreCompleted() {
if (compilationResult.processor.expectsAnotherRound()) {
- failWithActual("Test runner requested another round but that didn't happen")
+ failWithActual(
+ simpleFact("Test runner requested another round but that didn't happen")
+ )
}
}
@@ -427,7 +447,7 @@
}
}
if (matches.isEmpty()) {
- failWithActual(buildErrorMessage())
+ failWithActual(simpleFact(buildErrorMessage()))
}
return DiagnosticMessagesSubject.assertThat(matches)
}
@@ -449,7 +469,7 @@
}
}
if (matches.isEmpty()) {
- failWithActual(buildErrorMessage())
+ failWithActual(simpleFact(buildErrorMessage()))
}
return DiagnosticMessagesSubject.assertThat(matches)
}
@@ -470,10 +490,18 @@
}
companion object {
+ private val FACTORY =
+ Factory<CompilationResultSubject, CompilationResult> { metadata, actual ->
+ CompilationResultSubject(metadata, actual)
+ }
+
fun assertThat(
compilationResult: CompilationResult
- ): CompilationResultSubject =
- assertAbout(::CompilationResultSubject).that(compilationResult)
+ ): CompilationResultSubject {
+ return Truth.assertAbout(FACTORY).that(
+ compilationResult
+ )
+ }
}
}
diff --git a/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/DiagnosticMessagesSubject.kt b/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/DiagnosticMessagesSubject.kt
index de1f737..213eea0 100644
--- a/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/DiagnosticMessagesSubject.kt
+++ b/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/DiagnosticMessagesSubject.kt
@@ -16,9 +16,11 @@
package androidx.room.compiler.processing.util
-import androidx.kruth.FailureMetadata
-import androidx.kruth.Subject
-import androidx.kruth.assertAbout
+import com.google.common.truth.Fact.simpleFact
+import com.google.common.truth.FailureMetadata
+import com.google.common.truth.Subject
+import com.google.common.truth.Subject.Factory
+import com.google.common.truth.Truth
/**
* Truth subject for diagnostic messages
@@ -26,8 +28,8 @@
class DiagnosticMessagesSubject internal constructor(
failureMetadata: FailureMetadata,
private val diagnosticMessages: List<DiagnosticMessage>,
-) : Subject<List<DiagnosticMessage>>(
- diagnosticMessages, failureMetadata
+) : Subject<DiagnosticMessagesSubject, List<DiagnosticMessage>>(
+ failureMetadata, diagnosticMessages
) {
private val lineContents by lazy {
@@ -50,10 +52,15 @@
* Note that if there are multiple messages, any match will be sufficient.
*/
fun onLine(lineNumber: Int) = apply {
- if (locations.none { it.line == lineNumber }) {
+ if (locations.none {
+ it.line == lineNumber
+ }
+ ) {
failWithActual(
- "expected line $lineNumber but it was " +
- locations.joinToString(",")
+ simpleFact(
+ "expected line $lineNumber but it was " +
+ locations.joinToString(",")
+ )
)
}
}
@@ -64,7 +71,7 @@
fun hasCount(expected: Int) = apply {
if (diagnosticMessages.size != expected) {
failWithActual(
- "expected $expected messages, found ${diagnosticMessages.size}"
+ simpleFact("expected $expected messages, found ${diagnosticMessages.size}")
)
}
}
@@ -75,7 +82,7 @@
fun onLineContaining(content: String) = apply {
if (lineContents.isEmpty()) {
failWithActual(
- "Cannot validate line content due to missing location information"
+ simpleFact("Cannot validate line content due to missing location information")
)
}
if (lineContents.none {
@@ -83,8 +90,10 @@
}
) {
failWithActual(
- "expected line content with $content but was " +
- lineContents.joinToString("\n")
+ simpleFact(
+ "expected line content with $content but was " +
+ lineContents.joinToString("\n")
+ )
)
}
}
@@ -96,18 +105,28 @@
fun onSource(source: Source) = apply {
if (locations.none { it.source == source }) {
failWithActual(
- """
+ simpleFact(
+ """
Expected diagnostic to be on $source but found it on
${locations.joinToString(",")}
- """.trimIndent()
+ """.trimIndent()
+ )
)
}
}
companion object {
+ private val FACTORY =
+ Factory<DiagnosticMessagesSubject, List<DiagnosticMessage>> { metadata, actual ->
+ DiagnosticMessagesSubject(metadata, actual)
+ }
+
fun assertThat(
diagnosticMessages: List<DiagnosticMessage>
- ): DiagnosticMessagesSubject =
- assertAbout(::DiagnosticMessagesSubject).that(diagnosticMessages)
+ ): DiagnosticMessagesSubject {
+ return Truth.assertAbout(FACTORY).that(
+ diagnosticMessages
+ )
+ }
}
}
diff --git a/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/ProcessorTestExt.kt b/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/ProcessorTestExt.kt
index bcef38b..a0145e7 100644
--- a/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/ProcessorTestExt.kt
+++ b/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/ProcessorTestExt.kt
@@ -16,8 +16,6 @@
package androidx.room.compiler.processing.util
-import androidx.kruth.assertThat
-import androidx.kruth.assertWithMessage
import androidx.room.compiler.processing.ExperimentalProcessingApi
import androidx.room.compiler.processing.XProcessingEnvConfig
import androidx.room.compiler.processing.XProcessingEnvironmentTestConfigProvider
@@ -32,6 +30,8 @@
import androidx.room.compiler.processing.util.runner.KspCompilationTestRunner
import androidx.room.compiler.processing.util.runner.TestCompilationParameters
import com.google.common.io.Files
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
import com.google.devtools.ksp.processing.SymbolProcessorProvider
import java.io.File
import java.util.jar.JarEntry
diff --git a/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/XTestInvocation.kt b/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/XTestInvocation.kt
index 959e75e..4e230a0 100644
--- a/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/XTestInvocation.kt
+++ b/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/XTestInvocation.kt
@@ -16,10 +16,10 @@
package androidx.room.compiler.processing.util
-import androidx.kruth.assertWithMessage
import androidx.room.compiler.processing.ExperimentalProcessingApi
import androidx.room.compiler.processing.XProcessingEnv
import androidx.room.compiler.processing.XRoundEnv
+import com.google.common.truth.Truth
import kotlin.reflect.KClass
/**
@@ -100,7 +100,7 @@
}
private fun assertNotDisposed() {
- assertWithMessage("Cannot use a test invocation after it is disposed.")
+ Truth.assertWithMessage("Cannot use a test invocation after it is disposed.")
.that(disposed)
.isFalse()
}
diff --git a/room/room-compiler-processing-testing/src/test/java/androidx/room/compiler/processing/util/DiagnosticMessageCollectorTest.kt b/room/room-compiler-processing-testing/src/test/java/androidx/room/compiler/processing/util/DiagnosticMessageCollectorTest.kt
index 1c5c650..66b21fa5 100644
--- a/room/room-compiler-processing-testing/src/test/java/androidx/room/compiler/processing/util/DiagnosticMessageCollectorTest.kt
+++ b/room/room-compiler-processing-testing/src/test/java/androidx/room/compiler/processing/util/DiagnosticMessageCollectorTest.kt
@@ -16,10 +16,10 @@
package androidx.room.compiler.processing.util
-import androidx.kruth.assertThat
import androidx.room.compiler.processing.util.compiler.DiagnosticsMessageCollector
import androidx.room.compiler.processing.util.compiler.steps.RawDiagnosticMessage
import androidx.room.compiler.processing.util.compiler.steps.RawDiagnosticMessage.Location
+import com.google.common.truth.Truth.assertThat
import javax.tools.Diagnostic
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
import org.junit.Test
diff --git a/room/room-compiler-processing-testing/src/test/java/androidx/room/compiler/processing/util/DiagnosticsTest.kt b/room/room-compiler-processing-testing/src/test/java/androidx/room/compiler/processing/util/DiagnosticsTest.kt
index d211727..7829bd4 100644
--- a/room/room-compiler-processing-testing/src/test/java/androidx/room/compiler/processing/util/DiagnosticsTest.kt
+++ b/room/room-compiler-processing-testing/src/test/java/androidx/room/compiler/processing/util/DiagnosticsTest.kt
@@ -16,8 +16,8 @@
package androidx.room.compiler.processing.util
-import androidx.kruth.assertThat
import androidx.room.compiler.processing.ExperimentalProcessingApi
+import com.google.common.truth.Truth.assertThat
import javax.tools.Diagnostic
import org.junit.Test
import org.junit.runner.RunWith
diff --git a/room/room-compiler-processing-testing/src/test/java/androidx/room/compiler/processing/util/GeneratedCodeMatchTest.kt b/room/room-compiler-processing-testing/src/test/java/androidx/room/compiler/processing/util/GeneratedCodeMatchTest.kt
index 7d71f16..ba43888 100644
--- a/room/room-compiler-processing-testing/src/test/java/androidx/room/compiler/processing/util/GeneratedCodeMatchTest.kt
+++ b/room/room-compiler-processing-testing/src/test/java/androidx/room/compiler/processing/util/GeneratedCodeMatchTest.kt
@@ -16,11 +16,10 @@
package androidx.room.compiler.processing.util
-import androidx.kruth.assertThat
import androidx.room.compiler.processing.ExperimentalProcessingApi
import androidx.room.compiler.processing.XElement
import androidx.room.compiler.processing.compat.XConverters.toXProcessing
-import com.google.common.truth.Truth
+import com.google.common.truth.Truth.assertThat
import com.squareup.javapoet.JavaFile
import com.squareup.javapoet.TypeName
import com.squareup.javapoet.TypeSpec
@@ -339,8 +338,7 @@
}
}
- // Kruth doesn't support exceptions yet
- Truth.assertThat(result.exceptionOrNull())
+ assertThat(result.exceptionOrNull())
.hasCauseThat()
.hasMessageThat()
.contains(
diff --git a/room/room-compiler-processing-testing/src/test/java/androidx/room/compiler/processing/util/LoadFromDefaultEnvironmentConfigurationProviderTest.kt b/room/room-compiler-processing-testing/src/test/java/androidx/room/compiler/processing/util/LoadFromDefaultEnvironmentConfigurationProviderTest.kt
index 773c6cd..40bdb48 100644
--- a/room/room-compiler-processing-testing/src/test/java/androidx/room/compiler/processing/util/LoadFromDefaultEnvironmentConfigurationProviderTest.kt
+++ b/room/room-compiler-processing-testing/src/test/java/androidx/room/compiler/processing/util/LoadFromDefaultEnvironmentConfigurationProviderTest.kt
@@ -16,7 +16,7 @@
package androidx.room.compiler.processing.util
-import androidx.kruth.assertThat
+import com.google.common.truth.Truth.assertThat
import org.junit.Test
class LoadFromDefaultEnvironmentConfigurationProviderTest {
diff --git a/room/room-compiler-processing-testing/src/test/java/androidx/room/compiler/processing/util/MultiRoundProcessingTest.kt b/room/room-compiler-processing-testing/src/test/java/androidx/room/compiler/processing/util/MultiRoundProcessingTest.kt
index f05bf87..7df4df1 100644
--- a/room/room-compiler-processing-testing/src/test/java/androidx/room/compiler/processing/util/MultiRoundProcessingTest.kt
+++ b/room/room-compiler-processing-testing/src/test/java/androidx/room/compiler/processing/util/MultiRoundProcessingTest.kt
@@ -16,7 +16,7 @@
package androidx.room.compiler.processing.util
-import androidx.kruth.assertThat
+import com.google.common.truth.Truth.assertThat
import com.squareup.javapoet.ClassName
import com.squareup.javapoet.JavaFile
import com.squareup.javapoet.TypeSpec
diff --git a/room/room-compiler-processing-testing/src/test/java/androidx/room/compiler/processing/util/SourceSetTest.kt b/room/room-compiler-processing-testing/src/test/java/androidx/room/compiler/processing/util/SourceSetTest.kt
index 203f195..98fd644 100644
--- a/room/room-compiler-processing-testing/src/test/java/androidx/room/compiler/processing/util/SourceSetTest.kt
+++ b/room/room-compiler-processing-testing/src/test/java/androidx/room/compiler/processing/util/SourceSetTest.kt
@@ -16,8 +16,8 @@
package androidx.room.compiler.processing.util
-import androidx.kruth.assertThat
import androidx.room.compiler.processing.util.compiler.SourceSet
+import com.google.common.truth.Truth.assertThat
import org.jetbrains.kotlin.konan.file.File
import org.junit.Rule
import org.junit.Test
diff --git a/room/room-compiler-processing-testing/src/test/java/androidx/room/compiler/processing/util/TestConfigTest.kt b/room/room-compiler-processing-testing/src/test/java/androidx/room/compiler/processing/util/TestConfigTest.kt
index cde233d..7f731db 100644
--- a/room/room-compiler-processing-testing/src/test/java/androidx/room/compiler/processing/util/TestConfigTest.kt
+++ b/room/room-compiler-processing-testing/src/test/java/androidx/room/compiler/processing/util/TestConfigTest.kt
@@ -16,8 +16,8 @@
package androidx.room.compiler.processing.util
-import androidx.kruth.assertThat
import androidx.room.compiler.processing.util.CompilationTestCapabilities.Config
+import com.google.common.truth.Truth.assertThat
import org.junit.Test
class TestConfigTest {
diff --git a/room/room-compiler-processing-testing/src/test/java/androidx/room/compiler/processing/util/TestRunnerTest.kt b/room/room-compiler-processing-testing/src/test/java/androidx/room/compiler/processing/util/TestRunnerTest.kt
index 10cf032..32fc356 100644
--- a/room/room-compiler-processing-testing/src/test/java/androidx/room/compiler/processing/util/TestRunnerTest.kt
+++ b/room/room-compiler-processing-testing/src/test/java/androidx/room/compiler/processing/util/TestRunnerTest.kt
@@ -16,7 +16,6 @@
package androidx.room.compiler.processing.util
-import androidx.kruth.assertThat
import androidx.room.compiler.processing.ExperimentalProcessingApi
import androidx.room.compiler.processing.SyntheticJavacProcessor
import androidx.room.compiler.processing.SyntheticKspProcessor
@@ -29,7 +28,7 @@
import androidx.room.compiler.processing.util.compiler.TestCompilationArguments
import androidx.room.compiler.processing.util.compiler.compile
import androidx.room.compiler.processing.util.compiler.steps.KaptCompilationStep
-import com.google.common.truth.Truth
+import com.google.common.truth.Truth.assertThat
import com.google.devtools.ksp.processing.SymbolProcessor
import com.google.devtools.ksp.processing.SymbolProcessorEnvironment
import com.google.devtools.ksp.processing.SymbolProcessorProvider
@@ -144,8 +143,7 @@
"c" to "d"
)
val handler: (XTestInvocation) -> Unit = {
- // Kruth MapSubject doesn't support containsAtLeastEntriesIn yet
- Truth.assertThat(it.processingEnv.options).containsAtLeastEntriesIn(testOptions)
+ assertThat(it.processingEnv.options).containsAtLeastEntriesIn(testOptions)
}
runJavaProcessorTest(
sources = emptyList(),
@@ -425,7 +423,7 @@
"org.jetbrains.kotlin.kapt3",
listOf("-P", "plugin:org.jetbrains.kotlin.kapt3:correctErrorTypes=true")
).let { options ->
- assertThat(options).containsExactly("correctErrorTypes" to "true")
+ assertThat(options).containsExactly("correctErrorTypes", "true")
}
// zero args
@@ -441,7 +439,7 @@
"org.jetbrains.kotlin.kapt3",
listOf("-P", "plugin:org.jetbrains.kotlin.kapt3:correctErrorTypes=true", "-verbose")
).let { options ->
- assertThat(options).containsExactly("correctErrorTypes" to "true")
+ assertThat(options).containsExactly("correctErrorTypes", "true")
}
// illegal format (missing "=")
@@ -476,8 +474,8 @@
)
).let { options ->
assertThat(options).containsExactly(
- "correctErrorTypes" to "true",
- "sources" to "build/kapt/sources"
+ "correctErrorTypes", "true",
+ "sources", "build/kapt/sources"
)
}
}
diff --git a/room/room-compiler-processing/build.gradle b/room/room-compiler-processing/build.gradle
index 0e71dd2..2b4f78a2 100644
--- a/room/room-compiler-processing/build.gradle
+++ b/room/room-compiler-processing/build.gradle
@@ -84,6 +84,7 @@
testImplementation(libs.jsr250)
testImplementation(libs.ksp)
testImplementation(libs.kotlinMetadataJvm)
+ testImplementation(libs.testParameterInjector)
testImplementation(project(":room:room-compiler-processing-testing"))
testImplementation(project(":internal-testutils-common"))
testImplementation(project(":kruth:kruth"))
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/XTypeName.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/XTypeName.kt
index f4a145b..c76af83 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/XTypeName.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/XTypeName.kt
@@ -38,10 +38,12 @@
import com.squareup.kotlinpoet.javapoet.JClassName
import com.squareup.kotlinpoet.javapoet.JParameterizedTypeName
import com.squareup.kotlinpoet.javapoet.JTypeName
+import com.squareup.kotlinpoet.javapoet.JTypeVariableName
import com.squareup.kotlinpoet.javapoet.JWildcardTypeName
import com.squareup.kotlinpoet.javapoet.KClassName
import com.squareup.kotlinpoet.javapoet.KParameterizedTypeName
import com.squareup.kotlinpoet.javapoet.KTypeName
+import com.squareup.kotlinpoet.javapoet.KTypeVariableName
import com.squareup.kotlinpoet.javapoet.KWildcardTypeName
import kotlin.reflect.KClass
@@ -257,6 +259,16 @@
}
)
}
+
+ /**
+ * Creates a type variable named with bounds.
+ */
+ fun getTypeVariableName(name: String, bounds: List<XTypeName> = emptyList()): XTypeName {
+ return XTypeName(
+ java = JTypeVariableName.get(name, *bounds.map { it.java }.toTypedArray()),
+ kotlin = KTypeVariableName(name, bounds.map { it.kotlin })
+ )
+ }
}
}
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/JavaPoetExt.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/JavaPoetExt.kt
index fa7b4d7..08e61c1 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/JavaPoetExt.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/JavaPoetExt.kt
@@ -161,9 +161,13 @@
* * thrown types are copied if the backing element is from java
*/
@JvmStatic
+ @JvmOverloads
fun overriding(
elm: XMethodElement,
- owner: XType
+ owner: XType =
+ checkNotNull(elm.enclosingElement.type) {
+ "Cannot override method without enclosing class"
+ }
): MethodSpec.Builder {
val asMember = elm.asMemberOf(owner)
return overriding(
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/compat/XConverters.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/compat/XConverters.kt
index 814848a..843368a 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/compat/XConverters.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/compat/XConverters.kt
@@ -43,6 +43,8 @@
import androidx.room.compiler.processing.javac.JavacType
import androidx.room.compiler.processing.javac.JavacTypeElement
import androidx.room.compiler.processing.javac.JavacVariableElement
+import androidx.room.compiler.processing.ksp.KSClassDeclarationAsOriginatingElement
+import androidx.room.compiler.processing.ksp.KSFileAsOriginatingElement
import androidx.room.compiler.processing.ksp.KspAnnotation
import androidx.room.compiler.processing.ksp.KspAnnotationValue
import androidx.room.compiler.processing.ksp.KspElement
@@ -123,6 +125,10 @@
is TypeElement -> this.toXProcessing(env)
is ExecutableElement -> this.toXProcessing(env)
is VariableElement -> this.toXProcessing(env)
+ is KSFileAsOriginatingElement ->
+ (env as KspProcessingEnv).wrapKSFile(this.ksFile)
+ is KSClassDeclarationAsOriginatingElement ->
+ (env as KspProcessingEnv).wrapClassDeclaration(this.ksClassDeclaration)
else -> error(
"Don't know how to convert element of type '${this::class}' to a XElement"
)
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/kotlin/JvmDescriptorUtils.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/kotlin/JvmDescriptorUtils.kt
index f7dae3e..402ab9e 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/kotlin/JvmDescriptorUtils.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/kotlin/JvmDescriptorUtils.kt
@@ -169,12 +169,26 @@
NestingKind.MEMBER, NestingKind.LOCAL ->
enclosingElement.internalName + "$" + simpleName
NestingKind.ANONYMOUS ->
- error("Unsupported nesting $nestingKind")
+ elementError("Unsupported nesting $nestingKind", this)
else ->
- error("Unsupported, nestingKind == null")
+ elementError("Unsupported, nestingKind == null", this)
}
is ExecutableElement -> enclosingElement.internalName
is QualifiedNameable -> qualifiedName.toString().replace('.', '/')
else -> simpleName.toString()
}
+
+ /**
+ * Throws an exception with the error [msg] and the [element] and its enclosing elements appended.
+ */
+ private fun elementError(msg: String, element: Element): Nothing {
+ fun buildName(element: Element): String {
+ val enclosingPart =
+ element.enclosingElement?.let { buildName(it) + "." } ?: ""
+ val simpleName = element.simpleName.ifEmpty { "<unnamed>" }
+ return enclosingPart + simpleName
+ }
+ val name = buildName(element)
+ error("$msg - On element $name")
+ }
}
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspAnnotation.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspAnnotation.kt
index 13ace16..2012535 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspAnnotation.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspAnnotation.kt
@@ -51,26 +51,27 @@
}
override val annotationValues: List<XAnnotationValue> by lazy {
- // In KSP the annotation members may be represented by constructor parameters in kotlin
- // source or by abstract methods in java source so we check both.
- val declarationConstructors = typeElement.let {
- // We access constructor using declaration since for compatibility with KAPT,
- // XTypeElement.getConstructors() will return an empty list for annotation classes.
- check(it is KspTypeElement)
- it.declaration.getConstructors().map {
- KspConstructorElement(
- env = env,
- declaration = it
- )
+ // Whether the annotation value is being treated as property or abstract method depends on
+ // the actual usage of the annotation. If the annotation is being used on Java source, then
+ // the annotation value will have a corresponding method element, otherwise, it will become
+ // a kotlin property.
+ val typesByName =
+ buildMap {
+ typeElement.getDeclaredMethods()
+ .filter {
+ if ((typeElement as KspTypeElement).declaration
+ .getConstructors()
+ .single().parameters
+ .isNotEmpty()) {
+ it.isKotlinPropertyMethod()
+ } else {
+ it.isAbstract()
+ }
+ }.forEach {
+ put(it.name, it.returnType)
+ put(it.jvmName, it.returnType)
+ }
}
- }
- val typesByName = if (declarationConstructors.single().parameters.isNotEmpty()) {
- declarationConstructors.single().parameters.associate { it.name to it.type }
- } else {
- typeElement.getDeclaredMethods()
- .filter { it.isAbstract() }
- .associate { it.name to it.returnType }
- }
// KSAnnotated.arguments isn't guaranteed to have the same ordering as declared in the
// annotation declaration, so we order it manually using a map from name to index.
val indexByName = typesByName.keys.mapIndexed { index, name -> name to index }.toMap()
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/MethodSpecHelperTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/MethodSpecHelperTest.kt
index c427a9b..eee3dd6 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/MethodSpecHelperTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/MethodSpecHelperTest.kt
@@ -43,7 +43,8 @@
// if true, pre-compile sources then run the test to account for changes between .class files
// and source files
val preCompiledCode: Boolean,
- val shouldMarkParamsFinal: Boolean
+ val shouldMarkParamsFinal: Boolean,
+ val ignoreOwner: Boolean
) {
@Test
fun javaOverrides() {
@@ -460,17 +461,15 @@
) { invocation ->
val (target, methods) = invocation.getOverrideTestTargets(ignoreInheritedMethods)
methods.forEachIndexed { index, method ->
- val func = if (shouldMarkParamsFinal)
- MethodSpecHelper::overridingWithFinalParams
- else
- MethodSpecHelper::overriding
+ val subject =
+ if (ignoreOwner)
+ MethodSpecHelper.overriding(method)
+ else if (shouldMarkParamsFinal)
+ MethodSpecHelper.overridingWithFinalParams(method, target.type)
+ else
+ MethodSpecHelper.overriding(method, target.type)
- val subject = func(
- method,
- target.type
- ).toSignature()
-
- assertThat(subject).isEqualTo(golden[index])
+ assertThat(subject.toSignature()).isEqualTo(golden[index])
}
}
}
@@ -573,6 +572,9 @@
owner: DeclaredType,
typeUtils: Types
): MethodSpec.Builder {
+ if (ignoreOwner) {
+ return MethodSpec.overriding(elm)
+ }
val baseSpec = MethodSpec.overriding(elm, owner, typeUtils)
.build()
@@ -600,7 +602,10 @@
companion object {
@JvmStatic
- @Parameterized.Parameters(name = "preCompiledCode={0}, shouldMarkParamsFinal={1}")
- fun params() = generateAllEnumerations(listOf(false, true), listOf(false, true))
+ @Parameterized.Parameters(
+ name = "preCompiledCode={0}, shouldMarkParamsFinal={1}, ignoreOwner={2}")
+ fun params() =
+ generateAllEnumerations(
+ listOf(false, true), listOf(false, true), listOf(false, true))
}
}
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XAnnotationTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XAnnotationTest.kt
index 78c6bf9..2953253 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XAnnotationTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XAnnotationTest.kt
@@ -92,6 +92,101 @@
}
@Test
+ fun typeParameterAnnotationsOnFunction() {
+ val kotlinSource = Source.kotlin(
+ "foo.bar.Subject.kt",
+ """
+ package foo.bar
+ import kotlin.collections.*
+
+ @Target(AnnotationTarget.TYPE)
+ annotation class SomeAnnotation(val value: String)
+
+ class Subject {
+ fun myFunction(): Map<@SomeAnnotation("someString") Int, Int> {
+ return emptyMap()
+ }
+ }
+ """.trimIndent()
+ )
+ val javaSource = Source.java(
+ "foo.bar.Subject",
+ """
+ package foo.bar;
+ import java.lang.annotation.ElementType;
+ import java.lang.annotation.Target;
+ import java.util.Map;
+ import java.util.HashMap;
+
+ @Target(ElementType.TYPE_USE)
+ @interface SomeAnnotation {
+ String value();
+ }
+
+ class Subject {
+ Map<@SomeAnnotation("someString") Integer, Integer> myFunction() {
+ return new HashMap<>();
+ }
+ }
+ """.trimIndent()
+ )
+
+ listOf(javaSource, kotlinSource).forEach { source ->
+ runTest(
+ sources = listOf(source)
+ ) { invocation ->
+ if (!invocation.isKsp) return@runTest
+ val subject = invocation.processingEnv.requireTypeElement(
+ "foo.bar.Subject")
+ val method = subject.getMethodByJvmName("myFunction")
+ val firstArg = method.returnType.typeArguments.first()
+ val annotation = firstArg.getAllAnnotations().first()
+ assertThat(
+ annotation.name
+ ).isEqualTo("SomeAnnotation")
+
+ assertThat(
+ annotation.annotationValues.first().value
+ ).isEqualTo("someString")
+ }
+ }
+ }
+
+ @Test
+ fun testJvmNameAnnotationValue() {
+ val kotlinSrc = Source.kotlin(
+ "MyAnnotation.kt",
+ """
+ @Target(AnnotationTarget.CLASS)
+ annotation class MyAnnotation(
+ @get:JvmName("stringParameter")
+ val stringParam: String,
+ val intParam: Int,
+ @get:JvmName("longParameter")
+ val longParam: Long
+ )
+ """.trimIndent()
+ )
+ val javaSrc = Source.java(
+ "Foo",
+ """
+ @MyAnnotation(stringParameter = "1", intParam = 2, longParameter = 3)
+ public class Foo {}
+ """.trimIndent()
+ )
+ runTest(sources = listOf(javaSrc, kotlinSrc)) { invocation ->
+ val typeElement = invocation.processingEnv.requireTypeElement("Foo")
+ val annotation =
+ typeElement.getAllAnnotations().single { it.qualifiedName == "MyAnnotation" }
+ assertThat(
+ annotation.annotationValues.map { it.value }
+ ).containsExactly(
+ "1", 2, 3.toLong()
+ ).inOrder()
+ }
+ }
+
+ @Test
fun readsAnnotationsDeclaredInSources() {
val source = Source.kotlin(
"MyClass.kt",
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XAnnotationValueTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XAnnotationValueTest.kt
index 656c3c5..866159c 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XAnnotationValueTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XAnnotationValueTest.kt
@@ -1161,8 +1161,8 @@
""".trimIndent()
) as Source.KotlinSource
) { invocation ->
- val myEnumJTypeName = JClassName.get("", "test.MyEnum")
- val myEnumKTypeName = KClassName("", "test.MyEnum")
+ val myEnumJTypeName = JClassName.get("test", "MyEnum")
+ val myEnumKTypeName = KClassName("test", "MyEnum")
fun checkSingleValue(annotationValue: XAnnotationValue, expectedValue: String) {
assertThat(annotationValue.valueType.asTypeName().java)
@@ -1517,8 +1517,8 @@
""".trimIndent()
) as Source.KotlinSource
) { invocation ->
- val aJTypeName = JClassName.get("", "test.A")
- val aKTypeName = KClassName("", "test.A")
+ val aJTypeName = JClassName.get("test", "A")
+ val aKTypeName = KClassName("test", "A")
fun checkSingleValue(annotationValue: XAnnotationValue, expectedValue: String) {
assertThat(annotationValue.valueType.asTypeName().java)
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XElementTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XElementTest.kt
index 69ce06b..1763a8b 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XElementTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XElementTest.kt
@@ -30,7 +30,6 @@
import androidx.room.compiler.processing.util.XTestInvocation
import androidx.room.compiler.processing.util.asJClassName
import androidx.room.compiler.processing.util.compileFiles
-import androidx.room.compiler.processing.util.createXTypeVariableName
import androidx.room.compiler.processing.util.getField
import androidx.room.compiler.processing.util.getMethodByJvmName
import androidx.room.compiler.processing.util.getParameter
@@ -244,24 +243,24 @@
validateMethodElement(
element = it.processingEnv.requireTypeElement("foo.bar.Base"),
- tTypeName = createXTypeVariableName("T"),
- rTypeName = createXTypeVariableName("R")
+ tTypeName = XTypeName.getTypeVariableName("T", listOf(XTypeName.ANY_OBJECT)),
+ rTypeName = XTypeName.getTypeVariableName("R", listOf(XTypeName.ANY_OBJECT))
)
validateMethodElement(
element = it.processingEnv.requireTypeElement("foo.bar.Child"),
- tTypeName = createXTypeVariableName("T"),
- rTypeName = createXTypeVariableName("R")
+ tTypeName = XTypeName.getTypeVariableName("T", listOf(XTypeName.ANY_OBJECT)),
+ rTypeName = XTypeName.getTypeVariableName("R", listOf(XTypeName.ANY_OBJECT))
)
validateMethodTypeAsMemberOf(
element = it.processingEnv.requireTypeElement("foo.bar.Base"),
- tTypeName = createXTypeVariableName("T"),
- rTypeName = createXTypeVariableName("R")
+ tTypeName = XTypeName.getTypeVariableName("T", listOf(XTypeName.ANY_OBJECT)),
+ rTypeName = XTypeName.getTypeVariableName("R", listOf(XTypeName.ANY_OBJECT))
)
validateMethodTypeAsMemberOf(
element = it.processingEnv.requireTypeElement("foo.bar.Child"),
tTypeName = String::class.asClassName(),
- rTypeName = createXTypeVariableName("R")
+ rTypeName = XTypeName.getTypeVariableName("R", listOf(XTypeName.ANY_OBJECT))
)
}
}
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XExecutableElementTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XExecutableElementTest.kt
index a5d61f1..db2aa22 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XExecutableElementTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XExecutableElementTest.kt
@@ -25,12 +25,13 @@
import androidx.room.compiler.processing.util.UNIT_JCLASS_NAME
import androidx.room.compiler.processing.util.className
import androidx.room.compiler.processing.util.compileFiles
-import androidx.room.compiler.processing.util.createXTypeVariableName
import androidx.room.compiler.processing.util.getDeclaredMethodByJvmName
import androidx.room.compiler.processing.util.getMethodByJvmName
import androidx.room.compiler.processing.util.getParameter
import androidx.room.compiler.processing.util.runProcessorTest
import androidx.room.compiler.processing.util.typeName
+import com.google.testing.junit.testparameterinjector.TestParameter
+import com.google.testing.junit.testparameterinjector.TestParameterInjector
import com.squareup.javapoet.ClassName
import com.squareup.javapoet.ParameterizedTypeName
import com.squareup.javapoet.TypeName
@@ -47,9 +48,8 @@
import java.io.IOException
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-@RunWith(JUnit4::class)
+@RunWith(TestParameterInjector::class)
class XExecutableElementTest {
@Test
fun basic() {
@@ -1047,7 +1047,7 @@
}
element.getDeclaredMethodByJvmName("ext5").let { method ->
assertThat(method.parameters[0].type.asTypeName())
- .isEqualTo(createXTypeVariableName("T"))
+ .isEqualTo(XTypeName.getTypeVariableName("T"))
}
element.getDeclaredMethodByJvmName("ext6").let { method ->
assertThat(method.isSuspendFunction()).isTrue()
@@ -1068,7 +1068,7 @@
assertThat(method.isAbstract()).isTrue()
assertThat(method.isExtensionFunction()).isTrue()
assertThat(method.parameters[0].type.asTypeName())
- .isEqualTo(createXTypeVariableName("T"))
+ .isEqualTo(XTypeName.getTypeVariableName("T"))
val fooImpl = it.processingEnv.requireTypeElement("$pkg.FooImpl")
assertThat(method.parameters[0].asMemberOf(fooImpl.type).asTypeName())
@@ -1348,4 +1348,85 @@
}
}
}
+
+ @Test
+ fun parameterNames(
+ @TestParameter isJava: Boolean,
+ @TestParameter isPrecompiled: Boolean,
+ @TestParameter hasParametersFlag: Boolean,
+ @TestParameter hasDebugFlag: Boolean
+ ) {
+ val javaSource = Source.java(
+ "foo.bar.Baz",
+ """
+ package foo.bar;
+ public class Baz {
+ private Baz(String param1) {}
+ }
+ """.trimIndent())
+ val kotlinSource = Source.kotlin(
+ "foo.bar.Baz.kt",
+ """
+ package foo.bar
+ class Baz private constructor(param1: String)
+ """.trimIndent())
+
+ val sources: List<Source> =
+ if (isPrecompiled) {
+ emptyList()
+ } else {
+ if (isJava) {
+ listOf(javaSource)
+ } else {
+ listOf(kotlinSource)
+ }
+ }
+ val javacArgs = buildList {
+ if (hasParametersFlag) {
+ // This is used to generate `MethodParameters` in class files
+ add("-parameters")
+ }
+ if (hasDebugFlag) {
+ // This is used to generate `LocalVariableTable` in class files
+ add("-g:vars")
+ }
+ }
+ val classes: List<File> =
+ if (isPrecompiled) {
+ if (isJava) {
+ compileFiles(listOf(javaSource), javacArguments = javacArgs)
+ } else {
+ compileFiles(listOf(kotlinSource), javacArguments = javacArgs)
+ }
+ } else {
+ emptyList()
+ }
+ runProcessorTest(sources = sources, classpath = classes) {
+ val element = it.processingEnv.requireTypeElement("foo.bar.Baz")
+ assertThat(element.getConstructors().single().parameters.single().name)
+ .isEqualTo(
+ if (isJava) {
+ if (isPrecompiled) {
+ if (hasParametersFlag) {
+ "param1"
+ } else {
+ if (it.isKsp) {
+ "p0"
+ } else { // Javac/KAPT
+ if (hasDebugFlag) {
+ "param1"
+ } else {
+ "arg0"
+ }
+ }
+ }
+ } else { // Java sources
+ "param1"
+ }
+ } else { // Kotlin sources or classes
+ "param1"
+ }
+ )
+ }
+ }
}
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeElementTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeElementTest.kt
index 42465dc..b69db6e 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeElementTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeElementTest.kt
@@ -27,7 +27,6 @@
import androidx.room.compiler.processing.util.asKClassName
import androidx.room.compiler.processing.util.asMutableKClassName
import androidx.room.compiler.processing.util.compileFiles
-import androidx.room.compiler.processing.util.createXTypeVariableName
import androidx.room.compiler.processing.util.getAllFieldNames
import androidx.room.compiler.processing.util.getDeclaredField
import androidx.room.compiler.processing.util.getField
@@ -539,7 +538,7 @@
}
baseClass.getField("genericProp").let { field ->
- assertThat(field.type.asTypeName()).isEqualTo(createXTypeVariableName("T"))
+ assertThat(field.type.asTypeName()).isEqualTo(XTypeName.getTypeVariableName("T"))
}
subClass.getField("genericProp").let { field ->
@@ -1910,26 +1909,26 @@
method = method,
name = "method",
enclosingElement = abstractClass,
- returnType = createXTypeVariableName("T2"),
+ returnType = XTypeName.getTypeVariableName("T2"),
parameterTypes = arrayOf(
- createXTypeVariableName("T1"),
- createXTypeVariableName("T2")
+ XTypeName.getTypeVariableName("T1"),
+ XTypeName.getTypeVariableName("T2")
)
)
checkMethodType(
methodType = method.executableType,
- returnType = createXTypeVariableName("T2"),
+ returnType = XTypeName.getTypeVariableName("T2"),
parameterTypes = arrayOf(
- createXTypeVariableName("T1"),
- createXTypeVariableName("T2")
+ XTypeName.getTypeVariableName("T1"),
+ XTypeName.getTypeVariableName("T2")
)
)
checkMethodType(
methodType = method.asMemberOf(abstractClass.type),
- returnType = createXTypeVariableName("T2"),
+ returnType = XTypeName.getTypeVariableName("T2"),
parameterTypes = arrayOf(
- createXTypeVariableName("T1"),
- createXTypeVariableName("T2")
+ XTypeName.getTypeVariableName("T1"),
+ XTypeName.getTypeVariableName("T2")
)
)
checkMethodType(
@@ -1982,26 +1981,26 @@
method = abstractClassMethod,
name = "method",
enclosingElement = abstractClass,
- returnType = createXTypeVariableName("T2"),
+ returnType = XTypeName.getTypeVariableName("T2"),
parameterTypes = arrayOf(
- createXTypeVariableName("T1"),
- createXTypeVariableName("T2")
+ XTypeName.getTypeVariableName("T1"),
+ XTypeName.getTypeVariableName("T2")
)
)
checkMethodType(
methodType = abstractClassMethod.executableType,
- returnType = createXTypeVariableName("T2"),
+ returnType = XTypeName.getTypeVariableName("T2"),
parameterTypes = arrayOf(
- createXTypeVariableName("T1"),
- createXTypeVariableName("T2")
+ XTypeName.getTypeVariableName("T1"),
+ XTypeName.getTypeVariableName("T2")
)
)
checkMethodType(
methodType = abstractClassMethod.asMemberOf(abstractClass.type),
- returnType = createXTypeVariableName("T2"),
+ returnType = XTypeName.getTypeVariableName("T2"),
parameterTypes = arrayOf(
- createXTypeVariableName("T1"),
- createXTypeVariableName("T2"),
+ XTypeName.getTypeVariableName("T1"),
+ XTypeName.getTypeVariableName("T2"),
)
)
checkMethodType(
@@ -2089,7 +2088,7 @@
checkConstructorParameters(
typeElement = abstractClass,
expectedParameters = arrayOf(
- createXTypeVariableName("T")
+ XTypeName.getTypeVariableName("T")
)
)
checkConstructorParameters(
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeTest.kt
index 840d390..1a68ad3 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeTest.kt
@@ -89,7 +89,12 @@
if (it.isKsp) {
assertThat(type.asTypeName().kotlin).isEqualTo(
KClassName("foo.bar", "Parent")
- .parameterizedBy(KClassName("", "InputStreamType"))
+ .parameterizedBy(
+ KTypeVariableName(
+ "InputStreamType",
+ KClassName("java.io", "InputStream")
+ )
+ )
)
}
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/util/PoetTestExt.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/util/PoetTestExt.kt
index 86942af..7de5edb 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/util/PoetTestExt.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/util/PoetTestExt.kt
@@ -16,7 +16,6 @@
package androidx.room.compiler.processing.util
-import androidx.room.compiler.codegen.XTypeName
import com.squareup.kotlinpoet.MUTABLE_COLLECTION
import com.squareup.kotlinpoet.MUTABLE_ITERABLE
import com.squareup.kotlinpoet.MUTABLE_LIST
@@ -73,14 +72,6 @@
else -> this.asKClassName()
}
-// Creates a simple XTypeName wrapping JTypeVariableName and KTypeVariableName without bounds.
-fun createXTypeVariableName(name: String): XTypeName {
- return XTypeName(
- java = JTypeVariableName.get(name),
- kotlin = KTypeVariableName(name)
- )
-}
-
/**
* Dumps the typename with its bounds in a given depth, making tests more readable.
*/
diff --git a/room/room-compiler/build.gradle b/room/room-compiler/build.gradle
index 6d06fe8..c73ef07 100644
--- a/room/room-compiler/build.gradle
+++ b/room/room-compiler/build.gradle
@@ -14,7 +14,6 @@
* limitations under the License.
*/
-
import androidx.build.BuildOnServerKt
import androidx.build.LibraryType
import androidx.build.SdkHelperKt
@@ -29,12 +28,6 @@
id("com.github.johnrengelman.shadow")
}
-def antlrOut = "$buildDir/generated/antlr/grammar-gen/"
-sourceSets {
- main.java.srcDirs += "src/main/grammar-gen"
- main.java.srcDirs += antlrOut
-}
-
configurations {
/**
* shadowed is used for dependencies which we jarjar into the library jar instead of adding it
@@ -121,23 +114,27 @@
testImplementation(project(":internal-testutils-common"))
}
-def generateAntlrTask = task("generateAntlrGrammar", type: GenerateAntlrGrammar) {
- sqliteFile = file("$projectDir/SQLite.g4")
- antlrClasspath = configurations.compileClasspath
- outputDirectory = file(antlrOut)
+def generateAntlrTask = tasks.register("generateAntlrGrammar", GenerateAntlrGrammar) { task ->
+ task.getSqliteFile().set(layout.projectDirectory.file("SQLite.g4"))
+ task.getAntlrClasspath().from(configurations.compileClasspath)
+ task.getOutputDirectory().set(layout.buildDirectory.dir("generated/antlr/grammar-gen/"))
+}
+
+sourceSets {
+ main.java.srcDirs += generateAntlrTask.map { it.outputDirectory }
}
@CacheableTask
abstract class GenerateAntlrGrammar extends DefaultTask {
@PathSensitive(PathSensitivity.NONE)
@InputFile
- File sqliteFile
+ abstract RegularFileProperty getSqliteFile()
@Classpath
- FileCollection antlrClasspath
+ abstract ConfigurableFileCollection getAntlrClasspath()
@OutputDirectory
- File outputDirectory
+ abstract DirectoryProperty getOutputDirectory()
@Inject
abstract ExecOperations getExecOperations()
@@ -152,10 +149,10 @@
void generateAntlrGrammar() {
execOperations.javaexec {
mainClass.set("org.antlr.v4.Tool")
- classpath = antlrClasspath
- args "SQLite.g4",
+ classpath = getAntlrClasspath()
+ args getSqliteFile().asFile.get().absolutePath,
"-visitor",
- "-o", new File(outputDirectory, "androidx/room/parser").path,
+ "-o", new File(getOutputDirectory().asFile.get(), "androidx/room/parser").path,
"-package", "androidx.room.parser"
}
}
@@ -165,23 +162,23 @@
* Room compiler jarjars some dependencies. This task validates the published artifacts of room
* compiler to ensure dependencies are properly jarjarred.
*/
-class CheckArtifactTask extends DefaultTask {
+abstract class CheckArtifactTask extends DefaultTask {
@InputFiles
- FileCollection artifactInputs = project.objects.fileCollection()
+ abstract ConfigurableFileCollection getArtifactInputs()
@InputFile
- File pomFile
+ abstract RegularFileProperty getPomFile()
@OutputFile
- File result = new File(project.buildDir, "checkArtifactOutput.txt")
+ abstract RegularFileProperty getResult()
/**
* Checks the publish task's artifacts to make sure the classes.jar does include jarjarred
* antlr classes.
*/
- def validatePublishTaskOutputs() {
- if (artifactInputs.files.isEmpty()) {
+ void validatePublishTaskOutputs() {
+ if (getArtifactInputs().files.isEmpty()) {
throw new GradleException("Couldn't find the classes.jar for the room-compiler " +
"artifact. Ensure that publish is setup properly.")
}
- artifactInputs.forEach {
+ getArtifactInputs().forEach {
validateJarContents(it)
}
}
@@ -190,7 +187,7 @@
* Traverses the given jar file, looks for the classes that should be jarjarred and validates
* their location.
*/
- def validateJarContents(File jarFile) {
+ static void validateJarContents(File jarFile) {
Boolean found = false
ZipFile zip = new ZipFile(jarFile)
try {
@@ -223,11 +220,12 @@
* Checks the generated pom file to ensure it does not depend on any jarjarred dependencies
* but still depends on others.
*/
- def validatePomTaskOutputs() {
- if (!pomFile.canRead()) {
+ void validatePomTaskOutputs() {
+ File pom = getPomFile().asFile.get()
+ if (!pom.canRead()) {
throw new GradleException("Cannot find the pom file for room-compiler")
}
- def pomContents = pomFile.newReader().text
+ def pomContents = pom.newReader().text
if (pomContents.contains("antlr")) {
throw new GradleException("Room-compiler pom file should not depend on antlr.\n" +
"Pom Contents:\n $pomContents")
@@ -239,24 +237,29 @@
}
@TaskAction
- def validate() {
- result.write("fail\n")
+ void validate() {
+ getResult().asFile.get().write("fail\n")
validatePublishTaskOutputs()
validatePomTaskOutputs()
// have a no-op output to make gradle happy w/ input/output checking.
- result.write("ok\n")
+ getResult().asFile.get().write("ok\n")
}
}
-def checkArtifactContentsTask = tasks.register("checkArtifactTask", CheckArtifactTask) {
- def pomTask = (GenerateMavenPom) project.tasks.named("generatePomFileForMavenPublication").get()
- it.pomFile = pomTask.destination
+def checkArtifactContentsTask = tasks.register("checkArtifact", CheckArtifactTask) { task ->
+ task.getResult().set(layout.buildDirectory.file("checkArtifactOutput.txt"))
+ def pomTask = (TaskProvider<GenerateMavenPom>) project.tasks.named("generatePomFileForMavenPublication")
+ task.getPomFile().set(
+ project.objects.fileProperty().fileProvider(
+ pomTask.map { it.destination }
+ )
+ )
}
afterEvaluate {
def publishTaskProvider = project.tasks.named("publishMavenPublicationToMavenRepository")
- checkArtifactContentsTask.configure {
- it.artifactInputs.from {
+ checkArtifactContentsTask.configure { checkArtifactTask ->
+ checkArtifactTask.getArtifactInputs().from {
publishTaskProvider.map {
((PublishToMavenRepository) it).getPublication().artifacts.matching {
it.classifier == null
@@ -271,11 +274,6 @@
// make sure we validate published artifacts on the build server.
BuildOnServerKt.addToBuildOnServer(project, checkArtifactContentsTask)
-def tasksThatDependOnAntlr = ["compileKotlin", "sourceJar", "kotlinSourcesJar", "generateKmpDocs"]
-tasks.matching { tasksThatDependOnAntlr.contains(it.name) }.configureEach {
- it.dependsOn(generateAntlrTask)
-}
-
tasks.withType(KotlinCompile).configureEach {
kotlinOptions {
freeCompilerArgs += [
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/DeleteOrUpdateShortcutMethodProcessorTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/DeleteOrUpdateShortcutMethodProcessorTest.kt
index 32f453f..7f7e12a 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/DeleteOrUpdateShortcutMethodProcessorTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/DeleteOrUpdateShortcutMethodProcessorTest.kt
@@ -376,7 +376,7 @@
assertThat(
param.type.asTypeName(),
`is`(
- XClassName.get("foo.bar", "MyClass.MyList").parametrizedBy(
+ XClassName.get("foo.bar", "MyClass", "MyList").parametrizedBy(
CommonTypeNames.STRING, COMMON.USER_TYPE_NAME
)
)
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/InsertOrUpsertShortcutMethodProcessorTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/InsertOrUpsertShortcutMethodProcessorTest.kt
index fb756e4..45f42288 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/InsertOrUpsertShortcutMethodProcessorTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/InsertOrUpsertShortcutMethodProcessorTest.kt
@@ -400,7 +400,7 @@
val param = insertionUpsertion.parameters.first()
assertThat(param.type.asTypeName())
.isEqualTo(
- XClassName.get("foo.bar", "MyClass.MyList").parametrizedBy(
+ XClassName.get("foo.bar", "MyClass", "MyList").parametrizedBy(
CommonTypeNames.STRING, USER_TYPE_NAME
)
)
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/PojoProcessorTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/PojoProcessorTest.kt
index ec56bef..a4c0fd9 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/PojoProcessorTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/PojoProcessorTest.kt
@@ -212,7 +212,7 @@
assertThat(parent.field.name, `is`("myPoint"))
assertThat(
parent.pojo.typeName,
- `is`(XClassName.get("foo.bar.MyPojo", "Point"))
+ `is`(XClassName.get("foo.bar", "MyPojo", "Point"))
)
}
}
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/QueryMethodProcessorTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/QueryMethodProcessorTest.kt
index cbb26a4..13d0293 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/QueryMethodProcessorTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/QueryMethodProcessorTest.kt
@@ -335,7 +335,7 @@
"""
) { parsedQuery, invocation ->
val expected = MUTABLE_LIST.parametrizedBy(
- XClassName.get("", "T")
+ XTypeName.getTypeVariableName("T", listOf(XTypeName.ANY_OBJECT))
)
assertThat(parsedQuery.returnType.asTypeName(), `is`(expected))
invocation.assertCompilationResult {
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/RawQueryMethodProcessorTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/RawQueryMethodProcessorTest.kt
index e943139..291267e 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/RawQueryMethodProcessorTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/RawQueryMethodProcessorTest.kt
@@ -179,7 +179,7 @@
@Test
fun pojo() {
- val pojo = XClassName.get("foo.bar.MyClass", "MyPojo")
+ val pojo = XClassName.get("foo.bar", "MyClass", "MyPojo")
singleQueryMethod(
"""
public class MyPojo {
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/writer/DefaultsInDaoTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/writer/DefaultsInDaoTest.kt
index 3ee50134..6d6fcc7 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/writer/DefaultsInDaoTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/writer/DefaultsInDaoTest.kt
@@ -17,7 +17,6 @@
package androidx.room.writer
import COMMON
-import androidx.kruth.StringSubject
import androidx.room.compiler.codegen.CodeLanguage
import androidx.room.compiler.processing.XTypeElement
import androidx.room.compiler.processing.util.Source
@@ -25,6 +24,7 @@
import androidx.room.ext.RoomTypeNames.ROOM_DB
import androidx.room.processor.DaoProcessor
import androidx.room.testing.context
+import com.google.common.truth.StringSubject
import createVerifierFromEntitiesAndViews
import org.jetbrains.kotlin.config.JvmDefaultMode
import org.junit.Test
diff --git a/room/room-compiler/src/test/test-data/autoMigrationWriter/output/kotlin/AutoMigrationWithProvidedSpec.kt b/room/room-compiler/src/test/test-data/autoMigrationWriter/output/kotlin/AutoMigrationWithProvidedSpec.kt
index 647be54..90577fc 100644
--- a/room/room-compiler/src/test/test-data/autoMigrationWriter/output/kotlin/AutoMigrationWithProvidedSpec.kt
+++ b/room/room-compiler/src/test/test-data/autoMigrationWriter/output/kotlin/AutoMigrationWithProvidedSpec.kt
@@ -5,7 +5,6 @@
import androidx.sqlite.db.SupportSQLiteDatabase
import javax.`annotation`.processing.Generated
import kotlin.Suppress
-import kotlin.Unit
@Generated(value = ["androidx.room.RoomProcessor"])
@Suppress(names = ["UNCHECKED_CAST", "DEPRECATION", "REDUNDANT_PROJECTION"])
@@ -16,7 +15,7 @@
this.callback = callback
}
- public override fun migrate(db: SupportSQLiteDatabase): Unit {
+ public override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL("ALTER TABLE `Song` ADD COLUMN `artistId` INTEGER DEFAULT NULL")
callback.onPostMigrate(db)
}
diff --git a/room/room-compiler/src/test/test-data/autoMigrationWriter/output/kotlin/ValidAutoMigrationWithDefault.kt b/room/room-compiler/src/test/test-data/autoMigrationWriter/output/kotlin/ValidAutoMigrationWithDefault.kt
index 6b4d52e6..31f43d7 100644
--- a/room/room-compiler/src/test/test-data/autoMigrationWriter/output/kotlin/ValidAutoMigrationWithDefault.kt
+++ b/room/room-compiler/src/test/test-data/autoMigrationWriter/output/kotlin/ValidAutoMigrationWithDefault.kt
@@ -5,7 +5,6 @@
import androidx.sqlite.db.SupportSQLiteDatabase
import javax.`annotation`.processing.Generated
import kotlin.Suppress
-import kotlin.Unit
@Generated(value = ["androidx.room.RoomProcessor"])
@Suppress(names = ["UNCHECKED_CAST", "DEPRECATION", "REDUNDANT_PROJECTION"])
@@ -14,7 +13,7 @@
public constructor() : super(1, 2)
- public override fun migrate(db: SupportSQLiteDatabase): Unit {
+ public override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL("ALTER TABLE `Song` ADD COLUMN `artistId` INTEGER NOT NULL DEFAULT 0")
callback.onPostMigrate(db)
}
diff --git a/room/room-compiler/src/test/test-data/autoMigrationWriter/output/kotlin/ValidAutoMigrationWithoutDefault.kt b/room/room-compiler/src/test/test-data/autoMigrationWriter/output/kotlin/ValidAutoMigrationWithoutDefault.kt
index 93eab8f..f0ffd3a 100644
--- a/room/room-compiler/src/test/test-data/autoMigrationWriter/output/kotlin/ValidAutoMigrationWithoutDefault.kt
+++ b/room/room-compiler/src/test/test-data/autoMigrationWriter/output/kotlin/ValidAutoMigrationWithoutDefault.kt
@@ -5,7 +5,6 @@
import androidx.sqlite.db.SupportSQLiteDatabase
import javax.`annotation`.processing.Generated
import kotlin.Suppress
-import kotlin.Unit
@Generated(value = ["androidx.room.RoomProcessor"])
@Suppress(names = ["UNCHECKED_CAST", "DEPRECATION", "REDUNDANT_PROJECTION"])
@@ -14,7 +13,7 @@
public constructor() : super(1, 2)
- public override fun migrate(db: SupportSQLiteDatabase): Unit {
+ public override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL("ALTER TABLE `Song` ADD COLUMN `artistId` INTEGER DEFAULT NULL")
callback.onPostMigrate(db)
}
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/callableQuery_rx2.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/callableQuery_rx2.kt
index 32595b7..fd018379 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/callableQuery_rx2.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/callableQuery_rx2.kt
@@ -19,7 +19,6 @@
import kotlin.Int
import kotlin.String
import kotlin.Suppress
-import kotlin.Unit
import kotlin.collections.List
import kotlin.jvm.JvmStatic
@@ -73,7 +72,7 @@
}
}
- protected fun finalize(): Unit {
+ protected fun finalize() {
_statement.release()
}
})
@@ -119,7 +118,7 @@
}
}
- protected fun finalize(): Unit {
+ protected fun finalize() {
_statement.release()
}
})
@@ -168,7 +167,7 @@
}
}
- protected fun finalize(): Unit {
+ protected fun finalize() {
_statement.release()
}
})
@@ -214,7 +213,7 @@
}
}
- protected fun finalize(): Unit {
+ protected fun finalize() {
_statement.release()
}
})
@@ -260,7 +259,7 @@
}
}
- protected fun finalize(): Unit {
+ protected fun finalize() {
_statement.release()
}
})
@@ -306,7 +305,7 @@
}
}
- protected fun finalize(): Unit {
+ protected fun finalize() {
_statement.release()
}
})
@@ -355,7 +354,7 @@
}
}
- protected fun finalize(): Unit {
+ protected fun finalize() {
_statement.release()
}
})
@@ -401,7 +400,7 @@
}
}
- protected fun finalize(): Unit {
+ protected fun finalize() {
_statement.release()
}
})
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/callableQuery_rx3.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/callableQuery_rx3.kt
index 07fbdeb5..48492a3 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/callableQuery_rx3.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/callableQuery_rx3.kt
@@ -19,7 +19,6 @@
import kotlin.Int
import kotlin.String
import kotlin.Suppress
-import kotlin.Unit
import kotlin.collections.List
import kotlin.jvm.JvmStatic
@@ -73,7 +72,7 @@
}
}
- protected fun finalize(): Unit {
+ protected fun finalize() {
_statement.release()
}
})
@@ -119,7 +118,7 @@
}
}
- protected fun finalize(): Unit {
+ protected fun finalize() {
_statement.release()
}
})
@@ -168,7 +167,7 @@
}
}
- protected fun finalize(): Unit {
+ protected fun finalize() {
_statement.release()
}
})
@@ -214,7 +213,7 @@
}
}
- protected fun finalize(): Unit {
+ protected fun finalize() {
_statement.release()
}
})
@@ -260,7 +259,7 @@
}
}
- protected fun finalize(): Unit {
+ protected fun finalize() {
_statement.release()
}
})
@@ -306,7 +305,7 @@
}
}
- protected fun finalize(): Unit {
+ protected fun finalize() {
_statement.release()
}
})
@@ -355,7 +354,7 @@
}
}
- protected fun finalize(): Unit {
+ protected fun finalize() {
_statement.release()
}
})
@@ -401,7 +400,7 @@
}
}
- protected fun finalize(): Unit {
+ protected fun finalize() {
_statement.release()
}
})
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/coroutines.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/coroutines.kt
index d99e339..3cf1b4c 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/coroutines.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/coroutines.kt
@@ -18,7 +18,6 @@
import kotlin.Int
import kotlin.String
import kotlin.Suppress
-import kotlin.Unit
import kotlin.collections.List
import kotlin.collections.MutableList
import kotlin.jvm.JvmStatic
@@ -74,7 +73,7 @@
}
}
- protected fun finalize(): Unit {
+ protected fun finalize() {
_statement.release()
}
})
@@ -121,7 +120,7 @@
}
}
- protected fun finalize(): Unit {
+ protected fun finalize() {
_statement.release()
}
})
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/database_internalVisibility.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/database_internalVisibility.kt
index 585f176..442641a 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/database_internalVisibility.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/database_internalVisibility.kt
@@ -18,7 +18,6 @@
import kotlin.Lazy
import kotlin.String
import kotlin.Suppress
-import kotlin.Unit
import kotlin.collections.List
import kotlin.collections.Map
import kotlin.collections.MutableList
@@ -35,13 +34,13 @@
protected override fun createOpenHelper(config: DatabaseConfiguration): SupportSQLiteOpenHelper {
val _openCallback: SupportSQLiteOpenHelper.Callback = RoomOpenHelper(config, object :
RoomOpenHelper.Delegate(1) {
- public override fun createAllTables(db: SupportSQLiteDatabase): Unit {
+ public override fun createAllTables(db: SupportSQLiteDatabase) {
db.execSQL("CREATE TABLE IF NOT EXISTS `MyEntity` (`pk` INTEGER NOT NULL, PRIMARY KEY(`pk`))")
db.execSQL("CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)")
db.execSQL("INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '195d7974660177325bd1a32d2c7b8b8c')")
}
- public override fun dropAllTables(db: SupportSQLiteDatabase): Unit {
+ public override fun dropAllTables(db: SupportSQLiteDatabase) {
db.execSQL("DROP TABLE IF EXISTS `MyEntity`")
val _callbacks: List<RoomDatabase.Callback>? = mCallbacks
if (_callbacks != null) {
@@ -51,7 +50,7 @@
}
}
- public override fun onCreate(db: SupportSQLiteDatabase): Unit {
+ public override fun onCreate(db: SupportSQLiteDatabase) {
val _callbacks: List<RoomDatabase.Callback>? = mCallbacks
if (_callbacks != null) {
for (_callback: RoomDatabase.Callback in _callbacks) {
@@ -60,7 +59,7 @@
}
}
- public override fun onOpen(db: SupportSQLiteDatabase): Unit {
+ public override fun onOpen(db: SupportSQLiteDatabase) {
mDatabase = db
internalInitInvalidationTracker(db)
val _callbacks: List<RoomDatabase.Callback>? = mCallbacks
@@ -71,11 +70,11 @@
}
}
- public override fun onPreMigrate(db: SupportSQLiteDatabase): Unit {
+ public override fun onPreMigrate(db: SupportSQLiteDatabase) {
dropFtsSyncTriggers(db)
}
- public override fun onPostMigrate(db: SupportSQLiteDatabase): Unit {
+ public override fun onPostMigrate(db: SupportSQLiteDatabase) {
}
public override fun onValidateSchema(db: SupportSQLiteDatabase):
@@ -113,7 +112,7 @@
return InvalidationTracker(this, _shadowTablesMap, _viewTables, "MyEntity")
}
- public override fun clearAllTables(): Unit {
+ public override fun clearAllTables() {
super.assertNotMainThread()
val _db: SupportSQLiteDatabase = super.openHelper.writableDatabase
try {
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/database_simple.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/database_simple.kt
index 6209c74..50eb478 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/database_simple.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/database_simple.kt
@@ -18,7 +18,6 @@
import kotlin.Lazy
import kotlin.String
import kotlin.Suppress
-import kotlin.Unit
import kotlin.collections.List
import kotlin.collections.Map
import kotlin.collections.MutableList
@@ -34,13 +33,13 @@
protected override fun createOpenHelper(config: DatabaseConfiguration): SupportSQLiteOpenHelper {
val _openCallback: SupportSQLiteOpenHelper.Callback = RoomOpenHelper(config, object :
RoomOpenHelper.Delegate(1) {
- public override fun createAllTables(db: SupportSQLiteDatabase): Unit {
+ public override fun createAllTables(db: SupportSQLiteDatabase) {
db.execSQL("CREATE TABLE IF NOT EXISTS `MyEntity` (`pk` INTEGER NOT NULL, PRIMARY KEY(`pk`))")
db.execSQL("CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)")
db.execSQL("INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '195d7974660177325bd1a32d2c7b8b8c')")
}
- public override fun dropAllTables(db: SupportSQLiteDatabase): Unit {
+ public override fun dropAllTables(db: SupportSQLiteDatabase) {
db.execSQL("DROP TABLE IF EXISTS `MyEntity`")
val _callbacks: List<RoomDatabase.Callback>? = mCallbacks
if (_callbacks != null) {
@@ -50,7 +49,7 @@
}
}
- public override fun onCreate(db: SupportSQLiteDatabase): Unit {
+ public override fun onCreate(db: SupportSQLiteDatabase) {
val _callbacks: List<RoomDatabase.Callback>? = mCallbacks
if (_callbacks != null) {
for (_callback: RoomDatabase.Callback in _callbacks) {
@@ -59,7 +58,7 @@
}
}
- public override fun onOpen(db: SupportSQLiteDatabase): Unit {
+ public override fun onOpen(db: SupportSQLiteDatabase) {
mDatabase = db
internalInitInvalidationTracker(db)
val _callbacks: List<RoomDatabase.Callback>? = mCallbacks
@@ -70,11 +69,11 @@
}
}
- public override fun onPreMigrate(db: SupportSQLiteDatabase): Unit {
+ public override fun onPreMigrate(db: SupportSQLiteDatabase) {
dropFtsSyncTriggers(db)
}
- public override fun onPostMigrate(db: SupportSQLiteDatabase): Unit {
+ public override fun onPostMigrate(db: SupportSQLiteDatabase) {
}
public override fun onValidateSchema(db: SupportSQLiteDatabase):
@@ -112,7 +111,7 @@
return InvalidationTracker(this, _shadowTablesMap, _viewTables, "MyEntity")
}
- public override fun clearAllTables(): Unit {
+ public override fun clearAllTables() {
super.assertNotMainThread()
val _db: SupportSQLiteDatabase = super.openHelper.writableDatabase
try {
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/database_withFtsAndView.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/database_withFtsAndView.kt
index 8ab3bb8..2ec181a 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/database_withFtsAndView.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/database_withFtsAndView.kt
@@ -6,7 +6,6 @@
import androidx.room.migration.Migration
import androidx.room.util.FtsTableInfo
import androidx.room.util.TableInfo
-import androidx.room.util.TableInfo.Companion.read
import androidx.room.util.ViewInfo
import androidx.room.util.dropFtsSyncTriggers
import androidx.sqlite.db.SupportSQLiteDatabase
@@ -21,11 +20,13 @@
import kotlin.Lazy
import kotlin.String
import kotlin.Suppress
-import kotlin.Unit
import kotlin.collections.List
import kotlin.collections.Map
import kotlin.collections.MutableList
import kotlin.collections.Set
+import androidx.room.util.FtsTableInfo.Companion.read as ftsTableInfoRead
+import androidx.room.util.TableInfo.Companion.read as tableInfoRead
+import androidx.room.util.ViewInfo.Companion.read as viewInfoRead
@Generated(value = ["androidx.room.RoomProcessor"])
@Suppress(names = ["UNCHECKED_CAST", "DEPRECATION", "REDUNDANT_PROJECTION"])
@@ -34,10 +35,11 @@
MyDao_Impl(this)
}
+
protected override fun createOpenHelper(config: DatabaseConfiguration): SupportSQLiteOpenHelper {
val _openCallback: SupportSQLiteOpenHelper.Callback = RoomOpenHelper(config, object :
RoomOpenHelper.Delegate(1) {
- public override fun createAllTables(db: SupportSQLiteDatabase): Unit {
+ public override fun createAllTables(db: SupportSQLiteDatabase) {
db.execSQL("CREATE TABLE IF NOT EXISTS `MyParentEntity` (`parentKey` INTEGER NOT NULL, PRIMARY KEY(`parentKey`))")
db.execSQL("CREATE TABLE IF NOT EXISTS `MyEntity` (`pk` INTEGER NOT NULL, `indexedCol` TEXT NOT NULL, PRIMARY KEY(`pk`), FOREIGN KEY(`indexedCol`) REFERENCES `MyParentEntity`(`parentKey`) ON UPDATE NO ACTION ON DELETE CASCADE )")
db.execSQL("CREATE INDEX IF NOT EXISTS `index_MyEntity_indexedCol` ON `MyEntity` (`indexedCol`)")
@@ -47,7 +49,7 @@
db.execSQL("INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '89ba16fb8b062b50acf0eb06c853efcb')")
}
- public override fun dropAllTables(db: SupportSQLiteDatabase): Unit {
+ public override fun dropAllTables(db: SupportSQLiteDatabase) {
db.execSQL("DROP TABLE IF EXISTS `MyParentEntity`")
db.execSQL("DROP TABLE IF EXISTS `MyEntity`")
db.execSQL("DROP TABLE IF EXISTS `MyFtsEntity`")
@@ -60,7 +62,7 @@
}
}
- public override fun onCreate(db: SupportSQLiteDatabase): Unit {
+ public override fun onCreate(db: SupportSQLiteDatabase) {
val _callbacks: List<RoomDatabase.Callback>? = mCallbacks
if (_callbacks != null) {
for (_callback: RoomDatabase.Callback in _callbacks) {
@@ -69,7 +71,7 @@
}
}
- public override fun onOpen(db: SupportSQLiteDatabase): Unit {
+ public override fun onOpen(db: SupportSQLiteDatabase) {
mDatabase = db
db.execSQL("PRAGMA foreign_keys = ON")
internalInitInvalidationTracker(db)
@@ -81,11 +83,11 @@
}
}
- public override fun onPreMigrate(db: SupportSQLiteDatabase): Unit {
+ public override fun onPreMigrate(db: SupportSQLiteDatabase) {
dropFtsSyncTriggers(db)
}
- public override fun onPostMigrate(db: SupportSQLiteDatabase): Unit {
+ public override fun onPostMigrate(db: SupportSQLiteDatabase) {
}
public override fun onValidateSchema(db: SupportSQLiteDatabase):
@@ -99,15 +101,15 @@
val _indicesMyParentEntity: HashSet<TableInfo.Index> = HashSet<TableInfo.Index>(0)
val _infoMyParentEntity: TableInfo = TableInfo("MyParentEntity", _columnsMyParentEntity,
_foreignKeysMyParentEntity, _indicesMyParentEntity)
- val _existingMyParentEntity: TableInfo = read(db, "MyParentEntity")
+ val _existingMyParentEntity: TableInfo = tableInfoRead(db, "MyParentEntity")
if (!_infoMyParentEntity.equals(_existingMyParentEntity)) {
return RoomOpenHelper.ValidationResult(false, """
- |MyParentEntity(MyParentEntity).
- | Expected:
- |""".trimMargin() + _infoMyParentEntity + """
- |
- | Found:
- |""".trimMargin() + _existingMyParentEntity)
+ |MyParentEntity(MyParentEntity).
+ | Expected:
+ |""".trimMargin() + _infoMyParentEntity + """
+ |
+ | Found:
+ |""".trimMargin() + _existingMyParentEntity)
}
val _columnsMyEntity: HashMap<String, TableInfo.Column> =
HashMap<String, TableInfo.Column>(2)
@@ -123,41 +125,41 @@
listOf("indexedCol"), listOf("ASC")))
val _infoMyEntity: TableInfo = TableInfo("MyEntity", _columnsMyEntity, _foreignKeysMyEntity,
_indicesMyEntity)
- val _existingMyEntity: TableInfo = read(db, "MyEntity")
+ val _existingMyEntity: TableInfo = tableInfoRead(db, "MyEntity")
if (!_infoMyEntity.equals(_existingMyEntity)) {
return RoomOpenHelper.ValidationResult(false, """
- |MyEntity(MyEntity).
- | Expected:
- |""".trimMargin() + _infoMyEntity + """
- |
- | Found:
- |""".trimMargin() + _existingMyEntity)
+ |MyEntity(MyEntity).
+ | Expected:
+ |""".trimMargin() + _infoMyEntity + """
+ |
+ | Found:
+ |""".trimMargin() + _existingMyEntity)
}
val _columnsMyFtsEntity: HashSet<String> = HashSet<String>(2)
_columnsMyFtsEntity.add("text")
val _infoMyFtsEntity: FtsTableInfo = FtsTableInfo("MyFtsEntity", _columnsMyFtsEntity,
"CREATE VIRTUAL TABLE IF NOT EXISTS `MyFtsEntity` USING FTS4(`text` TEXT NOT NULL)")
- val _existingMyFtsEntity: FtsTableInfo = FtsTableInfo.Companion.read(db, "MyFtsEntity")
+ val _existingMyFtsEntity: FtsTableInfo = ftsTableInfoRead(db, "MyFtsEntity")
if (!_infoMyFtsEntity.equals(_existingMyFtsEntity)) {
return RoomOpenHelper.ValidationResult(false, """
- |MyFtsEntity(MyFtsEntity).
- | Expected:
- |""".trimMargin() + _infoMyFtsEntity + """
- |
- | Found:
- |""".trimMargin() + _existingMyFtsEntity)
+ |MyFtsEntity(MyFtsEntity).
+ | Expected:
+ |""".trimMargin() + _infoMyFtsEntity + """
+ |
+ | Found:
+ |""".trimMargin() + _existingMyFtsEntity)
}
val _infoMyView: ViewInfo = ViewInfo("MyView",
"CREATE VIEW `MyView` AS SELECT text FROM MyFtsEntity")
- val _existingMyView: ViewInfo = ViewInfo.Companion.read(db, "MyView")
+ val _existingMyView: ViewInfo = viewInfoRead(db, "MyView")
if (!_infoMyView.equals(_existingMyView)) {
return RoomOpenHelper.ValidationResult(false, """
- |MyView(MyView).
- | Expected:
- |""".trimMargin() + _infoMyView + """
- |
- | Found:
- |""".trimMargin() + _existingMyView)
+ |MyView(MyView).
+ | Expected:
+ |""".trimMargin() + _infoMyView + """
+ |
+ | Found:
+ |""".trimMargin() + _existingMyView)
}
return RoomOpenHelper.ValidationResult(true, null)
}
@@ -179,7 +181,7 @@
"MyParentEntity","MyEntity","MyFtsEntity")
}
- public override fun clearAllTables(): Unit {
+ public override fun clearAllTables() {
super.assertNotMainThread()
val _db: SupportSQLiteDatabase = super.openHelper.writableDatabase
val _supportsDeferForeignKeys: Boolean = android.os.Build.VERSION.SDK_INT >=
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/deleteOrUpdateMethodAdapter.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/deleteOrUpdateMethodAdapter.kt
index 1166ce2..233b23a 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/deleteOrUpdateMethodAdapter.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/deleteOrUpdateMethodAdapter.kt
@@ -6,7 +6,6 @@
import kotlin.Int
import kotlin.String
import kotlin.Suppress
-import kotlin.Unit
import kotlin.collections.List
import kotlin.jvm.JvmStatic
@@ -25,7 +24,7 @@
this.__deletionAdapterOfMyEntity = object : EntityDeletionOrUpdateAdapter<MyEntity>(__db) {
public override fun createQuery(): String = "DELETE FROM `MyEntity` WHERE `pk` = ?"
- public override fun bind(statement: SupportSQLiteStatement, entity: MyEntity): Unit {
+ public override fun bind(statement: SupportSQLiteStatement, entity: MyEntity) {
statement.bindLong(1, entity.pk)
}
}
@@ -33,7 +32,7 @@
public override fun createQuery(): String =
"UPDATE OR ABORT `MyEntity` SET `pk` = ?,`data` = ? WHERE `pk` = ?"
- public override fun bind(statement: SupportSQLiteStatement, entity: MyEntity): Unit {
+ public override fun bind(statement: SupportSQLiteStatement, entity: MyEntity) {
statement.bindLong(1, entity.pk)
statement.bindString(2, entity.data)
statement.bindLong(3, entity.pk)
@@ -41,7 +40,7 @@
}
}
- public override fun deleteEntity(item: MyEntity): Unit {
+ public override fun deleteEntity(item: MyEntity) {
__db.assertNotSuspendingTransaction()
__db.beginTransaction()
try {
@@ -65,7 +64,7 @@
}
}
- public override fun updateEntity(item: MyEntity): Unit {
+ public override fun updateEntity(item: MyEntity) {
__db.assertNotSuspendingTransaction()
__db.beginTransaction()
try {
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/entityRowAdapter.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/entityRowAdapter.kt
index e823e0a..d00f043 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/entityRowAdapter.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/entityRowAdapter.kt
@@ -13,7 +13,6 @@
import kotlin.Long
import kotlin.String
import kotlin.Suppress
-import kotlin.Unit
import kotlin.collections.List
import kotlin.jvm.JvmStatic
@@ -31,7 +30,7 @@
public override fun createQuery(): String =
"INSERT OR ABORT INTO `MyEntity` (`valuePrimitive`,`valueBoolean`,`valueString`,`valueNullableString`,`variablePrimitive`,`variableNullableBoolean`,`variableString`,`variableNullableString`) VALUES (?,?,?,?,?,?,?,?)"
- public override fun bind(statement: SupportSQLiteStatement, entity: MyEntity): Unit {
+ public override fun bind(statement: SupportSQLiteStatement, entity: MyEntity) {
statement.bindLong(1, entity.valuePrimitive)
val _tmp: Int = if (entity.valueBoolean) 1 else 0
statement.bindLong(2, _tmp.toLong())
@@ -61,7 +60,7 @@
}
}
- public override fun addEntity(item: MyEntity): Unit {
+ public override fun addEntity(item: MyEntity) {
__db.assertNotSuspendingTransaction()
__db.beginTransaction()
try {
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/guavaCallable.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/guavaCallable.kt
index 7a118e5..3d51fc4 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/guavaCallable.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/guavaCallable.kt
@@ -22,7 +22,6 @@
import kotlin.Long
import kotlin.String
import kotlin.Suppress
-import kotlin.Unit
import kotlin.collections.List
import kotlin.jvm.JvmStatic
@@ -46,7 +45,7 @@
public override fun createQuery(): String =
"INSERT OR ABORT INTO `MyEntity` (`pk`,`other`) VALUES (?,?)"
- public override fun bind(statement: SupportSQLiteStatement, entity: MyEntity): Unit {
+ public override fun bind(statement: SupportSQLiteStatement, entity: MyEntity) {
statement.bindLong(1, entity.pk.toLong())
statement.bindString(2, entity.other)
}
@@ -54,7 +53,7 @@
this.__deletionAdapterOfMyEntity = object : EntityDeletionOrUpdateAdapter<MyEntity>(__db) {
public override fun createQuery(): String = "DELETE FROM `MyEntity` WHERE `pk` = ?"
- public override fun bind(statement: SupportSQLiteStatement, entity: MyEntity): Unit {
+ public override fun bind(statement: SupportSQLiteStatement, entity: MyEntity) {
statement.bindLong(1, entity.pk.toLong())
}
}
@@ -62,7 +61,7 @@
public override fun createQuery(): String =
"UPDATE OR ABORT `MyEntity` SET `pk` = ?,`other` = ? WHERE `pk` = ?"
- public override fun bind(statement: SupportSQLiteStatement, entity: MyEntity): Unit {
+ public override fun bind(statement: SupportSQLiteStatement, entity: MyEntity) {
statement.bindLong(1, entity.pk.toLong())
statement.bindString(2, entity.other)
statement.bindLong(3, entity.pk.toLong())
@@ -73,7 +72,7 @@
public override fun createQuery(): String =
"INSERT INTO `MyEntity` (`pk`,`other`) VALUES (?,?)"
- public override fun bind(statement: SupportSQLiteStatement, entity: MyEntity): Unit {
+ public override fun bind(statement: SupportSQLiteStatement, entity: MyEntity) {
statement.bindLong(1, entity.pk.toLong())
statement.bindString(2, entity.other)
}
@@ -81,7 +80,7 @@
public override fun createQuery(): String =
"UPDATE `MyEntity` SET `pk` = ?,`other` = ? WHERE `pk` = ?"
- public override fun bind(statement: SupportSQLiteStatement, entity: MyEntity): Unit {
+ public override fun bind(statement: SupportSQLiteStatement, entity: MyEntity) {
statement.bindLong(1, entity.pk.toLong())
statement.bindString(2, entity.other)
statement.bindLong(3, entity.pk.toLong())
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/insertOrUpsertMethodAdapter.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/insertOrUpsertMethodAdapter.kt
index 9f9874c..246af07 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/insertOrUpsertMethodAdapter.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/insertOrUpsertMethodAdapter.kt
@@ -9,7 +9,6 @@
import kotlin.Long
import kotlin.String
import kotlin.Suppress
-import kotlin.Unit
import kotlin.collections.List
import kotlin.jvm.JvmStatic
@@ -29,7 +28,7 @@
public override fun createQuery(): String =
"INSERT OR ABORT INTO `MyEntity` (`pk`,`data`) VALUES (?,?)"
- public override fun bind(statement: SupportSQLiteStatement, entity: MyEntity): Unit {
+ public override fun bind(statement: SupportSQLiteStatement, entity: MyEntity) {
statement.bindLong(1, entity.pk)
statement.bindString(2, entity.data)
}
@@ -39,7 +38,7 @@
public override fun createQuery(): String =
"INSERT INTO `MyEntity` (`pk`,`data`) VALUES (?,?)"
- public override fun bind(statement: SupportSQLiteStatement, entity: MyEntity): Unit {
+ public override fun bind(statement: SupportSQLiteStatement, entity: MyEntity) {
statement.bindLong(1, entity.pk)
statement.bindString(2, entity.data)
}
@@ -47,7 +46,7 @@
public override fun createQuery(): String =
"UPDATE `MyEntity` SET `pk` = ?,`data` = ? WHERE `pk` = ?"
- public override fun bind(statement: SupportSQLiteStatement, entity: MyEntity): Unit {
+ public override fun bind(statement: SupportSQLiteStatement, entity: MyEntity) {
statement.bindLong(1, entity.pk)
statement.bindString(2, entity.data)
statement.bindLong(3, entity.pk)
@@ -55,7 +54,7 @@
})
}
- public override fun insertEntity(item: MyEntity): Unit {
+ public override fun insertEntity(item: MyEntity) {
__db.assertNotSuspendingTransaction()
__db.beginTransaction()
try {
@@ -90,7 +89,7 @@
}
}
- public override fun upsertEntity(item: MyEntity): Unit {
+ public override fun upsertEntity(item: MyEntity) {
__db.assertNotSuspendingTransaction()
__db.beginTransaction()
try {
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/liveDataCallable.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/liveDataCallable.kt
index aad53d7..ed6eb98 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/liveDataCallable.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/liveDataCallable.kt
@@ -14,7 +14,6 @@
import kotlin.Int
import kotlin.String
import kotlin.Suppress
-import kotlin.Unit
import kotlin.collections.List
import kotlin.jvm.JvmStatic
@@ -69,7 +68,7 @@
}
}
- protected fun finalize(): Unit {
+ protected fun finalize() {
_statement.release()
}
})
@@ -116,7 +115,7 @@
}
}
- protected fun finalize(): Unit {
+ protected fun finalize() {
_statement.release()
}
})
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/multiTypedPagingSourceResultBinder.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/multiTypedPagingSourceResultBinder.kt
index 921b262..70659ef 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/multiTypedPagingSourceResultBinder.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/multiTypedPagingSourceResultBinder.kt
@@ -1,13 +1,11 @@
import android.database.Cursor
import androidx.paging.ListenableFuturePagingSource
import androidx.paging.PagingSource
-import androidx.paging.rxjava2.RxPagingSource
import androidx.room.RoomDatabase
import androidx.room.RoomSQLiteQuery
import androidx.room.RoomSQLiteQuery.Companion.acquire
import androidx.room.paging.LimitOffsetPagingSource
import androidx.room.paging.guava.LimitOffsetListenableFuturePagingSource
-import androidx.room.paging.rxjava2.LimitOffsetRxPagingSource
import java.lang.Class
import java.util.ArrayList
import javax.`annotation`.processing.Generated
@@ -17,6 +15,10 @@
import kotlin.collections.List
import kotlin.collections.MutableList
import kotlin.jvm.JvmStatic
+import androidx.paging.rxjava2.RxPagingSource as Rxjava2RxPagingSource
+import androidx.paging.rxjava3.RxPagingSource as Rxjava3RxPagingSource
+import androidx.room.paging.rxjava2.LimitOffsetRxPagingSource as Rxjava2LimitOffsetRxPagingSource
+import androidx.room.paging.rxjava3.LimitOffsetRxPagingSource as Rxjava3LimitOffsetRxPagingSource
@Generated(value = ["androidx.room.RoomProcessor"])
@Suppress(names = ["UNCHECKED_CAST", "DEPRECATION", "REDUNDANT_PROJECTION"])
@@ -47,10 +49,10 @@
}
}
- public override fun getAllIdsRx2(): RxPagingSource<Int, MyEntity> {
+ public override fun getAllIdsRx2(): Rxjava2RxPagingSource<Int, MyEntity> {
val _sql: String = "SELECT pk FROM MyEntity"
val _statement: RoomSQLiteQuery = acquire(_sql, 0)
- return object : LimitOffsetRxPagingSource<MyEntity>(_statement, __db, "MyEntity") {
+ return object : Rxjava2LimitOffsetRxPagingSource<MyEntity>(_statement, __db, "MyEntity") {
protected override fun convertRows(cursor: Cursor): List<MyEntity> {
val _cursorIndexOfPk: Int = 0
val _result: MutableList<MyEntity> = ArrayList<MyEntity>(cursor.getCount())
@@ -66,11 +68,10 @@
}
}
- public override fun getAllIdsRx3(): androidx.paging.rxjava3.RxPagingSource<Int, MyEntity> {
+ public override fun getAllIdsRx3(): Rxjava3RxPagingSource<Int, MyEntity> {
val _sql: String = "SELECT pk FROM MyEntity"
val _statement: RoomSQLiteQuery = acquire(_sql, 0)
- return object : androidx.room.paging.rxjava3.LimitOffsetRxPagingSource<MyEntity>(_statement,
- __db, "MyEntity") {
+ return object : Rxjava3LimitOffsetRxPagingSource<MyEntity>(_statement, __db, "MyEntity") {
protected override fun convertRows(cursor: Cursor): List<MyEntity> {
val _cursorIndexOfPk: Int = 0
val _result: MutableList<MyEntity> = ArrayList<MyEntity>(cursor.getCount())
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_boolean.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_boolean.kt
index e4aa4f3..0c57f0e 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_boolean.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_boolean.kt
@@ -12,7 +12,6 @@
import kotlin.Int
import kotlin.String
import kotlin.Suppress
-import kotlin.Unit
import kotlin.collections.List
import kotlin.jvm.JvmStatic
@@ -30,7 +29,7 @@
public override fun createQuery(): String =
"INSERT OR ABORT INTO `MyEntity` (`pk`,`boolean`,`nullableBoolean`) VALUES (?,?,?)"
- public override fun bind(statement: SupportSQLiteStatement, entity: MyEntity): Unit {
+ public override fun bind(statement: SupportSQLiteStatement, entity: MyEntity) {
statement.bindLong(1, entity.pk.toLong())
val _tmp: Int = if (entity.boolean) 1 else 0
statement.bindLong(2, _tmp.toLong())
@@ -45,7 +44,7 @@
}
}
- public override fun addEntity(item: MyEntity): Unit {
+ public override fun addEntity(item: MyEntity) {
__db.assertNotSuspendingTransaction()
__db.beginTransaction()
try {
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_byteArray.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_byteArray.kt
index 284798e..f369507 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_byteArray.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_byteArray.kt
@@ -12,7 +12,6 @@
import kotlin.Int
import kotlin.String
import kotlin.Suppress
-import kotlin.Unit
import kotlin.collections.List
import kotlin.jvm.JvmStatic
@@ -30,7 +29,7 @@
public override fun createQuery(): String =
"INSERT OR ABORT INTO `MyEntity` (`pk`,`byteArray`,`nullableByteArray`) VALUES (?,?,?)"
- public override fun bind(statement: SupportSQLiteStatement, entity: MyEntity): Unit {
+ public override fun bind(statement: SupportSQLiteStatement, entity: MyEntity) {
statement.bindLong(1, entity.pk.toLong())
statement.bindBlob(2, entity.byteArray)
val _tmpNullableByteArray: ByteArray? = entity.nullableByteArray
@@ -43,7 +42,7 @@
}
}
- public override fun addEntity(item: MyEntity): Unit {
+ public override fun addEntity(item: MyEntity) {
__db.assertNotSuspendingTransaction()
__db.beginTransaction()
try {
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_customTypeConverter.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_customTypeConverter.kt
index 709f270..6e7794e 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_customTypeConverter.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_customTypeConverter.kt
@@ -11,7 +11,6 @@
import kotlin.Int
import kotlin.String
import kotlin.Suppress
-import kotlin.Unit
import kotlin.collections.List
import kotlin.jvm.JvmStatic
@@ -31,7 +30,7 @@
public override fun createQuery(): String =
"INSERT OR ABORT INTO `MyEntity` (`pk`,`foo`) VALUES (?,?)"
- public override fun bind(statement: SupportSQLiteStatement, entity: MyEntity): Unit {
+ public override fun bind(statement: SupportSQLiteStatement, entity: MyEntity) {
statement.bindLong(1, entity.pk.toLong())
val _tmp: String = __fooConverter.toString(entity.foo)
statement.bindString(2, _tmp)
@@ -39,7 +38,7 @@
}
}
- public override fun addEntity(item: MyEntity): Unit {
+ public override fun addEntity(item: MyEntity) {
__db.assertNotSuspendingTransaction()
__db.beginTransaction()
try {
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_customTypeConverter_composite.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_customTypeConverter_composite.kt
index 71d915e..96566e6 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_customTypeConverter_composite.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_customTypeConverter_composite.kt
@@ -11,7 +11,6 @@
import kotlin.Int
import kotlin.String
import kotlin.Suppress
-import kotlin.Unit
import kotlin.collections.List
import kotlin.jvm.JvmStatic
@@ -29,7 +28,7 @@
public override fun createQuery(): String =
"INSERT OR ABORT INTO `MyEntity` (`pk`,`bar`) VALUES (?,?)"
- public override fun bind(statement: SupportSQLiteStatement, entity: MyEntity): Unit {
+ public override fun bind(statement: SupportSQLiteStatement, entity: MyEntity) {
statement.bindLong(1, entity.pk.toLong())
val _tmp: Foo = FooBarConverter.toFoo(entity.bar)
val _tmp_1: String = FooBarConverter.toString(_tmp)
@@ -38,7 +37,7 @@
}
}
- public override fun addEntity(item: MyEntity): Unit {
+ public override fun addEntity(item: MyEntity) {
__db.assertNotSuspendingTransaction()
__db.beginTransaction()
try {
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_customTypeConverter_internalVisibility.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_customTypeConverter_internalVisibility.kt
index 9376fc3..0ba6f82 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_customTypeConverter_internalVisibility.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_customTypeConverter_internalVisibility.kt
@@ -11,7 +11,6 @@
import kotlin.Int
import kotlin.String
import kotlin.Suppress
-import kotlin.Unit
import kotlin.collections.List
import kotlin.jvm.JvmStatic
@@ -31,7 +30,7 @@
public override fun createQuery(): String =
"INSERT OR ABORT INTO `MyEntity` (`pk`,`foo`) VALUES (?,?)"
- public override fun bind(statement: SupportSQLiteStatement, entity: MyEntity): Unit {
+ public override fun bind(statement: SupportSQLiteStatement, entity: MyEntity) {
statement.bindLong(1, entity.pk.toLong())
val _tmp: String = __fooConverter.toString(entity.foo)
statement.bindString(2, _tmp)
@@ -39,7 +38,7 @@
}
}
- internal override fun addEntity(item: MyEntity): Unit {
+ internal override fun addEntity(item: MyEntity) {
__db.assertNotSuspendingTransaction()
__db.beginTransaction()
try {
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_customTypeConverter_nullAware.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_customTypeConverter_nullAware.kt
index a52bf88..0ea0420 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_customTypeConverter_nullAware.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_customTypeConverter_nullAware.kt
@@ -11,7 +11,6 @@
import kotlin.Int
import kotlin.String
import kotlin.Suppress
-import kotlin.Unit
import kotlin.collections.List
import kotlin.jvm.JvmStatic
@@ -29,7 +28,7 @@
public override fun createQuery(): String =
"INSERT OR ABORT INTO `MyEntity` (`pk`,`foo`,`bar`) VALUES (?,?,?)"
- public override fun bind(statement: SupportSQLiteStatement, entity: MyEntity): Unit {
+ public override fun bind(statement: SupportSQLiteStatement, entity: MyEntity) {
statement.bindLong(1, entity.pk.toLong())
val _tmp: String? = FooBarConverter.toString(entity.foo)
if (_tmp == null) {
@@ -48,7 +47,7 @@
}
}
- public override fun addEntity(item: MyEntity): Unit {
+ public override fun addEntity(item: MyEntity) {
__db.assertNotSuspendingTransaction()
__db.beginTransaction()
try {
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_customTypeConverter_provided.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_customTypeConverter_provided.kt
index a464f74..3895395 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_customTypeConverter_provided.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_customTypeConverter_provided.kt
@@ -12,7 +12,6 @@
import kotlin.Lazy
import kotlin.String
import kotlin.Suppress
-import kotlin.Unit
import kotlin.collections.List
import kotlin.jvm.JvmStatic
@@ -35,7 +34,7 @@
public override fun createQuery(): String =
"INSERT OR ABORT INTO `MyEntity` (`pk`,`foo`) VALUES (?,?)"
- public override fun bind(statement: SupportSQLiteStatement, entity: MyEntity): Unit {
+ public override fun bind(statement: SupportSQLiteStatement, entity: MyEntity) {
statement.bindLong(1, entity.pk.toLong())
val _tmp: String = __fooConverter().toString(entity.foo)
statement.bindString(2, _tmp)
@@ -43,7 +42,7 @@
}
}
- public override fun addEntity(item: MyEntity): Unit {
+ public override fun addEntity(item: MyEntity) {
__db.assertNotSuspendingTransaction()
__db.beginTransaction()
try {
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_customTypeConverter_upcast.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_customTypeConverter_upcast.kt
index 56fb164..e9eebbc 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_customTypeConverter_upcast.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_customTypeConverter_upcast.kt
@@ -11,7 +11,6 @@
import kotlin.Int
import kotlin.String
import kotlin.Suppress
-import kotlin.Unit
import kotlin.collections.List
import kotlin.jvm.JvmStatic
@@ -29,7 +28,7 @@
public override fun createQuery(): String =
"INSERT OR ABORT INTO `MyEntity` (`pk`,`foo`) VALUES (?,?)"
- public override fun bind(statement: SupportSQLiteStatement, entity: MyEntity): Unit {
+ public override fun bind(statement: SupportSQLiteStatement, entity: MyEntity) {
statement.bindLong(1, entity.pk.toLong())
val _tmp: String = FooConverter.nullableFooToString(entity.foo)
if (_tmp == null) {
@@ -41,7 +40,7 @@
}
}
- public override fun addEntity(item: MyEntity): Unit {
+ public override fun addEntity(item: MyEntity) {
__db.assertNotSuspendingTransaction()
__db.beginTransaction()
try {
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_embedded.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_embedded.kt
index 7986fc1..b9a01bc 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_embedded.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_embedded.kt
@@ -12,7 +12,6 @@
import kotlin.Long
import kotlin.String
import kotlin.Suppress
-import kotlin.Unit
import kotlin.collections.List
import kotlin.jvm.JvmStatic
@@ -30,7 +29,7 @@
public override fun createQuery(): String =
"INSERT OR ABORT INTO `MyEntity` (`pk`,`numberData`,`stringData`,`nullablenumberData`,`nullablestringData`) VALUES (?,?,?,?,?)"
- public override fun bind(statement: SupportSQLiteStatement, entity: MyEntity): Unit {
+ public override fun bind(statement: SupportSQLiteStatement, entity: MyEntity) {
statement.bindLong(1, entity.pk.toLong())
val _tmpFoo: Foo = entity.foo
statement.bindLong(2, _tmpFoo.numberData)
@@ -47,7 +46,7 @@
}
}
- public override fun addEntity(item: MyEntity): Unit {
+ public override fun addEntity(item: MyEntity) {
__db.assertNotSuspendingTransaction()
__db.beginTransaction()
try {
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_enum.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_enum.kt
index 25aa37e..a6f83a7 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_enum.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_enum.kt
@@ -12,7 +12,6 @@
import kotlin.Int
import kotlin.String
import kotlin.Suppress
-import kotlin.Unit
import kotlin.collections.List
import kotlin.jvm.JvmStatic
@@ -30,7 +29,7 @@
public override fun createQuery(): String =
"INSERT OR ABORT INTO `MyEntity` (`pk`,`enum`,`nullableEnum`) VALUES (?,?,?)"
- public override fun bind(statement: SupportSQLiteStatement, entity: MyEntity): Unit {
+ public override fun bind(statement: SupportSQLiteStatement, entity: MyEntity) {
statement.bindLong(1, entity.pk.toLong())
statement.bindString(2, __Fruit_enumToString(entity.enum))
val _tmpNullableEnum: Fruit? = entity.nullableEnum
@@ -43,7 +42,7 @@
}
}
- public override fun addEntity(item: MyEntity): Unit {
+ public override fun addEntity(item: MyEntity) {
__db.assertNotSuspendingTransaction()
__db.beginTransaction()
try {
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_internalVisibility.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_internalVisibility.kt
index a81a93e..db1f98c 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_internalVisibility.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_internalVisibility.kt
@@ -12,7 +12,6 @@
import kotlin.Long
import kotlin.String
import kotlin.Suppress
-import kotlin.Unit
import kotlin.collections.List
import kotlin.jvm.JvmStatic
@@ -30,7 +29,7 @@
public override fun createQuery(): String =
"INSERT OR ABORT INTO `MyEntity` (`pk`,`internalVal`,`internalVar`,`internalSetterVar`) VALUES (?,?,?,?)"
- public override fun bind(statement: SupportSQLiteStatement, entity: MyEntity): Unit {
+ public override fun bind(statement: SupportSQLiteStatement, entity: MyEntity) {
statement.bindLong(1, entity.pk.toLong())
statement.bindLong(2, entity.internalVal)
statement.bindLong(3, entity.internalVar)
@@ -39,7 +38,7 @@
}
}
- public override fun addEntity(item: MyEntity): Unit {
+ public override fun addEntity(item: MyEntity) {
__db.assertNotSuspendingTransaction()
__db.beginTransaction()
try {
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_otherModule.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_otherModule.kt
index 8b4945d..320a33b 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_otherModule.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_otherModule.kt
@@ -12,7 +12,6 @@
import kotlin.Long
import kotlin.String
import kotlin.Suppress
-import kotlin.Unit
import kotlin.collections.List
import kotlin.jvm.JvmStatic
@@ -30,7 +29,7 @@
public override fun createQuery(): String =
"INSERT OR ABORT INTO `MyEntity` (`pk`,`primitive`,`string`,`nullableString`,`fieldString`,`nullableFieldString`,`variablePrimitive`,`variableString`,`variableNullableString`,`variableFieldString`,`variableNullableFieldString`) VALUES (?,?,?,?,?,?,?,?,?,?,?)"
- public override fun bind(statement: SupportSQLiteStatement, entity: MyEntity): Unit {
+ public override fun bind(statement: SupportSQLiteStatement, entity: MyEntity) {
statement.bindLong(1, entity.pk.toLong())
statement.bindLong(2, entity.primitive)
statement.bindString(3, entity.string)
@@ -66,7 +65,7 @@
}
}
- public override fun addEntity(item: MyEntity): Unit {
+ public override fun addEntity(item: MyEntity) {
__db.assertNotSuspendingTransaction()
__db.beginTransaction()
try {
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_primitives.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_primitives.kt
index 28ed7ee..520ecbb 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_primitives.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_primitives.kt
@@ -17,7 +17,6 @@
import kotlin.Short
import kotlin.String
import kotlin.Suppress
-import kotlin.Unit
import kotlin.collections.List
import kotlin.jvm.JvmStatic
@@ -35,7 +34,7 @@
public override fun createQuery(): String =
"INSERT OR ABORT INTO `MyEntity` (`int`,`short`,`byte`,`long`,`char`,`float`,`double`) VALUES (?,?,?,?,?,?,?)"
- public override fun bind(statement: SupportSQLiteStatement, entity: MyEntity): Unit {
+ public override fun bind(statement: SupportSQLiteStatement, entity: MyEntity) {
statement.bindLong(1, entity.int.toLong())
statement.bindLong(2, entity.short.toLong())
statement.bindLong(3, entity.byte.toLong())
@@ -47,7 +46,7 @@
}
}
- public override fun addEntity(item: MyEntity): Unit {
+ public override fun addEntity(item: MyEntity) {
__db.assertNotSuspendingTransaction()
__db.beginTransaction()
try {
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_primitives_nullable.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_primitives_nullable.kt
index 3ec68d0..109df70 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_primitives_nullable.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_primitives_nullable.kt
@@ -17,7 +17,6 @@
import kotlin.Short
import kotlin.String
import kotlin.Suppress
-import kotlin.Unit
import kotlin.collections.List
import kotlin.jvm.JvmStatic
@@ -35,7 +34,7 @@
public override fun createQuery(): String =
"INSERT OR ABORT INTO `MyEntity` (`int`,`short`,`byte`,`long`,`char`,`float`,`double`) VALUES (?,?,?,?,?,?,?)"
- public override fun bind(statement: SupportSQLiteStatement, entity: MyEntity): Unit {
+ public override fun bind(statement: SupportSQLiteStatement, entity: MyEntity) {
val _tmpInt: Int? = entity.int
if (_tmpInt == null) {
statement.bindNull(1)
@@ -82,7 +81,7 @@
}
}
- public override fun addEntity(item: MyEntity): Unit {
+ public override fun addEntity(item: MyEntity) {
__db.assertNotSuspendingTransaction()
__db.beginTransaction()
try {
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_string.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_string.kt
index 0a4b23b..213539a 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_string.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_string.kt
@@ -11,7 +11,6 @@
import kotlin.Int
import kotlin.String
import kotlin.Suppress
-import kotlin.Unit
import kotlin.collections.List
import kotlin.jvm.JvmStatic
@@ -29,7 +28,7 @@
public override fun createQuery(): String =
"INSERT OR ABORT INTO `MyEntity` (`string`,`nullableString`) VALUES (?,?)"
- public override fun bind(statement: SupportSQLiteStatement, entity: MyEntity): Unit {
+ public override fun bind(statement: SupportSQLiteStatement, entity: MyEntity) {
statement.bindString(1, entity.string)
val _tmpNullableString: String? = entity.nullableString
if (_tmpNullableString == null) {
@@ -41,7 +40,7 @@
}
}
- public override fun addEntity(item: MyEntity): Unit {
+ public override fun addEntity(item: MyEntity) {
__db.assertNotSuspendingTransaction()
__db.beginTransaction()
try {
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_uuid.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_uuid.kt
index dd7f1df..a4770df 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_uuid.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_uuid.kt
@@ -14,7 +14,6 @@
import kotlin.Int
import kotlin.String
import kotlin.Suppress
-import kotlin.Unit
import kotlin.collections.List
import kotlin.jvm.JvmStatic
@@ -32,7 +31,7 @@
public override fun createQuery(): String =
"INSERT OR ABORT INTO `MyEntity` (`pk`,`uuid`,`nullableUuid`) VALUES (?,?,?)"
- public override fun bind(statement: SupportSQLiteStatement, entity: MyEntity): Unit {
+ public override fun bind(statement: SupportSQLiteStatement, entity: MyEntity) {
statement.bindLong(1, entity.pk.toLong())
statement.bindBlob(2, convertUUIDToByte(entity.uuid))
val _tmpNullableUuid: UUID? = entity.nullableUuid
@@ -45,7 +44,7 @@
}
}
- public override fun addEntity(item: MyEntity): Unit {
+ public override fun addEntity(item: MyEntity) {
__db.assertNotSuspendingTransaction()
__db.beginTransaction()
try {
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_valueClassConverter.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_valueClassConverter.kt
index 501c3ed..6ea9026 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_valueClassConverter.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_valueClassConverter.kt
@@ -15,7 +15,6 @@
import kotlin.Long
import kotlin.String
import kotlin.Suppress
-import kotlin.Unit
import kotlin.collections.List
import kotlin.jvm.JvmStatic
@@ -33,7 +32,7 @@
public override fun createQuery(): String =
"INSERT OR ABORT INTO `MyEntity` (`pk`,`uuidData`,`nullableUuidData`,`nullableLongData`,`doubleNullableLongData`,`genericData`) VALUES (?,?,?,?,?,?)"
- public override fun bind(statement: SupportSQLiteStatement, entity: MyEntity): Unit {
+ public override fun bind(statement: SupportSQLiteStatement, entity: MyEntity) {
val _data: Long = checkNotNull(entity.pk.data) {
"Cannot bind nullable value of inline class to a NOT NULL column." }
statement.bindLong(1, _data)
@@ -64,7 +63,7 @@
}
}
- public override fun addEntity(item: MyEntity): Unit {
+ public override fun addEntity(item: MyEntity) {
__db.assertNotSuspendingTransaction()
__db.beginTransaction()
try {
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_variableProperty.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_variableProperty.kt
index ea44a4f..6276d06 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_variableProperty.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_variableProperty.kt
@@ -11,7 +11,6 @@
import kotlin.Int
import kotlin.String
import kotlin.Suppress
-import kotlin.Unit
import kotlin.collections.List
import kotlin.jvm.JvmStatic
@@ -29,7 +28,7 @@
public override fun createQuery(): String =
"INSERT OR ABORT INTO `MyEntity` (`pk`,`variablePrimitive`,`variableString`,`variableNullableString`) VALUES (?,?,?,?)"
- public override fun bind(statement: SupportSQLiteStatement, entity: MyEntity): Unit {
+ public override fun bind(statement: SupportSQLiteStatement, entity: MyEntity) {
statement.bindLong(1, entity.pk.toLong())
statement.bindLong(2, entity.variablePrimitive)
statement.bindString(3, entity.variableString)
@@ -43,7 +42,7 @@
}
}
- public override fun addEntity(item: MyEntity): Unit {
+ public override fun addEntity(item: MyEntity) {
__db.assertNotSuspendingTransaction()
__db.beginTransaction()
try {
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_variableProperty_java.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_variableProperty_java.kt
index 9ffae96..127eb24 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_variableProperty_java.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_variableProperty_java.kt
@@ -12,7 +12,6 @@
import kotlin.Long
import kotlin.String
import kotlin.Suppress
-import kotlin.Unit
import kotlin.collections.List
import kotlin.jvm.JvmStatic
@@ -30,7 +29,7 @@
public override fun createQuery(): String =
"INSERT OR ABORT INTO `MyEntity` (`mValue`,`mNullableValue`) VALUES (?,?)"
- public override fun bind(statement: SupportSQLiteStatement, entity: MyEntity): Unit {
+ public override fun bind(statement: SupportSQLiteStatement, entity: MyEntity) {
statement.bindLong(1, entity.getValue())
val _tmpMNullableValue: String? = entity.getNullableValue()
if (_tmpMNullableValue == null) {
@@ -42,7 +41,7 @@
}
}
- public override fun addEntity(item: MyEntity): Unit {
+ public override fun addEntity(item: MyEntity) {
__db.assertNotSuspendingTransaction()
__db.beginTransaction()
try {
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/preparedQueryAdapter.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/preparedQueryAdapter.kt
index 8ce5067..687bc16 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/preparedQueryAdapter.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/preparedQueryAdapter.kt
@@ -7,7 +7,6 @@
import kotlin.Long
import kotlin.String
import kotlin.Suppress
-import kotlin.Unit
import kotlin.collections.List
import kotlin.jvm.JvmStatic
@@ -53,7 +52,7 @@
}
}
- public override fun insertEntity(id: Long): Unit {
+ public override fun insertEntity(id: Long) {
__db.assertNotSuspendingTransaction()
val _stmt: SupportSQLiteStatement = __preparedStmtOfInsertEntity.acquire()
var _argIndex: Int = 1
@@ -90,7 +89,7 @@
}
}
- public override fun updateEntity(text: String): Unit {
+ public override fun updateEntity(text: String) {
__db.assertNotSuspendingTransaction()
val _stmt: SupportSQLiteStatement = __preparedStmtOfUpdateEntity.acquire()
var _argIndex: Int = 1
@@ -129,7 +128,7 @@
}
}
- public override fun deleteEntity(): Unit {
+ public override fun deleteEntity() {
__db.assertNotSuspendingTransaction()
val _stmt: SupportSQLiteStatement = __preparedStmtOfDeleteEntity.acquire()
try {
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/relations.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/relations.kt
index e2a614c..cf8c543 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/relations.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/relations.kt
@@ -17,7 +17,6 @@
import kotlin.Long
import kotlin.String
import kotlin.Suppress
-import kotlin.Unit
import kotlin.collections.List
import kotlin.collections.Set
import kotlin.jvm.JvmStatic
@@ -150,7 +149,7 @@
}
}
- private fun __fetchRelationshipArtistAsArtist(_map: HashMap<Long, Artist?>): Unit {
+ private fun __fetchRelationshipArtistAsArtist(_map: HashMap<Long, Artist?>) {
val __mapKeySet: Set<Long> = _map.keys
if (__mapKeySet.isEmpty()) {
return
@@ -197,7 +196,7 @@
}
}
- private fun __fetchRelationshipSongAsSong(_map: HashMap<Long, ArrayList<Song>>): Unit {
+ private fun __fetchRelationshipSongAsSong(_map: HashMap<Long, ArrayList<Song>>) {
val __mapKeySet: Set<Long> = _map.keys
if (__mapKeySet.isEmpty()) {
return
@@ -248,7 +247,7 @@
}
}
- private fun __fetchRelationshipSongAsSong_1(_map: HashMap<Long, ArrayList<Song>>): Unit {
+ private fun __fetchRelationshipSongAsSong_1(_map: HashMap<Long, ArrayList<Song>>) {
val __mapKeySet: Set<Long> = _map.keys
if (__mapKeySet.isEmpty()) {
return
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/relations_arrayMap.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/relations_arrayMap.kt
index e4c901e..7d2189f 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/relations_arrayMap.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/relations_arrayMap.kt
@@ -17,7 +17,6 @@
import kotlin.Long
import kotlin.String
import kotlin.Suppress
-import kotlin.Unit
import kotlin.collections.List
import kotlin.collections.Set
import kotlin.jvm.JvmStatic
@@ -150,7 +149,7 @@
}
}
- private fun __fetchRelationshipArtistAsArtist(_map: ArrayMap<Long, Artist?>): Unit {
+ private fun __fetchRelationshipArtistAsArtist(_map: ArrayMap<Long, Artist?>) {
val __mapKeySet: Set<Long> = _map.keys
if (__mapKeySet.isEmpty()) {
return
@@ -197,7 +196,7 @@
}
}
- private fun __fetchRelationshipSongAsSong(_map: ArrayMap<Long, ArrayList<Song>>): Unit {
+ private fun __fetchRelationshipSongAsSong(_map: ArrayMap<Long, ArrayList<Song>>) {
val __mapKeySet: Set<Long> = _map.keys
if (__mapKeySet.isEmpty()) {
return
@@ -248,7 +247,7 @@
}
}
- private fun __fetchRelationshipSongAsSong_1(_map: ArrayMap<Long, ArrayList<Song>>): Unit {
+ private fun __fetchRelationshipSongAsSong_1(_map: ArrayMap<Long, ArrayList<Song>>) {
val __mapKeySet: Set<Long> = _map.keys
if (__mapKeySet.isEmpty()) {
return
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/relations_byteBufferKey.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/relations_byteBufferKey.kt
index 576dfdc..c8e642f 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/relations_byteBufferKey.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/relations_byteBufferKey.kt
@@ -18,7 +18,6 @@
import kotlin.Long
import kotlin.String
import kotlin.Suppress
-import kotlin.Unit
import kotlin.collections.List
import kotlin.collections.Set
import kotlin.jvm.JvmStatic
@@ -75,7 +74,7 @@
}
}
- private fun __fetchRelationshipArtistAsArtist(_map: HashMap<ByteBuffer, Artist?>): Unit {
+ private fun __fetchRelationshipArtistAsArtist(_map: HashMap<ByteBuffer, Artist?>) {
val __mapKeySet: Set<ByteBuffer> = _map.keys
if (__mapKeySet.isEmpty()) {
return
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/relations_longSparseArray.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/relations_longSparseArray.kt
index 3100619..0a73c03 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/relations_longSparseArray.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/relations_longSparseArray.kt
@@ -17,7 +17,6 @@
import kotlin.Long
import kotlin.String
import kotlin.Suppress
-import kotlin.Unit
import kotlin.collections.List
import kotlin.jvm.JvmStatic
@@ -149,7 +148,7 @@
}
}
- private fun __fetchRelationshipArtistAsArtist(_map: LongSparseArray<Artist?>): Unit {
+ private fun __fetchRelationshipArtistAsArtist(_map: LongSparseArray<Artist?>) {
if (_map.isEmpty()) {
return
}
@@ -196,7 +195,7 @@
}
}
- private fun __fetchRelationshipSongAsSong(_map: LongSparseArray<ArrayList<Song>>): Unit {
+ private fun __fetchRelationshipSongAsSong(_map: LongSparseArray<ArrayList<Song>>) {
if (_map.isEmpty()) {
return
}
@@ -247,7 +246,7 @@
}
}
- private fun __fetchRelationshipSongAsSong_1(_map: LongSparseArray<ArrayList<Song>>): Unit {
+ private fun __fetchRelationshipSongAsSong_1(_map: LongSparseArray<ArrayList<Song>>) {
if (_map.isEmpty()) {
return
}
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/relations_nullable.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/relations_nullable.kt
index 5ab786c..4618226 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/relations_nullable.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/relations_nullable.kt
@@ -17,7 +17,6 @@
import kotlin.Long
import kotlin.String
import kotlin.Suppress
-import kotlin.Unit
import kotlin.collections.List
import kotlin.collections.Set
import kotlin.jvm.JvmStatic
@@ -165,7 +164,7 @@
}
}
- private fun __fetchRelationshipArtistAsArtist(_map: HashMap<Long, Artist?>): Unit {
+ private fun __fetchRelationshipArtistAsArtist(_map: HashMap<Long, Artist?>) {
val __mapKeySet: Set<Long> = _map.keys
if (__mapKeySet.isEmpty()) {
return
@@ -212,7 +211,7 @@
}
}
- private fun __fetchRelationshipSongAsSong(_map: HashMap<Long, ArrayList<Song>>): Unit {
+ private fun __fetchRelationshipSongAsSong(_map: HashMap<Long, ArrayList<Song>>) {
val __mapKeySet: Set<Long> = _map.keys
if (__mapKeySet.isEmpty()) {
return
@@ -273,7 +272,7 @@
}
}
- private fun __fetchRelationshipSongAsSong_1(_map: HashMap<Long, ArrayList<Song>>): Unit {
+ private fun __fetchRelationshipSongAsSong_1(_map: HashMap<Long, ArrayList<Song>>) {
val __mapKeySet: Set<Long> = _map.keys
if (__mapKeySet.isEmpty()) {
return
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/shortcutMethods_rx2.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/shortcutMethods_rx2.kt
index 57294c6..dd1a9ca 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/shortcutMethods_rx2.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/shortcutMethods_rx2.kt
@@ -13,7 +13,6 @@
import kotlin.Long
import kotlin.String
import kotlin.Suppress
-import kotlin.Unit
import kotlin.collections.List
import kotlin.jvm.JvmStatic
@@ -37,7 +36,7 @@
public override fun createQuery(): String =
"INSERT OR ABORT INTO `MyEntity` (`pk`,`other`) VALUES (?,?)"
- public override fun bind(statement: SupportSQLiteStatement, entity: MyEntity): Unit {
+ public override fun bind(statement: SupportSQLiteStatement, entity: MyEntity) {
statement.bindLong(1, entity.pk.toLong())
statement.bindString(2, entity.other)
}
@@ -45,7 +44,7 @@
this.__deletionAdapterOfMyEntity = object : EntityDeletionOrUpdateAdapter<MyEntity>(__db) {
public override fun createQuery(): String = "DELETE FROM `MyEntity` WHERE `pk` = ?"
- public override fun bind(statement: SupportSQLiteStatement, entity: MyEntity): Unit {
+ public override fun bind(statement: SupportSQLiteStatement, entity: MyEntity) {
statement.bindLong(1, entity.pk.toLong())
}
}
@@ -53,7 +52,7 @@
public override fun createQuery(): String =
"UPDATE OR ABORT `MyEntity` SET `pk` = ?,`other` = ? WHERE `pk` = ?"
- public override fun bind(statement: SupportSQLiteStatement, entity: MyEntity): Unit {
+ public override fun bind(statement: SupportSQLiteStatement, entity: MyEntity) {
statement.bindLong(1, entity.pk.toLong())
statement.bindString(2, entity.other)
statement.bindLong(3, entity.pk.toLong())
@@ -64,7 +63,7 @@
public override fun createQuery(): String =
"INSERT INTO `MyEntity` (`pk`,`other`) VALUES (?,?)"
- public override fun bind(statement: SupportSQLiteStatement, entity: MyEntity): Unit {
+ public override fun bind(statement: SupportSQLiteStatement, entity: MyEntity) {
statement.bindLong(1, entity.pk.toLong())
statement.bindString(2, entity.other)
}
@@ -72,7 +71,7 @@
public override fun createQuery(): String =
"UPDATE `MyEntity` SET `pk` = ?,`other` = ? WHERE `pk` = ?"
- public override fun bind(statement: SupportSQLiteStatement, entity: MyEntity): Unit {
+ public override fun bind(statement: SupportSQLiteStatement, entity: MyEntity) {
statement.bindLong(1, entity.pk.toLong())
statement.bindString(2, entity.other)
statement.bindLong(3, entity.pk.toLong())
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/shortcutMethods_rx3.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/shortcutMethods_rx3.kt
index 614e7de..bbab573 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/shortcutMethods_rx3.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/shortcutMethods_rx3.kt
@@ -13,7 +13,6 @@
import kotlin.Long
import kotlin.String
import kotlin.Suppress
-import kotlin.Unit
import kotlin.collections.List
import kotlin.jvm.JvmStatic
@@ -37,7 +36,7 @@
public override fun createQuery(): String =
"INSERT OR ABORT INTO `MyEntity` (`pk`,`other`) VALUES (?,?)"
- public override fun bind(statement: SupportSQLiteStatement, entity: MyEntity): Unit {
+ public override fun bind(statement: SupportSQLiteStatement, entity: MyEntity) {
statement.bindLong(1, entity.pk.toLong())
statement.bindString(2, entity.other)
}
@@ -45,7 +44,7 @@
this.__deletionAdapterOfMyEntity = object : EntityDeletionOrUpdateAdapter<MyEntity>(__db) {
public override fun createQuery(): String = "DELETE FROM `MyEntity` WHERE `pk` = ?"
- public override fun bind(statement: SupportSQLiteStatement, entity: MyEntity): Unit {
+ public override fun bind(statement: SupportSQLiteStatement, entity: MyEntity) {
statement.bindLong(1, entity.pk.toLong())
}
}
@@ -53,7 +52,7 @@
public override fun createQuery(): String =
"UPDATE OR ABORT `MyEntity` SET `pk` = ?,`other` = ? WHERE `pk` = ?"
- public override fun bind(statement: SupportSQLiteStatement, entity: MyEntity): Unit {
+ public override fun bind(statement: SupportSQLiteStatement, entity: MyEntity) {
statement.bindLong(1, entity.pk.toLong())
statement.bindString(2, entity.other)
statement.bindLong(3, entity.pk.toLong())
@@ -64,7 +63,7 @@
public override fun createQuery(): String =
"INSERT INTO `MyEntity` (`pk`,`other`) VALUES (?,?)"
- public override fun bind(statement: SupportSQLiteStatement, entity: MyEntity): Unit {
+ public override fun bind(statement: SupportSQLiteStatement, entity: MyEntity) {
statement.bindLong(1, entity.pk.toLong())
statement.bindString(2, entity.other)
}
@@ -72,7 +71,7 @@
public override fun createQuery(): String =
"UPDATE `MyEntity` SET `pk` = ?,`other` = ? WHERE `pk` = ?"
- public override fun bind(statement: SupportSQLiteStatement, entity: MyEntity): Unit {
+ public override fun bind(statement: SupportSQLiteStatement, entity: MyEntity) {
statement.bindLong(1, entity.pk.toLong())
statement.bindString(2, entity.other)
statement.bindLong(3, entity.pk.toLong())
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/shortcutMethods_suspend.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/shortcutMethods_suspend.kt
index 01c90ca..3658985 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/shortcutMethods_suspend.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/shortcutMethods_suspend.kt
@@ -11,7 +11,6 @@
import kotlin.Long
import kotlin.String
import kotlin.Suppress
-import kotlin.Unit
import kotlin.collections.List
import kotlin.jvm.JvmStatic
@@ -35,7 +34,7 @@
public override fun createQuery(): String =
"INSERT OR ABORT INTO `MyEntity` (`pk`,`other`) VALUES (?,?)"
- public override fun bind(statement: SupportSQLiteStatement, entity: MyEntity): Unit {
+ public override fun bind(statement: SupportSQLiteStatement, entity: MyEntity) {
statement.bindLong(1, entity.pk.toLong())
statement.bindString(2, entity.other)
}
@@ -43,7 +42,7 @@
this.__deletionAdapterOfMyEntity = object : EntityDeletionOrUpdateAdapter<MyEntity>(__db) {
public override fun createQuery(): String = "DELETE FROM `MyEntity` WHERE `pk` = ?"
- public override fun bind(statement: SupportSQLiteStatement, entity: MyEntity): Unit {
+ public override fun bind(statement: SupportSQLiteStatement, entity: MyEntity) {
statement.bindLong(1, entity.pk.toLong())
}
}
@@ -51,7 +50,7 @@
public override fun createQuery(): String =
"UPDATE OR ABORT `MyEntity` SET `pk` = ?,`other` = ? WHERE `pk` = ?"
- public override fun bind(statement: SupportSQLiteStatement, entity: MyEntity): Unit {
+ public override fun bind(statement: SupportSQLiteStatement, entity: MyEntity) {
statement.bindLong(1, entity.pk.toLong())
statement.bindString(2, entity.other)
statement.bindLong(3, entity.pk.toLong())
@@ -62,7 +61,7 @@
public override fun createQuery(): String =
"INSERT INTO `MyEntity` (`pk`,`other`) VALUES (?,?)"
- public override fun bind(statement: SupportSQLiteStatement, entity: MyEntity): Unit {
+ public override fun bind(statement: SupportSQLiteStatement, entity: MyEntity) {
statement.bindLong(1, entity.pk.toLong())
statement.bindString(2, entity.other)
}
@@ -70,7 +69,7 @@
public override fun createQuery(): String =
"UPDATE `MyEntity` SET `pk` = ?,`other` = ? WHERE `pk` = ?"
- public override fun bind(statement: SupportSQLiteStatement, entity: MyEntity): Unit {
+ public override fun bind(statement: SupportSQLiteStatement, entity: MyEntity) {
statement.bindLong(1, entity.pk.toLong())
statement.bindString(2, entity.other)
statement.bindLong(3, entity.pk.toLong())
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/transactionMethodAdapter_abstractClass.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/transactionMethodAdapter_abstractClass.kt
index 361a336..4093cf2 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/transactionMethodAdapter_abstractClass.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/transactionMethodAdapter_abstractClass.kt
@@ -4,7 +4,6 @@
import javax.`annotation`.processing.Generated
import kotlin.Long
import kotlin.Suppress
-import kotlin.Unit
import kotlin.collections.List
import kotlin.jvm.JvmStatic
@@ -18,7 +17,7 @@
this.__db = __db
}
- public override fun baseConcrete(): Unit {
+ public override fun baseConcrete() {
__db.beginTransaction()
try {
super@MyDao_Impl.baseConcrete()
@@ -28,13 +27,13 @@
}
}
- public override suspend fun baseSuspendConcrete(): Unit {
+ public override suspend fun baseSuspendConcrete() {
__db.withTransaction {
super@MyDao_Impl.baseSuspendConcrete()
}
}
- public override fun concrete(): Unit {
+ public override fun concrete() {
__db.beginTransaction()
try {
super@MyDao_Impl.concrete()
@@ -44,7 +43,7 @@
}
}
- internal override fun concreteInternal(): Unit {
+ internal override fun concreteInternal() {
__db.beginTransaction()
try {
super@MyDao_Impl.concreteInternal()
@@ -54,13 +53,13 @@
}
}
- public override suspend fun suspendConcrete(): Unit {
+ public override suspend fun suspendConcrete() {
__db.withTransaction {
super@MyDao_Impl.suspendConcrete()
}
}
- public override fun concreteWithVararg(vararg arr: Long): Unit {
+ public override fun concreteWithVararg(vararg arr: Long) {
__db.beginTransaction()
try {
super@MyDao_Impl.concreteWithVararg(*arr)
@@ -70,7 +69,7 @@
}
}
- public override suspend fun suspendConcreteWithVararg(vararg arr: Long): Unit {
+ public override suspend fun suspendConcreteWithVararg(vararg arr: Long) {
__db.withTransaction {
super@MyDao_Impl.suspendConcreteWithVararg(*arr)
}
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/transactionMethodAdapter_interface.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/transactionMethodAdapter_interface.kt
index 0fec3af..9716d56 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/transactionMethodAdapter_interface.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/transactionMethodAdapter_interface.kt
@@ -21,7 +21,7 @@
this.__db = __db
}
- public override fun baseConcrete(): Unit {
+ public override fun baseConcrete() {
__db.beginTransaction()
try {
super@MyDao_Impl.baseConcrete()
@@ -31,13 +31,13 @@
}
}
- public override suspend fun baseSuspendConcrete(): Unit {
+ public override suspend fun baseSuspendConcrete() {
__db.withTransaction {
super@MyDao_Impl.baseSuspendConcrete()
}
}
- public override fun concrete(): Unit {
+ public override fun concrete() {
__db.beginTransaction()
try {
super@MyDao_Impl.concrete()
@@ -71,7 +71,7 @@
}
}
- public override fun concreteWithFunctionalParam(block: Function0<Unit>): Unit {
+ public override fun concreteWithFunctionalParam(block: Function0<Unit>) {
__db.beginTransaction()
try {
super@MyDao_Impl.concreteWithFunctionalParam(block)
@@ -81,7 +81,7 @@
}
}
- public override suspend fun suspendConcrete(): Unit {
+ public override suspend fun suspendConcrete() {
__db.withTransaction {
super@MyDao_Impl.suspendConcrete()
}
@@ -92,7 +92,7 @@
}
public override suspend
- fun suspendConcreteWithSuspendFunctionalParam(block: SuspendFunction0<Unit>): Unit {
+ fun suspendConcreteWithSuspendFunctionalParam(block: SuspendFunction0<Unit>) {
__db.withTransaction {
super@MyDao_Impl.suspendConcreteWithSuspendFunctionalParam(block)
}
diff --git a/room/room-runtime/src/androidTest/java/androidx/room/AutoClosingRoomOpenHelperTest.kt b/room/room-runtime/src/androidTest/java/androidx/room/AutoClosingRoomOpenHelperTest.kt
index c8d60c9..e212492 100644
--- a/room/room-runtime/src/androidTest/java/androidx/room/AutoClosingRoomOpenHelperTest.kt
+++ b/room/room-runtime/src/androidTest/java/androidx/room/AutoClosingRoomOpenHelperTest.kt
@@ -29,7 +29,6 @@
import androidx.test.filters.FlakyTest
import androidx.test.filters.SdkSuppress
import androidx.testutils.assertThrows
-import com.google.common.truth.Truth
import java.io.IOException
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
@@ -238,7 +237,7 @@
db.query("select * from users").useCursor {
assertThat(it.moveToFirst()).isTrue()
assertThat(it.getInt(0)).isEqualTo(123)
- Truth.assertThat(it.getDouble(1)).isWithin(.01).of(1.23)
+ assertThat(it.getDouble(1)).isWithin(.01).of(1.23)
assertThat(it.getBlob(2)).isEqualTo(byteArrayOf(1, 2, 3))
assertThat(it.isNull(3)).isTrue()
diff --git a/room/room-testing/src/androidTest/java/androidx/room/testing/kotlintestapp/migration/AutoMigrationAndMigrationTest.kt b/room/room-testing/src/androidTest/java/androidx/room/testing/kotlintestapp/migration/AutoMigrationAndMigrationTest.kt
index b0231ab..7269905 100644
--- a/room/room-testing/src/androidTest/java/androidx/room/testing/kotlintestapp/migration/AutoMigrationAndMigrationTest.kt
+++ b/room/room-testing/src/androidTest/java/androidx/room/testing/kotlintestapp/migration/AutoMigrationAndMigrationTest.kt
@@ -24,7 +24,6 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import androidx.test.platform.app.InstrumentationRegistry
-import com.google.common.truth.Truth
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -64,7 +63,7 @@
MIGRATION_1_2
)
} catch (e: SQLiteException) {
- Truth.assertThat(e.message).containsMatch("no such table: Entity0")
+ assertThat(e.message).contains("no such table: Entity0")
}
}
diff --git a/samples/MediaRoutingDemo/src/main/AndroidManifest.xml b/samples/MediaRoutingDemo/src/main/AndroidManifest.xml
index bb037aa..8afa6de 100644
--- a/samples/MediaRoutingDemo/src/main/AndroidManifest.xml
+++ b/samples/MediaRoutingDemo/src/main/AndroidManifest.xml
@@ -24,6 +24,7 @@
<uses-permission android:name="android.permission.BLUETOOTH"
android:maxSdkVersion="30" />
+ <uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<application
diff --git a/samples/MediaRoutingDemo/src/main/java/com/example/androidx/mediarouting/activities/systemrouting/SystemRoutingActivity.java b/samples/MediaRoutingDemo/src/main/java/com/example/androidx/mediarouting/activities/systemrouting/SystemRoutingActivity.java
index b3e588a..612d360 100644
--- a/samples/MediaRoutingDemo/src/main/java/com/example/androidx/mediarouting/activities/systemrouting/SystemRoutingActivity.java
+++ b/samples/MediaRoutingDemo/src/main/java/com/example/androidx/mediarouting/activities/systemrouting/SystemRoutingActivity.java
@@ -16,10 +16,12 @@
package com.example.androidx.mediarouting.activities.systemrouting;
-import android.Manifest;
+import static android.Manifest.permission.BLUETOOTH_CONNECT;
+import static android.Manifest.permission.BLUETOOTH_SCAN;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+
import android.content.Context;
import android.content.Intent;
-import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.widget.Toast;
@@ -94,7 +96,7 @@
if (requestCode == REQUEST_CODE_BLUETOOTH_CONNECT
&& grantResults.length > 0) {
- if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+ if (grantResults[0] == PERMISSION_GRANTED) {
onBluetoothPermissionGranted();
} else {
onBluetoothPermissionDenied();
@@ -112,15 +114,15 @@
}
private boolean hasBluetoothPermission() {
- return ContextCompat.checkSelfPermission(
- /* context= */ this, Manifest.permission.BLUETOOTH_CONNECT)
- == PackageManager.PERMISSION_GRANTED;
+ return ContextCompat.checkSelfPermission(/* context= */ this, BLUETOOTH_CONNECT)
+ == PERMISSION_GRANTED
+ && ContextCompat.checkSelfPermission(/* context= */ this, BLUETOOTH_SCAN)
+ == PERMISSION_GRANTED;
}
private void requestBluetoothPermission() {
ActivityCompat.requestPermissions(this,
- new String[]{Manifest.permission.BLUETOOTH_CONNECT},
- REQUEST_CODE_BLUETOOTH_CONNECT);
+ new String[]{BLUETOOTH_CONNECT, BLUETOOTH_SCAN}, REQUEST_CODE_BLUETOOTH_CONNECT);
}
private void onBluetoothPermissionGranted() {
diff --git a/settings.gradle b/settings.gradle
index 7f88274..ae52660 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -667,6 +667,7 @@
includeProject(":constraintlayout:constraintlayout-compose:integration-tests:demos", [BuildType.COMPOSE])
includeProject(":constraintlayout:constraintlayout-compose:integration-tests:macrobenchmark", [BuildType.COMPOSE])
includeProject(":constraintlayout:constraintlayout-compose:integration-tests:macrobenchmark-target", [BuildType.COMPOSE])
+includeProject(":constraintlayout:constraintlayout-compose:integration-tests:compose-benchmark", [BuildType.COMPOSE])
includeProject(":constraintlayout:constraintlayout", [BuildType.MAIN])
includeProject(":constraintlayout:constraintlayout-core", [BuildType.MAIN, BuildType.COMPOSE])
includeProject(":contentpager:contentpager", [BuildType.MAIN])
@@ -972,6 +973,7 @@
includeProject(":slice:slice-test", [BuildType.MAIN])
includeProject(":slice:slice-view", [BuildType.MAIN])
includeProject(":slidingpanelayout:slidingpanelayout", [BuildType.MAIN, BuildType.FLAN])
+includeProject(":slidingpanelayout:slidingpanelayout-testapp", [BuildType.MAIN])
includeProject(":sqlite:integration-tests:inspection-room-testapp", [BuildType.MAIN])
includeProject(":sqlite:integration-tests:inspection-sqldelight-testapp", [BuildType.MAIN])
includeProject(":sqlite:sqlite", [BuildType.MAIN, BuildType.COMPOSE])
diff --git a/slidingpanelayout/slidingpanelayout-testapp/build.gradle b/slidingpanelayout/slidingpanelayout-testapp/build.gradle
new file mode 100644
index 0000000..7d5ebec
--- /dev/null
+++ b/slidingpanelayout/slidingpanelayout-testapp/build.gradle
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import androidx.build.LibraryType
+
+plugins {
+ id("AndroidXPlugin")
+ id("com.android.application")
+ id("org.jetbrains.kotlin.android")
+}
+
+dependencies {
+ api(libs.kotlinStdlib)
+ // Add dependencies here
+ implementation(project(":slidingpanelayout:slidingpanelayout"))
+ implementation("androidx.recyclerview:recyclerview:1.2.1")
+}
+
+android {
+ namespace "androidx.slidingpanelayout.demo"
+}
+
+androidx {
+ name = "androidx.slidingpanelayout:slidingpanelayout-demo"
+ type = LibraryType.SAMPLES
+ inceptionYear = "2023"
+ description = "SlidingPaneLayout Demos"
+}
diff --git a/slidingpanelayout/slidingpanelayout-testapp/src/main/AndroidManifest.xml b/slidingpanelayout/slidingpanelayout-testapp/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..e026b28
--- /dev/null
+++ b/slidingpanelayout/slidingpanelayout-testapp/src/main/AndroidManifest.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools">
+ <application
+ android:allowBackup="true"
+ android:label="SlidingPaneLayout Demo"
+ android:supportsRtl="true">
+
+ <activity android:name="androidx.slidingpanelayout.SlidingPaneLayoutDemos"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="androidx.slidingpanelayout.SlidingPaneLayoutSample"
+ android:exported="true" />
+ </application>
+</manifest>
\ No newline at end of file
diff --git a/lifecycle/lifecycle-common/src/test/java/androidx/lifecycle/observers/DerivedSequence2.java b/slidingpanelayout/slidingpanelayout-testapp/src/main/java/androidx/slidingpanelayout/DemoItem.kt
similarity index 63%
copy from lifecycle/lifecycle-common/src/test/java/androidx/lifecycle/observers/DerivedSequence2.java
copy to slidingpanelayout/slidingpanelayout-testapp/src/main/java/androidx/slidingpanelayout/DemoItem.kt
index aeb87da..5415371 100644
--- a/lifecycle/lifecycle-common/src/test/java/androidx/lifecycle/observers/DerivedSequence2.java
+++ b/slidingpanelayout/slidingpanelayout-testapp/src/main/java/androidx/slidingpanelayout/DemoItem.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2017 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.
@@ -13,16 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package androidx.slidingpanelayout
-package androidx.lifecycle.observers;
+import android.app.Activity
-import androidx.lifecycle.Lifecycle;
-
-@SuppressWarnings("deprecation")
-public class DerivedSequence2 extends DerivedSequence1 {
-
- @androidx.lifecycle.OnLifecycleEvent(Lifecycle.Event.ON_STOP)
- void onStop() {
-
- }
-}
+class DemoItem<T : Activity>(
+ val clazz: Class<T>,
+ val title: String,
+ val description: String
+)
diff --git a/slidingpanelayout/slidingpanelayout-testapp/src/main/java/androidx/slidingpanelayout/ItemAdapter.kt b/slidingpanelayout/slidingpanelayout-testapp/src/main/java/androidx/slidingpanelayout/ItemAdapter.kt
new file mode 100644
index 0000000..93fac85
--- /dev/null
+++ b/slidingpanelayout/slidingpanelayout-testapp/src/main/java/androidx/slidingpanelayout/ItemAdapter.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.slidingpanelayout
+
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import androidx.recyclerview.widget.RecyclerView
+import androidx.slidingpanelayout.demo.R
+
+class ItemAdapter(private val items: List<DemoItem<*>>) : RecyclerView.Adapter<ItemViewHolder>() {
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder {
+ val inflater = LayoutInflater.from(parent.context)
+ val rootView = inflater.inflate(R.layout.item_viewholder, parent, false)
+ return ItemViewHolder(rootView)
+ }
+
+ override fun getItemCount(): Int {
+ return items.size
+ }
+
+ override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
+ holder.bind(items[position])
+ }
+}
diff --git a/slidingpanelayout/slidingpanelayout-testapp/src/main/java/androidx/slidingpanelayout/ItemViewHolder.kt b/slidingpanelayout/slidingpanelayout-testapp/src/main/java/androidx/slidingpanelayout/ItemViewHolder.kt
new file mode 100644
index 0000000..2c48ee1
--- /dev/null
+++ b/slidingpanelayout/slidingpanelayout-testapp/src/main/java/androidx/slidingpanelayout/ItemViewHolder.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.slidingpanelayout
+
+import android.content.Intent
+import android.view.View
+import android.widget.Button
+import android.widget.TextView
+import androidx.recyclerview.widget.RecyclerView
+import androidx.slidingpanelayout.demo.R
+
+class ItemViewHolder(rootView: View) : RecyclerView.ViewHolder(rootView) {
+
+ private val titleTextView = rootView.findViewById<TextView>(R.id.title_textview)
+ private val descriptionTextView = rootView.findViewById<TextView>(R.id.description_textview)
+ private val launchButton = rootView.findViewById<Button>(R.id.button_launch)
+
+ fun bind(item: DemoItem<*>) {
+ titleTextView.text = item.title
+ descriptionTextView.text = item.description
+ launchButton.setOnClickListener { view ->
+ val context = view.context
+ val intent = Intent(context, item.clazz)
+ context.startActivity(intent)
+ }
+ }
+}
diff --git a/slidingpanelayout/slidingpanelayout-testapp/src/main/java/androidx/slidingpanelayout/SlidingPaneLayoutDemos.kt b/slidingpanelayout/slidingpanelayout-testapp/src/main/java/androidx/slidingpanelayout/SlidingPaneLayoutDemos.kt
new file mode 100644
index 0000000..b6d4a1e1
--- /dev/null
+++ b/slidingpanelayout/slidingpanelayout-testapp/src/main/java/androidx/slidingpanelayout/SlidingPaneLayoutDemos.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.slidingpanelayout
+
+import android.app.Activity
+import android.os.Bundle
+import androidx.recyclerview.widget.RecyclerView
+import androidx.slidingpanelayout.demo.R
+
+class SlidingPaneLayoutDemos : Activity() {
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_slidingpanelayout_demos)
+ val recyclerView = findViewById<RecyclerView>(R.id.demo_recyclerview)
+
+ recyclerView.adapter = ItemAdapter(
+ listOf(
+ DemoItem(
+ SlidingPaneLayoutSample::class.java,
+ "SlidingPaneLayoutSample",
+ "Basic SlidingPaneLayoutSample"
+ )
+ )
+ )
+ }
+}
diff --git a/slidingpanelayout/slidingpanelayout-testapp/src/main/java/androidx/slidingpanelayout/SlidingPaneLayoutSample.kt b/slidingpanelayout/slidingpanelayout-testapp/src/main/java/androidx/slidingpanelayout/SlidingPaneLayoutSample.kt
new file mode 100644
index 0000000..59463f5
--- /dev/null
+++ b/slidingpanelayout/slidingpanelayout-testapp/src/main/java/androidx/slidingpanelayout/SlidingPaneLayoutSample.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.slidingpanelayout
+
+import android.app.Activity
+import android.os.Bundle
+
+class SlidingPaneLayoutSample : Activity() {
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(androidx.slidingpanelayout.demo.R.layout.activity_slidingpanelayout_sample)
+ }
+}
diff --git a/slidingpanelayout/slidingpanelayout-testapp/src/main/res/layout/activity_slidingpanelayout_demos.xml b/slidingpanelayout/slidingpanelayout-testapp/src/main/res/layout/activity_slidingpanelayout_demos.xml
new file mode 100644
index 0000000..58bbd7b
--- /dev/null
+++ b/slidingpanelayout/slidingpanelayout-testapp/src/main/res/layout/activity_slidingpanelayout_demos.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ Copyright 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:orientation="vertical"
+ android:id="@+id/demo_recyclerview"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
+
diff --git a/slidingpanelayout/slidingpanelayout-testapp/src/main/res/layout/activity_slidingpanelayout_sample.xml b/slidingpanelayout/slidingpanelayout-testapp/src/main/res/layout/activity_slidingpanelayout_sample.xml
new file mode 100644
index 0000000..436cc28
--- /dev/null
+++ b/slidingpanelayout/slidingpanelayout-testapp/src/main/res/layout/activity_slidingpanelayout_sample.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ Copyright 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<androidx.slidingpanelayout.widget.SlidingPaneLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <View
+ android:background="@android:color/holo_blue_light"
+ android:layout_width="200dp"
+ android:layout_height="match_parent"/>
+
+
+ <View
+ android:background="@android:color/holo_green_dark"
+ android:layout_width="200dp"
+ android:layout_weight="1"
+ android:layout_height="match_parent"/>
+
+</androidx.slidingpanelayout.widget.SlidingPaneLayout>
\ No newline at end of file
diff --git a/slidingpanelayout/slidingpanelayout-testapp/src/main/res/layout/item_viewholder.xml b/slidingpanelayout/slidingpanelayout-testapp/src/main/res/layout/item_viewholder.xml
new file mode 100644
index 0000000..631c063
--- /dev/null
+++ b/slidingpanelayout/slidingpanelayout-testapp/src/main/res/layout/item_viewholder.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <TextView
+ android:id="@+id/title_textview"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"/>
+
+ <TextView
+ android:id="@+id/description_textview"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+
+ <Button
+ android:id="@+id/button_launch"
+ android:text="@string/launch"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/slidingpanelayout/slidingpanelayout-testapp/src/main/res/values/strings.xml b/slidingpanelayout/slidingpanelayout-testapp/src/main/res/values/strings.xml
new file mode 100644
index 0000000..cbe5256
--- /dev/null
+++ b/slidingpanelayout/slidingpanelayout-testapp/src/main/res/values/strings.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources>
+ <string name="launch">Launch</string>
+</resources>
\ No newline at end of file
diff --git a/stableaidl/stableaidl-gradle-plugin/src/main/java/androidx/stableaidl/StableAidlTasks.kt b/stableaidl/stableaidl-gradle-plugin/src/main/java/androidx/stableaidl/StableAidlTasks.kt
index a8c07759..93bcbf8 100644
--- a/stableaidl/stableaidl-gradle-plugin/src/main/java/androidx/stableaidl/StableAidlTasks.kt
+++ b/stableaidl/stableaidl-gradle-plugin/src/main/java/androidx/stableaidl/StableAidlTasks.kt
@@ -24,7 +24,6 @@
import com.android.build.api.variant.SourceDirectories
import com.android.build.api.variant.Variant
import com.android.utils.usLocaleCapitalize
-import java.io.File
import org.gradle.api.Action
import org.gradle.api.DomainObjectCollection
import org.gradle.api.Project
@@ -36,6 +35,7 @@
import org.gradle.api.file.FileCollection
import org.gradle.api.file.RegularFile
import org.gradle.api.provider.Provider
+import org.gradle.api.tasks.TaskOutputFilePropertyBuilder
import org.gradle.api.tasks.TaskProvider
// Gradle task group used to identify Stable AIDL tasks.
@@ -68,7 +68,6 @@
}
}
-@Suppress("UnstableApiUsage") // SourceDirectories.Flat
fun registerCompileAidlApi(
project: Project,
variant: Variant,
@@ -277,18 +276,16 @@
/**
* Tells Gradle to skip running this task, even if this task declares no output files.
*/
-private fun Task.cacheEvenIfNoOutputs() {
- this.outputs.file(this.getPlaceholderOutput())
-}
+private fun Task.cacheEvenIfNoOutputs(): TaskOutputFilePropertyBuilder =
+ outputs.file(getPlaceholderOutput())
/**
* Returns an unused output path that we can pass to Gradle to prevent Gradle from thinking that we
* forgot to declare outputs of this task, and instead to skip this task if its inputs are
* unchanged.
*/
-private fun Task.getPlaceholderOutput(): File {
- return File(this.project.buildDir, "placeholderOutput/" + this.name.replace(":", "-"))
-}
+private fun Task.getPlaceholderOutput(): Provider<RegularFile> =
+ project.layout.buildDirectory.file("placeholderOutput/${name.replace(':', '-')}")
private fun computeTaskName(prefix: String, variant: Variant, suffix: String) =
"$prefix${variant.name.usLocaleCapitalize()}$suffix"
diff --git a/test/uiautomator/integration-tests/testapp/src/androidTest/java/androidx/test/uiautomator/testapp/MultiDisplayTest.java b/test/uiautomator/integration-tests/testapp/src/androidTest/java/androidx/test/uiautomator/testapp/MultiDisplayTest.java
index 8d9ef75..6b229e7 100644
--- a/test/uiautomator/integration-tests/testapp/src/androidTest/java/androidx/test/uiautomator/testapp/MultiDisplayTest.java
+++ b/test/uiautomator/integration-tests/testapp/src/androidTest/java/androidx/test/uiautomator/testapp/MultiDisplayTest.java
@@ -27,12 +27,14 @@
import android.content.Intent;
import android.hardware.display.DisplayManager;
import android.view.Display;
+import android.view.Surface;
import androidx.annotation.NonNull;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.filters.SdkSuppress;
import androidx.test.uiautomator.By;
import androidx.test.uiautomator.BySelector;
+import androidx.test.uiautomator.Orientation;
import androidx.test.uiautomator.UiObject2;
import androidx.test.uiautomator.Until;
@@ -129,6 +131,24 @@
}
}
+ @Test
+ public void testMultiDisplay_orientations() {
+ int secondaryDisplayId = getSecondaryDisplayId();
+
+ try {
+ mDevice.setOrientation(Orientation.ROTATION_180, secondaryDisplayId);
+ assertEquals(Surface.ROTATION_180, mDevice.getDisplayRotation(secondaryDisplayId));
+
+ mDevice.setOrientation(Orientation.FROZEN, secondaryDisplayId);
+ assertEquals(Surface.ROTATION_180, mDevice.getDisplayRotation(secondaryDisplayId));
+
+ mDevice.setOrientation(Orientation.ROTATION_270, secondaryDisplayId);
+ assertEquals(Surface.ROTATION_270, mDevice.getDisplayRotation(secondaryDisplayId));
+ } finally {
+ mDevice.setOrientation(Orientation.ROTATION_0, secondaryDisplayId);
+ }
+ }
+
// Helper to launch an activity on a specific display.
private void launchTestActivityOnDisplay(@NonNull Class<? extends Activity> activity,
int displayId) {
diff --git a/test/uiautomator/integration-tests/testapp/src/androidTest/java/androidx/test/uiautomator/testapp/UiDeviceTest.java b/test/uiautomator/integration-tests/testapp/src/androidTest/java/androidx/test/uiautomator/testapp/UiDeviceTest.java
index f14e6e6..8f94cc4 100644
--- a/test/uiautomator/integration-tests/testapp/src/androidTest/java/androidx/test/uiautomator/testapp/UiDeviceTest.java
+++ b/test/uiautomator/integration-tests/testapp/src/androidTest/java/androidx/test/uiautomator/testapp/UiDeviceTest.java
@@ -25,6 +25,7 @@
import android.app.UiAutomation;
import android.graphics.Point;
import android.view.KeyEvent;
+import android.view.Surface;
import android.widget.TextView;
import androidx.test.filters.LargeTest;
@@ -32,6 +33,7 @@
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.uiautomator.By;
import androidx.test.uiautomator.BySelector;
+import androidx.test.uiautomator.Orientation;
import androidx.test.uiautomator.UiDevice;
import androidx.test.uiautomator.UiObject2;
import androidx.test.uiautomator.Until;
@@ -390,24 +392,23 @@
}
@Test
- public void testSetOrientationPortrait() throws Exception {
+ public void testSetOrientation() {
launchTestActivity(KeycodeTestActivity.class);
- try {
- mDevice.setOrientationPortrait();
- assertTrue(mDevice.getDisplayHeight() > mDevice.getDisplayWidth());
- } finally {
- mDevice.unfreezeRotation();
- }
- }
- @Test
- public void testSetOrientationLandscape() throws Exception {
- launchTestActivity(KeycodeTestActivity.class);
try {
- mDevice.setOrientationLandscape();
- assertTrue(mDevice.getDisplayWidth() > mDevice.getDisplayHeight());
+ mDevice.setOrientation(Orientation.ROTATION_0);
+ assertEquals(Surface.ROTATION_0, mDevice.getDisplayRotation());
+
+ mDevice.setOrientation(Orientation.ROTATION_90);
+ assertEquals(Surface.ROTATION_90, mDevice.getDisplayRotation());
+
+ mDevice.setOrientation(Orientation.PORTRAIT);
+ assertTrue(mDevice.getDisplayHeight() >= mDevice.getDisplayWidth());
+
+ mDevice.setOrientation(Orientation.LANDSCAPE);
+ assertTrue(mDevice.getDisplayHeight() <= mDevice.getDisplayWidth());
} finally {
- mDevice.unfreezeRotation();
+ mDevice.setOrientation(Orientation.ROTATION_0);
}
}
diff --git a/test/uiautomator/uiautomator/api/current.txt b/test/uiautomator/uiautomator/api/current.txt
index 8f7bdde..f50179d 100644
--- a/test/uiautomator/uiautomator/api/current.txt
+++ b/test/uiautomator/uiautomator/api/current.txt
@@ -132,6 +132,17 @@
method public void sendStatus(int, android.os.Bundle);
}
+ public enum Orientation {
+ enum_constant public static final androidx.test.uiautomator.Orientation FROZEN;
+ enum_constant public static final androidx.test.uiautomator.Orientation LANDSCAPE;
+ enum_constant public static final androidx.test.uiautomator.Orientation PORTRAIT;
+ enum_constant public static final androidx.test.uiautomator.Orientation ROTATION_0;
+ enum_constant public static final androidx.test.uiautomator.Orientation ROTATION_180;
+ enum_constant public static final androidx.test.uiautomator.Orientation ROTATION_270;
+ enum_constant public static final androidx.test.uiautomator.Orientation ROTATION_90;
+ enum_constant public static final androidx.test.uiautomator.Orientation UNFROZEN;
+ }
+
public abstract class SearchCondition<U> implements androidx.test.uiautomator.Condition<androidx.test.uiautomator.Searchable,U> {
ctor public SearchCondition();
}
@@ -179,6 +190,7 @@
method @Px public int getDisplayHeight();
method @Px public int getDisplayHeight(int);
method public int getDisplayRotation();
+ method public int getDisplayRotation(int);
method public android.graphics.Point getDisplaySizeDp();
method @Px public int getDisplayWidth();
method @Px public int getDisplayWidth(int);
@@ -217,10 +229,10 @@
method public void runWatchers();
method @Deprecated public void setCompressedLayoutHeirarchy(boolean);
method public void setCompressedLayoutHierarchy(boolean);
- method public void setOrientationLandscape() throws android.os.RemoteException;
+ method public void setOrientation(androidx.test.uiautomator.Orientation);
+ method @RequiresApi(30) public void setOrientation(androidx.test.uiautomator.Orientation, int);
method public void setOrientationLeft() throws android.os.RemoteException;
method public void setOrientationNatural() throws android.os.RemoteException;
- method public void setOrientationPortrait() throws android.os.RemoteException;
method public void setOrientationRight() throws android.os.RemoteException;
method public void sleep() throws android.os.RemoteException;
method public boolean swipe(android.graphics.Point![], int);
diff --git a/test/uiautomator/uiautomator/api/restricted_current.txt b/test/uiautomator/uiautomator/api/restricted_current.txt
index 8f7bdde..f50179d 100644
--- a/test/uiautomator/uiautomator/api/restricted_current.txt
+++ b/test/uiautomator/uiautomator/api/restricted_current.txt
@@ -132,6 +132,17 @@
method public void sendStatus(int, android.os.Bundle);
}
+ public enum Orientation {
+ enum_constant public static final androidx.test.uiautomator.Orientation FROZEN;
+ enum_constant public static final androidx.test.uiautomator.Orientation LANDSCAPE;
+ enum_constant public static final androidx.test.uiautomator.Orientation PORTRAIT;
+ enum_constant public static final androidx.test.uiautomator.Orientation ROTATION_0;
+ enum_constant public static final androidx.test.uiautomator.Orientation ROTATION_180;
+ enum_constant public static final androidx.test.uiautomator.Orientation ROTATION_270;
+ enum_constant public static final androidx.test.uiautomator.Orientation ROTATION_90;
+ enum_constant public static final androidx.test.uiautomator.Orientation UNFROZEN;
+ }
+
public abstract class SearchCondition<U> implements androidx.test.uiautomator.Condition<androidx.test.uiautomator.Searchable,U> {
ctor public SearchCondition();
}
@@ -179,6 +190,7 @@
method @Px public int getDisplayHeight();
method @Px public int getDisplayHeight(int);
method public int getDisplayRotation();
+ method public int getDisplayRotation(int);
method public android.graphics.Point getDisplaySizeDp();
method @Px public int getDisplayWidth();
method @Px public int getDisplayWidth(int);
@@ -217,10 +229,10 @@
method public void runWatchers();
method @Deprecated public void setCompressedLayoutHeirarchy(boolean);
method public void setCompressedLayoutHierarchy(boolean);
- method public void setOrientationLandscape() throws android.os.RemoteException;
+ method public void setOrientation(androidx.test.uiautomator.Orientation);
+ method @RequiresApi(30) public void setOrientation(androidx.test.uiautomator.Orientation, int);
method public void setOrientationLeft() throws android.os.RemoteException;
method public void setOrientationNatural() throws android.os.RemoteException;
- method public void setOrientationPortrait() throws android.os.RemoteException;
method public void setOrientationRight() throws android.os.RemoteException;
method public void sleep() throws android.os.RemoteException;
method public boolean swipe(android.graphics.Point![], int);
diff --git a/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/Configurator.java b/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/Configurator.java
index 6b7931d..ad68c56 100644
--- a/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/Configurator.java
+++ b/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/Configurator.java
@@ -224,6 +224,11 @@
/**
* Sets the current tool type to use for motion events.
* @see MotionEvent#getToolType(int)
+ * @see MotionEvent#TOOL_TYPE_FINGER
+ * @see MotionEvent#TOOL_TYPE_STYLUS
+ * @see MotionEvent#TOOL_TYPE_MOUSE
+ * @see MotionEvent#TOOL_TYPE_ERASER
+ * @see MotionEvent#TOOL_TYPE_UNKNOWN
*/
public @NonNull Configurator setToolType(final int toolType) {
mToolType = toolType;
diff --git a/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/Orientation.java b/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/Orientation.java
new file mode 100644
index 0000000..d660ed9
--- /dev/null
+++ b/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/Orientation.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.test.uiautomator;
+
+/**
+ * Specifies the orientation state of a display. Used with
+ * {@link UiDevice#setOrientation(Orientation)} and
+ * {@link UiDevice#setOrientation(Orientation, int)}.
+ */
+public enum Orientation {
+ /** Sets the rotation to be the natural orientation. */
+ ROTATION_0,
+ /** Sets the rotation to be 90 degrees clockwise to the natural orientation. */
+ ROTATION_90,
+ /** Sets the rotation to be 180 degrees clockwise to the natural orientation. */
+ ROTATION_180,
+ /** Sets the rotation to be 270 degrees clockwise to the natural orientation. */
+ ROTATION_270,
+ /** Sets the rotation so that the display height will be >= the display width. */
+ PORTRAIT,
+ /** Sets the rotation so that the display height will be <= the display width. */
+ LANDSCAPE,
+ /** Freezes the current rotation. */
+ FROZEN,
+ /** Unfreezes the current rotation. Need to wait a short period for the rotation animation to
+ * complete before performing another operation. */
+ UNFROZEN;
+}
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 8d9dfd1..28fd77a 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
@@ -797,124 +797,274 @@
}
/**
- * @return true if device is in its natural orientation (0 or 180 degrees)
+ * @return true if default display is in its natural or flipped (180 degrees) orientation
*/
public boolean isNaturalOrientation() {
- int ret = getDisplayRotation();
- return ret == UiAutomation.ROTATION_FREEZE_0 ||
- ret == UiAutomation.ROTATION_FREEZE_180;
+ return isNaturalOrientation(Display.DEFAULT_DISPLAY);
}
/**
- * @return the current rotation of the display, as defined in {@link Surface}
+ * @return true if display with {@code displayId} is in its natural or flipped (180 degrees)
+ * orientation
+ */
+ private boolean isNaturalOrientation(int displayId) {
+ int ret = getDisplayRotation(displayId);
+ return ret == UiAutomation.ROTATION_FREEZE_0
+ || ret == UiAutomation.ROTATION_FREEZE_180;
+ }
+
+ /**
+ * @return the current rotation of the default display
+ * @see Display#getRotation()
*/
public int getDisplayRotation() {
- waitForIdle();
- return getDisplayById(Display.DEFAULT_DISPLAY).getRotation();
+ return getDisplayRotation(Display.DEFAULT_DISPLAY);
}
/**
- * Freezes the device rotation at its current state.
+ * @return the current rotation of the display with {@code displayId}
+ * @see Display#getRotation()
+ */
+ public int getDisplayRotation(int displayId) {
+ waitForIdle();
+ return getDisplayById(displayId).getRotation();
+ }
+
+ /**
+ * Freezes the default display rotation at its current state.
* @throws RemoteException never
*/
public void freezeRotation() throws RemoteException {
- Log.d(TAG, "Freezing rotation.");
- getUiAutomation().setRotation(UiAutomation.ROTATION_FREEZE_CURRENT);
+ setOrientation(Orientation.FROZEN);
}
/**
- * Un-freezes the device rotation allowing its contents to rotate with the device physical
- * rotation. During testing, it is best to keep the device frozen in a specific orientation.
+ * Un-freezes the default display rotation allowing its contents to rotate with its physical
+ * rotation. During testing, it is best to keep the default display frozen in a specific
+ * orientation.
+ * <p>Note: Need to wait a short period for the rotation animation to complete before
+ * performing another operation.
* @throws RemoteException never
*/
public void unfreezeRotation() throws RemoteException {
- Log.d(TAG, "Unfreezing rotation.");
- getUiAutomation().setRotation(UiAutomation.ROTATION_UNFREEZE);
+ setOrientation(Orientation.UNFROZEN);
}
/**
- * Orients the device to the left and freezes rotation. Use {@link #unfreezeRotation()} to
- * un-freeze the rotation.
+ * Orients the default display to the left and freezes its rotation. Use
+ * {@link #unfreezeRotation()} to un-freeze the rotation.
* <p>Note: This rotation is relative to the natural orientation which depends on the device
- * type (e.g. phone vs. tablet). Consider using {@link #setOrientationPortrait()} and
- * {@link #setOrientationLandscape()}.
+ * type (e.g. phone vs. tablet).
* @throws RemoteException never
*/
public void setOrientationLeft() throws RemoteException {
- Log.d(TAG, "Setting orientation to left.");
- rotate(UiAutomation.ROTATION_FREEZE_90);
+ setOrientation(Orientation.ROTATION_90);
}
/**
- * Orients the device to the right and freezes rotation. Use {@link #unfreezeRotation()} to
- * un-freeze the rotation.
+ * Orients the default display to the right and freezes its rotation. Use
+ * {@link #unfreezeRotation()} to un-freeze the rotation.
* <p>Note: This rotation is relative to the natural orientation which depends on the device
- * type (e.g. phone vs. tablet). Consider using {@link #setOrientationPortrait()} and
- * {@link #setOrientationLandscape()}.
+ * type (e.g. phone vs. tablet).
* @throws RemoteException never
*/
public void setOrientationRight() throws RemoteException {
- Log.d(TAG, "Setting orientation to right.");
- rotate(UiAutomation.ROTATION_FREEZE_270);
+ setOrientation(Orientation.ROTATION_270);
}
/**
- * Orients the device to its natural orientation (0 or 180 degrees) and freezes rotation. Use
- * {@link #unfreezeRotation()} to un-freeze the rotation.
+ * Orients the default display to its natural or flipped (180 degrees) orientation and
+ * freezes its rotation. Use {@link #unfreezeRotation()} to un-freeze the rotation.
* <p>Note: The natural orientation depends on the device type (e.g. phone vs. tablet).
- * Consider using {@link #setOrientationPortrait()} and {@link #setOrientationLandscape()}.
* @throws RemoteException never
*/
public void setOrientationNatural() throws RemoteException {
- Log.d(TAG, "Setting orientation to natural.");
- rotate(UiAutomation.ROTATION_FREEZE_0);
+ setOrientation(Orientation.ROTATION_0);
}
/**
- * Orients the device to its portrait orientation (height > width) and freezes rotation. Use
- * {@link #unfreezeRotation()} to un-freeze the rotation.
- * @throws RemoteException never
+ * Sets the default display to {@code orientation} and freezes its rotation.
+ * <p>Note: The orientation is relative to the natural orientation which depends on the
+ * device type (e.g. phone vs. tablet).
+ *
+ * @param orientation the desired orientation
*/
- public void setOrientationPortrait() throws RemoteException {
- Log.d(TAG, "Setting orientation to portrait.");
- if (getDisplayHeight() > getDisplayWidth()) {
- freezeRotation(); // Already in portrait orientation.
- } else if (isNaturalOrientation()) {
- rotate(UiAutomation.ROTATION_FREEZE_90);
- } else {
- rotate(UiAutomation.ROTATION_FREEZE_0);
+ public void setOrientation(@NonNull Orientation orientation) {
+ Log.d(TAG, String.format("Setting orientation to %s.",
+ orientation.name()));
+ switch (orientation) {
+ case ROTATION_90:
+ rotateWithUiAutomation(Surface.ROTATION_90);
+ break;
+ case ROTATION_270:
+ rotateWithUiAutomation(Surface.ROTATION_270);
+ break;
+ case ROTATION_0:
+ rotateWithUiAutomation(Surface.ROTATION_0);
+ break;
+ case ROTATION_180:
+ rotateWithUiAutomation(Surface.ROTATION_180);
+ break;
+ case PORTRAIT:
+ if (getDisplayHeight() >= getDisplayWidth()) {
+ // Freeze. Already in portrait orientation.
+ getUiAutomation().setRotation(UiAutomation.ROTATION_FREEZE_CURRENT);
+ } else if (isNaturalOrientation()) {
+ rotateWithUiAutomation(Surface.ROTATION_90);
+ } else {
+ rotateWithUiAutomation(Surface.ROTATION_0);
+ }
+ break;
+ case LANDSCAPE:
+ if (getDisplayHeight() <= getDisplayWidth()) {
+ // Freeze. Already in landscape orientation.
+ getUiAutomation().setRotation(UiAutomation.ROTATION_FREEZE_CURRENT);
+ } else if (isNaturalOrientation()) {
+ rotateWithUiAutomation(Surface.ROTATION_90);
+ } else {
+ rotateWithUiAutomation(Surface.ROTATION_0);
+ }
+ break;
+ case FROZEN:
+ getUiAutomation().setRotation(UiAutomation.ROTATION_FREEZE_CURRENT);
+ break;
+ case UNFROZEN:
+ getUiAutomation().setRotation(UiAutomation.ROTATION_UNFREEZE);
+ break;
+ default:
+ throw new IllegalArgumentException(
+ "Invalid input orientation for UiDevice#setOrientation.");
}
}
/**
- * Orients the device to its landscape orientation (width > height) and freezes rotation. Use
- * {@link #unfreezeRotation()} to un-freeze the rotation.
- * @throws RemoteException never
+ * Sets the display with {@code displayId} to {@code orientation} and freezes its rotation.
+ * <p>Note: The orientation is relative to the natural orientation which depends on the
+ * device type (e.g. phone vs. tablet).
+ * <p>Note: Some secondary displays don't have rotation sensors and therefore won't respond
+ * to {@link Orientation#UNFROZEN}.
+ *
+ * @param orientation the desired orientation
+ * @param displayId The display ID to match. Use {@link Display#getDisplayId()} to get the ID.
*/
- public void setOrientationLandscape() throws RemoteException {
- Log.d(TAG, "Setting orientation to landscape.");
- if (getDisplayWidth() > getDisplayHeight()) {
- freezeRotation(); // Already in landscape orientation.
- } else if (isNaturalOrientation()) {
- rotate(UiAutomation.ROTATION_FREEZE_90);
- } else {
- rotate(UiAutomation.ROTATION_FREEZE_0);
+ @RequiresApi(30)
+ public void setOrientation(@NonNull Orientation orientation, int displayId) {
+ Log.d(TAG, String.format("Setting orientation of display %d to %s.", displayId,
+ orientation.name()));
+ switch (orientation) {
+ case ROTATION_90:
+ rotateWithCommand(Surface.ROTATION_90, displayId);
+ break;
+ case ROTATION_270:
+ rotateWithCommand(Surface.ROTATION_270, displayId);
+ break;
+ case ROTATION_0:
+ rotateWithCommand(Surface.ROTATION_0, displayId);
+ break;
+ case ROTATION_180:
+ rotateWithCommand(Surface.ROTATION_180, displayId);
+ break;
+ case PORTRAIT:
+ if (getDisplayHeight() >= getDisplayWidth()) {
+ // Freeze. Already in portrait orientation.
+ freezeWithCommand(displayId);
+ } else if (isNaturalOrientation(displayId)) {
+ rotateWithCommand(Surface.ROTATION_90, displayId);
+ } else {
+ rotateWithCommand(Surface.ROTATION_0, displayId);
+ }
+ break;
+ case LANDSCAPE:
+ if (getDisplayHeight() <= getDisplayWidth()) {
+ // Freeze. Already in landscape orientation.
+ freezeWithCommand(displayId);
+ } else if (isNaturalOrientation(displayId)) {
+ rotateWithCommand(Surface.ROTATION_90, displayId);
+ } else {
+ rotateWithCommand(Surface.ROTATION_0, displayId);
+ }
+ break;
+ case FROZEN:
+ freezeWithCommand(displayId);
+ break;
+ case UNFROZEN:
+ unfreezeWithCommand(displayId);
+ break;
+ default:
+ throw new IllegalArgumentException(
+ "Invalid input orientation for UiDevice#setOrientation.");
}
}
- // Rotates the device and waits for the rotation to be detected.
- private void rotate(int rotation) {
+ /** Rotates the display using UiAutomation and waits for the rotation to be detected. */
+ private void rotateWithUiAutomation(int rotation) {
getUiAutomation().setRotation(rotation);
+ waitRotationComplete(rotation, Display.DEFAULT_DISPLAY);
+ }
+
+ /** Rotates the display using shell command and waits for the rotation to be detected. */
+ @RequiresApi(30)
+ private void rotateWithCommand(int rotation, int displayId) {
+ try {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ executeShellCommand(String.format("cmd window user-rotation -d %d lock %d",
+ displayId, rotation));
+ } else {
+ executeShellCommand(String.format("cmd window set-user-rotation lock -d %d %d",
+ displayId, rotation));
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ waitRotationComplete(rotation, displayId);
+ }
+
+ /** Freezes the display using shell command. */
+ @RequiresApi(30)
+ private void freezeWithCommand(int displayId) {
+ try {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ executeShellCommand(String.format("cmd window user-rotation -d %d lock",
+ displayId));
+ } else {
+ int rotation = getDisplayRotation(displayId);
+ executeShellCommand(String.format("cmd window set-user-rotation lock -d %d %d",
+ displayId, rotation));
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /** Unfreezes the display using shell command. */
+ @RequiresApi(30)
+ private void unfreezeWithCommand(int displayId) {
+ try {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ executeShellCommand(String.format("cmd window user-rotation -d %d free",
+ displayId));
+ } else {
+ executeShellCommand(String.format("cmd window set-user-rotation free -d %d",
+ displayId));
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /** Waits for the display with {@code displayId} to be in {@code rotation}. */
+ private void waitRotationComplete(int rotation, int displayId) {
Condition<UiDevice, Boolean> rotationCondition = new Condition<UiDevice, Boolean>() {
@Override
public Boolean apply(UiDevice device) {
- return device.getDisplayRotation() == rotation;
+ return device.getDisplayRotation(displayId) == rotation;
}
@NonNull
@Override
public String toString() {
- return String.format("Condition[displayRotation=%d]", rotation);
+ return String.format("Condition[displayRotation=%d, displayId=%d]", rotation,
+ displayId);
}
};
if (!wait(rotationCondition, ROTATION_TIMEOUT)) {
diff --git a/testutils/testutils-common/src/main/java/androidx/testutils/ParameterizedHelper.kt b/testutils/testutils-common/src/main/java/androidx/testutils/ParameterizedHelper.kt
index 236b362..844f968 100644
--- a/testutils/testutils-common/src/main/java/androidx/testutils/ParameterizedHelper.kt
+++ b/testutils/testutils-common/src/main/java/androidx/testutils/ParameterizedHelper.kt
@@ -33,6 +33,7 @@
*
* See [ParameterizedHelperTest] for more examples.
*/
+// TODO(kuanyingchou): Remove and replace with TestParameterInjector"
fun generateAllEnumerations(vararg args: List<Any>): List<Array<Any>> =
generateAllEnumerationsIteratively(args.toList()).map { it.toTypedArray() }
diff --git a/testutils/testutils-fonts/build.gradle b/testutils/testutils-fonts/build.gradle
index 911cf9b..c731e06 100644
--- a/testutils/testutils-fonts/build.gradle
+++ b/testutils/testutils-fonts/build.gradle
@@ -31,7 +31,7 @@
}
dependencies {
- implementation(libs.kotlinStdlib)
+ commonMainImplementation(libs.kotlinStdlib)
}
android {
diff --git a/transition/transition/api/current.txt b/transition/transition/api/current.txt
index 672d6db..b6bf3ae 100644
--- a/transition/transition/api/current.txt
+++ b/transition/transition/api/current.txt
@@ -209,6 +209,7 @@
method public static void beginDelayedTransition(android.view.ViewGroup);
method public static void beginDelayedTransition(android.view.ViewGroup, androidx.transition.Transition?);
method public static androidx.transition.TransitionSeekController? controlDelayedTransition(android.view.ViewGroup, androidx.transition.Transition);
+ method public static androidx.transition.TransitionSeekController? createSeekController(androidx.transition.Scene, androidx.transition.Transition);
method public static void endTransitions(android.view.ViewGroup?);
method public static void go(androidx.transition.Scene);
method public static void go(androidx.transition.Scene, androidx.transition.Transition?);
@@ -225,14 +226,18 @@
}
public interface TransitionSeekController {
+ method public void addOnProgressChangedListener(androidx.core.util.Consumer<androidx.transition.TransitionSeekController!>);
method public void addOnReadyListener(androidx.core.util.Consumer<androidx.transition.TransitionSeekController!>);
method public void animateToEnd();
method public void animateToStart();
- method public long getCurrentPlayTimeMillis();
- method public long getDurationMillis();
+ method @FloatRange(from=0.0, to=1.0) public float getCurrentFraction();
+ method @IntRange(from=0) public long getCurrentPlayTimeMillis();
+ method @IntRange(from=0) public long getDurationMillis();
method public boolean isReady();
+ method public void removeOnProgressChangedListener(androidx.core.util.Consumer<androidx.transition.TransitionSeekController!>);
method public void removeOnReadyListener(androidx.core.util.Consumer<androidx.transition.TransitionSeekController!>);
- method public void setCurrentPlayTimeMillis(long);
+ method public void setCurrentFraction(@FloatRange(from=0.0, to=1.0) float);
+ method public void setCurrentPlayTimeMillis(@IntRange(from=0) long);
}
public class TransitionSet extends androidx.transition.Transition {
diff --git a/transition/transition/api/restricted_current.txt b/transition/transition/api/restricted_current.txt
index 187290f..3f44ab2 100644
--- a/transition/transition/api/restricted_current.txt
+++ b/transition/transition/api/restricted_current.txt
@@ -242,6 +242,7 @@
method public static void beginDelayedTransition(android.view.ViewGroup);
method public static void beginDelayedTransition(android.view.ViewGroup, androidx.transition.Transition?);
method public static androidx.transition.TransitionSeekController? controlDelayedTransition(android.view.ViewGroup, androidx.transition.Transition);
+ method public static androidx.transition.TransitionSeekController? createSeekController(androidx.transition.Scene, androidx.transition.Transition);
method public static void endTransitions(android.view.ViewGroup?);
method public static void go(androidx.transition.Scene);
method public static void go(androidx.transition.Scene, androidx.transition.Transition?);
@@ -258,14 +259,18 @@
}
public interface TransitionSeekController {
+ method public void addOnProgressChangedListener(androidx.core.util.Consumer<androidx.transition.TransitionSeekController!>);
method public void addOnReadyListener(androidx.core.util.Consumer<androidx.transition.TransitionSeekController!>);
method public void animateToEnd();
method public void animateToStart();
- method public long getCurrentPlayTimeMillis();
- method public long getDurationMillis();
+ method @FloatRange(from=0.0, to=1.0) public float getCurrentFraction();
+ method @IntRange(from=0) public long getCurrentPlayTimeMillis();
+ method @IntRange(from=0) public long getDurationMillis();
method public boolean isReady();
+ method public void removeOnProgressChangedListener(androidx.core.util.Consumer<androidx.transition.TransitionSeekController!>);
method public void removeOnReadyListener(androidx.core.util.Consumer<androidx.transition.TransitionSeekController!>);
- method public void setCurrentPlayTimeMillis(long);
+ method public void setCurrentFraction(@FloatRange(from=0.0, to=1.0) float);
+ method public void setCurrentPlayTimeMillis(@IntRange(from=0) long);
}
public class TransitionSet extends androidx.transition.Transition {
diff --git a/transition/transition/build.gradle b/transition/transition/build.gradle
index b899cd4..8ed8a8e 100644
--- a/transition/transition/build.gradle
+++ b/transition/transition/build.gradle
@@ -12,6 +12,7 @@
implementation("androidx.collection:collection:1.1.0")
compileOnly("androidx.fragment:fragment:1.2.5")
compileOnly("androidx.appcompat:appcompat:1.0.1")
+ implementation("androidx.dynamicanimation:dynamicanimation:1.0.0")
androidTestImplementation(libs.kotlinStdlib)
androidTestImplementation(libs.testExtJunit)
diff --git a/transition/transition/src/androidTest/java/androidx/transition/SeekTransitionTest.kt b/transition/transition/src/androidTest/java/androidx/transition/SeekTransitionTest.kt
index 1700300..fd55d1a 100644
--- a/transition/transition/src/androidTest/java/androidx/transition/SeekTransitionTest.kt
+++ b/transition/transition/src/androidTest/java/androidx/transition/SeekTransitionTest.kt
@@ -27,6 +27,7 @@
import androidx.test.filters.SdkSuppress
import androidx.test.platform.app.InstrumentationRegistry
import androidx.testutils.AnimationDurationScaleRule.Companion.createForAllTests
+import androidx.testutils.PollingCheck
import androidx.transition.Transition.TransitionListener
import androidx.transition.test.R
import com.google.common.truth.Truth.assertThat
@@ -43,7 +44,7 @@
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
-@SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU)
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
@MediumTest
class SeekTransitionTest : BaseTest() {
@get:Rule
@@ -69,7 +70,6 @@
@Test(expected = IllegalArgumentException::class)
@UiThreadTest
fun onlySeekingTransitions() {
- if (Build.VERSION.SDK_INT < 34) throw IllegalArgumentException()
transition = object : Visibility() {}
TransitionManager.controlDelayedTransition(root, transition)
fail("Expected IllegalArgumentException")
@@ -77,7 +77,6 @@
@Test
fun waitForReady() {
- if (Build.VERSION.SDK_INT < 34) return
lateinit var seekController: TransitionSeekController
@Suppress("UNCHECKED_CAST")
@@ -99,7 +98,6 @@
@Test
fun waitForReadyNoChange() {
- if (Build.VERSION.SDK_INT < 34) return
lateinit var seekController: TransitionSeekController
@Suppress("UNCHECKED_CAST")
@@ -120,7 +118,6 @@
@Test
fun addListenerAfterReady() {
- if (Build.VERSION.SDK_INT < 34) return
lateinit var seekController: TransitionSeekController
@Suppress("UNCHECKED_CAST")
@@ -148,7 +145,6 @@
@Test
fun seekTransition() {
- if (Build.VERSION.SDK_INT < 34) return
lateinit var seekController: TransitionSeekController
val listener = spy(TransitionListenerAdapter())
@@ -170,15 +166,60 @@
assertThat(seekController.durationMillis).isEqualTo(300)
assertThat(seekController.currentPlayTimeMillis).isEqualTo(0)
+ assertThat(seekController.currentFraction).isEqualTo(0f)
assertThat(view.transitionAlpha).isEqualTo(1f)
seekController.currentPlayTimeMillis = 150
+ assertThat(seekController.currentFraction).isEqualTo(0.5f)
assertThat(view.transitionAlpha).isEqualTo(0.5f)
seekController.currentPlayTimeMillis = 299
+ assertThat(seekController.currentFraction).isWithin(0.001f).of(299f / 300f)
assertThat(view.transitionAlpha).isWithin(0.001f).of(1f / 300f)
seekController.currentPlayTimeMillis = 300
+ assertThat(seekController.currentFraction).isEqualTo(1f)
+ verify(listener, times(1)).onTransitionEnd(any())
+ assertThat(view.transitionAlpha).isEqualTo(1f)
+ assertThat(view.visibility).isEqualTo(View.GONE)
+ }
+ }
+
+ @Test
+ fun seekTransitionWithFraction() {
+ lateinit var seekController: TransitionSeekController
+
+ val listener = spy(TransitionListenerAdapter())
+ transition.addListener(listener)
+
+ rule.runOnUiThread {
+ val controller = TransitionManager.controlDelayedTransition(root, transition)
+ assertThat(controller).isNotNull()
+ seekController = controller!!
+ assertThat(seekController.isReady).isFalse()
+ view.visibility = View.GONE
+ }
+
+ verify(listener, timeout(1000)).onTransitionStart(any())
+ verify(listener, times(0)).onTransitionEnd(any())
+
+ rule.runOnUiThread {
+ assertThat(view.visibility).isEqualTo(View.VISIBLE)
+
+ assertThat(seekController.durationMillis).isEqualTo(300)
+ assertThat(seekController.currentPlayTimeMillis).isEqualTo(0)
+ assertThat(seekController.currentFraction).isEqualTo(0f)
+
+ assertThat(view.transitionAlpha).isEqualTo(1f)
+
+ seekController.currentFraction = 0.5f
+ assertThat(seekController.currentPlayTimeMillis).isEqualTo(150)
+ assertThat(view.transitionAlpha).isEqualTo(0.5f)
+ seekController.currentFraction = 299f / 300f
+ assertThat(seekController.currentPlayTimeMillis).isEqualTo(299)
+ assertThat(view.transitionAlpha).isWithin(0.001f).of(1f / 300f)
+ seekController.currentFraction = 1f
+ assertThat(seekController.currentPlayTimeMillis).isEqualTo(300)
verify(listener, times(1)).onTransitionEnd(any())
assertThat(view.transitionAlpha).isEqualTo(1f)
@@ -188,7 +229,6 @@
@Test
fun animationDoesNotTakeOverSeek() {
- if (Build.VERSION.SDK_INT < 34) return
lateinit var seekController: TransitionSeekController
val stateListener1 = spy(TransitionListenerAdapter())
@@ -231,7 +271,6 @@
@Test
fun seekCannotTakeOverAnimation() {
- if (Build.VERSION.SDK_INT < 34) return
lateinit var seekController: TransitionSeekController
val stateListener1 = spy(TransitionListenerAdapter())
@@ -271,7 +310,6 @@
@Test
fun seekCannotTakeOverSeek() {
- if (Build.VERSION.SDK_INT < 34) return
lateinit var seekController1: TransitionSeekController
val stateListener1 = spy(TransitionListenerAdapter())
@@ -317,7 +355,6 @@
@Test
fun seekReplacesSeek() {
- if (Build.VERSION.SDK_INT < 34) return
lateinit var seekController1: TransitionSeekController
val stateListener1 = spy(TransitionListenerAdapter())
@@ -360,7 +397,6 @@
@Test
fun animateToEnd() {
- if (Build.VERSION.SDK_INT < 34) return
lateinit var seekController: TransitionSeekController
val listener = spy(TransitionListenerAdapter())
@@ -387,7 +423,6 @@
@Test
fun animateToStart() {
- if (Build.VERSION.SDK_INT < 34) return
lateinit var seekController: TransitionSeekController
val listener = spy(TransitionListenerAdapter())
@@ -428,7 +463,6 @@
@Test
fun animateToStartAfterAnimateToEnd() {
- if (Build.VERSION.SDK_INT < 34) return
lateinit var seekController: TransitionSeekController
val listener = spy(TransitionListenerAdapter())
@@ -448,7 +482,7 @@
seekController.animateToStart()
}
- verify(listener, timeout(3000)).onTransitionEnd(any())
+ verify(listener, timeout(3000)).onTransitionEnd(any(), eq(true))
rule.runOnUiThread {
assertThat(view.visibility).isEqualTo(View.VISIBLE)
@@ -458,7 +492,6 @@
@Test
fun animateToEndAfterAnimateToStart() {
- if (Build.VERSION.SDK_INT < 34) return
lateinit var seekController: TransitionSeekController
val listener = spy(TransitionListenerAdapter())
@@ -488,7 +521,6 @@
@Test(expected = IllegalStateException::class)
fun seekAfterAnimate() {
- if (Build.VERSION.SDK_INT < 34) throw IllegalStateException("Not supported before U")
lateinit var seekController: TransitionSeekController
transition.duration = 5000
@@ -507,9 +539,28 @@
}
}
+ @Test(expected = IllegalStateException::class)
+ fun seekFractionAfterAnimate() {
+ lateinit var seekController: TransitionSeekController
+ transition.duration = 5000
+
+ rule.runOnUiThread {
+ seekController = TransitionManager.controlDelayedTransition(root, transition)!!
+ view.visibility = View.GONE
+ }
+
+ rule.runOnUiThread {
+ seekController.currentFraction = 0.5f
+ seekController.animateToEnd()
+ }
+
+ rule.runOnUiThread {
+ seekController.currentFraction = 0.2f
+ }
+ }
+
@Test
fun seekTransitionSet() {
- if (Build.VERSION.SDK_INT < 34) return
transition = TransitionSet().also {
it.addTransition(Fade(Fade.MODE_OUT))
.addTransition(Fade(Fade.MODE_IN))
@@ -579,7 +630,6 @@
@Test
fun animateToEndTransitionSet() {
- if (Build.VERSION.SDK_INT < 34) return
transition = TransitionSet().also {
it.addTransition(Fade(Fade.MODE_OUT))
.addTransition(Fade(Fade.MODE_IN))
@@ -629,7 +679,6 @@
@Test
fun animateToStartTransitionSet() {
- if (Build.VERSION.SDK_INT < 34) return
transition = TransitionSet().also {
it.addTransition(Fade(Fade.MODE_OUT))
.addTransition(Fade(Fade.MODE_IN))
@@ -695,7 +744,6 @@
@Test
fun cancelPartOfTransitionSet() {
- if (Build.VERSION.SDK_INT < 34) return
transition = TransitionSet().also {
it.addTransition(Fade(Fade.MODE_OUT))
.addTransition(Fade(Fade.MODE_IN))
@@ -763,7 +811,6 @@
@Test
fun onTransitionCallsForwardAndReversed() {
- if (Build.VERSION.SDK_INT < 34) return
val listener = spy(TransitionListenerAdapter())
transition = Fade()
transition.addListener(listener)
@@ -792,7 +839,6 @@
@Test
fun onTransitionCallsForwardAndReversedTransitionSet() {
- if (Build.VERSION.SDK_INT < 34) return
val fadeOut = Fade(Fade.MODE_OUT)
val outListener = spy(TransitionListenerAdapter())
fadeOut.addListener(outListener)
@@ -881,7 +927,6 @@
@Test
fun pauseResumeOnSeek() {
- if (Build.VERSION.SDK_INT < 34) return
var pauseCount = 0
var resumeCount = 0
var setPauseCount = 0
@@ -942,4 +987,131 @@
assertThat(setResumeCount).isEqualTo(1)
}
}
+
+ @Test
+ fun animationListener() {
+ lateinit var seekController: TransitionSeekController
+ var animatedFraction = -1f
+ var animatedMillis = -1L
+ rule.runOnUiThread {
+ seekController = TransitionManager.controlDelayedTransition(root, Fade())!!
+ view.visibility = View.GONE
+
+ seekController.addOnProgressChangedListener {
+ animatedFraction = it.currentFraction
+ animatedMillis = it.currentPlayTimeMillis
+ }
+ }
+
+ rule.runOnUiThread {
+ assertThat(animatedFraction).isEqualTo(0f)
+ assertThat(animatedMillis).isEqualTo(0)
+ seekController.currentFraction = 0.25f
+ assertThat(animatedFraction).isEqualTo(0.25f)
+ assertThat(animatedMillis).isEqualTo(75)
+ seekController.animateToEnd()
+ }
+
+ PollingCheck.waitFor {
+ animatedFraction == 1f
+ }
+ }
+
+ @Test
+ fun animationListenerRemoval() {
+ lateinit var seekController: TransitionSeekController
+ rule.runOnUiThread {
+ seekController = TransitionManager.controlDelayedTransition(root, Fade())!!
+ view.visibility = View.GONE
+ }
+
+ var animatedFraction = -1f
+ var animatedMillis = -1L
+ val removeListener = object : Consumer<TransitionSeekController> {
+ override fun accept(t: TransitionSeekController?) {
+ seekController.removeOnProgressChangedListener(this)
+ }
+ }
+ seekController.addOnProgressChangedListener(removeListener)
+ val changeListener = Consumer<TransitionSeekController> {
+ animatedFraction = it.currentFraction
+ animatedMillis = it.currentPlayTimeMillis
+ }
+ seekController.addOnProgressChangedListener(changeListener)
+
+ rule.runOnUiThread {
+ assertThat(animatedFraction).isEqualTo(0f)
+ assertThat(animatedMillis).isEqualTo(0)
+ seekController.removeOnProgressChangedListener(changeListener)
+ seekController.currentFraction = 0.25f
+ assertThat(animatedFraction).isEqualTo(0)
+ assertThat(animatedMillis).isEqualTo(0)
+ }
+ }
+
+ @Test
+ fun seekToScene() {
+ lateinit var seekController: TransitionSeekController
+ val scene1 = Scene(root, view)
+ val view2 = View(view.context)
+ val scene2 = Scene(root, view2)
+ rule.runOnUiThread {
+ TransitionManager.go(scene1)
+ }
+
+ rule.runOnUiThread {
+ val controller = TransitionManager.createSeekController(scene2, Fade())
+ assertThat(controller).isNotNull()
+ seekController = controller!!
+ }
+
+ rule.runOnUiThread {
+ assertThat(seekController.currentFraction).isEqualTo(0f)
+ assertThat(view.visibility).isEqualTo(View.VISIBLE)
+ assertThat(view.transitionAlpha).isEqualTo(1f)
+ assertThat(view.isAttachedToWindow).isTrue()
+ assertThat(view2.visibility).isEqualTo(View.VISIBLE)
+ assertThat(view2.transitionAlpha).isEqualTo(0f)
+ assertThat(view2.isAttachedToWindow).isTrue()
+ seekController.currentFraction = 1f
+ assertThat(view.visibility).isEqualTo(View.VISIBLE)
+ assertThat(view.transitionAlpha).isEqualTo(1f)
+ assertThat(view.isAttachedToWindow).isFalse()
+ assertThat(view2.visibility).isEqualTo(View.VISIBLE)
+ assertThat(view2.transitionAlpha).isEqualTo(1f)
+ assertThat(view2.isAttachedToWindow).isTrue()
+ }
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun seekToScene_notSupportedTransition() {
+ class NoSeekingTransition : Fade() {
+ override fun isSeekingSupported(): Boolean = false
+ }
+ val scene1 = Scene(root, view)
+ val view2 = View(view.context)
+ val scene2 = Scene(root, view2)
+ rule.runOnUiThread {
+ TransitionManager.go(scene1)
+ }
+
+ rule.runOnUiThread {
+ TransitionManager.createSeekController(scene2, NoSeekingTransition())
+ }
+ }
+
+ @Test
+ fun seekToScene_alreadyRunningTransition() {
+ val scene1 = Scene(root, view)
+ val view2 = View(view.context)
+ val scene2 = Scene(root, view2)
+ rule.runOnUiThread {
+ TransitionManager.go(scene1)
+ }
+
+ rule.runOnUiThread {
+ TransitionManager.go(scene2, Fade())
+ assertThat(TransitionManager.createSeekController(scene1, Fade())).isNull()
+ }
+ }
}
diff --git a/transition/transition/src/androidTest/java/androidx/transition/VelocityTracker1DTest.kt b/transition/transition/src/androidTest/java/androidx/transition/VelocityTracker1DTest.kt
new file mode 100644
index 0000000..d74c93e
--- /dev/null
+++ b/transition/transition/src/androidTest/java/androidx/transition/VelocityTracker1DTest.kt
@@ -0,0 +1,430 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.transition
+
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth
+import com.google.common.truth.Truth.assertThat
+import kotlin.math.abs
+import org.junit.Test
+
+// Velocities between (1-Tolerance)*RV and (1+Tolerance)*RV are accepted
+// where RV is the "Real Velocity"
+private const val Tolerance: Float = 0.2f
+
+@SmallTest
+class VelocityTracker1DTest : BaseTest() {
+ @Test
+ fun twoPoints_nonDifferentialValues() {
+ checkTestCase(
+ VelocityTrackingTestCase(
+ dataPoints = listOf(1 to 5f, 2 to 15f),
+ expectedVelocity = 10000f
+ )
+ )
+ }
+
+ @Test
+ fun threePoints_pointerStoppedMoving_nonDifferentialValues() {
+ checkTestCase(
+ VelocityTrackingTestCase(
+ dataPoints = listOf(
+ 25 to 25f,
+ 50 to 50f,
+ 100 to 100f,
+ ),
+ // Expect 0 velocities, as the pointer will be considered to have stopped moving,
+ // due to the (100-50)=40ms gap from the last data point (i.e. it's effectively
+ // a data set with only 1 data point).
+ expectedVelocity = 0f,
+ )
+ )
+ }
+
+ /** Impulse strategy specific test cases. */
+ @Test
+ fun threePoints_zeroVelocity_nonDifferentialValues() {
+ checkTestCase(
+ VelocityTrackingTestCase(
+ dataPoints = listOf(
+ 0 to 273f,
+ 1 to 273f,
+ 2 to 273f,
+ ),
+ expectedVelocity = 0f
+ ),
+ )
+ }
+
+ @Test
+ fun resetTracking_defaultConstructor() {
+ // Fixed velocity at 5 points per 10 milliseconds
+ val tracker = VelocityTracker1D()
+ tracker.addDataPoint(0, 0f)
+ tracker.addDataPoint(10, 5f)
+ tracker.addDataPoint(20, 10f)
+ tracker.addDataPoint(30, 15f)
+ tracker.addDataPoint(40, 30f)
+
+ tracker.resetTracking()
+
+ assertThat(tracker.calculateVelocity()).isZero()
+ }
+
+ @Test
+ fun resetTracking_nonDifferentialValues_impulse() {
+ // Fixed velocity at 5 points per 10 milliseconds
+ val tracker = VelocityTracker1D()
+ tracker.addDataPoint(0, 0f)
+ tracker.addDataPoint(10, 5f)
+ tracker.addDataPoint(20, 10f)
+ tracker.addDataPoint(30, 15f)
+ tracker.addDataPoint(40, 30f)
+
+ tracker.resetTracking()
+
+ assertThat(tracker.calculateVelocity()).isZero()
+ }
+
+ @Test
+ fun linearMotion_positiveVelocity_positiveDataPoints_nonDifferentialValues() {
+ // Fixed velocity at 5 points per 10 milliseconds
+ checkTestCase(
+ VelocityTrackingTestCase(
+ dataPoints = listOf(
+ 0 to 0f,
+ 10 to 5f,
+ 20 to 10f,
+ 30 to 15f,
+ ),
+ expectedVelocity = 500f,
+ )
+ )
+ }
+
+ @Test
+ fun linearMotion_positiveVelocity_negativeDataPoints_nonDifferentialValues() {
+ // Fixed velocity at 5 points per 10 milliseconds
+ checkTestCase(
+ VelocityTrackingTestCase(
+ dataPoints = listOf(
+ 0 to -20f,
+ 10 to -15f,
+ 20 to -10f,
+ ),
+ expectedVelocity = 500f,
+ )
+ )
+ }
+
+ @Test
+ fun linearMotion_positiveVelocity_mixedSignDataPoints_nonDifferentialValues() {
+ // Fixed velocity at 5 points per 10 milliseconds
+ checkTestCase(
+ VelocityTrackingTestCase(
+ dataPoints = listOf(
+ 0 to -5f,
+ 10 to 0f,
+ 20 to 5f,
+ ),
+ expectedVelocity = 500f,
+ )
+ )
+ }
+
+ @Test
+ fun linearMotion_negativeVelocity_negativeDataPoints_nonDifferentialValues() {
+ // Fixed velocity at 5 points per 10 milliseconds
+ checkTestCase(
+ VelocityTrackingTestCase(
+ dataPoints = listOf(
+ 0 to 0f,
+ 10 to -5f,
+ 20 to -10f,
+ ),
+ expectedVelocity = -500f,
+ )
+ )
+ }
+
+ @Test
+ fun linearMotion_negativeVelocity_postiveDataPoints_nonDifferentialValues() {
+ // Fixed velocity at 5 points per 10 milliseconds
+ checkTestCase(
+ VelocityTrackingTestCase(
+ dataPoints = listOf(
+ 0 to 20f,
+ 10 to 15f,
+ 20 to 10f,
+ ),
+ expectedVelocity = -500f,
+ )
+ )
+ }
+
+ @Test
+ fun linearMotion_negativeVelocity_mixedSignDataPoints_nonDifferentialValues() {
+ // Fixed velocity at 5 points per 10 milliseconds
+ checkTestCase(
+ VelocityTrackingTestCase(
+ dataPoints = listOf(
+ 0 to 5f,
+ 10 to 0f,
+ 20 to -5f,
+ ),
+ expectedVelocity = -500f,
+ )
+ )
+ }
+
+ @Test
+ fun linearHalfMotion() {
+ // Stay still for 50 ms, and then move 100 points in the final 50 ms.
+ // The final line is sloped at 2 units/ms.
+ // This can be visualized as 2 lines: flat line (50ms), and line with slope of 2 units/ms.
+ checkTestCase(
+ VelocityTrackingTestCase(
+ dataPoints = listOf(
+ 0 to 0f,
+ 10 to 0f,
+ 20 to 0f,
+ 30 to 0f,
+ 40 to 0f,
+ 50 to 0f,
+ 60 to 20f,
+ 70 to 40f,
+ 80 to 60f,
+ 90 to 80f,
+ 100 to 100f,
+ ),
+ expectedVelocity = 2000f
+ ),
+ )
+ }
+
+ @Test
+ fun linearHalfMotionSampled() {
+ // Linear half motion, but sampled much less frequently. The resulting velocity is higher
+ // than the previous test, because the path looks significantly different now if you
+ // were to just plot these points.
+ checkTestCase(
+ VelocityTrackingTestCase(
+ dataPoints = listOf(
+ 0 to 0f,
+ 30 to 0f,
+ 40 to 0f,
+ 70 to 40f,
+ 100 to 100f,
+ ),
+ expectedVelocity = 2018.2f
+ )
+ )
+ }
+
+ @Test
+ fun linearMotionFollowedByFlatLine() {
+ // Fixed velocity at first, but flat line afterwards.
+ checkTestCase(
+ VelocityTrackingTestCase(
+ dataPoints = listOf(
+ 0 to 0f,
+ 10 to 10f,
+ 20 to 20f,
+ 30 to 30f,
+ 40 to 40f,
+ 50 to 50f,
+ 60 to 50f,
+ 70 to 50f,
+ 80 to 50f,
+ 90 to 50f,
+ 100 to 50f,
+ ),
+ expectedVelocity = 1000f
+ )
+ )
+ }
+
+ @Test
+ fun linearMotionFollowedByFlatLineWithoutIntermediatePoints() {
+ // Fixed velocity at first, but flat line afterwards
+ checkTestCase(
+ VelocityTrackingTestCase(
+ dataPoints = listOf(
+ 0 to 0f,
+ 50 to 50f,
+ 100 to 50f,
+ ),
+ expectedVelocity = 0f
+ ),
+ )
+ }
+
+ @Test
+ fun swordfishFlingDown_xValues() {
+ checkTestCase(
+ VelocityTrackingTestCase(
+ dataPoints = listOf(
+ 0 to 271f,
+ 16 to 269.786346f,
+ 35 to 267.983063f,
+ 52 to 262.638397f,
+ 68 to 266.138824f,
+ 85 to 274.79245f,
+ 96 to 274.79245f,
+ ),
+ expectedVelocity = 623.57f
+ )
+ )
+ }
+
+ @Test
+ fun swordfishFlingDown_yValues() {
+ checkTestCase(
+ VelocityTrackingTestCase(
+ dataPoints = listOf(
+ 0 to 96f,
+ 16 to 106.922775f,
+ 35 to 156.660034f,
+ 52 to 220.339081f,
+ 68 to 331.581116f,
+ 85 to 428.113159f,
+ 96 to 428.113159f,
+ ),
+ expectedVelocity = 5970.73f
+ )
+ )
+ }
+
+ @Test
+ fun sailfishFlingUpSlow_xValues() {
+ checkTestCase(
+ VelocityTrackingTestCase(
+ dataPoints = listOf(
+ 235089067 to 528.0f,
+ 235089084 to 527.0f,
+ 235089093 to 527.0f,
+ 235089095 to 527.0f,
+ 235089101 to 527.0f,
+ 235089110 to 528.0f,
+ 235089112 to 528.25f,
+ 235089118 to 531.0f,
+ 235089126 to 535.0f,
+ 235089129 to 536.33f,
+ 235089135 to 540.0f,
+ 235089144 to 546.0f,
+ 235089146 to 547.21f,
+ 235089152 to 553.0f,
+ 235089160 to 559.0f,
+ 235089162 to 560.66f,
+ ),
+ expectedVelocity = 764.34f,
+ )
+ )
+ }
+
+ @Test
+ fun sailfishFlingUpSlow_yValues() {
+ checkTestCase(
+ VelocityTrackingTestCase(
+ dataPoints = listOf(
+ 235089067 to 983.0f,
+ 235089084 to 981.0f,
+ 235089093 to 977.0f,
+ 235089095 to 975.93f,
+ 235089101 to 970.0f,
+ 235089110 to 960.0f,
+ 235089112 to 957.51f,
+ 235089118 to 946.0f,
+ 235089126 to 931.0f,
+ 235089129 to 926.02f,
+ 235089135 to 914.0f,
+ 235089144 to 896.0f,
+ 235089146 to 892.36f,
+ 235089152 to 877.0f,
+ 235089160 to 851.0f,
+ 235089162 to 843.82f,
+ ),
+ expectedVelocity = -3604.82f,
+ )
+ )
+ }
+
+ @Test
+ fun sailfishFlingUpFast_xValues() {
+ // Some "repeated" data points are removed, since the conversion from ns to ms made some
+ // data ponits "repeated"
+ checkTestCase(
+ VelocityTrackingTestCase(
+ dataPoints = listOf(
+ 920922 to 561.0f,
+ 920930 to 559.0f,
+ 920938 to 559.0f,
+ 920947 to 562.91f,
+ 920955 to 577.0f,
+ 920963 to 596.87f,
+ 920972 to 631.0f,
+ 920980 to 671.31f,
+ 920989 to 715.0f,
+ ),
+ expectedVelocity = 5670.32f,
+ )
+ )
+ }
+
+ @Test
+ fun sailfishFlingUpFast_yValues() {
+ // Some "repeated" data points are removed, since the conversion from ns to ms made some
+ // data ponits "repeated"
+ checkTestCase(
+ VelocityTrackingTestCase(
+ dataPoints = listOf(
+ 920922 to 1412.0f,
+ 920930 to 1377.0f,
+ 920938 to 1371.0f,
+ 920947 to 1342.68f,
+ 920955 to 1272.0f,
+ 920963 to 1190.54f,
+ 920972 to 1093.0f,
+ 920980 to 994.68f,
+ 920989 to 903.0f,
+ ),
+ expectedVelocity = -13021.10f,
+ )
+ )
+ }
+
+ private fun checkTestCase(testCase: VelocityTrackingTestCase) {
+ val expectedVelocity = testCase.expectedVelocity
+ val tracker = VelocityTracker1D()
+ testCase.dataPoints.forEach {
+ tracker.addDataPoint(it.first.toLong(), it.second)
+ }
+
+ Truth.assertWithMessage(
+ "Wrong velocity for data points: ${testCase.dataPoints}" +
+ "\nExpected velocity: {$expectedVelocity}"
+ )
+ .that(tracker.calculateVelocity())
+ .isWithin(abs(expectedVelocity) * Tolerance)
+ .of(expectedVelocity)
+ }
+}
+
+/** Holds configs for a velocity tracking test case, for convenience. */
+private data class VelocityTrackingTestCase(
+ val dataPoints: List<Pair<Int, Float>>,
+ val expectedVelocity: Float
+)
diff --git a/transition/transition/src/main/java/androidx/transition/Transition.java b/transition/transition/src/main/java/androidx/transition/Transition.java
index c2c0986..9cdb387 100644
--- a/transition/transition/src/main/java/androidx/transition/Transition.java
+++ b/transition/transition/src/main/java/androidx/transition/Transition.java
@@ -22,7 +22,6 @@
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.TimeInterpolator;
-import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
@@ -55,6 +54,10 @@
import androidx.core.content.res.TypedArrayUtils;
import androidx.core.util.Consumer;
import androidx.core.view.ViewCompat;
+import androidx.dynamicanimation.animation.DynamicAnimation;
+import androidx.dynamicanimation.animation.FloatValueHolder;
+import androidx.dynamicanimation.animation.SpringAnimation;
+import androidx.dynamicanimation.animation.SpringForce;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -1920,7 +1923,7 @@
runAnimators();
} else if (Build.VERSION.SDK_INT >= 34) {
prepareAnimatorsForSeeking();
- mSeekController.setCurrentPlayTimeMillis(0);
+ mSeekController.initPlayTime();
mSeekController.ready();
}
}
@@ -2690,14 +2693,17 @@
*/
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
class SeekController extends TransitionListenerAdapter implements TransitionSeekController,
- ValueAnimator.AnimatorUpdateListener {
+ DynamicAnimation.OnAnimationUpdateListener {
+ // Animation calculations appear to work better with numbers that range greater than 1
private long mCurrentPlayTime = -1;
private ArrayList<Consumer<TransitionSeekController>> mOnReadyListeners = null;
+ private ArrayList<Consumer<TransitionSeekController>> mOnProgressListeners = null;
private boolean mIsReady;
private boolean mIsCanceled;
- private ValueAnimator mAnimator;
- private boolean mIsAnimatingReversed;
+ private SpringAnimation mSpringAnimation;
+ private Consumer<TransitionSeekController>[] mListenerCache = null;
+ private final VelocityTracker1D mVelocityTracker = new VelocityTracker1D();
@Override
public long getDurationMillis() {
@@ -2710,6 +2716,11 @@
}
@Override
+ public float getCurrentFraction() {
+ return ((float) getCurrentPlayTimeMillis()) / ((float) getDurationMillis());
+ }
+
+ @Override
public boolean isReady() {
return mIsReady;
}
@@ -2723,34 +2734,54 @@
onReadyListeners.get(i).accept(this);
}
}
+ callProgressListeners();
}
@Override
public void setCurrentPlayTimeMillis(long playTimeMillis) {
- if (mAnimator != null) {
+ if (mSpringAnimation != null) {
throw new IllegalStateException("setCurrentPlayTimeMillis() called after animation "
+ "has been started");
}
- if (playTimeMillis == mCurrentPlayTime) {
+ if (playTimeMillis == mCurrentPlayTime || !isReady()) {
return; // no change
}
+ long targetPlayTime = playTimeMillis;
if (!mIsCanceled) {
- if (playTimeMillis == 0 && mCurrentPlayTime > 0) {
+ if (targetPlayTime == 0 && mCurrentPlayTime > 0) {
// Force the transition to end
- playTimeMillis = -1;
+ targetPlayTime = -1;
} else {
long duration = getDurationMillis();
// Force the transition to the end
- if (playTimeMillis == duration && mCurrentPlayTime < duration) {
- playTimeMillis = duration + 1;
+ if (targetPlayTime == duration && mCurrentPlayTime < duration) {
+ targetPlayTime = duration + 1;
}
}
- if (playTimeMillis != mCurrentPlayTime) {
- Transition.this.setCurrentPlayTimeMillis(playTimeMillis, mCurrentPlayTime);
- mCurrentPlayTime = playTimeMillis;
+ if (targetPlayTime != mCurrentPlayTime) {
+ Transition.this.setCurrentPlayTimeMillis(targetPlayTime, mCurrentPlayTime);
+ mCurrentPlayTime = targetPlayTime;
}
}
+ callProgressListeners();
+ mVelocityTracker.addDataPoint(AnimationUtils.currentAnimationTimeMillis(),
+ (float) targetPlayTime);
+ }
+
+ void initPlayTime() {
+ long playTime = (getDurationMillis() == 0) ? 1 : 0;
+ Transition.this.setCurrentPlayTimeMillis(playTime, mCurrentPlayTime);
+ mCurrentPlayTime = playTime;
+ }
+
+ @Override
+ public void setCurrentFraction(float fraction) {
+ if (mSpringAnimation != null) {
+ throw new IllegalStateException("setCurrentFraction() called after animation "
+ + "has been started");
+ }
+ setCurrentPlayTimeMillis((long) (fraction * getDurationMillis()));
}
@Override
@@ -2785,55 +2816,85 @@
}
@Override
- public void onAnimationUpdate(@NonNull ValueAnimator valueAnimator) {
- long time = Math.max(-1,
- Math.min(getDurationMillis() + 1, mAnimator.getCurrentPlayTime())
- );
- if (mIsAnimatingReversed) {
- time = getDurationMillis() - time;
- }
+ public void onAnimationUpdate(DynamicAnimation animation, float value, float velocity) {
+ long time = Math.max(-1, Math.min(getDurationMillis() + 1, Math.round((double) value)));
Transition.this.setCurrentPlayTimeMillis(time, mCurrentPlayTime);
mCurrentPlayTime = time;
+ callProgressListeners();
}
- private void createAnimator() {
- long duration = getDurationMillis() + 1;
- mAnimator = ValueAnimator.ofInt((int) duration);
- mAnimator.setInterpolator(null);
- mAnimator.setDuration(duration);
- mAnimator.addUpdateListener(this);
+ private void ensureAnimation() {
+ if (mSpringAnimation != null) {
+ return;
+ }
+ mVelocityTracker.addDataPoint(AnimationUtils.currentAnimationTimeMillis(),
+ (float) mCurrentPlayTime);
+ mSpringAnimation = new SpringAnimation(new FloatValueHolder());
+ SpringForce springForce = new SpringForce();
+ springForce.setDampingRatio(SpringForce.DAMPING_RATIO_NO_BOUNCY);
+ springForce.setStiffness(SpringForce.STIFFNESS_LOW);
+ mSpringAnimation.setSpring(springForce);
+ mSpringAnimation.setStartValue((float) mCurrentPlayTime);
+ mSpringAnimation.addUpdateListener(this);
+ mSpringAnimation.setStartVelocity(mVelocityTracker.calculateVelocity());
+ mSpringAnimation.setMaxValue((float) (getDurationMillis() + 1));
+ mSpringAnimation.setMinValue(-1f);
+ mSpringAnimation.setMinimumVisibleChange(4f); // 4 milliseconds ~ 1/2 frame @ 120Hz
+ mSpringAnimation.addEndListener((anim, canceled, value, velocity) -> {
+ if (!canceled) {
+ boolean isReversed = value < 1f;
+ notifyListeners(TransitionNotification.ON_END, isReversed);
+ }
+ mSpringAnimation = null;
+ });
}
@Override
public void animateToEnd() {
- if (mAnimator != null) {
- mAnimator.cancel();
- }
- final long duration = getDurationMillis();
- if (mCurrentPlayTime > duration) {
- return; // we're already at the end
- }
- createAnimator();
- mIsAnimatingReversed = false;
- mAnimator.setCurrentPlayTime(mCurrentPlayTime);
- mAnimator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- notifyListeners(TransitionNotification.ON_END, false);
- }
- });
- mAnimator.start();
+ ensureAnimation();
+ mSpringAnimation.animateToFinalPosition((float) (getDurationMillis() + 1));
}
@Override
public void animateToStart() {
- if (mAnimator != null) {
- mAnimator.cancel();
+ ensureAnimation();
+ mSpringAnimation.animateToFinalPosition(-1);
+ }
+
+ @Override
+ public void addOnProgressChangedListener(
+ @NonNull Consumer<TransitionSeekController> consumer) {
+ if (mOnProgressListeners == null) {
+ mOnProgressListeners = new ArrayList<>();
}
- createAnimator();
- mAnimator.setCurrentPlayTime(getDurationMillis() - mCurrentPlayTime);
- mIsAnimatingReversed = true;
- mAnimator.start();
+ mOnProgressListeners.add(consumer);
+ }
+
+ @Override
+ public void removeOnProgressChangedListener(
+ @NonNull Consumer<TransitionSeekController> consumer) {
+ if (mOnProgressListeners != null) {
+ mOnProgressListeners.remove(consumer);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private void callProgressListeners() {
+ if (mOnProgressListeners == null || mOnProgressListeners.isEmpty()) {
+ return;
+ }
+ int size = mOnProgressListeners.size();
+ if (mListenerCache == null) {
+ mListenerCache = new Consumer[size];
+ }
+ Consumer<TransitionSeekController>[] cache =
+ mOnProgressListeners.toArray(mListenerCache);
+ mListenerCache = null;
+ for (int i = 0; i < size; i++) {
+ cache[i].accept(this);
+ cache[i] = null;
+ }
+ mListenerCache = cache;
}
}
}
diff --git a/transition/transition/src/main/java/androidx/transition/TransitionManager.java b/transition/transition/src/main/java/androidx/transition/TransitionManager.java
index 62ee14c..261738f4 100644
--- a/transition/transition/src/main/java/androidx/transition/TransitionManager.java
+++ b/transition/transition/src/main/java/androidx/transition/TransitionManager.java
@@ -350,6 +350,59 @@
}
/**
+ * Convenience method to seek to the given scene using the given transition. If seeking
+ * is not supported because the device is {@link Build.VERSION_CODES.TIRAMISU} or earlier,
+ * the scene transition is immediate and {@code null} is returned.
+ *
+ * @param scene The Scene to change to
+ * @param transition The transition to use for this scene change.
+ * @return a {@link TransitionSeekController} that can be used control the animation to the
+ * destination scene. {@code null} is returned when seeking is not supported on the scene,
+ * either because it is running on {@link android.os.Build.VERSION_CODES.TIRAMISU} or earlier,
+ * another Transition is being captured for {@code sceneRoot}, or {@code sceneRoot} hasn't
+ * had a layout yet.
+ * @throws IllegalArgumentException if {@code transition} returns {@code false} from
+ * {@link Transition#isSeekingSupported()}.
+ */
+ @Nullable
+ public static TransitionSeekController createSeekController(
+ @NonNull Scene scene,
+ @NonNull Transition transition
+ ) {
+ final ViewGroup sceneRoot = scene.getSceneRoot();
+
+ if (!transition.isSeekingSupported()) {
+ throw new IllegalArgumentException("The Transition must support seeking.");
+ }
+ if (sPendingTransitions.contains(sceneRoot)) {
+ return null; // Already in the process of transitioning
+ }
+ Scene oldScene = Scene.getCurrentScene(sceneRoot);
+ if (!ViewCompat.isLaidOut(sceneRoot)
+ || Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE
+ ) {
+ // Can't control it, so just change the scene immediately
+ if (oldScene != null) {
+ oldScene.exit();
+ }
+ scene.enter();
+ return null;
+ }
+ sPendingTransitions.add(sceneRoot);
+ final Transition transitionClone = transition.clone();
+ final TransitionSet set = new TransitionSet();
+ set.addTransition(transitionClone);
+ if (oldScene != null && oldScene.isCreatedFromLayoutResource()) {
+ set.setCanRemoveViews(true);
+ }
+ sceneChangeSetup(sceneRoot, set);
+ scene.enter();
+
+ sceneChangeRunTransition(sceneRoot, set);
+ return set.createSeekController();
+ }
+
+ /**
* Convenience method to simply change to the given scene using
* the given transition.
*
diff --git a/transition/transition/src/main/java/androidx/transition/TransitionSeekController.java b/transition/transition/src/main/java/androidx/transition/TransitionSeekController.java
index bcede11..9e4f228 100644
--- a/transition/transition/src/main/java/androidx/transition/TransitionSeekController.java
+++ b/transition/transition/src/main/java/androidx/transition/TransitionSeekController.java
@@ -18,6 +18,8 @@
import android.view.ViewGroup;
+import androidx.annotation.FloatRange;
+import androidx.annotation.IntRange;
import androidx.annotation.NonNull;
import androidx.core.util.Consumer;
@@ -31,15 +33,24 @@
/**
* @return The total duration, in milliseconds, of the Transition's animations.
*/
+ @IntRange(from = 0)
long getDurationMillis();
/**
* @return The time, in milliseconds, of the animation. This will be between 0
* and {@link #getDurationMillis()}.
*/
+ @IntRange(from = 0)
long getCurrentPlayTimeMillis();
/**
+ * @return The fraction, between 0 and 1, inclusive, of the progress of the transition.
+ * @see #getCurrentPlayTimeMillis()
+ */
+ @FloatRange(from = 0.0, to = 1.0)
+ float getCurrentFraction();
+
+ /**
* Returns {@code true} when the Transition is ready to seek or {@code false}
* when the Transition's animations have yet to be built.
*/
@@ -73,14 +84,26 @@
void animateToEnd();
/**
+ * Sets the position of the Transition's animation. {@code fraction} should be
+ * between 0 and 1, inclusive, where 0 indicates that the transition hasn't progressed and 1
+ * indicates that the transition is completed. Calling this before {@link #isReady()} is
+ * {@code true} will do nothing.
+ *
+ * @param fraction The fraction, between 0 and 1, inclusive, of the progress of the transition.
+ * @see #setCurrentPlayTimeMillis(long)
+ */
+ void setCurrentFraction(@FloatRange(from = 0.0, to = 1.0) float fraction);
+
+ /**
* Sets the position of the Transition's animation. {@code playTimeMillis} should be
- * between 0 and {@link #getDurationMillis()}. This should not be called when
- * {@link #isReady()} is {@code false}.
+ * between 0 and {@link #getDurationMillis()}. Calling this before {@link #isReady()} is
+ * {@code true} will do nothing.
*
* @param playTimeMillis The time, between 0 and {@link #getDurationMillis()} that the
* animation should play.
+ * @see #setCurrentFraction(float)
*/
- void setCurrentPlayTimeMillis(long playTimeMillis);
+ void setCurrentPlayTimeMillis(@IntRange(from = 0) long playTimeMillis);
/**
* Adds a listener to know when {@link #isReady()} is {@code true}. The listener will
@@ -98,5 +121,20 @@
* @param onReadyListener The listener to be removed so that it won't be notified when ready.
*/
void removeOnReadyListener(@NonNull Consumer<TransitionSeekController> onReadyListener);
+
+ /**
+ * Add a listener for whenever the progress of the transition is changed. This will be called
+ * when {@link #setCurrentPlayTimeMillis(long)} or {@link #setCurrentFraction(float)} are
+ * called as well as when the animation from {@link #animateToEnd()} or
+ * {@link #animateToStart()} changes the progress.
+ * @param consumer A method that accepts this TransitionSeekController.
+ */
+ void addOnProgressChangedListener(@NonNull Consumer<TransitionSeekController> consumer);
+
+ /**
+ * Remove a listener previously added in {@link #addOnProgressChangedListener(Consumer)}
+ * @param consumer The listener to be removed.
+ */
+ void removeOnProgressChangedListener(@NonNull Consumer<TransitionSeekController> consumer);
}
diff --git a/transition/transition/src/main/java/androidx/transition/VelocityTracker1D.java b/transition/transition/src/main/java/androidx/transition/VelocityTracker1D.java
new file mode 100644
index 0000000..e4e7ccb
--- /dev/null
+++ b/transition/transition/src/main/java/androidx/transition/VelocityTracker1D.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.transition;
+
+import java.util.Arrays;
+
+/**
+ * Velocity Tracker, simplified from compose's VelocityTracker1D.
+ */
+class VelocityTracker1D {
+ private static final int HISTORY_SIZE = 20;
+ private static final int ASSUME_POINTER_MOVE_STOPPED_MILLIS = 40;
+ private static final int HORIZON_MILLIS = 100;
+
+ // Circular buffer; current sample at index.
+ private long[] mTimeSamples = new long[HISTORY_SIZE];
+ private float[] mDataSamples = new float[HISTORY_SIZE];
+ private int mIndex = 0;
+
+ VelocityTracker1D() {
+ Arrays.fill(mTimeSamples, Long.MIN_VALUE);
+ }
+
+ /**
+ * Adds a data point for velocity calculation at a given time, {@code timeMillis}. The data
+ * point represents an absolute position.
+ * <p>
+ * Use the same units for the data points provided. For example, having some data points in `cm`
+ * and some in `m` will result in incorrect velocity calculations, as this method (and the
+ * tracker) has no knowledge of the units used.
+ */
+ public void addDataPoint(long timeMillis, float data) {
+ mIndex = (mIndex + 1) % HISTORY_SIZE;
+ mTimeSamples[mIndex] = timeMillis;
+ mDataSamples[mIndex] = data;
+ }
+
+ public void resetTracking() {
+ mIndex = 0;
+ Arrays.fill(mTimeSamples, Long.MIN_VALUE);
+ Arrays.fill(mDataSamples, 0f);
+ }
+
+ /**
+ * Computes the estimated velocity at the time of the last provided data point. The units of
+ * velocity will be `units/second`, where `units` is the units of the data points provided via
+ * [addDataPoint].
+ *
+ * This can be expensive. Only call this when you need the velocity.
+ */
+ float calculateVelocity() {
+ int sampleCount = 0;
+ int index = mIndex;
+
+ if (index == 0 && mTimeSamples[index] == Long.MIN_VALUE) {
+ return 0f; // We haven't received any data
+ }
+
+ // The sample at index is our newest sample. If it is null, we have no samples so return.
+ long newestTime = mTimeSamples[index];
+
+ long previousTime = newestTime;
+
+ // Starting with the most recent sample, iterate backwards while
+ // the samples represent continuous motion.
+ do {
+ long sampleTime = mTimeSamples[index];
+ if (sampleTime == Long.MIN_VALUE) {
+ break; // no point here
+ }
+ float age = newestTime - sampleTime;
+ float delta = Math.abs(sampleTime - previousTime);
+ previousTime = sampleTime;
+
+ if (age > HORIZON_MILLIS || delta > ASSUME_POINTER_MOVE_STOPPED_MILLIS) {
+ break;
+ }
+
+ index = (index == 0 ? HISTORY_SIZE : index) - 1;
+ sampleCount++;
+ } while (sampleCount < HISTORY_SIZE);
+
+ if (sampleCount < 2) {
+ return 0f; // Not enough data to have a velocity
+ }
+
+ if (sampleCount == 2) {
+ // Simple diff in time
+ int prevIndex = mIndex == 0 ? HISTORY_SIZE - 1 : mIndex - 1;
+ float timeDiff = mTimeSamples[mIndex] - mTimeSamples[prevIndex];
+ if (timeDiff == 0f) {
+ return 0f;
+ }
+ float dataDiff = mDataSamples[mIndex] - mDataSamples[prevIndex];
+ return dataDiff / timeDiff * 1000;
+ }
+
+ float work = 0f;
+ int startIndex = (mIndex - sampleCount + HISTORY_SIZE + 1) % HISTORY_SIZE;
+ int endIndex = (mIndex + 1 + HISTORY_SIZE) % HISTORY_SIZE;
+ previousTime = mTimeSamples[startIndex];
+ float previousData = mDataSamples[startIndex];
+ for (int i = (startIndex + 1) % HISTORY_SIZE; i != endIndex; i = (i + 1) % HISTORY_SIZE) {
+ long time = mTimeSamples[i];
+ long timeDelta = time - previousTime;
+ if (timeDelta == 0f) {
+ continue;
+ }
+ float data = mDataSamples[i];
+ float vPrev = kineticEnergyToVelocity(work);
+ float dataPointsDelta = data - previousData;
+
+ float vCurr = dataPointsDelta / timeDelta;
+ work += (vCurr - vPrev) * Math.abs(vCurr);
+ if (i == startIndex + 1) {
+ work = (work * 0.5f);
+ }
+ previousTime = time;
+ previousData = data;
+ }
+ return kineticEnergyToVelocity(work) * 1000;
+ }
+
+ private float kineticEnergyToVelocity(float kineticEnergy) {
+ return (float) (Math.signum(kineticEnergy) * Math.sqrt(2 * Math.abs(kineticEnergy)));
+ }
+}
diff --git a/tv/tv-material/src/main/java/androidx/tv/material3/Surface.kt b/tv/tv-material/src/main/java/androidx/tv/material3/Surface.kt
index c0832bb..2546e1c 100644
--- a/tv/tv-material/src/main/java/androidx/tv/material3/Surface.kt
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/Surface.kt
@@ -645,7 +645,7 @@
@get:ExperimentalTvMaterial3Api
val LocalAbsoluteTonalElevation = compositionLocalOf { 0.dp }
-private val AcceptableKeys = hashSetOf(
+private val AcceptableKeys = intArrayOf(
NativeKeyEvent.KEYCODE_DPAD_CENTER,
NativeKeyEvent.KEYCODE_ENTER,
NativeKeyEvent.KEYCODE_NUMPAD_ENTER
diff --git a/wear/compose/compose-material-core/src/main/java/androidx/wear/compose/materialcore/Button.kt b/wear/compose/compose-material-core/src/main/java/androidx/wear/compose/materialcore/Button.kt
index e389f5c..9284438 100644
--- a/wear/compose/compose-material-core/src/main/java/androidx/wear/compose/materialcore/Button.kt
+++ b/wear/compose/compose-material-core/src/main/java/androidx/wear/compose/materialcore/Button.kt
@@ -34,6 +34,8 @@
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.semantics.Role
+import androidx.compose.ui.semantics.role
+import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.Dp
/**
@@ -80,14 +82,16 @@
.clickable(
onClick = onClick,
enabled = enabled,
- role = Role.Button,
+ role = null, // provide the role via Modifier.semantics
interactionSource = interactionSource,
indication = rememberRipple(),
)
.then(
// Make sure modifier ordering is clip > clickable > padding > size,
// so that the ripple applies to the entire button shape and size.
+ // Then, apply semantics to apply the default semantic role (can be overridden)
modifier
+ .semantics { role = Role.Button }
)
.size(buttonSize)
.clip(shape) // Clip for the painted background area after size has been applied.
diff --git a/wear/compose/compose-material/src/androidTest/kotlin/androidx/wear/compose/material/ToggleButtonTest.kt b/wear/compose/compose-material/src/androidTest/kotlin/androidx/wear/compose/material/ToggleButtonTest.kt
index 84db9176..ae4e264 100644
--- a/wear/compose/compose-material/src/androidTest/kotlin/androidx/wear/compose/material/ToggleButtonTest.kt
+++ b/wear/compose/compose-material/src/androidTest/kotlin/androidx/wear/compose/material/ToggleButtonTest.kt
@@ -767,11 +767,14 @@
setContentWithTheme {
background = MaterialTheme.colors.surface
buttonColor = MaterialTheme.colors.primary
- content(
- Modifier
- .testTag(TEST_TAG)
- .padding(padding)
- .background(background))
+ Box(Modifier.background(background)) {
+ content(
+ Modifier
+ .testTag(TEST_TAG)
+ .padding(padding)
+ .background(background)
+ )
+ }
}
onNodeWithTag(TEST_TAG)
diff --git a/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/ToggleButton.kt b/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/ToggleButton.kt
index 812ac3d..4a208e3 100644
--- a/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/ToggleButton.kt
+++ b/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/ToggleButton.kt
@@ -15,28 +15,22 @@
*/
package androidx.wear.compose.material
-import androidx.compose.foundation.background
import androidx.compose.foundation.interaction.Interaction
import androidx.compose.foundation.interaction.MutableInteractionSource
-import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
-import androidx.compose.foundation.layout.defaultMinSize
-import androidx.compose.foundation.selection.toggleable
import androidx.compose.foundation.shape.CircleShape
-import androidx.compose.material.ripple.rememberRipple
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.State
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberUpdatedState
-import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.semantics.Role
+import androidx.compose.ui.semantics.role
+import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.dp
/**
@@ -219,36 +213,24 @@
role: Role = ToggleButtonDefaults.DefaultRole,
content: @Composable BoxScope.() -> Unit,
) {
- Box(
- contentAlignment = Alignment.Center,
- modifier = modifier
- .defaultMinSize(
- minWidth = ToggleButtonDefaults.DefaultToggleButtonSize,
- minHeight = ToggleButtonDefaults.DefaultToggleButtonSize
- )
- .clip(shape)
- .toggleable(
- value = checked,
- onValueChange = onCheckedChange,
- enabled = enabled,
- role = role,
- interactionSource = interactionSource,
- indication = rememberRipple()
- )
- .background(
- color = colors.backgroundColor(enabled = enabled, checked = checked).value,
- shape = shape
- )
- ) {
- val contentColor = colors.contentColor(enabled = enabled, checked = checked).value
- CompositionLocalProvider(
- LocalContentColor provides contentColor,
- LocalContentAlpha provides contentColor.alpha,
- LocalTextStyle provides MaterialTheme.typography.button,
- ) {
- content()
- }
- }
+ androidx.wear.compose.materialcore.ToggleButton(
+ checked = checked,
+ onCheckedChange = onCheckedChange,
+ modifier = modifier.semantics { this.role = role },
+ enabled = enabled,
+ backgroundColor = { isEnabled, isChecked ->
+ colors.backgroundColor(enabled = isEnabled, checked = isChecked)
+ },
+ border = { _, _ -> null },
+ toggleButtonSize = ToggleButtonDefaults.DefaultToggleButtonSize,
+ interactionSource = interactionSource,
+ shape = shape,
+ content = provideScopeContent(
+ colors.contentColor(enabled = enabled, checked = checked),
+ MaterialTheme.typography.button,
+ content
+ )
+ )
}
/**
* Represents the background and content colors used in a toggle button in different states.
diff --git a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/ButtonDemo.kt b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/ButtonDemo.kt
index 76cddf8..d2c396f 100644
--- a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/ButtonDemo.kt
+++ b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/ButtonDemo.kt
@@ -29,6 +29,7 @@
import androidx.compose.ui.text.style.TextOverflow
import androidx.wear.compose.foundation.lazy.ScalingLazyColumn
import androidx.wear.compose.material3.Button
+import androidx.wear.compose.material3.ButtonColors
import androidx.wear.compose.material3.ButtonDefaults
import androidx.wear.compose.material3.ChildButton
import androidx.wear.compose.material3.FilledTonalButton
@@ -310,13 +311,17 @@
@Composable
private fun AvatarButton(enabled: Boolean) =
MultilineButton(
- enabled = enabled, icon = { AvatarIcon() }, label = { Text("Primary text") }
+ enabled = enabled,
+ colors = ButtonDefaults.filledTonalButtonColors(),
+ icon = { AvatarIcon() },
+ label = { Text("Primary text") }
)
@Composable
private fun Avatar3SlotButton(enabled: Boolean) =
Multiline3SlotButton(
enabled = enabled,
+ colors = ButtonDefaults.filledTonalButtonColors(),
icon = { AvatarIcon() },
label = { Text("Primary text") },
secondaryLabel = { Text("Secondary label") }
@@ -325,6 +330,7 @@
@Composable
private fun MultilineButton(
enabled: Boolean,
+ colors: ButtonColors = ButtonDefaults.filledButtonColors(),
icon: (@Composable BoxScope.() -> Unit)? = null,
label: @Composable RowScope.() -> Unit = {
Text(
@@ -338,13 +344,15 @@
onClick = { /* Do something */ },
icon = icon,
label = label,
- enabled = enabled
+ enabled = enabled,
+ colors = colors,
)
}
@Composable
private fun Multiline3SlotButton(
enabled: Boolean,
+ colors: ButtonColors = ButtonDefaults.filledButtonColors(),
icon: (@Composable BoxScope.() -> Unit)? = null,
label: @Composable RowScope.() -> Unit = {
Text(
@@ -366,7 +374,8 @@
icon = icon,
label = label,
secondaryLabel = secondaryLabel,
- enabled = enabled
+ enabled = enabled,
+ colors = colors,
)
}
diff --git a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/CardDemo.kt b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/CardDemo.kt
new file mode 100644
index 0000000..45c3ad7
--- /dev/null
+++ b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/CardDemo.kt
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.compose.material3.demos
+
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.size
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Favorite
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.semantics
+import androidx.wear.compose.foundation.lazy.ScalingLazyColumn
+import androidx.wear.compose.material3.AppCard
+import androidx.wear.compose.material3.CardDefaults
+import androidx.wear.compose.material3.Icon
+import androidx.wear.compose.material3.ListHeader
+import androidx.wear.compose.material3.MaterialTheme
+import androidx.wear.compose.material3.Text
+import androidx.wear.compose.material3.samples.AppCardSample
+import androidx.wear.compose.material3.samples.AppCardWithIconSample
+import androidx.wear.compose.material3.samples.CardSample
+import androidx.wear.compose.material3.samples.OutlinedAppCardSample
+import androidx.wear.compose.material3.samples.OutlinedCardSample
+import androidx.wear.compose.material3.samples.OutlinedTitleCardSample
+import androidx.wear.compose.material3.samples.R
+import androidx.wear.compose.material3.samples.TitleCardSample
+import androidx.wear.compose.material3.samples.TitleCardWithImageSample
+
+@Composable
+fun CardDemo() {
+ ScalingLazyColumn(
+ modifier = Modifier.fillMaxSize(),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ item { ListHeader { Text("Card") } }
+ item { CardSample() }
+ item { OutlinedCardSample() }
+
+ item { ListHeader { Text("App card") } }
+ item { AppCardSample() }
+ item { AppCardWithIconSample() }
+ item { OutlinedAppCardSample() }
+
+ item { ListHeader { Text("Title card") } }
+ item { TitleCardSample() }
+ item { OutlinedTitleCardSample() }
+
+ item { ListHeader { Text("Image card") } }
+ item {
+ AppCard(
+ onClick = { /* Do something */ },
+ appName = { Text("App name") },
+ appImage = {
+ Icon(
+ Icons.Filled.Favorite,
+ contentDescription = "favourites",
+ modifier = Modifier.size(CardDefaults.AppImageSize)
+ )
+ },
+ title = { Text("Card title") },
+ time = { Text("now") },
+ colors = CardDefaults.imageCardColors(
+ containerPainter = CardDefaults.imageWithScrimBackgroundPainter(
+ backgroundImagePainter = painterResource(id = R.drawable.backgroundimage)
+ ),
+ contentColor = MaterialTheme.colorScheme.onSurface,
+ titleColor = MaterialTheme.colorScheme.onSurface
+ ),
+ modifier = Modifier.semantics { contentDescription = "Background image" }
+ ) {
+ Text("Card content")
+ }
+ }
+ item { TitleCardWithImageSample() }
+ }
+}
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 13e7784..c239cfe 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
@@ -16,27 +16,16 @@
package androidx.wear.compose.material3.demos
-import androidx.compose.ui.Alignment
-import androidx.wear.compose.foundation.lazy.AutoCenteringParams
-import androidx.wear.compose.foundation.lazy.ScalingLazyColumn
import androidx.wear.compose.integration.demos.common.Centralize
import androidx.wear.compose.integration.demos.common.ComposableDemo
import androidx.wear.compose.integration.demos.common.DemoCategory
-import androidx.wear.compose.material3.samples.AppCardSample
-import androidx.wear.compose.material3.samples.AppCardWithIconSample
-import androidx.wear.compose.material3.samples.CardSample
import androidx.wear.compose.material3.samples.EdgeSwipeForSwipeToDismiss
import androidx.wear.compose.material3.samples.FixedFontSize
-import androidx.wear.compose.material3.samples.OutlinedAppCardSample
-import androidx.wear.compose.material3.samples.OutlinedCardSample
-import androidx.wear.compose.material3.samples.OutlinedTitleCardSample
import androidx.wear.compose.material3.samples.SimpleSwipeToDismissBox
import androidx.wear.compose.material3.samples.StatefulSwipeToDismissBox
import androidx.wear.compose.material3.samples.StepperSample
import androidx.wear.compose.material3.samples.StepperWithIntegerSample
import androidx.wear.compose.material3.samples.StepperWithRangeSemanticsSample
-import androidx.wear.compose.material3.samples.TitleCardSample
-import androidx.wear.compose.material3.samples.TitleCardWithImageSample
val WearMaterial3Demos = DemoCategory(
"Material 3",
@@ -64,26 +53,9 @@
},
)
),
- DemoCategory(
- "Card",
- listOf(
- ComposableDemo("Samples") {
- ScalingLazyColumn(
- horizontalAlignment = Alignment.CenterHorizontally,
- autoCentering = AutoCenteringParams(itemIndex = 0)
- ) {
- item { CardSample() }
- item { AppCardSample() }
- item { AppCardWithIconSample() }
- item { TitleCardSample() }
- item { TitleCardWithImageSample() }
- item { OutlinedCardSample() }
- item { OutlinedAppCardSample() }
- item { OutlinedTitleCardSample() }
- }
- }
- )
- ),
+ ComposableDemo("Card") {
+ CardDemo()
+ },
ComposableDemo("Text Button") {
TextButtonDemo()
},
@@ -113,7 +85,7 @@
"Slider",
SliderDemos
),
- ComposableDemo("List Headers") {
+ ComposableDemo("List Header") {
Centralize {
ListHeaderDemo()
}
diff --git a/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/CardSample.kt b/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/CardSample.kt
index 4e5af0f..17424be 100644
--- a/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/CardSample.kt
+++ b/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/CardSample.kt
@@ -19,10 +19,14 @@
import androidx.annotation.Sampled
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.wrapContentSize
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Favorite
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.semantics
import androidx.wear.compose.material3.AppCard
import androidx.wear.compose.material3.Card
import androidx.wear.compose.material3.CardDefaults
@@ -47,8 +51,8 @@
fun AppCardSample() {
AppCard(
onClick = { /* Do something */ },
- appName = { Text("AppName") },
- title = { Text("AppCard") },
+ appName = { Text("App name") },
+ title = { Text("Card title") },
time = { Text("now") },
) {
Text("Card content")
@@ -60,7 +64,7 @@
fun AppCardWithIconSample() {
AppCard(
onClick = { /* Do something */ },
- appName = { Text("AppName") },
+ appName = { Text("App name") },
appImage = {
Icon(
painter = painterResource(id = android.R.drawable.star_big_off),
@@ -70,7 +74,7 @@
.wrapContentSize(align = Alignment.Center),
)
},
- title = { Text("AppCard with icon") },
+ title = { Text("Card title") },
time = { Text("now") },
) {
Text("Card content")
@@ -82,7 +86,7 @@
fun TitleCardSample() {
TitleCard(
onClick = { /* Do something */ },
- title = { Text("TitleCard") },
+ title = { Text("Title card") },
time = { Text("now") },
) {
Text("Card content")
@@ -94,14 +98,16 @@
fun TitleCardWithImageSample() {
TitleCard(
onClick = { /* Do something */ },
- title = { Text("TitleCard With an ImageBackground") },
+ title = { Text("Card title") },
+ time = { Text("now") },
colors = CardDefaults.imageCardColors(
containerPainter = CardDefaults.imageWithScrimBackgroundPainter(
backgroundImagePainter = painterResource(id = R.drawable.backgroundimage)
),
contentColor = MaterialTheme.colorScheme.onSurface,
titleColor = MaterialTheme.colorScheme.onSurface
- )
+ ),
+ modifier = Modifier.semantics { contentDescription = "Background image" }
) {
Text("Card content")
}
@@ -113,7 +119,7 @@
OutlinedCard(
onClick = { /* Do something */ },
) {
- Text("OutlinedCard")
+ Text("Outlined card")
}
}
@@ -122,8 +128,16 @@
fun OutlinedAppCardSample() {
AppCard(
onClick = { /* Do something */ },
- appName = { Text("AppName") },
- title = { Text("Outlined AppCard") },
+ appName = { Text("App name") },
+ appImage = {
+ Icon(
+ Icons.Filled.Favorite,
+ contentDescription = "favourites",
+ modifier = Modifier.size(CardDefaults.AppImageSize)
+ )
+ },
+ title = { Text("App card") },
+ time = { Text("now") },
colors = CardDefaults.outlinedCardColors(),
border = CardDefaults.outlinedCardBorder(),
) {
@@ -136,7 +150,8 @@
fun OutlinedTitleCardSample() {
TitleCard(
onClick = { /* Do something */ },
- title = { Text("Outlined TitleCard") },
+ title = { Text("Title card") },
+ time = { Text("now") },
colors = CardDefaults.outlinedCardColors(),
border = CardDefaults.outlinedCardBorder(),
) {
diff --git a/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/ListHeaderSample.kt b/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/ListHeaderSample.kt
index ef263e8..3b32885 100644
--- a/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/ListHeaderSample.kt
+++ b/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/ListHeaderSample.kt
@@ -29,7 +29,7 @@
@Composable
fun ListHeaderSample() {
ListHeader {
- Text("List Header")
+ Text("Header")
}
}
@@ -37,7 +37,7 @@
@Composable
fun ListSubheaderSample() {
ListSubheader {
- Text("List Subheader")
+ Text("Subheader")
}
}
@@ -45,7 +45,7 @@
@Composable
fun ListSubheaderWithIconSample() {
ListSubheader(
- label = { Text(text = "List Subheader") },
+ label = { Text(text = "Subheader") },
icon = { Icon(imageVector = Icons.Outlined.Home, "home") }
)
}
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/ButtonTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/ButtonTest.kt
index eb4b7c0..a5da87e 100644
--- a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/ButtonTest.kt
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/ButtonTest.kt
@@ -386,7 +386,7 @@
status = Status.Disabled,
colors = { ButtonDefaults.filledButtonColors() },
expectedContainerColor = { MaterialTheme.colorScheme.onSurface.copy(
- alpha = DisabledBorderAndContainerAlpha
+ alpha = DisabledContainerAlpha
) },
expectedContentColor = { MaterialTheme.colorScheme.onSurface.copy(
alpha = ContentAlpha.disabled
@@ -412,7 +412,7 @@
status = Status.Disabled,
colors = { ButtonDefaults.filledTonalButtonColors() },
expectedContainerColor = { MaterialTheme.colorScheme.onSurface.copy(
- alpha = DisabledBorderAndContainerAlpha
+ alpha = DisabledContainerAlpha
) },
expectedContentColor = { MaterialTheme.colorScheme.onSurface.copy(
alpha = ContentAlpha.disabled
@@ -461,9 +461,7 @@
rule.verifyButtonColors(
status = Status.Disabled,
colors = { ButtonDefaults.childButtonColors() },
- expectedContainerColor = { MaterialTheme.colorScheme.onSurface.copy(
- alpha = DisabledBorderAndContainerAlpha
- ) },
+ expectedContainerColor = { Color.Transparent },
expectedContentColor = { MaterialTheme.colorScheme.onSurface.copy(
alpha = ContentAlpha.disabled
) },
@@ -563,7 +561,9 @@
fun gives_disabled_outlined_button_correct_border_colors() {
val status = Status.Disabled
rule.verifyButtonBorderColor(
- expectedBorderColor = { MaterialTheme.colorScheme.onSurface.copy(alpha = 0.12f) },
+ expectedBorderColor = {
+ MaterialTheme.colorScheme.onSurface.copy(alpha = DisabledBorderAlpha)
+ },
content = { modifier: Modifier ->
OutlinedButton(
onClick = {},
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/IconButtonScreenshotTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/IconButtonScreenshotTest.kt
index e1bcf92..07d2ceb 100644
--- a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/IconButtonScreenshotTest.kt
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/IconButtonScreenshotTest.kt
@@ -70,7 +70,7 @@
}
@Test
- fun filled_icon_button_disabled() = verifyScreenshot("icon_button_disabled") {
+ fun filled_icon_button_disabled() = verifyScreenshot {
sampleFilledIconButton(enabled = false, isCompact = false)
}
@@ -80,7 +80,7 @@
}
@Test
- fun filled_tonal_icon_button_disabled() = verifyScreenshot("icon_button_disabled") {
+ fun filled_tonal_icon_button_disabled() = verifyScreenshot {
sampleFilledTonalIconButton(enabled = false, isCompact = false)
}
@@ -110,7 +110,7 @@
}
@Test
- fun filled_compact_icon_button_disabled() = verifyScreenshot("compact_icon_button_disabled") {
+ fun filled_compact_icon_button_disabled() = verifyScreenshot {
sampleFilledIconButton(enabled = false, isCompact = true)
}
@@ -121,7 +121,7 @@
@Test
fun filled_tonal_compact_icon_button_disabled() =
- verifyScreenshot("compact_icon_button_disabled") {
+ verifyScreenshot {
sampleFilledTonalIconButton(enabled = false, isCompact = true)
}
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/IconButtonTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/IconButtonTest.kt
index d12eb43..f6f9e3b 100644
--- a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/IconButtonTest.kt
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/IconButtonTest.kt
@@ -31,6 +31,8 @@
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.semantics.SemanticsProperties
+import androidx.compose.ui.semantics.role
+import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.test.SemanticsMatcher
import androidx.compose.ui.test.assert
import androidx.compose.ui.test.assertHasClickAction
@@ -191,6 +193,27 @@
}
@Test
+ fun allows_custom_role() {
+ val overrideRole = Role.Checkbox
+
+ rule.setContentWithTheme {
+ IconButton(
+ onClick = {},
+ modifier = Modifier.testTag(TEST_TAG).semantics { role = overrideRole }
+ ) {
+ TestImage()
+ }
+ }
+
+ rule.onNodeWithTag(TEST_TAG).assert(
+ SemanticsMatcher.expectValue(
+ SemanticsProperties.Role,
+ overrideRole
+ )
+ )
+ }
+
+ @Test
fun gives_default_button_correct_tap_size() {
rule.verifyTapSize(DefaultButtonSize) { modifier ->
IconButton(
@@ -317,7 +340,7 @@
status = Status.Disabled,
colors = { IconButtonDefaults.filledIconButtonColors() },
expectedContainerColor = { MaterialTheme.colorScheme.onSurface.copy(
- alpha = DisabledBorderAndContainerAlpha
+ alpha = DisabledContainerAlpha
) },
expectedContentColor = { MaterialTheme.colorScheme.onSurface.copy(
alpha = ContentAlpha.disabled
@@ -343,7 +366,7 @@
status = Status.Disabled,
colors = { IconButtonDefaults.filledTonalIconButtonColors() },
expectedContainerColor = { MaterialTheme.colorScheme.onSurface.copy(
- alpha = DisabledBorderAndContainerAlpha
+ alpha = DisabledContainerAlpha
) },
expectedContentColor = { MaterialTheme.colorScheme.onSurface.copy(
alpha = ContentAlpha.disabled
@@ -397,7 +420,7 @@
val status = Status.Disabled
rule.verifyButtonBorderColor(
expectedBorderColor = {
- MaterialTheme.colorScheme.onSurface.copy(alpha = DisabledBorderAndContainerAlpha)
+ MaterialTheme.colorScheme.onSurface.copy(alpha = DisabledBorderAlpha)
},
content = { modifier: Modifier ->
OutlinedIconButton(
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/TextButtonScreenshotTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/TextButtonScreenshotTest.kt
index 967714f3..11b0b55 100644
--- a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/TextButtonScreenshotTest.kt
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/TextButtonScreenshotTest.kt
@@ -66,7 +66,7 @@
@Test
fun filled_text_button_disabled() =
- verifyScreenshot("text_button_disabled") {
+ verifyScreenshot {
sampleFilledTextButton(enabled = false, isCompact = false)
}
@@ -77,7 +77,7 @@
@Test
fun filled_tonal_text_button_disabled() =
- verifyScreenshot("text_button_disabled") {
+ verifyScreenshot {
sampleFilledTonalTextButton(enabled = false, isCompact = false)
}
@@ -108,7 +108,7 @@
@Test
fun filled_compact_text_button_disabled() =
- verifyScreenshot("compact_text_button_disabled") {
+ verifyScreenshot {
sampleFilledTextButton(enabled = false, isCompact = true)
}
@@ -119,7 +119,7 @@
@Test
fun filled_tonal_compact_text_button_disabled() =
- verifyScreenshot("compact_text_button_disabled") {
+ verifyScreenshot {
sampleFilledTonalTextButton(enabled = false, isCompact = true)
}
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/TextButtonTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/TextButtonTest.kt
index a37ef7b..f6a0b7b 100644
--- a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/TextButtonTest.kt
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/TextButtonTest.kt
@@ -31,6 +31,8 @@
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.semantics.SemanticsProperties
+import androidx.compose.ui.semantics.role
+import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.test.SemanticsMatcher
import androidx.compose.ui.test.assert
import androidx.compose.ui.test.assertHasClickAction
@@ -222,6 +224,27 @@
}
@Test
+ fun allows_custom_role() {
+ val overrideRole = Role.Checkbox
+
+ rule.setContentWithTheme {
+ TextButton(
+ onClick = {},
+ modifier = Modifier.testTag(TEST_TAG).semantics { role = overrideRole }
+ ) {
+ Text("Test")
+ }
+ }
+
+ rule.onNodeWithTag(TEST_TAG).assert(
+ SemanticsMatcher.expectValue(
+ SemanticsProperties.Role,
+ overrideRole
+ )
+ )
+ }
+
+ @Test
fun sets_correct_font() {
var actualTextStyle = TextStyle.Default
var expectedTextStyle = TextStyle.Default
@@ -365,7 +388,7 @@
status = Status.Disabled,
colors = { TextButtonDefaults.filledTextButtonColors() },
expectedContainerColor = { MaterialTheme.colorScheme.onSurface.copy(
- alpha = DisabledBorderAndContainerAlpha
+ alpha = DisabledContainerAlpha
) },
expectedContentColor = { MaterialTheme.colorScheme.onSurface.copy(
alpha = ContentAlpha.disabled
@@ -391,7 +414,7 @@
status = Status.Disabled,
colors = { TextButtonDefaults.filledTonalTextButtonColors() },
expectedContainerColor = { MaterialTheme.colorScheme.onSurface.copy(
- alpha = DisabledBorderAndContainerAlpha
+ alpha = DisabledContainerAlpha
) },
expectedContentColor = { MaterialTheme.colorScheme.onSurface.copy(
alpha = ContentAlpha.disabled
@@ -416,9 +439,7 @@
rule.verifyTextButtonColors(
status = Status.Disabled,
colors = { TextButtonDefaults.outlinedTextButtonColors() },
- expectedContainerColor = { MaterialTheme.colorScheme.onSurface.copy(
- alpha = DisabledBorderAndContainerAlpha
- ) },
+ expectedContainerColor = { Color.Transparent },
expectedContentColor = { MaterialTheme.colorScheme.onSurface.copy(
alpha = ContentAlpha.disabled
) }
@@ -449,7 +470,7 @@
val status = Status.Disabled
rule.verifyButtonBorderColor(
expectedBorderColor = {
- MaterialTheme.colorScheme.onSurface.copy(alpha = DisabledBorderAndContainerAlpha)
+ MaterialTheme.colorScheme.onSurface.copy(alpha = DisabledBorderAlpha)
},
content = { modifier: Modifier ->
TextButton(
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Button.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Button.kt
index 6c8de9eb59..e608707 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Button.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Button.kt
@@ -715,7 +715,7 @@
containerColor: Color = MaterialTheme.colorScheme.surface,
contentColor: Color = MaterialTheme.colorScheme.onSurface,
secondaryContentColor: Color = MaterialTheme.colorScheme.onSurfaceVariant,
- iconColor: Color = MaterialTheme.colorScheme.onSurface
+ iconColor: Color = MaterialTheme.colorScheme.primary,
): ButtonColors {
return buttonColors(
containerColor = containerColor,
@@ -742,13 +742,14 @@
fun outlinedButtonColors(
contentColor: Color = MaterialTheme.colorScheme.onSurface,
secondaryContentColor: Color = MaterialTheme.colorScheme.onSurfaceVariant,
- iconColor: Color = MaterialTheme.colorScheme.onSurface
+ iconColor: Color = MaterialTheme.colorScheme.primary,
): ButtonColors {
return buttonColors(
containerColor = Color.Transparent,
contentColor = contentColor,
secondaryContentColor = secondaryContentColor,
- iconColor = iconColor
+ iconColor = iconColor,
+ disabledContainerColor = Color.Transparent,
)
}
@@ -769,13 +770,14 @@
fun childButtonColors(
contentColor: Color = MaterialTheme.colorScheme.onSurface,
secondaryContentColor: Color = MaterialTheme.colorScheme.onSurfaceVariant,
- iconColor: Color = MaterialTheme.colorScheme.onSurface
+ iconColor: Color = MaterialTheme.colorScheme.primary
): ButtonColors {
return buttonColors(
containerColor = Color.Transparent,
contentColor = contentColor,
secondaryContentColor = secondaryContentColor,
- iconColor = iconColor
+ iconColor = iconColor,
+ disabledContainerColor = Color.Transparent,
)
}
@@ -851,7 +853,7 @@
enabled: Boolean,
borderColor: Color = MaterialTheme.colorScheme.outline,
disabledBorderColor: Color = MaterialTheme.colorScheme.onSurface.toDisabledColor(
- disabledAlpha = DisabledBorderAndContainerAlpha
+ disabledAlpha = DisabledBorderAlpha
),
borderWidth: Dp = 1.dp
): BorderStroke {
@@ -901,7 +903,7 @@
secondaryContentColor: Color = contentColor,
iconColor: Color = contentColor,
disabledContainerColor: Color = MaterialTheme.colorScheme.onSurface.toDisabledColor(
- disabledAlpha = DisabledBorderAndContainerAlpha
+ disabledAlpha = DisabledContainerAlpha
),
disabledContentColor: Color = MaterialTheme.colorScheme.onSurface.toDisabledColor(),
disabledSecondaryContentColor: Color =
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/ContentAlpha.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/ContentAlpha.kt
index 8471524..b978a2b 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/ContentAlpha.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/ContentAlpha.kt
@@ -33,18 +33,18 @@
* body text.
*
*/
-public val LocalContentAlpha: ProvidableCompositionLocal<Float> = compositionLocalOf { 1f }
+val LocalContentAlpha: ProvidableCompositionLocal<Float> = compositionLocalOf { 1f }
/**
* Default alpha levels used by Material components.
*
* See [LocalContentAlpha].
*/
-public object ContentAlpha {
+object ContentAlpha {
/**
* A high level of content alpha, used to represent high emphasis text.
*/
- public val high: Float
+ val high: Float
@Composable
get() = contentAlpha(
highContrastAlpha = HighContrastContentAlpha.high,
@@ -55,7 +55,7 @@
* A medium level of content alpha, used to represent medium emphasis text such as
* placeholder text.
*/
- public val medium: Float
+ val medium: Float
@Composable
get() = contentAlpha(
highContrastAlpha = HighContrastContentAlpha.medium,
@@ -66,7 +66,7 @@
* A low level of content alpha used to represent disabled components, such as text in a
* disabled Button.
*/
- public val disabled: Float
+ val disabled: Float
@Composable
get() = contentAlpha(
highContrastAlpha = HighContrastContentAlpha.disabled,
@@ -123,4 +123,5 @@
const val disabled: Float = 0.38f
}
-internal const val DisabledBorderAndContainerAlpha = 0.12f
+internal const val DisabledContainerAlpha = 0.12f
+internal const val DisabledBorderAlpha = 0.20f
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/IconButton.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/IconButton.kt
index 042f777..ff669b8 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/IconButton.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/IconButton.kt
@@ -358,7 +358,10 @@
): IconButtonColors {
return iconButtonColors(
containerColor = containerColor,
- contentColor = contentColor
+ contentColor = contentColor,
+ disabledContainerColor = MaterialTheme.colorScheme.onSurface.toDisabledColor(
+ disabledAlpha = DisabledContainerAlpha
+ ),
)
}
@@ -378,7 +381,10 @@
): IconButtonColors {
return iconButtonColors(
containerColor = containerColor,
- contentColor = contentColor
+ contentColor = contentColor,
+ disabledContainerColor = MaterialTheme.colorScheme.onSurface.toDisabledColor(
+ disabledAlpha = DisabledContainerAlpha
+ ),
)
}
@@ -396,7 +402,7 @@
): IconButtonColors {
return iconButtonColors(
containerColor = Color.Transparent,
- contentColor = contentColor
+ contentColor = contentColor,
)
}
@@ -415,9 +421,7 @@
fun iconButtonColors(
containerColor: Color = Color.Transparent,
contentColor: Color = MaterialTheme.colorScheme.onBackground,
- disabledContainerColor: Color = MaterialTheme.colorScheme.onSurface.toDisabledColor(
- disabledAlpha = DisabledBorderAndContainerAlpha
- ),
+ disabledContainerColor: Color = Color.Transparent,
disabledContentColor: Color = MaterialTheme.colorScheme.onSurface.toDisabledColor()
): IconButtonColors = IconButtonColors(
containerColor = containerColor,
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/SelectionControls.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/SelectionControls.kt
index adf549b..128b9a5 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/SelectionControls.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/SelectionControls.kt
@@ -491,11 +491,11 @@
uncheckedBoxColor = uncheckedBoxColor,
uncheckedCheckmarkColor = uncheckedCheckmarkColor,
disabledCheckedBoxColor = checkedBoxColor.toDisabledColor(
- disabledAlpha = DisabledBorderAndContainerAlpha
+ disabledAlpha = DisabledContainerAlpha
),
disabledCheckedCheckmarkColor = checkedCheckmarkColor.toDisabledColor(),
disabledUncheckedBoxColor = uncheckedBoxColor.toDisabledColor(
- disabledAlpha = DisabledBorderAndContainerAlpha
+ disabledAlpha = DisabledContainerAlpha
),
disabledUncheckedCheckmarkColor = uncheckedCheckmarkColor.toDisabledColor()
)
@@ -539,18 +539,18 @@
disabledCheckedThumbColor = checkedThumbColor.toDisabledColor(),
disabledCheckedThumbIconColor = checkedThumbIconColor.toDisabledColor(),
disabledCheckedTrackColor = checkedTrackColor.toDisabledColor(
- disabledAlpha = DisabledBorderAndContainerAlpha
+ disabledAlpha = DisabledContainerAlpha
),
disabledCheckedTrackBorderColor = checkedTrackStrokeColor.toDisabledColor(
- disabledAlpha = DisabledBorderAndContainerAlpha
+ disabledAlpha = DisabledBorderAlpha
),
disabledUncheckedThumbColor = uncheckedThumbColor.toDisabledColor(),
disabledUncheckedThumbIconColor = uncheckedThumbIconColor.toDisabledColor(),
disabledUncheckedTrackColor = uncheckedTrackColor.toDisabledColor(
- disabledAlpha = DisabledBorderAndContainerAlpha
+ disabledAlpha = DisabledContainerAlpha
),
disabledUncheckedTrackBorderColor = uncheckedTrackStrokeColor.toDisabledColor(
- disabledAlpha = DisabledBorderAndContainerAlpha
+ disabledAlpha = DisabledBorderAlpha
)
)
}
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Slider.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Slider.kt
index d6a155f..9563288 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Slider.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Slider.kt
@@ -328,13 +328,13 @@
unselectedBarColor: Color = MaterialTheme.colorScheme.background.copy(alpha = 0.3f),
barSeparatorColor: Color = MaterialTheme.colorScheme.primaryDim,
disabledContainerColor: Color = containerColor.toDisabledColor(
- disabledAlpha = DisabledBorderAndContainerAlpha
+ disabledAlpha = DisabledContainerAlpha
),
disabledButtonIconColor: Color = buttonIconColor.toDisabledColor(),
disabledSelectedBarColor: Color = selectedBarColor.toDisabledColor(),
disabledUnselectedBarColor: Color = unselectedBarColor.toDisabledColor(),
disabledBarSeparatorColor: Color = barSeparatorColor.toDisabledColor(
- disabledAlpha = DisabledBorderAndContainerAlpha
+ disabledAlpha = DisabledContainerAlpha
)
): InlineSliderColors = InlineSliderColors(
containerColor = containerColor,
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/TextButton.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/TextButton.kt
index f3d2722..e0349a1 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/TextButton.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/TextButton.kt
@@ -135,7 +135,7 @@
* @param content The text to be drawn inside the toggle button.
*/
@Composable
-public fun TextToggleButton(
+fun TextToggleButton(
checked: Boolean,
onCheckedChange: (Boolean) -> Unit,
modifier: Modifier = Modifier,
@@ -194,7 +194,10 @@
): TextButtonColors {
return textButtonColors(
containerColor = containerColor,
- contentColor = contentColor
+ contentColor = contentColor,
+ disabledContainerColor = MaterialTheme.colorScheme.onSurface.toDisabledColor(
+ disabledAlpha = DisabledContainerAlpha
+ ),
)
}
@@ -217,7 +220,10 @@
): TextButtonColors {
return textButtonColors(
containerColor = containerColor,
- contentColor = contentColor
+ contentColor = contentColor,
+ disabledContainerColor = MaterialTheme.colorScheme.onSurface.toDisabledColor(
+ disabledAlpha = DisabledContainerAlpha
+ ),
)
}
@@ -256,9 +262,7 @@
fun textButtonColors(
containerColor: Color = Color.Transparent,
contentColor: Color = MaterialTheme.colorScheme.onBackground,
- disabledContainerColor: Color = MaterialTheme.colorScheme.onSurface.toDisabledColor(
- disabledAlpha = DisabledBorderAndContainerAlpha
- ),
+ disabledContainerColor: Color = Color.Transparent,
disabledContentColor: Color = MaterialTheme.colorScheme.onSurface.toDisabledColor()
): TextButtonColors = TextButtonColors(
containerColor = containerColor,
@@ -291,7 +295,7 @@
* unchecked and not enabled
*/
@Composable
- public fun textToggleButtonColors(
+ fun textToggleButtonColors(
checkedContainerColor: Color = MaterialTheme.colorScheme.primary,
checkedContentColor: Color = MaterialTheme.colorScheme.onPrimary,
uncheckedContainerColor: Color = MaterialTheme.colorScheme.surface,
diff --git a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/ScrollAwayDemos.kt b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/ScrollAwayDemos.kt
index 8dd28b2..2109c6a 100644
--- a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/ScrollAwayDemos.kt
+++ b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/ScrollAwayDemos.kt
@@ -158,7 +158,7 @@
)
) {
val modifier = Modifier.height(LocalConfiguration.current.screenHeightDp.dp / 2)
- repeat(3) { i ->
+ repeat(10) { i ->
ExampleCard(modifier, i)
}
}
@@ -192,7 +192,7 @@
focusRequester = focusRequester
)
) {
- items(5) { i ->
+ items(10) { i ->
val modifier = Modifier.fillParentMaxHeight(0.5f)
ExampleCard(modifier = modifier, i = i)
}
@@ -235,7 +235,7 @@
ListHeader { Text("Cards") }
}
- items(5) { i ->
+ items(10) { i ->
ExampleCard(Modifier.fillParentMaxHeight(0.5f), i)
}
}
@@ -276,7 +276,7 @@
ListHeader { Text("Chips") }
}
- items(5) { i ->
+ items(10) { i ->
ExampleChip(Modifier.fillMaxWidth(), i)
}
}
diff --git a/wear/protolayout/protolayout-expression/build.gradle b/wear/protolayout/protolayout-expression/build.gradle
index 85d4ee8..6421982 100644
--- a/wear/protolayout/protolayout-expression/build.gradle
+++ b/wear/protolayout/protolayout-expression/build.gradle
@@ -25,13 +25,10 @@
annotationProcessor(libs.nullaway)
api("androidx.annotation:annotation:1.2.0")
- implementation("androidx.annotation:annotation-experimental:1.3.0")
+ implementation("androidx.annotation:annotation-experimental:1.3.1")
implementation("androidx.collection:collection:1.2.0")
implementation(project(path: ":wear:protolayout:protolayout-proto", configuration: "shadow"))
- // Upgrade transitive kotlin-stdlib dependency from annotation-experimental.
- implementation(libs.kotlinStdlib)
-
testImplementation(libs.testExtJunit)
testImplementation(libs.testExtTruth)
testImplementation(libs.testRunner)
diff --git a/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/common/ProtoLayoutDiffer.java b/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/common/ProtoLayoutDiffer.java
index 906634e..65f4fa2 100644
--- a/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/common/ProtoLayoutDiffer.java
+++ b/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/common/ProtoLayoutDiffer.java
@@ -272,13 +272,24 @@
return new LayoutDiff(changedNodes);
}
- /** Check whether 2 nodes represented by the given fingerprints are equivalent. */
+ /** Check whether two nodes represented by the given fingerprints are equivalent. */
@RestrictTo(Scope.LIBRARY_GROUP)
public static boolean areNodesEquivalent(
@NonNull NodeFingerprint nodeA, @NonNull NodeFingerprint nodeB) {
return getChangeType(nodeA, nodeB) == NodeChangeType.NO_CHANGE;
}
+ /** Check whether two {@link TreeFingerprint} objects are equivalent. */
+ @RestrictTo(Scope.LIBRARY_GROUP)
+ public static boolean areSameFingerprints(
+ @NonNull TreeFingerprint first, @NonNull TreeFingerprint second) {
+ NodeFingerprint prev = first.getRoot();
+ NodeFingerprint current = second.getRoot();
+ return current.getSelfTypeValue() == prev.getSelfTypeValue()
+ && current.getSelfPropsValue() == prev.getSelfPropsValue()
+ && current.getChildNodesValue() == prev.getChildNodesValue();
+ }
+
private static void addChangedNodes(
@NonNull NodeFingerprint prevNodeFingerprint,
@NonNull TreeNode node,
diff --git a/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/impl/ProtoLayoutViewInstance.java b/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/impl/ProtoLayoutViewInstance.java
index 5f53a02..1762d0b 100644
--- a/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/impl/ProtoLayoutViewInstance.java
+++ b/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/impl/ProtoLayoutViewInstance.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2021 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.
@@ -39,12 +39,16 @@
import androidx.wear.protolayout.expression.pipeline.FixedQuotaManagerImpl;
import androidx.wear.protolayout.expression.pipeline.PlatformDataProvider;
import androidx.wear.protolayout.expression.pipeline.StateStore;
+import androidx.wear.protolayout.proto.LayoutElementProto.ArcLayoutElement;
+import androidx.wear.protolayout.proto.LayoutElementProto.ArcLayoutElement.InnerCase;
import androidx.wear.protolayout.proto.LayoutElementProto.Layout;
+import androidx.wear.protolayout.proto.LayoutElementProto.LayoutElement;
import androidx.wear.protolayout.proto.ResourceProto;
import androidx.wear.protolayout.proto.StateProto.State;
import androidx.wear.protolayout.renderer.ProtoLayoutExtensionViewProvider;
import androidx.wear.protolayout.renderer.ProtoLayoutTheme;
import androidx.wear.protolayout.renderer.ProtoLayoutVisibilityState;
+import androidx.wear.protolayout.renderer.common.ProtoLayoutDiffer;
import androidx.wear.protolayout.renderer.dynamicdata.ProtoLayoutDynamicDataPipeline;
import androidx.wear.protolayout.renderer.inflater.ProtoLayoutInflater;
import androidx.wear.protolayout.renderer.inflater.ProtoLayoutInflater.InflateResult;
@@ -55,12 +59,14 @@
import androidx.wear.protolayout.renderer.inflater.ResourceResolvers;
import androidx.wear.protolayout.renderer.inflater.StandardResourceResolvers;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.SettableFuture;
+import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CancellationException;
@@ -89,7 +95,7 @@
}
private static final int DEFAULT_MAX_CONCURRENT_RUNNING_ANIMATIONS = 4;
-
+ static final int MAX_LAYOUT_ELEMENT_DEPTH = 30;
@NonNull private static final String TAG = "ProtoLayoutViewInstance";
@NonNull private final Context mUiContext;
@@ -134,6 +140,12 @@
@Nullable private Layout mPrevLayout = null;
/**
+ * This field is used to avoid unnecessarily checking layout depth if the layout was previously
+ * failing the check.
+ */
+ private boolean mPrevLayoutAlreadyFailingDepthCheck = false;
+
+ /**
* This is used as the Future for the currently running inflation session. The first time
* "attach" is called, it should start the renderer. Subsequent attach calls should only ever
* re-attach "inflateParent".
@@ -703,6 +715,21 @@
return new FailedRenderResult();
}
+ boolean sameFingerprint =
+ prevRenderedMetadata != null
+ && ProtoLayoutDiffer.areSameFingerprints(
+ prevRenderedMetadata.getTreeFingerprint(), layout.getFingerprint());
+
+ if (sameFingerprint) {
+ if (mPrevLayoutAlreadyFailingDepthCheck) {
+ throwExceptionForLayoutDepthCheckFailure();
+ }
+ } else {
+ checkLayoutDepth(layout.getRoot(), MAX_LAYOUT_ELEMENT_DEPTH);
+ }
+
+ mPrevLayoutAlreadyFailingDepthCheck = false;
+
ProtoLayoutInflater.Config.Builder inflaterConfigBuilder =
new ProtoLayoutInflater.Config.Builder(mUiContext, layout, resolvers)
.setLoadActionExecutor(mUiExecutorService)
@@ -1059,6 +1086,52 @@
}
}
+ /** Returns true if the layout element depth doesn't exceed the given {@code allowedDepth}. */
+ private void checkLayoutDepth(LayoutElement layoutElement, int allowedDepth) {
+ if (allowedDepth <= 0) {
+ throwExceptionForLayoutDepthCheckFailure();
+ }
+ List<LayoutElement> children = ImmutableList.of();
+ switch (layoutElement.getInnerCase()) {
+ case COLUMN:
+ children = layoutElement.getColumn().getContentsList();
+ break;
+ case ROW:
+ children = layoutElement.getRow().getContentsList();
+ break;
+ case BOX:
+ children = layoutElement.getBox().getContentsList();
+ break;
+ case ARC:
+ List<ArcLayoutElement> arcElements = layoutElement.getArc().getContentsList();
+ if (!arcElements.isEmpty() && allowedDepth == 1) {
+ throwExceptionForLayoutDepthCheckFailure();
+ }
+ for (ArcLayoutElement element : arcElements) {
+ if (element.getInnerCase() == InnerCase.ADAPTER) {
+ checkLayoutDepth(element.getAdapter().getContent(), allowedDepth - 1);
+ }
+ }
+ break;
+ case SPANNABLE:
+ if (layoutElement.getSpannable().getSpansCount() > 0 && allowedDepth == 1) {
+ throwExceptionForLayoutDepthCheckFailure();
+ }
+ break;
+ default:
+ // Other LayoutElements have depth of one.
+ }
+ for (LayoutElement child : children) {
+ checkLayoutDepth(child, allowedDepth - 1);
+ }
+ }
+
+ private void throwExceptionForLayoutDepthCheckFailure() {
+ mPrevLayoutAlreadyFailingDepthCheck = true;
+ throw new IllegalStateException(
+ "Layout depth exceeds maximum allowed depth: " + MAX_LAYOUT_ELEMENT_DEPTH);
+ }
+
@Override
public void close() throws Exception {
detachInternal();
diff --git a/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/inflater/RenderedMetadata.java b/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/inflater/RenderedMetadata.java
index 11cbf345..5b6bdc5 100644
--- a/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/inflater/RenderedMetadata.java
+++ b/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/inflater/RenderedMetadata.java
@@ -43,7 +43,7 @@
}
@NonNull
- TreeFingerprint getTreeFingerprint() {
+ public TreeFingerprint getTreeFingerprint() {
return mTreeFingerprint;
}
diff --git a/wear/protolayout/protolayout-renderer/src/test/java/androidx/wear/protolayout/renderer/common/ProtoLayoutDifferTest.java b/wear/protolayout/protolayout-renderer/src/test/java/androidx/wear/protolayout/renderer/common/ProtoLayoutDifferTest.java
index b86f294..deb6dbc 100644
--- a/wear/protolayout/protolayout-renderer/src/test/java/androidx/wear/protolayout/renderer/common/ProtoLayoutDifferTest.java
+++ b/wear/protolayout/protolayout-renderer/src/test/java/androidx/wear/protolayout/renderer/common/ProtoLayoutDifferTest.java
@@ -303,6 +303,30 @@
}
@Test
+ public void areSameFingerprints() {
+ assertThat(
+ ProtoLayoutDiffer.areSameFingerprints(
+ referenceLayout().getFingerprint(),
+ referenceLayout().getFingerprint()))
+ .isTrue();
+ assertThat(
+ ProtoLayoutDiffer.areSameFingerprints(
+ referenceLayout().getFingerprint(),
+ layoutWithOneUpdatedNode().getFingerprint()))
+ .isFalse();
+ assertThat(
+ ProtoLayoutDiffer.areSameFingerprints(
+ referenceLayout().getFingerprint(),
+ layoutWithDifferentNumberOfChildren().getFingerprint()))
+ .isFalse();
+ assertThat(
+ ProtoLayoutDiffer.areSameFingerprints(
+ referenceLayout().getFingerprint(),
+ layoutWithUpdateToNodeSelfFingerprint().getFingerprint()))
+ .isFalse();
+ }
+
+ @Test
public void isChildOf_forAnActualChild_returnsTrue() {
String childPosId = "pT1.2.3";
String parentPosId = "pT1.2";
diff --git a/wear/protolayout/protolayout-renderer/src/test/java/androidx/wear/protolayout/renderer/helper/TestDsl.java b/wear/protolayout/protolayout-renderer/src/test/java/androidx/wear/protolayout/renderer/helper/TestDsl.java
index b158c38..9f7018ae 100644
--- a/wear/protolayout/protolayout-renderer/src/test/java/androidx/wear/protolayout/renderer/helper/TestDsl.java
+++ b/wear/protolayout/protolayout-renderer/src/test/java/androidx/wear/protolayout/renderer/helper/TestDsl.java
@@ -37,6 +37,7 @@
import androidx.wear.protolayout.proto.FingerprintProto.TreeFingerprint;
import androidx.wear.protolayout.proto.LayoutElementProto;
import androidx.wear.protolayout.proto.LayoutElementProto.Arc;
+import androidx.wear.protolayout.proto.LayoutElementProto.ArcAdapter;
import androidx.wear.protolayout.proto.LayoutElementProto.ArcLayoutElement;
import androidx.wear.protolayout.proto.LayoutElementProto.ArcText;
import androidx.wear.protolayout.proto.LayoutElementProto.Box;
@@ -433,6 +434,10 @@
return arcInternal(/* propsConsumer= */ null, nodes);
}
+ public static LayoutNode arcAdapter(LayoutNode layoutNode) {
+ return arcAdapterInternal(layoutNode);
+ }
+
private static LayoutNode arcInternal(
@Nullable Consumer<ArcProps> propsConsumer, LayoutNode... nodes) {
LayoutNode element = new LayoutNode();
@@ -449,6 +454,15 @@
return element;
}
+ private static LayoutNode arcAdapterInternal(LayoutNode node) {
+ LayoutNode element = new LayoutNode();
+ ArcAdapter.Builder builder = ArcAdapter.newBuilder().setContent(node.mLayoutElement);
+ int selfPropsFingerprint = 0;
+ element.mArcLayoutElement = ArcLayoutElement.newBuilder().setAdapter(builder.build());
+ element.mFingerprint = fingerprint("arcAdapter", selfPropsFingerprint, node);
+ return element;
+ }
+
public static LayoutNode arcText(String text) {
LayoutNode element = new LayoutNode();
element.mArcLayoutElement =
diff --git a/wear/protolayout/protolayout-renderer/src/test/java/androidx/wear/protolayout/renderer/impl/ProtoLayoutViewInstanceTest.java b/wear/protolayout/protolayout-renderer/src/test/java/androidx/wear/protolayout/renderer/impl/ProtoLayoutViewInstanceTest.java
index ca3f94e..3ad5656 100644
--- a/wear/protolayout/protolayout-renderer/src/test/java/androidx/wear/protolayout/renderer/impl/ProtoLayoutViewInstanceTest.java
+++ b/wear/protolayout/protolayout-renderer/src/test/java/androidx/wear/protolayout/renderer/impl/ProtoLayoutViewInstanceTest.java
@@ -17,10 +17,16 @@
package androidx.wear.protolayout.renderer.impl;
import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+import static androidx.wear.protolayout.renderer.helper.TestDsl.arc;
+import static androidx.wear.protolayout.renderer.helper.TestDsl.arcAdapter;
+import static androidx.wear.protolayout.renderer.helper.TestDsl.box;
import static androidx.wear.protolayout.renderer.helper.TestDsl.column;
import static androidx.wear.protolayout.renderer.helper.TestDsl.dynamicFixedText;
import static androidx.wear.protolayout.renderer.helper.TestDsl.layout;
+import static androidx.wear.protolayout.renderer.helper.TestDsl.spanText;
+import static androidx.wear.protolayout.renderer.helper.TestDsl.spannable;
import static androidx.wear.protolayout.renderer.helper.TestDsl.text;
+import static androidx.wear.protolayout.renderer.impl.ProtoLayoutViewInstance.MAX_LAYOUT_ELEMENT_DEPTH;
import static com.google.common.truth.Truth.assertThat;
@@ -39,6 +45,7 @@
import androidx.wear.protolayout.expression.pipeline.StateStore;
import androidx.wear.protolayout.proto.LayoutElementProto.Layout;
import androidx.wear.protolayout.proto.ResourceProto.Resources;
+import androidx.wear.protolayout.renderer.helper.TestDsl.LayoutNode;
import androidx.wear.protolayout.renderer.impl.ProtoLayoutViewInstance.Config;
import com.google.common.collect.ImmutableList;
@@ -55,6 +62,7 @@
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.AbstractExecutorService;
+import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
@RunWith(AndroidJUnit4.class)
@@ -390,6 +398,107 @@
assertThat(mRootContainer.getChildCount()).isEqualTo(0);
}
+ @Test
+ public void layoutDepthExceedsMaximumDepth_renderingFail() throws Exception {
+ setupInstance(/* adaptiveUpdateRatesEnabled= */ false);
+ assertThrows(
+ ExecutionException.class,
+ () -> renderAndAttachLayout(layout(recursiveBox(MAX_LAYOUT_ELEMENT_DEPTH + 1))));
+ }
+
+ @Test
+ public void layoutDepthIsEqualToMaximumDepth_renderingPass() throws Exception {
+ setupInstance(/* adaptiveUpdateRatesEnabled= */ false);
+
+ LayoutNode[] children = new LayoutNode[MAX_LAYOUT_ELEMENT_DEPTH];
+ for (int i = 0; i < children.length; i++) {
+ children[i] = recursiveBox(MAX_LAYOUT_ELEMENT_DEPTH - 1);
+ }
+ ListenableFuture<Void> result =
+ mInstanceUnderTest.renderAndAttach(
+ // MAX_LAYOUT_ELEMENT_DEPTH branches of depth MAX_LAYOUT_ELEMENT_DEPTH - 1.
+ // Total depth is MAX_LAYOUT_ELEMENT_DEPTH (if we count the head).
+ layout(box(children)), RESOURCES, mRootContainer);
+ shadowOf(Looper.getMainLooper()).idle();
+
+ assertNoException(result);
+ assertThat(mRootContainer.getChildCount()).isEqualTo(1);
+ }
+
+ @Test
+ public void layoutDepthForLayoutWithSpanner() throws Exception {
+ setupInstance(/* adaptiveUpdateRatesEnabled= */ false);
+
+ assertThrows(
+ ExecutionException.class,
+ () ->
+ renderAndAttachLayout(
+ // Total number of views is = MAX_LAYOUT_ELEMENT_DEPTH + 1 (span
+ // text)
+ layout(
+ recursiveBox(
+ MAX_LAYOUT_ELEMENT_DEPTH,
+ spannable(spanText("Hello"))))));
+
+ ListenableFuture<Void> result =
+ mInstanceUnderTest.renderAndAttach(
+ // Total number of views is = (MAX_LAYOUT_ELEMENT_DEPTH -1) + 1 (span text)
+ layout(
+ recursiveBox(
+ MAX_LAYOUT_ELEMENT_DEPTH - 1,
+ spannable(spanText("Hello")))),
+ RESOURCES,
+ mRootContainer);
+ shadowOf(Looper.getMainLooper()).idle();
+
+ assertNoException(result);
+ assertThat(mRootContainer.getChildCount()).isEqualTo(1);
+ }
+
+ @Test
+ public void layoutDepthForLayoutWithArcAdapter() throws Exception {
+ setupInstance(/* adaptiveUpdateRatesEnabled= */ false);
+ assertThrows(
+ ExecutionException.class,
+ () ->
+ renderAndAttachLayout(
+ // Total number of views is = 1 (Arc) + (MAX_LAYOUT_ELEMENT_DEPTH)
+ layout(arc(arcAdapter(recursiveBox(MAX_LAYOUT_ELEMENT_DEPTH))))));
+
+ ListenableFuture<Void> result =
+ mInstanceUnderTest.renderAndAttach(
+ // Total number of views is = 1 (Arc) + (MAX_LAYOUT_ELEMENT_DEPTH - 1)
+ // = MAX_LAYOUT_ELEMENT_DEPTH
+ layout(arc(arcAdapter(recursiveBox(MAX_LAYOUT_ELEMENT_DEPTH - 1)))),
+ RESOURCES,
+ mRootContainer);
+ shadowOf(Looper.getMainLooper()).idle();
+
+ assertNoException(result);
+ assertThat(mRootContainer.getChildCount()).isEqualTo(1);
+ }
+
+ private void renderAndAttachLayout(Layout layout) throws Exception {
+ ListenableFuture<Void> result =
+ mInstanceUnderTest.renderAndAttach(layout, RESOURCES, mRootContainer);
+ shadowOf(Looper.getMainLooper()).idle();
+ assertNoException(result);
+ }
+
+ private static LayoutNode recursiveBox(int depth) {
+ if (depth == 1) {
+ return box();
+ }
+ return box(recursiveBox(depth - 1));
+ }
+
+ private static LayoutNode recursiveBox(int depth, LayoutNode leaf) {
+ if (depth == 1) {
+ return leaf;
+ }
+ return box(recursiveBox(depth - 1, leaf));
+ }
+
private void setupInstance(boolean adaptiveUpdateRatesEnabled) {
FakeExecutorService uiThreadExecutor =
new FakeExecutorService(new Handler(Looper.getMainLooper()));
diff --git a/wear/protolayout/protolayout/api/1.0.0-beta01.txt b/wear/protolayout/protolayout/api/1.0.0-beta01.txt
index 55ef432..21ffdf4 100644
--- a/wear/protolayout/protolayout/api/1.0.0-beta01.txt
+++ b/wear/protolayout/protolayout/api/1.0.0-beta01.txt
@@ -354,7 +354,6 @@
method public androidx.wear.protolayout.DimensionBuilders.DegreesProp? getAnchorAngle();
method public androidx.wear.protolayout.LayoutElementBuilders.ArcAnchorTypeProp? getAnchorType();
method public java.util.List<androidx.wear.protolayout.LayoutElementBuilders.ArcLayoutElement!> getContents();
- method public androidx.wear.protolayout.DimensionBuilders.AngularLayoutConstraint? getLayoutConstraintsForDynamicAnchorAngle();
method public androidx.wear.protolayout.ModifiersBuilders.Modifiers? getModifiers();
method public androidx.wear.protolayout.LayoutElementBuilders.VerticalAlignmentProp? getVerticalAlign();
}
@@ -366,7 +365,6 @@
method public androidx.wear.protolayout.LayoutElementBuilders.Arc.Builder setAnchorAngle(androidx.wear.protolayout.DimensionBuilders.DegreesProp);
method public androidx.wear.protolayout.LayoutElementBuilders.Arc.Builder setAnchorType(androidx.wear.protolayout.LayoutElementBuilders.ArcAnchorTypeProp);
method public androidx.wear.protolayout.LayoutElementBuilders.Arc.Builder setAnchorType(int);
- method public androidx.wear.protolayout.LayoutElementBuilders.Arc.Builder setLayoutConstraintsForDynamicAnchorAngle(androidx.wear.protolayout.DimensionBuilders.AngularLayoutConstraint);
method public androidx.wear.protolayout.LayoutElementBuilders.Arc.Builder setModifiers(androidx.wear.protolayout.ModifiersBuilders.Modifiers);
method public androidx.wear.protolayout.LayoutElementBuilders.Arc.Builder setVerticalAlign(androidx.wear.protolayout.LayoutElementBuilders.VerticalAlignmentProp);
method public androidx.wear.protolayout.LayoutElementBuilders.Arc.Builder setVerticalAlign(int);
diff --git a/wear/protolayout/protolayout/api/current.ignore b/wear/protolayout/protolayout/api/current.ignore
index fc89635..2d69c6a 100644
--- a/wear/protolayout/protolayout/api/current.ignore
+++ b/wear/protolayout/protolayout/api/current.ignore
@@ -1,403 +1,5 @@
// Baseline format: 1.0
-BecameUnchecked: androidx.wear.protolayout.DeviceParametersBuilders.Capabilities:
- Removed class androidx.wear.protolayout.DeviceParametersBuilders.Capabilities from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.DeviceParametersBuilders.Capabilities#getMinimumFreshnessLimitMillis():
- Removed method androidx.wear.protolayout.DeviceParametersBuilders.Capabilities.getMinimumFreshnessLimitMillis() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.DeviceParametersBuilders.Capabilities.Builder:
- Removed class androidx.wear.protolayout.DeviceParametersBuilders.Capabilities.Builder from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.DeviceParametersBuilders.Capabilities.Builder#Builder():
- Removed constructor androidx.wear.protolayout.DeviceParametersBuilders.Capabilities.Builder() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.DeviceParametersBuilders.Capabilities.Builder#build():
- Removed method androidx.wear.protolayout.DeviceParametersBuilders.Capabilities.Builder.build() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.DeviceParametersBuilders.Capabilities.Builder#setMinimumFreshnessLimitMillis(long):
- Removed method androidx.wear.protolayout.DeviceParametersBuilders.Capabilities.Builder.setMinimumFreshnessLimitMillis(long) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.DeviceParametersBuilders.Capabilities.Builder#setMinimumFreshnessLimitMillis(long) parameter #0:
- Removed parameter arg1 in androidx.wear.protolayout.DeviceParametersBuilders.Capabilities.Builder.setMinimumFreshnessLimitMillis(long arg1) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.DeviceParametersBuilders.DeviceParameters#getCapabilities():
- Removed method androidx.wear.protolayout.DeviceParametersBuilders.DeviceParameters.getCapabilities() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.DeviceParametersBuilders.DeviceParameters.Builder#setCapabilities(androidx.wear.protolayout.DeviceParametersBuilders.Capabilities):
- Removed method androidx.wear.protolayout.DeviceParametersBuilders.DeviceParameters.Builder.setCapabilities(androidx.wear.protolayout.DeviceParametersBuilders.Capabilities) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.DeviceParametersBuilders.DeviceParameters.Builder#setCapabilities(androidx.wear.protolayout.DeviceParametersBuilders.Capabilities) parameter #0:
- Removed parameter arg1 in androidx.wear.protolayout.DeviceParametersBuilders.DeviceParameters.Builder.setCapabilities(androidx.wear.protolayout.DeviceParametersBuilders.Capabilities arg1) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.DimensionBuilders.ExtensionDimension:
- Removed class androidx.wear.protolayout.DimensionBuilders.ExtensionDimension from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders#FONT_WEIGHT_MEDIUM:
- Removed field androidx.wear.protolayout.LayoutElementBuilders.FONT_WEIGHT_MEDIUM from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders#TEXT_OVERFLOW_MARQUEE:
- Removed field androidx.wear.protolayout.LayoutElementBuilders.TEXT_OVERFLOW_MARQUEE from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders.AndroidTextStyle:
- Removed class androidx.wear.protolayout.LayoutElementBuilders.AndroidTextStyle from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders.AndroidTextStyle#getExcludeFontPadding():
- Removed method androidx.wear.protolayout.LayoutElementBuilders.AndroidTextStyle.getExcludeFontPadding() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders.AndroidTextStyle.Builder:
- Removed class androidx.wear.protolayout.LayoutElementBuilders.AndroidTextStyle.Builder from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders.AndroidTextStyle.Builder#Builder():
- Removed constructor androidx.wear.protolayout.LayoutElementBuilders.AndroidTextStyle.Builder() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders.AndroidTextStyle.Builder#build():
- Removed method androidx.wear.protolayout.LayoutElementBuilders.AndroidTextStyle.Builder.build() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders.AndroidTextStyle.Builder#setExcludeFontPadding(boolean):
- Removed method androidx.wear.protolayout.LayoutElementBuilders.AndroidTextStyle.Builder.setExcludeFontPadding(boolean) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders.AndroidTextStyle.Builder#setExcludeFontPadding(boolean) parameter #0:
- Removed parameter arg1 in androidx.wear.protolayout.LayoutElementBuilders.AndroidTextStyle.Builder.setExcludeFontPadding(boolean arg1) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders.ExtensionLayoutElement:
- Removed class androidx.wear.protolayout.LayoutElementBuilders.ExtensionLayoutElement from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders.ExtensionLayoutElement#getExtensionId():
- Removed method androidx.wear.protolayout.LayoutElementBuilders.ExtensionLayoutElement.getExtensionId() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders.ExtensionLayoutElement#getHeight():
- Removed method androidx.wear.protolayout.LayoutElementBuilders.ExtensionLayoutElement.getHeight() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders.ExtensionLayoutElement#getPayload():
- Removed method androidx.wear.protolayout.LayoutElementBuilders.ExtensionLayoutElement.getPayload() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders.ExtensionLayoutElement#getWidth():
- Removed method androidx.wear.protolayout.LayoutElementBuilders.ExtensionLayoutElement.getWidth() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders.ExtensionLayoutElement.Builder:
- Removed class androidx.wear.protolayout.LayoutElementBuilders.ExtensionLayoutElement.Builder from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders.ExtensionLayoutElement.Builder#Builder():
- Removed constructor androidx.wear.protolayout.LayoutElementBuilders.ExtensionLayoutElement.Builder() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders.ExtensionLayoutElement.Builder#build():
- Removed method androidx.wear.protolayout.LayoutElementBuilders.ExtensionLayoutElement.Builder.build() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders.ExtensionLayoutElement.Builder#setExtensionId(String):
- Removed method androidx.wear.protolayout.LayoutElementBuilders.ExtensionLayoutElement.Builder.setExtensionId(String) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders.ExtensionLayoutElement.Builder#setExtensionId(String) parameter #0:
- Removed parameter arg1 in androidx.wear.protolayout.LayoutElementBuilders.ExtensionLayoutElement.Builder.setExtensionId(String arg1) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders.ExtensionLayoutElement.Builder#setHeight(androidx.wear.protolayout.DimensionBuilders.ExtensionDimension):
- Removed method androidx.wear.protolayout.LayoutElementBuilders.ExtensionLayoutElement.Builder.setHeight(androidx.wear.protolayout.DimensionBuilders.ExtensionDimension) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders.ExtensionLayoutElement.Builder#setHeight(androidx.wear.protolayout.DimensionBuilders.ExtensionDimension) parameter #0:
- Removed parameter arg1 in androidx.wear.protolayout.LayoutElementBuilders.ExtensionLayoutElement.Builder.setHeight(androidx.wear.protolayout.DimensionBuilders.ExtensionDimension arg1) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders.ExtensionLayoutElement.Builder#setPayload(byte[]):
- Removed method androidx.wear.protolayout.LayoutElementBuilders.ExtensionLayoutElement.Builder.setPayload(byte[]) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders.ExtensionLayoutElement.Builder#setPayload(byte[]) parameter #0:
- Removed parameter arg1 in androidx.wear.protolayout.LayoutElementBuilders.ExtensionLayoutElement.Builder.setPayload(byte[] arg1) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders.ExtensionLayoutElement.Builder#setWidth(androidx.wear.protolayout.DimensionBuilders.ExtensionDimension):
- Removed method androidx.wear.protolayout.LayoutElementBuilders.ExtensionLayoutElement.Builder.setWidth(androidx.wear.protolayout.DimensionBuilders.ExtensionDimension) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders.ExtensionLayoutElement.Builder#setWidth(androidx.wear.protolayout.DimensionBuilders.ExtensionDimension) parameter #0:
- Removed parameter arg1 in androidx.wear.protolayout.LayoutElementBuilders.ExtensionLayoutElement.Builder.setWidth(androidx.wear.protolayout.DimensionBuilders.ExtensionDimension arg1) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders.FontStyle#getVariant():
- Removed method androidx.wear.protolayout.LayoutElementBuilders.FontStyle.getVariant() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders.FontStyle.Builder#setVariant(androidx.wear.protolayout.LayoutElementBuilders.FontVariantProp):
- Removed method androidx.wear.protolayout.LayoutElementBuilders.FontStyle.Builder.setVariant(androidx.wear.protolayout.LayoutElementBuilders.FontVariantProp) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders.FontStyle.Builder#setVariant(androidx.wear.protolayout.LayoutElementBuilders.FontVariantProp) parameter #0:
- Removed parameter arg1 in androidx.wear.protolayout.LayoutElementBuilders.FontStyle.Builder.setVariant(androidx.wear.protolayout.LayoutElementBuilders.FontVariantProp arg1) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders.FontStyle.Builder#setVariant(int):
- Removed method androidx.wear.protolayout.LayoutElementBuilders.FontStyle.Builder.setVariant(int) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders.FontStyle.Builder#setVariant(int) parameter #0:
- Removed parameter arg1 in androidx.wear.protolayout.LayoutElementBuilders.FontStyle.Builder.setVariant(int arg1) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders.FontVariantProp:
- Removed class androidx.wear.protolayout.LayoutElementBuilders.FontVariantProp from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders.FontVariantProp#getValue():
- Removed method androidx.wear.protolayout.LayoutElementBuilders.FontVariantProp.getValue() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders.FontVariantProp.Builder:
- Removed class androidx.wear.protolayout.LayoutElementBuilders.FontVariantProp.Builder from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders.FontVariantProp.Builder#Builder():
- Removed constructor androidx.wear.protolayout.LayoutElementBuilders.FontVariantProp.Builder() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders.FontVariantProp.Builder#build():
- Removed method androidx.wear.protolayout.LayoutElementBuilders.FontVariantProp.Builder.build() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders.FontVariantProp.Builder#setValue(int):
- Removed method androidx.wear.protolayout.LayoutElementBuilders.FontVariantProp.Builder.setValue(int) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders.FontVariantProp.Builder#setValue(int) parameter #0:
- Removed parameter arg1 in androidx.wear.protolayout.LayoutElementBuilders.FontVariantProp.Builder.setValue(int arg1) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders.Layout#fromByteArray(byte[]):
- Removed method androidx.wear.protolayout.LayoutElementBuilders.Layout.fromByteArray(byte[]) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders.Layout#fromByteArray(byte[]) parameter #0:
- Removed parameter arg1 in androidx.wear.protolayout.LayoutElementBuilders.Layout.fromByteArray(byte[] arg1) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders.Layout#toByteArray():
- Removed method androidx.wear.protolayout.LayoutElementBuilders.Layout.toByteArray() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders.SpanText#getAndroidTextStyle():
- Removed method androidx.wear.protolayout.LayoutElementBuilders.SpanText.getAndroidTextStyle() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders.SpanText.Builder#setAndroidTextStyle(androidx.wear.protolayout.LayoutElementBuilders.AndroidTextStyle):
- Removed method androidx.wear.protolayout.LayoutElementBuilders.SpanText.Builder.setAndroidTextStyle(androidx.wear.protolayout.LayoutElementBuilders.AndroidTextStyle) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders.SpanText.Builder#setAndroidTextStyle(androidx.wear.protolayout.LayoutElementBuilders.AndroidTextStyle) parameter #0:
- Removed parameter arg1 in androidx.wear.protolayout.LayoutElementBuilders.SpanText.Builder.setAndroidTextStyle(androidx.wear.protolayout.LayoutElementBuilders.AndroidTextStyle arg1) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders.Spannable#getMarqueeIterations():
- Removed method androidx.wear.protolayout.LayoutElementBuilders.Spannable.getMarqueeIterations() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders.Spannable.Builder#setMarqueeIterations(int):
- Removed method androidx.wear.protolayout.LayoutElementBuilders.Spannable.Builder.setMarqueeIterations(int) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders.Spannable.Builder#setMarqueeIterations(int) parameter #0:
- Removed parameter arg1 in androidx.wear.protolayout.LayoutElementBuilders.Spannable.Builder.setMarqueeIterations(int arg1) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders.Text#getAndroidTextStyle():
- Removed method androidx.wear.protolayout.LayoutElementBuilders.Text.getAndroidTextStyle() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders.Text#getMarqueeIterations():
- Removed method androidx.wear.protolayout.LayoutElementBuilders.Text.getMarqueeIterations() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders.Text.Builder#setAndroidTextStyle(androidx.wear.protolayout.LayoutElementBuilders.AndroidTextStyle):
- Removed method androidx.wear.protolayout.LayoutElementBuilders.Text.Builder.setAndroidTextStyle(androidx.wear.protolayout.LayoutElementBuilders.AndroidTextStyle) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders.Text.Builder#setAndroidTextStyle(androidx.wear.protolayout.LayoutElementBuilders.AndroidTextStyle) parameter #0:
- Removed parameter arg1 in androidx.wear.protolayout.LayoutElementBuilders.Text.Builder.setAndroidTextStyle(androidx.wear.protolayout.LayoutElementBuilders.AndroidTextStyle arg1) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders.Text.Builder#setMarqueeIterations(int):
- Removed method androidx.wear.protolayout.LayoutElementBuilders.Text.Builder.setMarqueeIterations(int) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders.Text.Builder#setMarqueeIterations(int) parameter #0:
- Removed parameter arg1 in androidx.wear.protolayout.LayoutElementBuilders.Text.Builder.setMarqueeIterations(int arg1) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders#SLIDE_DIRECTION_BOTTOM_TO_TOP:
- Removed field androidx.wear.protolayout.ModifiersBuilders.SLIDE_DIRECTION_BOTTOM_TO_TOP from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders#SLIDE_DIRECTION_LEFT_TO_RIGHT:
- Removed field androidx.wear.protolayout.ModifiersBuilders.SLIDE_DIRECTION_LEFT_TO_RIGHT from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders#SLIDE_DIRECTION_RIGHT_TO_LEFT:
- Removed field androidx.wear.protolayout.ModifiersBuilders.SLIDE_DIRECTION_RIGHT_TO_LEFT from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders#SLIDE_DIRECTION_TOP_TO_BOTTOM:
- Removed field androidx.wear.protolayout.ModifiersBuilders.SLIDE_DIRECTION_TOP_TO_BOTTOM from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders#SLIDE_DIRECTION_UNDEFINED:
- Removed field androidx.wear.protolayout.ModifiersBuilders.SLIDE_DIRECTION_UNDEFINED from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders#SLIDE_PARENT_SNAP_TO_INSIDE:
- Removed field androidx.wear.protolayout.ModifiersBuilders.SLIDE_PARENT_SNAP_TO_INSIDE from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders#SLIDE_PARENT_SNAP_TO_OUTSIDE:
- Removed field androidx.wear.protolayout.ModifiersBuilders.SLIDE_PARENT_SNAP_TO_OUTSIDE from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders#SLIDE_PARENT_SNAP_UNDEFINED:
- Removed field androidx.wear.protolayout.ModifiersBuilders.SLIDE_PARENT_SNAP_UNDEFINED from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.AnimatedVisibility:
- Removed class androidx.wear.protolayout.ModifiersBuilders.AnimatedVisibility from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.AnimatedVisibility#getEnterTransition():
- Removed method androidx.wear.protolayout.ModifiersBuilders.AnimatedVisibility.getEnterTransition() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.AnimatedVisibility#getExitTransition():
- Removed method androidx.wear.protolayout.ModifiersBuilders.AnimatedVisibility.getExitTransition() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.AnimatedVisibility.Builder:
- Removed class androidx.wear.protolayout.ModifiersBuilders.AnimatedVisibility.Builder from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.AnimatedVisibility.Builder#Builder():
- Removed constructor androidx.wear.protolayout.ModifiersBuilders.AnimatedVisibility.Builder() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.AnimatedVisibility.Builder#build():
- Removed method androidx.wear.protolayout.ModifiersBuilders.AnimatedVisibility.Builder.build() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.AnimatedVisibility.Builder#setEnterTransition(androidx.wear.protolayout.ModifiersBuilders.EnterTransition):
- Removed method androidx.wear.protolayout.ModifiersBuilders.AnimatedVisibility.Builder.setEnterTransition(androidx.wear.protolayout.ModifiersBuilders.EnterTransition) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.AnimatedVisibility.Builder#setEnterTransition(androidx.wear.protolayout.ModifiersBuilders.EnterTransition) parameter #0:
- Removed parameter arg1 in androidx.wear.protolayout.ModifiersBuilders.AnimatedVisibility.Builder.setEnterTransition(androidx.wear.protolayout.ModifiersBuilders.EnterTransition arg1) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.AnimatedVisibility.Builder#setExitTransition(androidx.wear.protolayout.ModifiersBuilders.ExitTransition):
- Removed method androidx.wear.protolayout.ModifiersBuilders.AnimatedVisibility.Builder.setExitTransition(androidx.wear.protolayout.ModifiersBuilders.ExitTransition) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.AnimatedVisibility.Builder#setExitTransition(androidx.wear.protolayout.ModifiersBuilders.ExitTransition) parameter #0:
- Removed parameter arg1 in androidx.wear.protolayout.ModifiersBuilders.AnimatedVisibility.Builder.setExitTransition(androidx.wear.protolayout.ModifiersBuilders.ExitTransition arg1) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.DefaultContentTransitions:
- Removed class androidx.wear.protolayout.ModifiersBuilders.DefaultContentTransitions from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.DefaultContentTransitions#fadeIn():
- Removed method androidx.wear.protolayout.ModifiersBuilders.DefaultContentTransitions.fadeIn() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.DefaultContentTransitions#fadeInSlideIn(int):
- Removed method androidx.wear.protolayout.ModifiersBuilders.DefaultContentTransitions.fadeInSlideIn(int) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.DefaultContentTransitions#fadeInSlideIn(int) parameter #0:
- Removed parameter arg1 in androidx.wear.protolayout.ModifiersBuilders.DefaultContentTransitions.fadeInSlideIn(int arg1) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.DefaultContentTransitions#fadeOut():
- Removed method androidx.wear.protolayout.ModifiersBuilders.DefaultContentTransitions.fadeOut() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.DefaultContentTransitions#fadeOutSlideOut(int):
- Removed method androidx.wear.protolayout.ModifiersBuilders.DefaultContentTransitions.fadeOutSlideOut(int) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.DefaultContentTransitions#fadeOutSlideOut(int) parameter #0:
- Removed parameter arg1 in androidx.wear.protolayout.ModifiersBuilders.DefaultContentTransitions.fadeOutSlideOut(int arg1) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.DefaultContentTransitions#slideIn(int):
- Removed method androidx.wear.protolayout.ModifiersBuilders.DefaultContentTransitions.slideIn(int) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.DefaultContentTransitions#slideIn(int) parameter #0:
- Removed parameter arg1 in androidx.wear.protolayout.ModifiersBuilders.DefaultContentTransitions.slideIn(int arg1) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.DefaultContentTransitions#slideOut(int):
- Removed method androidx.wear.protolayout.ModifiersBuilders.DefaultContentTransitions.slideOut(int) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.DefaultContentTransitions#slideOut(int) parameter #0:
- Removed parameter arg1 in androidx.wear.protolayout.ModifiersBuilders.DefaultContentTransitions.slideOut(int arg1) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.EnterTransition:
- Removed class androidx.wear.protolayout.ModifiersBuilders.EnterTransition from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.EnterTransition#getFadeIn():
- Removed method androidx.wear.protolayout.ModifiersBuilders.EnterTransition.getFadeIn() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.EnterTransition#getSlideIn():
- Removed method androidx.wear.protolayout.ModifiersBuilders.EnterTransition.getSlideIn() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.EnterTransition.Builder:
- Removed class androidx.wear.protolayout.ModifiersBuilders.EnterTransition.Builder from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.EnterTransition.Builder#Builder():
- Removed constructor androidx.wear.protolayout.ModifiersBuilders.EnterTransition.Builder() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.EnterTransition.Builder#build():
- Removed method androidx.wear.protolayout.ModifiersBuilders.EnterTransition.Builder.build() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.EnterTransition.Builder#setFadeIn(androidx.wear.protolayout.ModifiersBuilders.FadeInTransition):
- Removed method androidx.wear.protolayout.ModifiersBuilders.EnterTransition.Builder.setFadeIn(androidx.wear.protolayout.ModifiersBuilders.FadeInTransition) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.EnterTransition.Builder#setFadeIn(androidx.wear.protolayout.ModifiersBuilders.FadeInTransition) parameter #0:
- Removed parameter arg1 in androidx.wear.protolayout.ModifiersBuilders.EnterTransition.Builder.setFadeIn(androidx.wear.protolayout.ModifiersBuilders.FadeInTransition arg1) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.EnterTransition.Builder#setSlideIn(androidx.wear.protolayout.ModifiersBuilders.SlideInTransition):
- Removed method androidx.wear.protolayout.ModifiersBuilders.EnterTransition.Builder.setSlideIn(androidx.wear.protolayout.ModifiersBuilders.SlideInTransition) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.EnterTransition.Builder#setSlideIn(androidx.wear.protolayout.ModifiersBuilders.SlideInTransition) parameter #0:
- Removed parameter arg1 in androidx.wear.protolayout.ModifiersBuilders.EnterTransition.Builder.setSlideIn(androidx.wear.protolayout.ModifiersBuilders.SlideInTransition arg1) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.ExitTransition:
- Removed class androidx.wear.protolayout.ModifiersBuilders.ExitTransition from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.ExitTransition#getFadeOut():
- Removed method androidx.wear.protolayout.ModifiersBuilders.ExitTransition.getFadeOut() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.ExitTransition#getSlideOut():
- Removed method androidx.wear.protolayout.ModifiersBuilders.ExitTransition.getSlideOut() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.ExitTransition.Builder:
- Removed class androidx.wear.protolayout.ModifiersBuilders.ExitTransition.Builder from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.ExitTransition.Builder#Builder():
- Removed constructor androidx.wear.protolayout.ModifiersBuilders.ExitTransition.Builder() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.ExitTransition.Builder#build():
- Removed method androidx.wear.protolayout.ModifiersBuilders.ExitTransition.Builder.build() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.ExitTransition.Builder#setFadeOut(androidx.wear.protolayout.ModifiersBuilders.FadeOutTransition):
- Removed method androidx.wear.protolayout.ModifiersBuilders.ExitTransition.Builder.setFadeOut(androidx.wear.protolayout.ModifiersBuilders.FadeOutTransition) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.ExitTransition.Builder#setFadeOut(androidx.wear.protolayout.ModifiersBuilders.FadeOutTransition) parameter #0:
- Removed parameter arg1 in androidx.wear.protolayout.ModifiersBuilders.ExitTransition.Builder.setFadeOut(androidx.wear.protolayout.ModifiersBuilders.FadeOutTransition arg1) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.ExitTransition.Builder#setSlideOut(androidx.wear.protolayout.ModifiersBuilders.SlideOutTransition):
- Removed method androidx.wear.protolayout.ModifiersBuilders.ExitTransition.Builder.setSlideOut(androidx.wear.protolayout.ModifiersBuilders.SlideOutTransition) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.ExitTransition.Builder#setSlideOut(androidx.wear.protolayout.ModifiersBuilders.SlideOutTransition) parameter #0:
- Removed parameter arg1 in androidx.wear.protolayout.ModifiersBuilders.ExitTransition.Builder.setSlideOut(androidx.wear.protolayout.ModifiersBuilders.SlideOutTransition arg1) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.FadeInTransition:
- Removed class androidx.wear.protolayout.ModifiersBuilders.FadeInTransition from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.FadeInTransition#getAnimationSpec():
- Removed method androidx.wear.protolayout.ModifiersBuilders.FadeInTransition.getAnimationSpec() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.FadeInTransition#getInitialAlpha():
- Removed method androidx.wear.protolayout.ModifiersBuilders.FadeInTransition.getInitialAlpha() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.FadeInTransition.Builder:
- Removed class androidx.wear.protolayout.ModifiersBuilders.FadeInTransition.Builder from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.FadeInTransition.Builder#Builder():
- Removed constructor androidx.wear.protolayout.ModifiersBuilders.FadeInTransition.Builder() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.FadeInTransition.Builder#build():
- Removed method androidx.wear.protolayout.ModifiersBuilders.FadeInTransition.Builder.build() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.FadeInTransition.Builder#setAnimationSpec(androidx.wear.protolayout.expression.AnimationParameterBuilders.AnimationSpec):
- Removed method androidx.wear.protolayout.ModifiersBuilders.FadeInTransition.Builder.setAnimationSpec(androidx.wear.protolayout.expression.AnimationParameterBuilders.AnimationSpec) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.FadeInTransition.Builder#setAnimationSpec(androidx.wear.protolayout.expression.AnimationParameterBuilders.AnimationSpec) parameter #0:
- Removed parameter arg1 in androidx.wear.protolayout.ModifiersBuilders.FadeInTransition.Builder.setAnimationSpec(androidx.wear.protolayout.expression.AnimationParameterBuilders.AnimationSpec arg1) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.FadeInTransition.Builder#setInitialAlpha(float):
- Removed method androidx.wear.protolayout.ModifiersBuilders.FadeInTransition.Builder.setInitialAlpha(float) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.FadeInTransition.Builder#setInitialAlpha(float) parameter #0:
- Removed parameter arg1 in androidx.wear.protolayout.ModifiersBuilders.FadeInTransition.Builder.setInitialAlpha(float arg1) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.FadeOutTransition:
- Removed class androidx.wear.protolayout.ModifiersBuilders.FadeOutTransition from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.FadeOutTransition#getAnimationSpec():
- Removed method androidx.wear.protolayout.ModifiersBuilders.FadeOutTransition.getAnimationSpec() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.FadeOutTransition#getTargetAlpha():
- Removed method androidx.wear.protolayout.ModifiersBuilders.FadeOutTransition.getTargetAlpha() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.FadeOutTransition.Builder:
- Removed class androidx.wear.protolayout.ModifiersBuilders.FadeOutTransition.Builder from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.FadeOutTransition.Builder#Builder():
- Removed constructor androidx.wear.protolayout.ModifiersBuilders.FadeOutTransition.Builder() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.FadeOutTransition.Builder#build():
- Removed method androidx.wear.protolayout.ModifiersBuilders.FadeOutTransition.Builder.build() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.FadeOutTransition.Builder#setAnimationSpec(androidx.wear.protolayout.expression.AnimationParameterBuilders.AnimationSpec):
- Removed method androidx.wear.protolayout.ModifiersBuilders.FadeOutTransition.Builder.setAnimationSpec(androidx.wear.protolayout.expression.AnimationParameterBuilders.AnimationSpec) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.FadeOutTransition.Builder#setAnimationSpec(androidx.wear.protolayout.expression.AnimationParameterBuilders.AnimationSpec) parameter #0:
- Removed parameter arg1 in androidx.wear.protolayout.ModifiersBuilders.FadeOutTransition.Builder.setAnimationSpec(androidx.wear.protolayout.expression.AnimationParameterBuilders.AnimationSpec arg1) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.FadeOutTransition.Builder#setTargetAlpha(float):
- Removed method androidx.wear.protolayout.ModifiersBuilders.FadeOutTransition.Builder.setTargetAlpha(float) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.FadeOutTransition.Builder#setTargetAlpha(float) parameter #0:
- Removed parameter arg1 in androidx.wear.protolayout.ModifiersBuilders.FadeOutTransition.Builder.setTargetAlpha(float arg1) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.Modifiers#getContentUpdateAnimation():
- Removed method androidx.wear.protolayout.ModifiersBuilders.Modifiers.getContentUpdateAnimation() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.Modifiers.Builder#setContentUpdateAnimation(androidx.wear.protolayout.ModifiersBuilders.AnimatedVisibility):
- Removed method androidx.wear.protolayout.ModifiersBuilders.Modifiers.Builder.setContentUpdateAnimation(androidx.wear.protolayout.ModifiersBuilders.AnimatedVisibility) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.Modifiers.Builder#setContentUpdateAnimation(androidx.wear.protolayout.ModifiersBuilders.AnimatedVisibility) parameter #0:
- Removed parameter arg1 in androidx.wear.protolayout.ModifiersBuilders.Modifiers.Builder.setContentUpdateAnimation(androidx.wear.protolayout.ModifiersBuilders.AnimatedVisibility arg1) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.SlideBound:
- Removed class androidx.wear.protolayout.ModifiersBuilders.SlideBound from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.SlideInTransition:
- Removed class androidx.wear.protolayout.ModifiersBuilders.SlideInTransition from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.SlideInTransition#getAnimationSpec():
- Removed method androidx.wear.protolayout.ModifiersBuilders.SlideInTransition.getAnimationSpec() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.SlideInTransition#getDirection():
- Removed method androidx.wear.protolayout.ModifiersBuilders.SlideInTransition.getDirection() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.SlideInTransition#getInitialSlideBound():
- Removed method androidx.wear.protolayout.ModifiersBuilders.SlideInTransition.getInitialSlideBound() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.SlideInTransition.Builder:
- Removed class androidx.wear.protolayout.ModifiersBuilders.SlideInTransition.Builder from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.SlideInTransition.Builder#Builder():
- Removed constructor androidx.wear.protolayout.ModifiersBuilders.SlideInTransition.Builder() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.SlideInTransition.Builder#build():
- Removed method androidx.wear.protolayout.ModifiersBuilders.SlideInTransition.Builder.build() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.SlideInTransition.Builder#setAnimationSpec(androidx.wear.protolayout.expression.AnimationParameterBuilders.AnimationSpec):
- Removed method androidx.wear.protolayout.ModifiersBuilders.SlideInTransition.Builder.setAnimationSpec(androidx.wear.protolayout.expression.AnimationParameterBuilders.AnimationSpec) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.SlideInTransition.Builder#setAnimationSpec(androidx.wear.protolayout.expression.AnimationParameterBuilders.AnimationSpec) parameter #0:
- Removed parameter arg1 in androidx.wear.protolayout.ModifiersBuilders.SlideInTransition.Builder.setAnimationSpec(androidx.wear.protolayout.expression.AnimationParameterBuilders.AnimationSpec arg1) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.SlideInTransition.Builder#setDirection(int):
- Removed method androidx.wear.protolayout.ModifiersBuilders.SlideInTransition.Builder.setDirection(int) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.SlideInTransition.Builder#setDirection(int) parameter #0:
- Removed parameter arg1 in androidx.wear.protolayout.ModifiersBuilders.SlideInTransition.Builder.setDirection(int arg1) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.SlideInTransition.Builder#setInitialSlideBound(androidx.wear.protolayout.ModifiersBuilders.SlideBound):
- Removed method androidx.wear.protolayout.ModifiersBuilders.SlideInTransition.Builder.setInitialSlideBound(androidx.wear.protolayout.ModifiersBuilders.SlideBound) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.SlideInTransition.Builder#setInitialSlideBound(androidx.wear.protolayout.ModifiersBuilders.SlideBound) parameter #0:
- Removed parameter arg1 in androidx.wear.protolayout.ModifiersBuilders.SlideInTransition.Builder.setInitialSlideBound(androidx.wear.protolayout.ModifiersBuilders.SlideBound arg1) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.SlideOutTransition:
- Removed class androidx.wear.protolayout.ModifiersBuilders.SlideOutTransition from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.SlideOutTransition#getAnimationSpec():
- Removed method androidx.wear.protolayout.ModifiersBuilders.SlideOutTransition.getAnimationSpec() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.SlideOutTransition#getDirection():
- Removed method androidx.wear.protolayout.ModifiersBuilders.SlideOutTransition.getDirection() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.SlideOutTransition#getTargetSlideBound():
- Removed method androidx.wear.protolayout.ModifiersBuilders.SlideOutTransition.getTargetSlideBound() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.SlideOutTransition.Builder:
- Removed class androidx.wear.protolayout.ModifiersBuilders.SlideOutTransition.Builder from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.SlideOutTransition.Builder#Builder():
- Removed constructor androidx.wear.protolayout.ModifiersBuilders.SlideOutTransition.Builder() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.SlideOutTransition.Builder#build():
- Removed method androidx.wear.protolayout.ModifiersBuilders.SlideOutTransition.Builder.build() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.SlideOutTransition.Builder#setAnimationSpec(androidx.wear.protolayout.expression.AnimationParameterBuilders.AnimationSpec):
- Removed method androidx.wear.protolayout.ModifiersBuilders.SlideOutTransition.Builder.setAnimationSpec(androidx.wear.protolayout.expression.AnimationParameterBuilders.AnimationSpec) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.SlideOutTransition.Builder#setAnimationSpec(androidx.wear.protolayout.expression.AnimationParameterBuilders.AnimationSpec) parameter #0:
- Removed parameter arg1 in androidx.wear.protolayout.ModifiersBuilders.SlideOutTransition.Builder.setAnimationSpec(androidx.wear.protolayout.expression.AnimationParameterBuilders.AnimationSpec arg1) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.SlideOutTransition.Builder#setDirection(int):
- Removed method androidx.wear.protolayout.ModifiersBuilders.SlideOutTransition.Builder.setDirection(int) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.SlideOutTransition.Builder#setDirection(int) parameter #0:
- Removed parameter arg1 in androidx.wear.protolayout.ModifiersBuilders.SlideOutTransition.Builder.setDirection(int arg1) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.SlideOutTransition.Builder#setTargetSlideBound(androidx.wear.protolayout.ModifiersBuilders.SlideBound):
- Removed method androidx.wear.protolayout.ModifiersBuilders.SlideOutTransition.Builder.setTargetSlideBound(androidx.wear.protolayout.ModifiersBuilders.SlideBound) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.SlideOutTransition.Builder#setTargetSlideBound(androidx.wear.protolayout.ModifiersBuilders.SlideBound) parameter #0:
- Removed parameter arg1 in androidx.wear.protolayout.ModifiersBuilders.SlideOutTransition.Builder.setTargetSlideBound(androidx.wear.protolayout.ModifiersBuilders.SlideBound arg1) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.SlideParentBound:
- Removed class androidx.wear.protolayout.ModifiersBuilders.SlideParentBound from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.SlideParentBound#getSnapTo():
- Removed method androidx.wear.protolayout.ModifiersBuilders.SlideParentBound.getSnapTo() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.SlideParentBound.Builder:
- Removed class androidx.wear.protolayout.ModifiersBuilders.SlideParentBound.Builder from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.SlideParentBound.Builder#Builder():
- Removed constructor androidx.wear.protolayout.ModifiersBuilders.SlideParentBound.Builder() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.SlideParentBound.Builder#build():
- Removed method androidx.wear.protolayout.ModifiersBuilders.SlideParentBound.Builder.build() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.SlideParentBound.Builder#setSnapTo(int):
- Removed method androidx.wear.protolayout.ModifiersBuilders.SlideParentBound.Builder.setSnapTo(int) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.SlideParentBound.Builder#setSnapTo(int) parameter #0:
- Removed parameter arg1 in androidx.wear.protolayout.ModifiersBuilders.SlideParentBound.Builder.setSnapTo(int arg1) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ResourceBuilders.AndroidAnimatedImageResourceByResId:
- Removed class androidx.wear.protolayout.ResourceBuilders.AndroidAnimatedImageResourceByResId from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ResourceBuilders.AndroidAnimatedImageResourceByResId#getAnimatedImageFormat():
- Removed method androidx.wear.protolayout.ResourceBuilders.AndroidAnimatedImageResourceByResId.getAnimatedImageFormat() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ResourceBuilders.AndroidAnimatedImageResourceByResId#getResourceId():
- Removed method androidx.wear.protolayout.ResourceBuilders.AndroidAnimatedImageResourceByResId.getResourceId() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ResourceBuilders.AndroidAnimatedImageResourceByResId#getStartTrigger():
- Removed method androidx.wear.protolayout.ResourceBuilders.AndroidAnimatedImageResourceByResId.getStartTrigger() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ResourceBuilders.AndroidAnimatedImageResourceByResId.Builder:
- Removed class androidx.wear.protolayout.ResourceBuilders.AndroidAnimatedImageResourceByResId.Builder from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ResourceBuilders.AndroidAnimatedImageResourceByResId.Builder#Builder():
- Removed constructor androidx.wear.protolayout.ResourceBuilders.AndroidAnimatedImageResourceByResId.Builder() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ResourceBuilders.AndroidAnimatedImageResourceByResId.Builder#build():
- Removed method androidx.wear.protolayout.ResourceBuilders.AndroidAnimatedImageResourceByResId.Builder.build() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ResourceBuilders.AndroidAnimatedImageResourceByResId.Builder#setAnimatedImageFormat(int):
- Removed method androidx.wear.protolayout.ResourceBuilders.AndroidAnimatedImageResourceByResId.Builder.setAnimatedImageFormat(int) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ResourceBuilders.AndroidAnimatedImageResourceByResId.Builder#setAnimatedImageFormat(int) parameter #0:
- Removed parameter arg1 in androidx.wear.protolayout.ResourceBuilders.AndroidAnimatedImageResourceByResId.Builder.setAnimatedImageFormat(int arg1) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ResourceBuilders.AndroidAnimatedImageResourceByResId.Builder#setResourceId(int):
- Removed method androidx.wear.protolayout.ResourceBuilders.AndroidAnimatedImageResourceByResId.Builder.setResourceId(int) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ResourceBuilders.AndroidAnimatedImageResourceByResId.Builder#setResourceId(int) parameter #0:
- Removed parameter arg1 in androidx.wear.protolayout.ResourceBuilders.AndroidAnimatedImageResourceByResId.Builder.setResourceId(int arg1) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ResourceBuilders.AndroidAnimatedImageResourceByResId.Builder#setStartTrigger(androidx.wear.protolayout.TriggerBuilders.Trigger):
- Removed method androidx.wear.protolayout.ResourceBuilders.AndroidAnimatedImageResourceByResId.Builder.setStartTrigger(androidx.wear.protolayout.TriggerBuilders.Trigger) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ResourceBuilders.AndroidAnimatedImageResourceByResId.Builder#setStartTrigger(androidx.wear.protolayout.TriggerBuilders.Trigger) parameter #0:
- Removed parameter arg1 in androidx.wear.protolayout.ResourceBuilders.AndroidAnimatedImageResourceByResId.Builder.setStartTrigger(androidx.wear.protolayout.TriggerBuilders.Trigger arg1) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ResourceBuilders.AndroidSeekableAnimatedImageResourceByResId:
- Removed class androidx.wear.protolayout.ResourceBuilders.AndroidSeekableAnimatedImageResourceByResId from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ResourceBuilders.AndroidSeekableAnimatedImageResourceByResId#getAnimatedImageFormat():
- Removed method androidx.wear.protolayout.ResourceBuilders.AndroidSeekableAnimatedImageResourceByResId.getAnimatedImageFormat() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ResourceBuilders.AndroidSeekableAnimatedImageResourceByResId#getProgress():
- Removed method androidx.wear.protolayout.ResourceBuilders.AndroidSeekableAnimatedImageResourceByResId.getProgress() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ResourceBuilders.AndroidSeekableAnimatedImageResourceByResId#getResourceId():
- Removed method androidx.wear.protolayout.ResourceBuilders.AndroidSeekableAnimatedImageResourceByResId.getResourceId() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ResourceBuilders.AndroidSeekableAnimatedImageResourceByResId.Builder:
- Removed class androidx.wear.protolayout.ResourceBuilders.AndroidSeekableAnimatedImageResourceByResId.Builder from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ResourceBuilders.AndroidSeekableAnimatedImageResourceByResId.Builder#Builder():
- Removed constructor androidx.wear.protolayout.ResourceBuilders.AndroidSeekableAnimatedImageResourceByResId.Builder() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ResourceBuilders.AndroidSeekableAnimatedImageResourceByResId.Builder#build():
- Removed method androidx.wear.protolayout.ResourceBuilders.AndroidSeekableAnimatedImageResourceByResId.Builder.build() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ResourceBuilders.AndroidSeekableAnimatedImageResourceByResId.Builder#setAnimatedImageFormat(int):
- Removed method androidx.wear.protolayout.ResourceBuilders.AndroidSeekableAnimatedImageResourceByResId.Builder.setAnimatedImageFormat(int) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ResourceBuilders.AndroidSeekableAnimatedImageResourceByResId.Builder#setAnimatedImageFormat(int) parameter #0:
- Removed parameter arg1 in androidx.wear.protolayout.ResourceBuilders.AndroidSeekableAnimatedImageResourceByResId.Builder.setAnimatedImageFormat(int arg1) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ResourceBuilders.AndroidSeekableAnimatedImageResourceByResId.Builder#setProgress(androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat):
- Removed method androidx.wear.protolayout.ResourceBuilders.AndroidSeekableAnimatedImageResourceByResId.Builder.setProgress(androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ResourceBuilders.AndroidSeekableAnimatedImageResourceByResId.Builder#setProgress(androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat) parameter #0:
- Removed parameter arg1 in androidx.wear.protolayout.ResourceBuilders.AndroidSeekableAnimatedImageResourceByResId.Builder.setProgress(androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat arg1) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ResourceBuilders.AndroidSeekableAnimatedImageResourceByResId.Builder#setResourceId(int):
- Removed method androidx.wear.protolayout.ResourceBuilders.AndroidSeekableAnimatedImageResourceByResId.Builder.setResourceId(int) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ResourceBuilders.AndroidSeekableAnimatedImageResourceByResId.Builder#setResourceId(int) parameter #0:
- Removed parameter arg1 in androidx.wear.protolayout.ResourceBuilders.AndroidSeekableAnimatedImageResourceByResId.Builder.setResourceId(int arg1) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ResourceBuilders.ImageResource#getAndroidAnimatedResourceByResId():
- Removed method androidx.wear.protolayout.ResourceBuilders.ImageResource.getAndroidAnimatedResourceByResId() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ResourceBuilders.ImageResource#getAndroidSeekableAnimatedResourceByResId():
- Removed method androidx.wear.protolayout.ResourceBuilders.ImageResource.getAndroidSeekableAnimatedResourceByResId() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ResourceBuilders.ImageResource.Builder#setAndroidAnimatedResourceByResId(androidx.wear.protolayout.ResourceBuilders.AndroidAnimatedImageResourceByResId):
- Removed method androidx.wear.protolayout.ResourceBuilders.ImageResource.Builder.setAndroidAnimatedResourceByResId(androidx.wear.protolayout.ResourceBuilders.AndroidAnimatedImageResourceByResId) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ResourceBuilders.ImageResource.Builder#setAndroidAnimatedResourceByResId(androidx.wear.protolayout.ResourceBuilders.AndroidAnimatedImageResourceByResId) parameter #0:
- Removed parameter arg1 in androidx.wear.protolayout.ResourceBuilders.ImageResource.Builder.setAndroidAnimatedResourceByResId(androidx.wear.protolayout.ResourceBuilders.AndroidAnimatedImageResourceByResId arg1) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ResourceBuilders.ImageResource.Builder#setAndroidSeekableAnimatedResourceByResId(androidx.wear.protolayout.ResourceBuilders.AndroidSeekableAnimatedImageResourceByResId):
- Removed method androidx.wear.protolayout.ResourceBuilders.ImageResource.Builder.setAndroidSeekableAnimatedResourceByResId(androidx.wear.protolayout.ResourceBuilders.AndroidSeekableAnimatedImageResourceByResId) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ResourceBuilders.ImageResource.Builder#setAndroidSeekableAnimatedResourceByResId(androidx.wear.protolayout.ResourceBuilders.AndroidSeekableAnimatedImageResourceByResId) parameter #0:
- Removed parameter arg1 in androidx.wear.protolayout.ResourceBuilders.ImageResource.Builder.setAndroidSeekableAnimatedResourceByResId(androidx.wear.protolayout.ResourceBuilders.AndroidSeekableAnimatedImageResourceByResId arg1) from compatibility checked API surface
+RemovedMethod: androidx.wear.protolayout.LayoutElementBuilders.Arc#getLayoutConstraintsForDynamicAnchorAngle():
+ Removed method androidx.wear.protolayout.LayoutElementBuilders.Arc.getLayoutConstraintsForDynamicAnchorAngle()
+RemovedMethod: androidx.wear.protolayout.LayoutElementBuilders.Arc.Builder#setLayoutConstraintsForDynamicAnchorAngle(androidx.wear.protolayout.DimensionBuilders.AngularLayoutConstraint):
+ Removed method androidx.wear.protolayout.LayoutElementBuilders.Arc.Builder.setLayoutConstraintsForDynamicAnchorAngle(androidx.wear.protolayout.DimensionBuilders.AngularLayoutConstraint)
diff --git a/wear/protolayout/protolayout/api/current.txt b/wear/protolayout/protolayout/api/current.txt
index 55ef432..21ffdf4 100644
--- a/wear/protolayout/protolayout/api/current.txt
+++ b/wear/protolayout/protolayout/api/current.txt
@@ -354,7 +354,6 @@
method public androidx.wear.protolayout.DimensionBuilders.DegreesProp? getAnchorAngle();
method public androidx.wear.protolayout.LayoutElementBuilders.ArcAnchorTypeProp? getAnchorType();
method public java.util.List<androidx.wear.protolayout.LayoutElementBuilders.ArcLayoutElement!> getContents();
- method public androidx.wear.protolayout.DimensionBuilders.AngularLayoutConstraint? getLayoutConstraintsForDynamicAnchorAngle();
method public androidx.wear.protolayout.ModifiersBuilders.Modifiers? getModifiers();
method public androidx.wear.protolayout.LayoutElementBuilders.VerticalAlignmentProp? getVerticalAlign();
}
@@ -366,7 +365,6 @@
method public androidx.wear.protolayout.LayoutElementBuilders.Arc.Builder setAnchorAngle(androidx.wear.protolayout.DimensionBuilders.DegreesProp);
method public androidx.wear.protolayout.LayoutElementBuilders.Arc.Builder setAnchorType(androidx.wear.protolayout.LayoutElementBuilders.ArcAnchorTypeProp);
method public androidx.wear.protolayout.LayoutElementBuilders.Arc.Builder setAnchorType(int);
- method public androidx.wear.protolayout.LayoutElementBuilders.Arc.Builder setLayoutConstraintsForDynamicAnchorAngle(androidx.wear.protolayout.DimensionBuilders.AngularLayoutConstraint);
method public androidx.wear.protolayout.LayoutElementBuilders.Arc.Builder setModifiers(androidx.wear.protolayout.ModifiersBuilders.Modifiers);
method public androidx.wear.protolayout.LayoutElementBuilders.Arc.Builder setVerticalAlign(androidx.wear.protolayout.LayoutElementBuilders.VerticalAlignmentProp);
method public androidx.wear.protolayout.LayoutElementBuilders.Arc.Builder setVerticalAlign(int);
diff --git a/wear/protolayout/protolayout/api/restricted_1.0.0-beta01.txt b/wear/protolayout/protolayout/api/restricted_1.0.0-beta01.txt
index 55ef432..21ffdf4 100644
--- a/wear/protolayout/protolayout/api/restricted_1.0.0-beta01.txt
+++ b/wear/protolayout/protolayout/api/restricted_1.0.0-beta01.txt
@@ -354,7 +354,6 @@
method public androidx.wear.protolayout.DimensionBuilders.DegreesProp? getAnchorAngle();
method public androidx.wear.protolayout.LayoutElementBuilders.ArcAnchorTypeProp? getAnchorType();
method public java.util.List<androidx.wear.protolayout.LayoutElementBuilders.ArcLayoutElement!> getContents();
- method public androidx.wear.protolayout.DimensionBuilders.AngularLayoutConstraint? getLayoutConstraintsForDynamicAnchorAngle();
method public androidx.wear.protolayout.ModifiersBuilders.Modifiers? getModifiers();
method public androidx.wear.protolayout.LayoutElementBuilders.VerticalAlignmentProp? getVerticalAlign();
}
@@ -366,7 +365,6 @@
method public androidx.wear.protolayout.LayoutElementBuilders.Arc.Builder setAnchorAngle(androidx.wear.protolayout.DimensionBuilders.DegreesProp);
method public androidx.wear.protolayout.LayoutElementBuilders.Arc.Builder setAnchorType(androidx.wear.protolayout.LayoutElementBuilders.ArcAnchorTypeProp);
method public androidx.wear.protolayout.LayoutElementBuilders.Arc.Builder setAnchorType(int);
- method public androidx.wear.protolayout.LayoutElementBuilders.Arc.Builder setLayoutConstraintsForDynamicAnchorAngle(androidx.wear.protolayout.DimensionBuilders.AngularLayoutConstraint);
method public androidx.wear.protolayout.LayoutElementBuilders.Arc.Builder setModifiers(androidx.wear.protolayout.ModifiersBuilders.Modifiers);
method public androidx.wear.protolayout.LayoutElementBuilders.Arc.Builder setVerticalAlign(androidx.wear.protolayout.LayoutElementBuilders.VerticalAlignmentProp);
method public androidx.wear.protolayout.LayoutElementBuilders.Arc.Builder setVerticalAlign(int);
diff --git a/wear/protolayout/protolayout/api/restricted_current.ignore b/wear/protolayout/protolayout/api/restricted_current.ignore
index fc89635..2d69c6a 100644
--- a/wear/protolayout/protolayout/api/restricted_current.ignore
+++ b/wear/protolayout/protolayout/api/restricted_current.ignore
@@ -1,403 +1,5 @@
// Baseline format: 1.0
-BecameUnchecked: androidx.wear.protolayout.DeviceParametersBuilders.Capabilities:
- Removed class androidx.wear.protolayout.DeviceParametersBuilders.Capabilities from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.DeviceParametersBuilders.Capabilities#getMinimumFreshnessLimitMillis():
- Removed method androidx.wear.protolayout.DeviceParametersBuilders.Capabilities.getMinimumFreshnessLimitMillis() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.DeviceParametersBuilders.Capabilities.Builder:
- Removed class androidx.wear.protolayout.DeviceParametersBuilders.Capabilities.Builder from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.DeviceParametersBuilders.Capabilities.Builder#Builder():
- Removed constructor androidx.wear.protolayout.DeviceParametersBuilders.Capabilities.Builder() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.DeviceParametersBuilders.Capabilities.Builder#build():
- Removed method androidx.wear.protolayout.DeviceParametersBuilders.Capabilities.Builder.build() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.DeviceParametersBuilders.Capabilities.Builder#setMinimumFreshnessLimitMillis(long):
- Removed method androidx.wear.protolayout.DeviceParametersBuilders.Capabilities.Builder.setMinimumFreshnessLimitMillis(long) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.DeviceParametersBuilders.Capabilities.Builder#setMinimumFreshnessLimitMillis(long) parameter #0:
- Removed parameter arg1 in androidx.wear.protolayout.DeviceParametersBuilders.Capabilities.Builder.setMinimumFreshnessLimitMillis(long arg1) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.DeviceParametersBuilders.DeviceParameters#getCapabilities():
- Removed method androidx.wear.protolayout.DeviceParametersBuilders.DeviceParameters.getCapabilities() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.DeviceParametersBuilders.DeviceParameters.Builder#setCapabilities(androidx.wear.protolayout.DeviceParametersBuilders.Capabilities):
- Removed method androidx.wear.protolayout.DeviceParametersBuilders.DeviceParameters.Builder.setCapabilities(androidx.wear.protolayout.DeviceParametersBuilders.Capabilities) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.DeviceParametersBuilders.DeviceParameters.Builder#setCapabilities(androidx.wear.protolayout.DeviceParametersBuilders.Capabilities) parameter #0:
- Removed parameter arg1 in androidx.wear.protolayout.DeviceParametersBuilders.DeviceParameters.Builder.setCapabilities(androidx.wear.protolayout.DeviceParametersBuilders.Capabilities arg1) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.DimensionBuilders.ExtensionDimension:
- Removed class androidx.wear.protolayout.DimensionBuilders.ExtensionDimension from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders#FONT_WEIGHT_MEDIUM:
- Removed field androidx.wear.protolayout.LayoutElementBuilders.FONT_WEIGHT_MEDIUM from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders#TEXT_OVERFLOW_MARQUEE:
- Removed field androidx.wear.protolayout.LayoutElementBuilders.TEXT_OVERFLOW_MARQUEE from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders.AndroidTextStyle:
- Removed class androidx.wear.protolayout.LayoutElementBuilders.AndroidTextStyle from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders.AndroidTextStyle#getExcludeFontPadding():
- Removed method androidx.wear.protolayout.LayoutElementBuilders.AndroidTextStyle.getExcludeFontPadding() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders.AndroidTextStyle.Builder:
- Removed class androidx.wear.protolayout.LayoutElementBuilders.AndroidTextStyle.Builder from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders.AndroidTextStyle.Builder#Builder():
- Removed constructor androidx.wear.protolayout.LayoutElementBuilders.AndroidTextStyle.Builder() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders.AndroidTextStyle.Builder#build():
- Removed method androidx.wear.protolayout.LayoutElementBuilders.AndroidTextStyle.Builder.build() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders.AndroidTextStyle.Builder#setExcludeFontPadding(boolean):
- Removed method androidx.wear.protolayout.LayoutElementBuilders.AndroidTextStyle.Builder.setExcludeFontPadding(boolean) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders.AndroidTextStyle.Builder#setExcludeFontPadding(boolean) parameter #0:
- Removed parameter arg1 in androidx.wear.protolayout.LayoutElementBuilders.AndroidTextStyle.Builder.setExcludeFontPadding(boolean arg1) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders.ExtensionLayoutElement:
- Removed class androidx.wear.protolayout.LayoutElementBuilders.ExtensionLayoutElement from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders.ExtensionLayoutElement#getExtensionId():
- Removed method androidx.wear.protolayout.LayoutElementBuilders.ExtensionLayoutElement.getExtensionId() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders.ExtensionLayoutElement#getHeight():
- Removed method androidx.wear.protolayout.LayoutElementBuilders.ExtensionLayoutElement.getHeight() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders.ExtensionLayoutElement#getPayload():
- Removed method androidx.wear.protolayout.LayoutElementBuilders.ExtensionLayoutElement.getPayload() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders.ExtensionLayoutElement#getWidth():
- Removed method androidx.wear.protolayout.LayoutElementBuilders.ExtensionLayoutElement.getWidth() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders.ExtensionLayoutElement.Builder:
- Removed class androidx.wear.protolayout.LayoutElementBuilders.ExtensionLayoutElement.Builder from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders.ExtensionLayoutElement.Builder#Builder():
- Removed constructor androidx.wear.protolayout.LayoutElementBuilders.ExtensionLayoutElement.Builder() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders.ExtensionLayoutElement.Builder#build():
- Removed method androidx.wear.protolayout.LayoutElementBuilders.ExtensionLayoutElement.Builder.build() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders.ExtensionLayoutElement.Builder#setExtensionId(String):
- Removed method androidx.wear.protolayout.LayoutElementBuilders.ExtensionLayoutElement.Builder.setExtensionId(String) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders.ExtensionLayoutElement.Builder#setExtensionId(String) parameter #0:
- Removed parameter arg1 in androidx.wear.protolayout.LayoutElementBuilders.ExtensionLayoutElement.Builder.setExtensionId(String arg1) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders.ExtensionLayoutElement.Builder#setHeight(androidx.wear.protolayout.DimensionBuilders.ExtensionDimension):
- Removed method androidx.wear.protolayout.LayoutElementBuilders.ExtensionLayoutElement.Builder.setHeight(androidx.wear.protolayout.DimensionBuilders.ExtensionDimension) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders.ExtensionLayoutElement.Builder#setHeight(androidx.wear.protolayout.DimensionBuilders.ExtensionDimension) parameter #0:
- Removed parameter arg1 in androidx.wear.protolayout.LayoutElementBuilders.ExtensionLayoutElement.Builder.setHeight(androidx.wear.protolayout.DimensionBuilders.ExtensionDimension arg1) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders.ExtensionLayoutElement.Builder#setPayload(byte[]):
- Removed method androidx.wear.protolayout.LayoutElementBuilders.ExtensionLayoutElement.Builder.setPayload(byte[]) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders.ExtensionLayoutElement.Builder#setPayload(byte[]) parameter #0:
- Removed parameter arg1 in androidx.wear.protolayout.LayoutElementBuilders.ExtensionLayoutElement.Builder.setPayload(byte[] arg1) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders.ExtensionLayoutElement.Builder#setWidth(androidx.wear.protolayout.DimensionBuilders.ExtensionDimension):
- Removed method androidx.wear.protolayout.LayoutElementBuilders.ExtensionLayoutElement.Builder.setWidth(androidx.wear.protolayout.DimensionBuilders.ExtensionDimension) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders.ExtensionLayoutElement.Builder#setWidth(androidx.wear.protolayout.DimensionBuilders.ExtensionDimension) parameter #0:
- Removed parameter arg1 in androidx.wear.protolayout.LayoutElementBuilders.ExtensionLayoutElement.Builder.setWidth(androidx.wear.protolayout.DimensionBuilders.ExtensionDimension arg1) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders.FontStyle#getVariant():
- Removed method androidx.wear.protolayout.LayoutElementBuilders.FontStyle.getVariant() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders.FontStyle.Builder#setVariant(androidx.wear.protolayout.LayoutElementBuilders.FontVariantProp):
- Removed method androidx.wear.protolayout.LayoutElementBuilders.FontStyle.Builder.setVariant(androidx.wear.protolayout.LayoutElementBuilders.FontVariantProp) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders.FontStyle.Builder#setVariant(androidx.wear.protolayout.LayoutElementBuilders.FontVariantProp) parameter #0:
- Removed parameter arg1 in androidx.wear.protolayout.LayoutElementBuilders.FontStyle.Builder.setVariant(androidx.wear.protolayout.LayoutElementBuilders.FontVariantProp arg1) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders.FontStyle.Builder#setVariant(int):
- Removed method androidx.wear.protolayout.LayoutElementBuilders.FontStyle.Builder.setVariant(int) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders.FontStyle.Builder#setVariant(int) parameter #0:
- Removed parameter arg1 in androidx.wear.protolayout.LayoutElementBuilders.FontStyle.Builder.setVariant(int arg1) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders.FontVariantProp:
- Removed class androidx.wear.protolayout.LayoutElementBuilders.FontVariantProp from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders.FontVariantProp#getValue():
- Removed method androidx.wear.protolayout.LayoutElementBuilders.FontVariantProp.getValue() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders.FontVariantProp.Builder:
- Removed class androidx.wear.protolayout.LayoutElementBuilders.FontVariantProp.Builder from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders.FontVariantProp.Builder#Builder():
- Removed constructor androidx.wear.protolayout.LayoutElementBuilders.FontVariantProp.Builder() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders.FontVariantProp.Builder#build():
- Removed method androidx.wear.protolayout.LayoutElementBuilders.FontVariantProp.Builder.build() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders.FontVariantProp.Builder#setValue(int):
- Removed method androidx.wear.protolayout.LayoutElementBuilders.FontVariantProp.Builder.setValue(int) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders.FontVariantProp.Builder#setValue(int) parameter #0:
- Removed parameter arg1 in androidx.wear.protolayout.LayoutElementBuilders.FontVariantProp.Builder.setValue(int arg1) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders.Layout#fromByteArray(byte[]):
- Removed method androidx.wear.protolayout.LayoutElementBuilders.Layout.fromByteArray(byte[]) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders.Layout#fromByteArray(byte[]) parameter #0:
- Removed parameter arg1 in androidx.wear.protolayout.LayoutElementBuilders.Layout.fromByteArray(byte[] arg1) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders.Layout#toByteArray():
- Removed method androidx.wear.protolayout.LayoutElementBuilders.Layout.toByteArray() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders.SpanText#getAndroidTextStyle():
- Removed method androidx.wear.protolayout.LayoutElementBuilders.SpanText.getAndroidTextStyle() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders.SpanText.Builder#setAndroidTextStyle(androidx.wear.protolayout.LayoutElementBuilders.AndroidTextStyle):
- Removed method androidx.wear.protolayout.LayoutElementBuilders.SpanText.Builder.setAndroidTextStyle(androidx.wear.protolayout.LayoutElementBuilders.AndroidTextStyle) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders.SpanText.Builder#setAndroidTextStyle(androidx.wear.protolayout.LayoutElementBuilders.AndroidTextStyle) parameter #0:
- Removed parameter arg1 in androidx.wear.protolayout.LayoutElementBuilders.SpanText.Builder.setAndroidTextStyle(androidx.wear.protolayout.LayoutElementBuilders.AndroidTextStyle arg1) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders.Spannable#getMarqueeIterations():
- Removed method androidx.wear.protolayout.LayoutElementBuilders.Spannable.getMarqueeIterations() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders.Spannable.Builder#setMarqueeIterations(int):
- Removed method androidx.wear.protolayout.LayoutElementBuilders.Spannable.Builder.setMarqueeIterations(int) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders.Spannable.Builder#setMarqueeIterations(int) parameter #0:
- Removed parameter arg1 in androidx.wear.protolayout.LayoutElementBuilders.Spannable.Builder.setMarqueeIterations(int arg1) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders.Text#getAndroidTextStyle():
- Removed method androidx.wear.protolayout.LayoutElementBuilders.Text.getAndroidTextStyle() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders.Text#getMarqueeIterations():
- Removed method androidx.wear.protolayout.LayoutElementBuilders.Text.getMarqueeIterations() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders.Text.Builder#setAndroidTextStyle(androidx.wear.protolayout.LayoutElementBuilders.AndroidTextStyle):
- Removed method androidx.wear.protolayout.LayoutElementBuilders.Text.Builder.setAndroidTextStyle(androidx.wear.protolayout.LayoutElementBuilders.AndroidTextStyle) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders.Text.Builder#setAndroidTextStyle(androidx.wear.protolayout.LayoutElementBuilders.AndroidTextStyle) parameter #0:
- Removed parameter arg1 in androidx.wear.protolayout.LayoutElementBuilders.Text.Builder.setAndroidTextStyle(androidx.wear.protolayout.LayoutElementBuilders.AndroidTextStyle arg1) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders.Text.Builder#setMarqueeIterations(int):
- Removed method androidx.wear.protolayout.LayoutElementBuilders.Text.Builder.setMarqueeIterations(int) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.LayoutElementBuilders.Text.Builder#setMarqueeIterations(int) parameter #0:
- Removed parameter arg1 in androidx.wear.protolayout.LayoutElementBuilders.Text.Builder.setMarqueeIterations(int arg1) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders#SLIDE_DIRECTION_BOTTOM_TO_TOP:
- Removed field androidx.wear.protolayout.ModifiersBuilders.SLIDE_DIRECTION_BOTTOM_TO_TOP from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders#SLIDE_DIRECTION_LEFT_TO_RIGHT:
- Removed field androidx.wear.protolayout.ModifiersBuilders.SLIDE_DIRECTION_LEFT_TO_RIGHT from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders#SLIDE_DIRECTION_RIGHT_TO_LEFT:
- Removed field androidx.wear.protolayout.ModifiersBuilders.SLIDE_DIRECTION_RIGHT_TO_LEFT from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders#SLIDE_DIRECTION_TOP_TO_BOTTOM:
- Removed field androidx.wear.protolayout.ModifiersBuilders.SLIDE_DIRECTION_TOP_TO_BOTTOM from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders#SLIDE_DIRECTION_UNDEFINED:
- Removed field androidx.wear.protolayout.ModifiersBuilders.SLIDE_DIRECTION_UNDEFINED from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders#SLIDE_PARENT_SNAP_TO_INSIDE:
- Removed field androidx.wear.protolayout.ModifiersBuilders.SLIDE_PARENT_SNAP_TO_INSIDE from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders#SLIDE_PARENT_SNAP_TO_OUTSIDE:
- Removed field androidx.wear.protolayout.ModifiersBuilders.SLIDE_PARENT_SNAP_TO_OUTSIDE from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders#SLIDE_PARENT_SNAP_UNDEFINED:
- Removed field androidx.wear.protolayout.ModifiersBuilders.SLIDE_PARENT_SNAP_UNDEFINED from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.AnimatedVisibility:
- Removed class androidx.wear.protolayout.ModifiersBuilders.AnimatedVisibility from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.AnimatedVisibility#getEnterTransition():
- Removed method androidx.wear.protolayout.ModifiersBuilders.AnimatedVisibility.getEnterTransition() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.AnimatedVisibility#getExitTransition():
- Removed method androidx.wear.protolayout.ModifiersBuilders.AnimatedVisibility.getExitTransition() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.AnimatedVisibility.Builder:
- Removed class androidx.wear.protolayout.ModifiersBuilders.AnimatedVisibility.Builder from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.AnimatedVisibility.Builder#Builder():
- Removed constructor androidx.wear.protolayout.ModifiersBuilders.AnimatedVisibility.Builder() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.AnimatedVisibility.Builder#build():
- Removed method androidx.wear.protolayout.ModifiersBuilders.AnimatedVisibility.Builder.build() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.AnimatedVisibility.Builder#setEnterTransition(androidx.wear.protolayout.ModifiersBuilders.EnterTransition):
- Removed method androidx.wear.protolayout.ModifiersBuilders.AnimatedVisibility.Builder.setEnterTransition(androidx.wear.protolayout.ModifiersBuilders.EnterTransition) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.AnimatedVisibility.Builder#setEnterTransition(androidx.wear.protolayout.ModifiersBuilders.EnterTransition) parameter #0:
- Removed parameter arg1 in androidx.wear.protolayout.ModifiersBuilders.AnimatedVisibility.Builder.setEnterTransition(androidx.wear.protolayout.ModifiersBuilders.EnterTransition arg1) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.AnimatedVisibility.Builder#setExitTransition(androidx.wear.protolayout.ModifiersBuilders.ExitTransition):
- Removed method androidx.wear.protolayout.ModifiersBuilders.AnimatedVisibility.Builder.setExitTransition(androidx.wear.protolayout.ModifiersBuilders.ExitTransition) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.AnimatedVisibility.Builder#setExitTransition(androidx.wear.protolayout.ModifiersBuilders.ExitTransition) parameter #0:
- Removed parameter arg1 in androidx.wear.protolayout.ModifiersBuilders.AnimatedVisibility.Builder.setExitTransition(androidx.wear.protolayout.ModifiersBuilders.ExitTransition arg1) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.DefaultContentTransitions:
- Removed class androidx.wear.protolayout.ModifiersBuilders.DefaultContentTransitions from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.DefaultContentTransitions#fadeIn():
- Removed method androidx.wear.protolayout.ModifiersBuilders.DefaultContentTransitions.fadeIn() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.DefaultContentTransitions#fadeInSlideIn(int):
- Removed method androidx.wear.protolayout.ModifiersBuilders.DefaultContentTransitions.fadeInSlideIn(int) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.DefaultContentTransitions#fadeInSlideIn(int) parameter #0:
- Removed parameter arg1 in androidx.wear.protolayout.ModifiersBuilders.DefaultContentTransitions.fadeInSlideIn(int arg1) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.DefaultContentTransitions#fadeOut():
- Removed method androidx.wear.protolayout.ModifiersBuilders.DefaultContentTransitions.fadeOut() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.DefaultContentTransitions#fadeOutSlideOut(int):
- Removed method androidx.wear.protolayout.ModifiersBuilders.DefaultContentTransitions.fadeOutSlideOut(int) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.DefaultContentTransitions#fadeOutSlideOut(int) parameter #0:
- Removed parameter arg1 in androidx.wear.protolayout.ModifiersBuilders.DefaultContentTransitions.fadeOutSlideOut(int arg1) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.DefaultContentTransitions#slideIn(int):
- Removed method androidx.wear.protolayout.ModifiersBuilders.DefaultContentTransitions.slideIn(int) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.DefaultContentTransitions#slideIn(int) parameter #0:
- Removed parameter arg1 in androidx.wear.protolayout.ModifiersBuilders.DefaultContentTransitions.slideIn(int arg1) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.DefaultContentTransitions#slideOut(int):
- Removed method androidx.wear.protolayout.ModifiersBuilders.DefaultContentTransitions.slideOut(int) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.DefaultContentTransitions#slideOut(int) parameter #0:
- Removed parameter arg1 in androidx.wear.protolayout.ModifiersBuilders.DefaultContentTransitions.slideOut(int arg1) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.EnterTransition:
- Removed class androidx.wear.protolayout.ModifiersBuilders.EnterTransition from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.EnterTransition#getFadeIn():
- Removed method androidx.wear.protolayout.ModifiersBuilders.EnterTransition.getFadeIn() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.EnterTransition#getSlideIn():
- Removed method androidx.wear.protolayout.ModifiersBuilders.EnterTransition.getSlideIn() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.EnterTransition.Builder:
- Removed class androidx.wear.protolayout.ModifiersBuilders.EnterTransition.Builder from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.EnterTransition.Builder#Builder():
- Removed constructor androidx.wear.protolayout.ModifiersBuilders.EnterTransition.Builder() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.EnterTransition.Builder#build():
- Removed method androidx.wear.protolayout.ModifiersBuilders.EnterTransition.Builder.build() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.EnterTransition.Builder#setFadeIn(androidx.wear.protolayout.ModifiersBuilders.FadeInTransition):
- Removed method androidx.wear.protolayout.ModifiersBuilders.EnterTransition.Builder.setFadeIn(androidx.wear.protolayout.ModifiersBuilders.FadeInTransition) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.EnterTransition.Builder#setFadeIn(androidx.wear.protolayout.ModifiersBuilders.FadeInTransition) parameter #0:
- Removed parameter arg1 in androidx.wear.protolayout.ModifiersBuilders.EnterTransition.Builder.setFadeIn(androidx.wear.protolayout.ModifiersBuilders.FadeInTransition arg1) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.EnterTransition.Builder#setSlideIn(androidx.wear.protolayout.ModifiersBuilders.SlideInTransition):
- Removed method androidx.wear.protolayout.ModifiersBuilders.EnterTransition.Builder.setSlideIn(androidx.wear.protolayout.ModifiersBuilders.SlideInTransition) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.EnterTransition.Builder#setSlideIn(androidx.wear.protolayout.ModifiersBuilders.SlideInTransition) parameter #0:
- Removed parameter arg1 in androidx.wear.protolayout.ModifiersBuilders.EnterTransition.Builder.setSlideIn(androidx.wear.protolayout.ModifiersBuilders.SlideInTransition arg1) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.ExitTransition:
- Removed class androidx.wear.protolayout.ModifiersBuilders.ExitTransition from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.ExitTransition#getFadeOut():
- Removed method androidx.wear.protolayout.ModifiersBuilders.ExitTransition.getFadeOut() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.ExitTransition#getSlideOut():
- Removed method androidx.wear.protolayout.ModifiersBuilders.ExitTransition.getSlideOut() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.ExitTransition.Builder:
- Removed class androidx.wear.protolayout.ModifiersBuilders.ExitTransition.Builder from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.ExitTransition.Builder#Builder():
- Removed constructor androidx.wear.protolayout.ModifiersBuilders.ExitTransition.Builder() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.ExitTransition.Builder#build():
- Removed method androidx.wear.protolayout.ModifiersBuilders.ExitTransition.Builder.build() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.ExitTransition.Builder#setFadeOut(androidx.wear.protolayout.ModifiersBuilders.FadeOutTransition):
- Removed method androidx.wear.protolayout.ModifiersBuilders.ExitTransition.Builder.setFadeOut(androidx.wear.protolayout.ModifiersBuilders.FadeOutTransition) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.ExitTransition.Builder#setFadeOut(androidx.wear.protolayout.ModifiersBuilders.FadeOutTransition) parameter #0:
- Removed parameter arg1 in androidx.wear.protolayout.ModifiersBuilders.ExitTransition.Builder.setFadeOut(androidx.wear.protolayout.ModifiersBuilders.FadeOutTransition arg1) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.ExitTransition.Builder#setSlideOut(androidx.wear.protolayout.ModifiersBuilders.SlideOutTransition):
- Removed method androidx.wear.protolayout.ModifiersBuilders.ExitTransition.Builder.setSlideOut(androidx.wear.protolayout.ModifiersBuilders.SlideOutTransition) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.ExitTransition.Builder#setSlideOut(androidx.wear.protolayout.ModifiersBuilders.SlideOutTransition) parameter #0:
- Removed parameter arg1 in androidx.wear.protolayout.ModifiersBuilders.ExitTransition.Builder.setSlideOut(androidx.wear.protolayout.ModifiersBuilders.SlideOutTransition arg1) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.FadeInTransition:
- Removed class androidx.wear.protolayout.ModifiersBuilders.FadeInTransition from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.FadeInTransition#getAnimationSpec():
- Removed method androidx.wear.protolayout.ModifiersBuilders.FadeInTransition.getAnimationSpec() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.FadeInTransition#getInitialAlpha():
- Removed method androidx.wear.protolayout.ModifiersBuilders.FadeInTransition.getInitialAlpha() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.FadeInTransition.Builder:
- Removed class androidx.wear.protolayout.ModifiersBuilders.FadeInTransition.Builder from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.FadeInTransition.Builder#Builder():
- Removed constructor androidx.wear.protolayout.ModifiersBuilders.FadeInTransition.Builder() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.FadeInTransition.Builder#build():
- Removed method androidx.wear.protolayout.ModifiersBuilders.FadeInTransition.Builder.build() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.FadeInTransition.Builder#setAnimationSpec(androidx.wear.protolayout.expression.AnimationParameterBuilders.AnimationSpec):
- Removed method androidx.wear.protolayout.ModifiersBuilders.FadeInTransition.Builder.setAnimationSpec(androidx.wear.protolayout.expression.AnimationParameterBuilders.AnimationSpec) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.FadeInTransition.Builder#setAnimationSpec(androidx.wear.protolayout.expression.AnimationParameterBuilders.AnimationSpec) parameter #0:
- Removed parameter arg1 in androidx.wear.protolayout.ModifiersBuilders.FadeInTransition.Builder.setAnimationSpec(androidx.wear.protolayout.expression.AnimationParameterBuilders.AnimationSpec arg1) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.FadeInTransition.Builder#setInitialAlpha(float):
- Removed method androidx.wear.protolayout.ModifiersBuilders.FadeInTransition.Builder.setInitialAlpha(float) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.FadeInTransition.Builder#setInitialAlpha(float) parameter #0:
- Removed parameter arg1 in androidx.wear.protolayout.ModifiersBuilders.FadeInTransition.Builder.setInitialAlpha(float arg1) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.FadeOutTransition:
- Removed class androidx.wear.protolayout.ModifiersBuilders.FadeOutTransition from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.FadeOutTransition#getAnimationSpec():
- Removed method androidx.wear.protolayout.ModifiersBuilders.FadeOutTransition.getAnimationSpec() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.FadeOutTransition#getTargetAlpha():
- Removed method androidx.wear.protolayout.ModifiersBuilders.FadeOutTransition.getTargetAlpha() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.FadeOutTransition.Builder:
- Removed class androidx.wear.protolayout.ModifiersBuilders.FadeOutTransition.Builder from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.FadeOutTransition.Builder#Builder():
- Removed constructor androidx.wear.protolayout.ModifiersBuilders.FadeOutTransition.Builder() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.FadeOutTransition.Builder#build():
- Removed method androidx.wear.protolayout.ModifiersBuilders.FadeOutTransition.Builder.build() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.FadeOutTransition.Builder#setAnimationSpec(androidx.wear.protolayout.expression.AnimationParameterBuilders.AnimationSpec):
- Removed method androidx.wear.protolayout.ModifiersBuilders.FadeOutTransition.Builder.setAnimationSpec(androidx.wear.protolayout.expression.AnimationParameterBuilders.AnimationSpec) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.FadeOutTransition.Builder#setAnimationSpec(androidx.wear.protolayout.expression.AnimationParameterBuilders.AnimationSpec) parameter #0:
- Removed parameter arg1 in androidx.wear.protolayout.ModifiersBuilders.FadeOutTransition.Builder.setAnimationSpec(androidx.wear.protolayout.expression.AnimationParameterBuilders.AnimationSpec arg1) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.FadeOutTransition.Builder#setTargetAlpha(float):
- Removed method androidx.wear.protolayout.ModifiersBuilders.FadeOutTransition.Builder.setTargetAlpha(float) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.FadeOutTransition.Builder#setTargetAlpha(float) parameter #0:
- Removed parameter arg1 in androidx.wear.protolayout.ModifiersBuilders.FadeOutTransition.Builder.setTargetAlpha(float arg1) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.Modifiers#getContentUpdateAnimation():
- Removed method androidx.wear.protolayout.ModifiersBuilders.Modifiers.getContentUpdateAnimation() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.Modifiers.Builder#setContentUpdateAnimation(androidx.wear.protolayout.ModifiersBuilders.AnimatedVisibility):
- Removed method androidx.wear.protolayout.ModifiersBuilders.Modifiers.Builder.setContentUpdateAnimation(androidx.wear.protolayout.ModifiersBuilders.AnimatedVisibility) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.Modifiers.Builder#setContentUpdateAnimation(androidx.wear.protolayout.ModifiersBuilders.AnimatedVisibility) parameter #0:
- Removed parameter arg1 in androidx.wear.protolayout.ModifiersBuilders.Modifiers.Builder.setContentUpdateAnimation(androidx.wear.protolayout.ModifiersBuilders.AnimatedVisibility arg1) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.SlideBound:
- Removed class androidx.wear.protolayout.ModifiersBuilders.SlideBound from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.SlideInTransition:
- Removed class androidx.wear.protolayout.ModifiersBuilders.SlideInTransition from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.SlideInTransition#getAnimationSpec():
- Removed method androidx.wear.protolayout.ModifiersBuilders.SlideInTransition.getAnimationSpec() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.SlideInTransition#getDirection():
- Removed method androidx.wear.protolayout.ModifiersBuilders.SlideInTransition.getDirection() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.SlideInTransition#getInitialSlideBound():
- Removed method androidx.wear.protolayout.ModifiersBuilders.SlideInTransition.getInitialSlideBound() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.SlideInTransition.Builder:
- Removed class androidx.wear.protolayout.ModifiersBuilders.SlideInTransition.Builder from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.SlideInTransition.Builder#Builder():
- Removed constructor androidx.wear.protolayout.ModifiersBuilders.SlideInTransition.Builder() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.SlideInTransition.Builder#build():
- Removed method androidx.wear.protolayout.ModifiersBuilders.SlideInTransition.Builder.build() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.SlideInTransition.Builder#setAnimationSpec(androidx.wear.protolayout.expression.AnimationParameterBuilders.AnimationSpec):
- Removed method androidx.wear.protolayout.ModifiersBuilders.SlideInTransition.Builder.setAnimationSpec(androidx.wear.protolayout.expression.AnimationParameterBuilders.AnimationSpec) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.SlideInTransition.Builder#setAnimationSpec(androidx.wear.protolayout.expression.AnimationParameterBuilders.AnimationSpec) parameter #0:
- Removed parameter arg1 in androidx.wear.protolayout.ModifiersBuilders.SlideInTransition.Builder.setAnimationSpec(androidx.wear.protolayout.expression.AnimationParameterBuilders.AnimationSpec arg1) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.SlideInTransition.Builder#setDirection(int):
- Removed method androidx.wear.protolayout.ModifiersBuilders.SlideInTransition.Builder.setDirection(int) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.SlideInTransition.Builder#setDirection(int) parameter #0:
- Removed parameter arg1 in androidx.wear.protolayout.ModifiersBuilders.SlideInTransition.Builder.setDirection(int arg1) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.SlideInTransition.Builder#setInitialSlideBound(androidx.wear.protolayout.ModifiersBuilders.SlideBound):
- Removed method androidx.wear.protolayout.ModifiersBuilders.SlideInTransition.Builder.setInitialSlideBound(androidx.wear.protolayout.ModifiersBuilders.SlideBound) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.SlideInTransition.Builder#setInitialSlideBound(androidx.wear.protolayout.ModifiersBuilders.SlideBound) parameter #0:
- Removed parameter arg1 in androidx.wear.protolayout.ModifiersBuilders.SlideInTransition.Builder.setInitialSlideBound(androidx.wear.protolayout.ModifiersBuilders.SlideBound arg1) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.SlideOutTransition:
- Removed class androidx.wear.protolayout.ModifiersBuilders.SlideOutTransition from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.SlideOutTransition#getAnimationSpec():
- Removed method androidx.wear.protolayout.ModifiersBuilders.SlideOutTransition.getAnimationSpec() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.SlideOutTransition#getDirection():
- Removed method androidx.wear.protolayout.ModifiersBuilders.SlideOutTransition.getDirection() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.SlideOutTransition#getTargetSlideBound():
- Removed method androidx.wear.protolayout.ModifiersBuilders.SlideOutTransition.getTargetSlideBound() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.SlideOutTransition.Builder:
- Removed class androidx.wear.protolayout.ModifiersBuilders.SlideOutTransition.Builder from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.SlideOutTransition.Builder#Builder():
- Removed constructor androidx.wear.protolayout.ModifiersBuilders.SlideOutTransition.Builder() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.SlideOutTransition.Builder#build():
- Removed method androidx.wear.protolayout.ModifiersBuilders.SlideOutTransition.Builder.build() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.SlideOutTransition.Builder#setAnimationSpec(androidx.wear.protolayout.expression.AnimationParameterBuilders.AnimationSpec):
- Removed method androidx.wear.protolayout.ModifiersBuilders.SlideOutTransition.Builder.setAnimationSpec(androidx.wear.protolayout.expression.AnimationParameterBuilders.AnimationSpec) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.SlideOutTransition.Builder#setAnimationSpec(androidx.wear.protolayout.expression.AnimationParameterBuilders.AnimationSpec) parameter #0:
- Removed parameter arg1 in androidx.wear.protolayout.ModifiersBuilders.SlideOutTransition.Builder.setAnimationSpec(androidx.wear.protolayout.expression.AnimationParameterBuilders.AnimationSpec arg1) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.SlideOutTransition.Builder#setDirection(int):
- Removed method androidx.wear.protolayout.ModifiersBuilders.SlideOutTransition.Builder.setDirection(int) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.SlideOutTransition.Builder#setDirection(int) parameter #0:
- Removed parameter arg1 in androidx.wear.protolayout.ModifiersBuilders.SlideOutTransition.Builder.setDirection(int arg1) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.SlideOutTransition.Builder#setTargetSlideBound(androidx.wear.protolayout.ModifiersBuilders.SlideBound):
- Removed method androidx.wear.protolayout.ModifiersBuilders.SlideOutTransition.Builder.setTargetSlideBound(androidx.wear.protolayout.ModifiersBuilders.SlideBound) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.SlideOutTransition.Builder#setTargetSlideBound(androidx.wear.protolayout.ModifiersBuilders.SlideBound) parameter #0:
- Removed parameter arg1 in androidx.wear.protolayout.ModifiersBuilders.SlideOutTransition.Builder.setTargetSlideBound(androidx.wear.protolayout.ModifiersBuilders.SlideBound arg1) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.SlideParentBound:
- Removed class androidx.wear.protolayout.ModifiersBuilders.SlideParentBound from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.SlideParentBound#getSnapTo():
- Removed method androidx.wear.protolayout.ModifiersBuilders.SlideParentBound.getSnapTo() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.SlideParentBound.Builder:
- Removed class androidx.wear.protolayout.ModifiersBuilders.SlideParentBound.Builder from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.SlideParentBound.Builder#Builder():
- Removed constructor androidx.wear.protolayout.ModifiersBuilders.SlideParentBound.Builder() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.SlideParentBound.Builder#build():
- Removed method androidx.wear.protolayout.ModifiersBuilders.SlideParentBound.Builder.build() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.SlideParentBound.Builder#setSnapTo(int):
- Removed method androidx.wear.protolayout.ModifiersBuilders.SlideParentBound.Builder.setSnapTo(int) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ModifiersBuilders.SlideParentBound.Builder#setSnapTo(int) parameter #0:
- Removed parameter arg1 in androidx.wear.protolayout.ModifiersBuilders.SlideParentBound.Builder.setSnapTo(int arg1) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ResourceBuilders.AndroidAnimatedImageResourceByResId:
- Removed class androidx.wear.protolayout.ResourceBuilders.AndroidAnimatedImageResourceByResId from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ResourceBuilders.AndroidAnimatedImageResourceByResId#getAnimatedImageFormat():
- Removed method androidx.wear.protolayout.ResourceBuilders.AndroidAnimatedImageResourceByResId.getAnimatedImageFormat() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ResourceBuilders.AndroidAnimatedImageResourceByResId#getResourceId():
- Removed method androidx.wear.protolayout.ResourceBuilders.AndroidAnimatedImageResourceByResId.getResourceId() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ResourceBuilders.AndroidAnimatedImageResourceByResId#getStartTrigger():
- Removed method androidx.wear.protolayout.ResourceBuilders.AndroidAnimatedImageResourceByResId.getStartTrigger() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ResourceBuilders.AndroidAnimatedImageResourceByResId.Builder:
- Removed class androidx.wear.protolayout.ResourceBuilders.AndroidAnimatedImageResourceByResId.Builder from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ResourceBuilders.AndroidAnimatedImageResourceByResId.Builder#Builder():
- Removed constructor androidx.wear.protolayout.ResourceBuilders.AndroidAnimatedImageResourceByResId.Builder() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ResourceBuilders.AndroidAnimatedImageResourceByResId.Builder#build():
- Removed method androidx.wear.protolayout.ResourceBuilders.AndroidAnimatedImageResourceByResId.Builder.build() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ResourceBuilders.AndroidAnimatedImageResourceByResId.Builder#setAnimatedImageFormat(int):
- Removed method androidx.wear.protolayout.ResourceBuilders.AndroidAnimatedImageResourceByResId.Builder.setAnimatedImageFormat(int) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ResourceBuilders.AndroidAnimatedImageResourceByResId.Builder#setAnimatedImageFormat(int) parameter #0:
- Removed parameter arg1 in androidx.wear.protolayout.ResourceBuilders.AndroidAnimatedImageResourceByResId.Builder.setAnimatedImageFormat(int arg1) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ResourceBuilders.AndroidAnimatedImageResourceByResId.Builder#setResourceId(int):
- Removed method androidx.wear.protolayout.ResourceBuilders.AndroidAnimatedImageResourceByResId.Builder.setResourceId(int) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ResourceBuilders.AndroidAnimatedImageResourceByResId.Builder#setResourceId(int) parameter #0:
- Removed parameter arg1 in androidx.wear.protolayout.ResourceBuilders.AndroidAnimatedImageResourceByResId.Builder.setResourceId(int arg1) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ResourceBuilders.AndroidAnimatedImageResourceByResId.Builder#setStartTrigger(androidx.wear.protolayout.TriggerBuilders.Trigger):
- Removed method androidx.wear.protolayout.ResourceBuilders.AndroidAnimatedImageResourceByResId.Builder.setStartTrigger(androidx.wear.protolayout.TriggerBuilders.Trigger) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ResourceBuilders.AndroidAnimatedImageResourceByResId.Builder#setStartTrigger(androidx.wear.protolayout.TriggerBuilders.Trigger) parameter #0:
- Removed parameter arg1 in androidx.wear.protolayout.ResourceBuilders.AndroidAnimatedImageResourceByResId.Builder.setStartTrigger(androidx.wear.protolayout.TriggerBuilders.Trigger arg1) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ResourceBuilders.AndroidSeekableAnimatedImageResourceByResId:
- Removed class androidx.wear.protolayout.ResourceBuilders.AndroidSeekableAnimatedImageResourceByResId from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ResourceBuilders.AndroidSeekableAnimatedImageResourceByResId#getAnimatedImageFormat():
- Removed method androidx.wear.protolayout.ResourceBuilders.AndroidSeekableAnimatedImageResourceByResId.getAnimatedImageFormat() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ResourceBuilders.AndroidSeekableAnimatedImageResourceByResId#getProgress():
- Removed method androidx.wear.protolayout.ResourceBuilders.AndroidSeekableAnimatedImageResourceByResId.getProgress() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ResourceBuilders.AndroidSeekableAnimatedImageResourceByResId#getResourceId():
- Removed method androidx.wear.protolayout.ResourceBuilders.AndroidSeekableAnimatedImageResourceByResId.getResourceId() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ResourceBuilders.AndroidSeekableAnimatedImageResourceByResId.Builder:
- Removed class androidx.wear.protolayout.ResourceBuilders.AndroidSeekableAnimatedImageResourceByResId.Builder from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ResourceBuilders.AndroidSeekableAnimatedImageResourceByResId.Builder#Builder():
- Removed constructor androidx.wear.protolayout.ResourceBuilders.AndroidSeekableAnimatedImageResourceByResId.Builder() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ResourceBuilders.AndroidSeekableAnimatedImageResourceByResId.Builder#build():
- Removed method androidx.wear.protolayout.ResourceBuilders.AndroidSeekableAnimatedImageResourceByResId.Builder.build() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ResourceBuilders.AndroidSeekableAnimatedImageResourceByResId.Builder#setAnimatedImageFormat(int):
- Removed method androidx.wear.protolayout.ResourceBuilders.AndroidSeekableAnimatedImageResourceByResId.Builder.setAnimatedImageFormat(int) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ResourceBuilders.AndroidSeekableAnimatedImageResourceByResId.Builder#setAnimatedImageFormat(int) parameter #0:
- Removed parameter arg1 in androidx.wear.protolayout.ResourceBuilders.AndroidSeekableAnimatedImageResourceByResId.Builder.setAnimatedImageFormat(int arg1) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ResourceBuilders.AndroidSeekableAnimatedImageResourceByResId.Builder#setProgress(androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat):
- Removed method androidx.wear.protolayout.ResourceBuilders.AndroidSeekableAnimatedImageResourceByResId.Builder.setProgress(androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ResourceBuilders.AndroidSeekableAnimatedImageResourceByResId.Builder#setProgress(androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat) parameter #0:
- Removed parameter arg1 in androidx.wear.protolayout.ResourceBuilders.AndroidSeekableAnimatedImageResourceByResId.Builder.setProgress(androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat arg1) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ResourceBuilders.AndroidSeekableAnimatedImageResourceByResId.Builder#setResourceId(int):
- Removed method androidx.wear.protolayout.ResourceBuilders.AndroidSeekableAnimatedImageResourceByResId.Builder.setResourceId(int) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ResourceBuilders.AndroidSeekableAnimatedImageResourceByResId.Builder#setResourceId(int) parameter #0:
- Removed parameter arg1 in androidx.wear.protolayout.ResourceBuilders.AndroidSeekableAnimatedImageResourceByResId.Builder.setResourceId(int arg1) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ResourceBuilders.ImageResource#getAndroidAnimatedResourceByResId():
- Removed method androidx.wear.protolayout.ResourceBuilders.ImageResource.getAndroidAnimatedResourceByResId() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ResourceBuilders.ImageResource#getAndroidSeekableAnimatedResourceByResId():
- Removed method androidx.wear.protolayout.ResourceBuilders.ImageResource.getAndroidSeekableAnimatedResourceByResId() from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ResourceBuilders.ImageResource.Builder#setAndroidAnimatedResourceByResId(androidx.wear.protolayout.ResourceBuilders.AndroidAnimatedImageResourceByResId):
- Removed method androidx.wear.protolayout.ResourceBuilders.ImageResource.Builder.setAndroidAnimatedResourceByResId(androidx.wear.protolayout.ResourceBuilders.AndroidAnimatedImageResourceByResId) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ResourceBuilders.ImageResource.Builder#setAndroidAnimatedResourceByResId(androidx.wear.protolayout.ResourceBuilders.AndroidAnimatedImageResourceByResId) parameter #0:
- Removed parameter arg1 in androidx.wear.protolayout.ResourceBuilders.ImageResource.Builder.setAndroidAnimatedResourceByResId(androidx.wear.protolayout.ResourceBuilders.AndroidAnimatedImageResourceByResId arg1) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ResourceBuilders.ImageResource.Builder#setAndroidSeekableAnimatedResourceByResId(androidx.wear.protolayout.ResourceBuilders.AndroidSeekableAnimatedImageResourceByResId):
- Removed method androidx.wear.protolayout.ResourceBuilders.ImageResource.Builder.setAndroidSeekableAnimatedResourceByResId(androidx.wear.protolayout.ResourceBuilders.AndroidSeekableAnimatedImageResourceByResId) from compatibility checked API surface
-BecameUnchecked: androidx.wear.protolayout.ResourceBuilders.ImageResource.Builder#setAndroidSeekableAnimatedResourceByResId(androidx.wear.protolayout.ResourceBuilders.AndroidSeekableAnimatedImageResourceByResId) parameter #0:
- Removed parameter arg1 in androidx.wear.protolayout.ResourceBuilders.ImageResource.Builder.setAndroidSeekableAnimatedResourceByResId(androidx.wear.protolayout.ResourceBuilders.AndroidSeekableAnimatedImageResourceByResId arg1) from compatibility checked API surface
+RemovedMethod: androidx.wear.protolayout.LayoutElementBuilders.Arc#getLayoutConstraintsForDynamicAnchorAngle():
+ Removed method androidx.wear.protolayout.LayoutElementBuilders.Arc.getLayoutConstraintsForDynamicAnchorAngle()
+RemovedMethod: androidx.wear.protolayout.LayoutElementBuilders.Arc.Builder#setLayoutConstraintsForDynamicAnchorAngle(androidx.wear.protolayout.DimensionBuilders.AngularLayoutConstraint):
+ Removed method androidx.wear.protolayout.LayoutElementBuilders.Arc.Builder.setLayoutConstraintsForDynamicAnchorAngle(androidx.wear.protolayout.DimensionBuilders.AngularLayoutConstraint)
diff --git a/wear/protolayout/protolayout/api/restricted_current.txt b/wear/protolayout/protolayout/api/restricted_current.txt
index 55ef432..21ffdf4 100644
--- a/wear/protolayout/protolayout/api/restricted_current.txt
+++ b/wear/protolayout/protolayout/api/restricted_current.txt
@@ -354,7 +354,6 @@
method public androidx.wear.protolayout.DimensionBuilders.DegreesProp? getAnchorAngle();
method public androidx.wear.protolayout.LayoutElementBuilders.ArcAnchorTypeProp? getAnchorType();
method public java.util.List<androidx.wear.protolayout.LayoutElementBuilders.ArcLayoutElement!> getContents();
- method public androidx.wear.protolayout.DimensionBuilders.AngularLayoutConstraint? getLayoutConstraintsForDynamicAnchorAngle();
method public androidx.wear.protolayout.ModifiersBuilders.Modifiers? getModifiers();
method public androidx.wear.protolayout.LayoutElementBuilders.VerticalAlignmentProp? getVerticalAlign();
}
@@ -366,7 +365,6 @@
method public androidx.wear.protolayout.LayoutElementBuilders.Arc.Builder setAnchorAngle(androidx.wear.protolayout.DimensionBuilders.DegreesProp);
method public androidx.wear.protolayout.LayoutElementBuilders.Arc.Builder setAnchorType(androidx.wear.protolayout.LayoutElementBuilders.ArcAnchorTypeProp);
method public androidx.wear.protolayout.LayoutElementBuilders.Arc.Builder setAnchorType(int);
- method public androidx.wear.protolayout.LayoutElementBuilders.Arc.Builder setLayoutConstraintsForDynamicAnchorAngle(androidx.wear.protolayout.DimensionBuilders.AngularLayoutConstraint);
method public androidx.wear.protolayout.LayoutElementBuilders.Arc.Builder setModifiers(androidx.wear.protolayout.ModifiersBuilders.Modifiers);
method public androidx.wear.protolayout.LayoutElementBuilders.Arc.Builder setVerticalAlign(androidx.wear.protolayout.LayoutElementBuilders.VerticalAlignmentProp);
method public androidx.wear.protolayout.LayoutElementBuilders.Arc.Builder setVerticalAlign(int);
diff --git a/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/LayoutElementBuilders.java b/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/LayoutElementBuilders.java
index 3219024..5d13328 100644
--- a/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/LayoutElementBuilders.java
+++ b/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/LayoutElementBuilders.java
@@ -2834,10 +2834,16 @@
* Sets the style of font to use (size, bold etc). If not specified, defaults to the
* platform's default body font.
*
+ * DynamicColor is not supported for SpanText.
+ *
* @since 1.0
*/
@NonNull
public Builder setFontStyle(@NonNull FontStyle fontStyle) {
+ ColorProp colorProp = fontStyle.getColor();
+ if (colorProp != null && colorProp.getDynamicValue() != null) {
+ throw new IllegalArgumentException("SpanText does not support DynamicColor.");
+ }
mImpl.setFontStyle(fontStyle.toProto());
mFingerprint.recordPropertyUpdate(
2, checkNotNull(fontStyle.getFingerprint()).aggregateValueAsInt());
@@ -4065,16 +4071,6 @@
}
/**
- * This is a No-Op as using constraints for dynamic AnchorAngle has no effect.
- *
- * @since 1.2
- */
- @Nullable
- public AngularLayoutConstraint getLayoutConstraintsForDynamicAnchorAngle() {
- throw new UnsupportedOperationException("This method is No-Op. Do not use!");
- }
-
- /**
* Gets how to align the contents of this container relative to anchor_angle. If not
* defined, defaults to ARC_ANCHOR_CENTER.
*
@@ -4214,17 +4210,6 @@
}
/**
- * This is a No-Op as using constraints for dynamic AnchorAngle has no effect.
- *
- * @since 1.2
- */
- @NonNull
- public Builder setLayoutConstraintsForDynamicAnchorAngle(
- @NonNull DimensionBuilders.AngularLayoutConstraint angularLayoutConstraint) {
- return this;
- }
-
- /**
* Sets how to align the contents of this container relative to anchor_angle. If not
* defined, defaults to ARC_ANCHOR_CENTER.
*
diff --git a/wear/tiles/tiles-proto/build.gradle b/wear/tiles/tiles-proto/build.gradle
index 16bb24b..6b05bec 100644
--- a/wear/tiles/tiles-proto/build.gradle
+++ b/wear/tiles/tiles-proto/build.gradle
@@ -38,7 +38,7 @@
// For some reason not specifying the configuration causes undeclared changes to the inputs to
// extractIncludeProto task (making it out-dated without any source change).
compileOnly(project(path:":wear:protolayout:protolayout-proto", configuration:"archives"))
- api(project(path:":wear:protolayout:protolayout-proto", configuration:"shadow"))
+ api(project(path:":wear:protolayout:protolayout-proto", configuration:"default"))
}
// HACK: Move standard JAR to have another suffix and build a shadowJar with
// no classifier (so it's picked up as the primary artifact).
diff --git a/wear/watchface/watchface-complications-data-source-samples/build.gradle b/wear/watchface/watchface-complications-data-source-samples/build.gradle
index 8fe99bd..5bc5294 100644
--- a/wear/watchface/watchface-complications-data-source-samples/build.gradle
+++ b/wear/watchface/watchface-complications-data-source-samples/build.gradle
@@ -22,7 +22,6 @@
dependencies {
api(project(":wear:watchface:watchface-complications-data-source"))
api(libs.guavaAndroid)
- api(libs.kotlinStdlib)
implementation("androidx.core:core:1.1.0")
implementation("androidx.wear.protolayout:protolayout-expression:1.0.0-beta01")
}
diff --git a/wear/watchface/watchface-complications-data-source/src/main/java/androidx/wear/watchface/complications/datasource/ComplicationDataSourceService.kt b/wear/watchface/watchface-complications-data-source/src/main/java/androidx/wear/watchface/complications/datasource/ComplicationDataSourceService.kt
index dd4398b..1790ab2 100644
--- a/wear/watchface/watchface-complications-data-source/src/main/java/androidx/wear/watchface/complications/datasource/ComplicationDataSourceService.kt
+++ b/wear/watchface/watchface-complications-data-source/src/main/java/androidx/wear/watchface/complications/datasource/ComplicationDataSourceService.kt
@@ -183,11 +183,11 @@
*
* Manifest requirements:
* - The manifest declaration of this service must include an intent filter for
- * android.support.wearable.complications.ACTION_COMPLICATION_UPDATE_REQUEST.
+ * `android.support.wearable.complications.ACTION_COMPLICATION_UPDATE_REQUEST`.
* - A ComplicationDataSourceService must include a `meta-data` tag with
- * android.support.wearable.complications.SUPPORTED_TYPES in its manifest entry.
+ * `android.support.wearable.complications.SUPPORTED_TYPES` in its manifest entry.
*
- * The value of android.support.wearable.complications.SUPPORTED_TYPES should be a comma separated
+ * The value of `android.support.wearable.complications.SUPPORTED_TYPES` should be a comma separated
* list of types supported by the data source, from this table:
*
* | Androidx class | Tag name |
@@ -205,47 +205,49 @@
* multiple types in a single complication slot, the watch face will determine which types it
* prefers.
*
- * For example, a complication data source that supports the RANGED_VALUE, SHORT_TEXT, and ICON
- * types would include the following in its manifest entry:
+ * For example, a complication data source that supports the `RANGED_VALUE`, `SHORT_TEXT`, and
+ * `ICON` types would include the following in its manifest entry:
* ```
- * <meta-data android:name="android.support.wearable.complications.SUPPORTED_TYPES"
- * android:value="RANGED_VALUE,SHORT_TEXT,ICON"/>
+ * <meta-data
+ * android:name="android.support.wearable.complications.SUPPORTED_TYPES"
+ * android:value="RANGED_VALUE,SHORT_TEXT,ICON" />
* ```
*
- * From android T onwards, it is recommended for Complication DataSourceServices to be direct boot
+ * From android T onwards, it is recommended for [ComplicationDataSourceService]s to be direct boot
* aware because the system is able to fetch complications before the lock screen has been removed.
- * To do this add android:directBootAware="true" to your service tag.
+ * To do this add `android:directBootAware="true"` to your service tag.
* - A provider can choose to trust one or more watch faces by including the following in its
* manifest entry:
* ```
- * <meta-data android:name="android.support.wearable.complications.SAFE_WATCH_FACES
- * android:value="com.pkg1/com.trusted.wf1,com.pkg2/com.trusted.wf2"/>
+ * <meta-data
+ * android:name="android.support.wearable.complications.SAFE_WATCH_FACES"
+ * android:value="com.pkg1/com.trusted.wf1,com.pkg2/com.trusted.wf2" />
* ```
*
* The listed watch faces will not need
- * 'com.google.android.wearable.permission.RECEIVE_COMPLICATION_DATA' in order to receive
+ * `com.google.android.wearable.permission.RECEIVE_COMPLICATION_DATA` in order to receive
* complications from this provider. Also the provider may choose to serve different types to safe
* watch faces by including the following in its manifest:
* ```
- * <meta-data android:name=
- * "androidx.wear.watchface.complications.datasource.SAFE_WATCH_FACE_SUPPORTED_TYPES"
- * android:value="ICON"/>
+ * <meta-data
+ * android:name="androidx.wear.watchface.complications.datasource.SAFE_WATCH_FACE_SUPPORTED_TYPES"
+ * android:value="ICON" />
* ```
*
* In addition the provider can learn if a request is for a safe watchface by examining
- * [ComplicationRequest.isForSafeWatchFace]. Note SAFE_WATCH_FACE_SUPPORTED_TYPES and
- * isForSafeWatchFace are gated behind the privileged permission
+ * [ComplicationRequest.isForSafeWatchFace]. Note `SAFE_WATCH_FACE_SUPPORTED_TYPES` and
+ * `isForSafeWatchFace` are gated behind the privileged permission
* `com.google.wear.permission.GET_IS_FOR_SAFE_WATCH_FACE`.
* - A ComplicationDataSourceService should include a `meta-data` tag with
- * `android.support.wearable.complications.UPDATE_PERIOD_SECONDS` its manifest entry. The value of
- * this tag is the number of seconds the complication data source would like to elapse between
+ * `android.support.wearable.complications.UPDATE_PERIOD_SECONDS` in its manifest entry. The value
+ * of this tag is the number of seconds the complication data source would like to elapse between
* update requests.
*
* **Note that update requests are not guaranteed to be sent with this frequency.** For
- * complications with frequent updates they can also register a separate
- * [METADATA_KEY_IMMEDIATE_UPDATE_PERIOD_MILLISECONDS] meta data tag which supports sampling at up
- * to 1Hz when the watch face is visible and non-ambient, however this also requires the
- * DataSourceService to have the privileged permission
+ * complications with frequent updates they can also register a separate `meta-data` tag with
+ * `androidx.wear.watchface.complications.data.source.IMMEDIATE_UPDATE_PERIOD_MILLISECONDS` in their
+ * manifest which supports sampling at up to 1Hz when the watch face is visible and non-ambient,
+ * however this also requires the application to have the privileged permission
* `com.google.android.wearable.permission.USE_IMMEDIATE_COMPLICATION_UPDATE`.
*
* If a complication data source never needs to receive update requests beyond the one sent when a
@@ -254,37 +256,39 @@
* For example, a complication data source that would like to update at most every hour should
* include the following in its manifest entry:
* ```
- * <meta-data android:name="android.support.wearable.complications.UPDATE_PERIOD_SECONDS"
- * android:value="3600"/>
+ * <meta-data
+ * android:name="android.support.wearable.complications.UPDATE_PERIOD_SECONDS"
+ * android:value="3600" />
* ```
- * - A ComplicationDataSourceService can include a `meta-data` tag with
+ * - A [ComplicationDataSourceService] can include a `meta-data` tag with
* android.support.wearable.complications.PROVIDER_CONFIG_ACTION its manifest entry to cause a
* configuration activity to be shown when the complication data source is selected.
*
* The configuration activity must reside in the same package as the complication data source, and
* must register an intent filter for the action specified here, including
- * android.support.wearable.complications.category.PROVIDER_CONFIG as well as
+ * `android.support.wearable.complications.category.PROVIDER_CONFIG` as well as
* [Intent.CATEGORY_DEFAULT] as categories.
*
* The complication id being configured will be included in the intent that starts the config
- * activity using the extra key android.support.wearable.complications.EXTRA_CONFIG_COMPLICATION_ID.
+ * activity using the extra key
+ * `android.support.wearable.complications.EXTRA_CONFIG_COMPLICATION_ID`.
*
* The complication type that will be requested from the complication data source will also be
- * included, using the extra key android.support.wearable.complications
- * .EXTRA_CONFIG_COMPLICATION_TYPE.
+ * included, using the extra key
+ * `android.support.wearable.complications.EXTRA_CONFIG_COMPLICATION_TYPE`.
*
* The complication data source's [ComponentName] will also be included in the intent that starts
* the config activity, using the extra key
- * android.support.wearable.complications.EXTRA_CONFIG_PROVIDER_COMPONENT.
+ * `android.support.wearable.complications.EXTRA_CONFIG_PROVIDER_COMPONENT`.
*
* The config activity must call [Activity.setResult] with either [Activity.RESULT_OK] or
* [Activity.RESULT_CANCELED] before it is finished, to tell the system whether or not the
* complication data source should be set on the given complication.
*
- * It is possible to provide additional 'meta-data' tag
- * androidx.watchface.complications.datasource.DEFAULT_CONFIG_SUPPORTED in the service set to "true"
- * to let the system know that the data source is able to provide complication data before it is
- * configured.
+ * It is possible to provide additional `meta-data` tag
+ * `androidx.watchface.complications.datasource.DEFAULT_CONFIG_SUPPORTED` in the service set to
+ * `"true"` to let the system know that the data source is able to provide complication data before
+ * it is configured.
* - The manifest entry for the service should also include an android:icon attribute. The icon
* provided there should be a single-color white icon that represents the complication data
* source. This icon will be shown in the complication data source chooser interface, and may also
@@ -298,8 +302,8 @@
* limit of 100 data sources per APK. Above that the companion watchface editor won't support this
* complication data source app.
*
- * There's no need to call setDataSource for any the ComplicationData Builders because the system
- * will append this value on your behalf.
+ * There's no need to call `setDataSource` for any the [ComplicationData] Builders because the
+ * system will append this value on your behalf.
*/
public abstract class ComplicationDataSourceService : Service() {
private var wrapper: IComplicationProviderWrapper? = null
@@ -768,15 +772,18 @@
* the [ComplicationData], but omitting the "TYPE_" prefix, e.g. `SHORT_TEXT`, `LONG_TEXT`,
* `RANGED_VALUE`.
*
- * The order in which types are listed has no significance. In the case where a watch face
- * supports multiple types in a single complication slot, the watch face will determine
- * which types it prefers.
+ * The order of types in `METADATA_KEY_SUPPORTED_TYPES` has no significance. During
+ * complication data source selection, each item in the complication slot's supported types
+ * is checked against entries in the data source's `METADATA_KEY_SUPPORTED_TYPES` and the
+ * first matching entry from the slot's support types (if any) is chosen. If there are no
+ * matches then this data source is not eligible to be selected in that slot.
*
* For example, a complication data source that supports the RANGED_VALUE, SHORT_TEXT, and
* ICON type would include the following in its manifest entry:
* ```
- * <meta-data android:name="android.support.wearable.complications.SUPPORTED_TYPES"
- * android:value="RANGED_VALUE,SHORT_TEXT,ICON"/>
+ * <meta-data
+ * android:name="android.support.wearable.complications.SUPPORTED_TYPES"
+ * android:value="RANGED_VALUE,SHORT_TEXT,ICON" />
* ```
*/
// TODO(b/192233205): Migrate value to androidx.
@@ -812,8 +819,9 @@
* For example, a complication data source that would like to update every ten minutes
* should include the following in its manifest entry:
* ```
- * <meta-data android:name="android.support.wearable.complications.UPDATE_PERIOD_SECONDS"
- * android:value="600"/>
+ * <meta-data
+ * android:name="android.support.wearable.complications.UPDATE_PERIOD_SECONDS"
+ * android:value="600" />
* ```
*/
// TODO(b/192233205): Migrate value to androidx.
diff --git a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/ComplicationSlot.kt b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/ComplicationSlot.kt
index 5f14a6f..c36b80e 100644
--- a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/ComplicationSlot.kt
+++ b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/ComplicationSlot.kt
@@ -345,8 +345,11 @@
* @param accessibilityTraversalIndex Used to sort Complications when generating accessibility
* content description labels.
* @param bounds The complication slot's [ComplicationSlotBounds].
- * @param supportedTypes The list of [ComplicationType]s accepted by this complication slot. Used
- * during complication data source selection, this list should be non-empty.
+ * @param supportedTypes The list of [ComplicationType]s accepted by this complication slot, must be
+ * non-empty. During complication data source selection, each item in this list is compared in
+ * turn with entries from a data source's data source's supported types. The first matching entry
+ * from `supportedTypes` is chosen. If there are no matches then that data source is not eligible
+ * to be selected in this slot.
* @param defaultPolicy The [DefaultComplicationDataSourcePolicy] which controls the initial
* complication data source when the watch face is first installed.
* @param defaultDataSourceType The default [ComplicationType] for the default complication data
diff --git a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFaceService.kt b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFaceService.kt
index aff5089..c32fa26 100644
--- a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFaceService.kt
+++ b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFaceService.kt
@@ -1320,7 +1320,12 @@
internal var immutableSystemStateDone = false
internal var immutableChinHeightDone = false
internal var systemHasSentWatchUiState = false
- internal var resourceOnlyWatchFacePackageName: String? = headlessComponentName?.packageName
+ internal var resourceOnlyWatchFacePackageName: String? =
+ if (this@WatchFaceService is WatchFaceRuntimeService) {
+ headlessComponentName?.packageName
+ } else {
+ null
+ }
private var asyncWatchFaceConstructionPending = false
@@ -2103,7 +2108,10 @@
setWatchUiState(params.watchUiState, fromSysUi = false)
initialUserStyle = params.userStyle
- resourceOnlyWatchFacePackageName = params.auxiliaryComponentPackageName
+ // For a resource only watch face, the auxiliaryComponentPackageName will be null.
+ if (this@WatchFaceService is WatchFaceRuntimeService) {
+ resourceOnlyWatchFacePackageName = params.auxiliaryComponentPackageName
+ }
mutableWatchState.watchFaceInstanceId.value = sanitizeWatchFaceId(params.instanceId)
val watchState = mutableWatchState.asWatchState()
diff --git a/wear/watchface/watchface/src/test/java/androidx/wear/watchface/WatchFaceServiceTest.kt b/wear/watchface/watchface/src/test/java/androidx/wear/watchface/WatchFaceServiceTest.kt
index d55140d..3a6c4ec 100644
--- a/wear/watchface/watchface/src/test/java/androidx/wear/watchface/WatchFaceServiceTest.kt
+++ b/wear/watchface/watchface/src/test/java/androidx/wear/watchface/WatchFaceServiceTest.kt
@@ -88,6 +88,7 @@
import androidx.wear.watchface.data.WatchUiState
import androidx.wear.watchface.style.CurrentUserStyleRepository
import androidx.wear.watchface.style.UserStyle
+import androidx.wear.watchface.style.UserStyleFlavors
import androidx.wear.watchface.style.UserStyleSchema
import androidx.wear.watchface.style.UserStyleSetting
import androidx.wear.watchface.style.UserStyleSetting.ComplicationSlotsUserStyleSetting
@@ -6549,11 +6550,12 @@
@Test
public fun createHeadlessSessionDelegate_onDestroy() {
val context = ApplicationProvider.getApplicationContext<Context>()
- val componentName = ComponentName(context, TestNopCanvasWatchFaceService::class.java)
+ val componentName =
+ ComponentName(context, TestNopCanvasWatchFaceServiceWithHandler::class.java)
lateinit var delegate: WatchFace.EditorDelegate
// Allows us to programmatically control tasks.
- TestNopCanvasWatchFaceService.handler = this.handler
+ TestNopCanvasWatchFaceServiceWithHandler.handler = this.handler
CoroutineScope(handler.asCoroutineDispatcher().immediate).launch {
delegate =
@@ -6609,7 +6611,7 @@
)
// Allows us to programmatically control tasks.
- TestNopCanvasWatchFaceService.handler = this.handler
+ TestNopCanvasWatchFaceServiceWithHandler.handler = this.handler
CoroutineScope(handler.asCoroutineDispatcher().immediate).launch {
delegate =
@@ -6709,6 +6711,115 @@
.isEqualTo(INTERACTIVE_INSTANCE_ID)
}
+ @Test
+ @Config(sdk = [Build.VERSION_CODES.R])
+ @RequiresApi(Build.VERSION_CODES.R)
+ public fun interactive_wf_has_null_resourceOnlyWatchFacePackageName() {
+ initWallpaperInteractiveWatchFaceInstance(
+ WatchFaceType.ANALOG,
+ emptyList(),
+ UserStyleSchema(emptyList()),
+ WallpaperInteractiveWatchFaceInstanceParams(
+ INTERACTIVE_INSTANCE_ID,
+ DeviceConfig(false, false, 0, 0),
+ WatchUiState(false, 0),
+ UserStyle(emptyMap()).toWireFormat(),
+ null,
+ null,
+ null
+ )
+ )
+
+ assertThat(engineWrapper.resourceOnlyWatchFacePackageName).isNull()
+ }
+
+ @Test
+ @Config(sdk = [Build.VERSION_CODES.R])
+ @RequiresApi(Build.VERSION_CODES.O_MR1)
+ public fun headless_wf_has_null_resourceOnlyWatchFacePackageName() {
+ val service = TestNopCanvasWatchFaceService(context)
+ val componentName = ComponentName("test.watchface.app", "test.watchface.class")
+
+ engineWrapper =
+ service.createHeadlessEngine(componentName) as WatchFaceService.EngineWrapper
+ engineWrapper.createHeadlessInstance(
+ HeadlessWatchFaceInstanceParams(
+ componentName,
+ DeviceConfig(false, false, 100, 200),
+ 100,
+ 100,
+ null
+ )
+ )
+
+ assertThat(engineWrapper.resourceOnlyWatchFacePackageName).isNull()
+ }
+
+ @Test
+ @Config(sdk = [Build.VERSION_CODES.R])
+ @RequiresApi(Build.VERSION_CODES.R)
+ public fun interactive_wf_runtime_has_non_null_resourceOnlyWatchFacePackageName() {
+ val service = TestNopWatchFaceRuntimeService(context)
+ InteractiveInstanceManager
+ .getExistingInstanceOrSetPendingWallpaperInteractiveWatchFaceInstance(
+ InteractiveInstanceManager.PendingWallpaperInteractiveWatchFaceInstance(
+ WallpaperInteractiveWatchFaceInstanceParams(
+ SYSTEM_SUPPORTS_CONSISTENT_IDS_PREFIX + "Interactive",
+ DeviceConfig(false, false, 0, 0),
+ WatchUiState(false, 0),
+ UserStyle(emptyMap()).toWireFormat(),
+ emptyList(),
+ "com.resource.only.package",
+ null
+ ),
+ object : IPendingInteractiveWatchFace.Stub() {
+ override fun getApiVersion() = IPendingInteractiveWatchFace.API_VERSION
+
+ override fun onInteractiveWatchFaceCreated(
+ iInteractiveWatchFace: IInteractiveWatchFace
+ ) {
+ interactiveWatchFaceInstance = iInteractiveWatchFace
+ }
+
+ override fun onInteractiveWatchFaceCrashed(exception: CrashInfoParcel?) {
+ fail("WatchFace crashed: $exception")
+ }
+ }
+ )
+ )
+
+ engineWrapper = service.onCreateEngine() as WatchFaceService.EngineWrapper
+ engineWrapper.onCreate(surfaceHolder)
+ engineWrapper.onSurfaceChanged(surfaceHolder, 0, 100, 100)
+
+ assertThat(engineWrapper.resourceOnlyWatchFacePackageName)
+ .isEqualTo("com.resource.only.package")
+ engineWrapper.onDestroy()
+ }
+
+ @Test
+ @Config(sdk = [Build.VERSION_CODES.R])
+ @RequiresApi(Build.VERSION_CODES.O_MR1)
+ public fun headless_wf_runtime_has_non_null_resourceOnlyWatchFacePackageName() {
+ val service = TestNopWatchFaceRuntimeService(context)
+ val componentName = ComponentName("com.resource.only.package", "null")
+
+ engineWrapper =
+ service.createHeadlessEngine(componentName) as WatchFaceService.EngineWrapper
+ engineWrapper.createHeadlessInstance(
+ HeadlessWatchFaceInstanceParams(
+ componentName,
+ DeviceConfig(false, false, 100, 200),
+ 100,
+ 100,
+ null
+ )
+ )
+
+ assertThat(engineWrapper.resourceOnlyWatchFacePackageName)
+ .isEqualTo("com.resource.only.package")
+ }
+
private fun getLeftShortTextComplicationDataText(): CharSequence {
val complication =
complicationSlotsManager[LEFT_COMPLICATION_ID]!!.complicationData.value
@@ -6743,7 +6854,115 @@
private suspend fun <T> Deferred<T>.awaitWithTimeout(): T = withTimeout(1000) { await() }
}
-class TestNopCanvasWatchFaceService : WatchFaceService() {
+class TestNopWatchFaceRuntimeService(testContext: Context) : WatchFaceRuntimeService() {
+ var lastResourceOnlyWatchFacePackageName: String? = null
+
+ init {
+ attachBaseContext(testContext)
+ }
+
+ override fun createUserStyleSchema(resourceOnlyWatchFacePackageName: String) =
+ UserStyleSchema(emptyList())
+
+ override fun createComplicationSlotsManager(
+ currentUserStyleRepository: CurrentUserStyleRepository,
+ resourceOnlyWatchFacePackageName: String
+ ) = ComplicationSlotsManager(emptyList(), currentUserStyleRepository)
+
+ override fun createUserStyleFlavors(
+ currentUserStyleRepository: CurrentUserStyleRepository,
+ complicationSlotsManager: ComplicationSlotsManager,
+ resourceOnlyWatchFacePackageName: String
+ ) = UserStyleFlavors()
+
+ override suspend fun createWatchFace(
+ surfaceHolder: SurfaceHolder,
+ watchState: WatchState,
+ complicationSlotsManager: ComplicationSlotsManager,
+ currentUserStyleRepository: CurrentUserStyleRepository,
+ resourceOnlyWatchFacePackageName: String
+ ): WatchFace {
+ lastResourceOnlyWatchFacePackageName = resourceOnlyWatchFacePackageName
+ System.out.println("<<< createWatchFace " + resourceOnlyWatchFacePackageName)
+ return WatchFace(
+ WatchFaceType.DIGITAL,
+ @Suppress("deprecation")
+ object :
+ Renderer.CanvasRenderer(
+ surfaceHolder,
+ currentUserStyleRepository,
+ watchState,
+ CanvasType.HARDWARE,
+ 16
+ ) {
+ override fun render(canvas: Canvas, bounds: Rect, zonedDateTime: ZonedDateTime) {
+ // Intentionally empty.
+ }
+
+ override fun renderHighlightLayer(
+ canvas: Canvas,
+ bounds: Rect,
+ zonedDateTime: ZonedDateTime
+ ) {
+ // Intentionally empty.
+ }
+ }
+ )
+ }
+
+ override fun getSystemTimeProvider() =
+ object : SystemTimeProvider {
+ override fun getSystemTimeMillis() = 123456789L
+
+ override fun getSystemTimeZoneId() = ZoneId.of("UTC")
+ }
+}
+
+class TestNopCanvasWatchFaceService(testContext: Context) : WatchFaceService() {
+ init {
+ attachBaseContext(testContext)
+ }
+
+ override suspend fun createWatchFace(
+ surfaceHolder: SurfaceHolder,
+ watchState: WatchState,
+ complicationSlotsManager: ComplicationSlotsManager,
+ currentUserStyleRepository: CurrentUserStyleRepository
+ ) =
+ WatchFace(
+ WatchFaceType.DIGITAL,
+ @Suppress("deprecation")
+ object :
+ Renderer.CanvasRenderer(
+ surfaceHolder,
+ currentUserStyleRepository,
+ watchState,
+ CanvasType.HARDWARE,
+ 16
+ ) {
+ override fun render(canvas: Canvas, bounds: Rect, zonedDateTime: ZonedDateTime) {
+ // Intentionally empty.
+ }
+
+ override fun renderHighlightLayer(
+ canvas: Canvas,
+ bounds: Rect,
+ zonedDateTime: ZonedDateTime
+ ) {
+ // Intentionally empty.
+ }
+ }
+ )
+
+ override fun getSystemTimeProvider() =
+ object : SystemTimeProvider {
+ override fun getSystemTimeMillis() = 123456789L
+
+ override fun getSystemTimeZoneId() = ZoneId.of("UTC")
+ }
+}
+
+class TestNopCanvasWatchFaceServiceWithHandler : WatchFaceService() {
companion object {
lateinit var handler: Handler
}
@@ -6796,6 +7015,6 @@
@RequiresApi(27)
class TestWatchFaceControlService : WatchFaceControlService() {
override fun createWatchFaceService(watchFaceName: ComponentName): WatchFaceService? {
- return TestNopCanvasWatchFaceService()
+ return TestNopCanvasWatchFaceServiceWithHandler()
}
}
diff --git a/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/internal/AssetHelperTest.java b/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/internal/AssetHelperTest.java
index 8a9d2e1..95ab916 100644
--- a/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/internal/AssetHelperTest.java
+++ b/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/internal/AssetHelperTest.java
@@ -35,6 +35,8 @@
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
+import java.net.FileNameMap;
+import java.net.URLConnection;
@RunWith(AndroidJUnit4.class)
public class AssetHelperTest {
@@ -43,6 +45,7 @@
private static final String TEST_STRING = "Just a test";
private AssetHelper mAssetHelper;
private File mInternalStorageTestDir;
+ private FileNameMap mDefaultFileNameMap;
@Before
public void setup() {
@@ -50,11 +53,13 @@
mAssetHelper = new AssetHelper(context);
mInternalStorageTestDir = new File(context.getFilesDir(), "test_dir");
mInternalStorageTestDir.mkdirs();
+ mDefaultFileNameMap = URLConnection.getFileNameMap();
}
@After
public void tearDown() {
WebkitUtils.recursivelyDeleteFile(mInternalStorageTestDir);
+ URLConnection.setFileNameMap(mDefaultFileNameMap);
}
@Test
@@ -220,6 +225,48 @@
}
}
+ @Test
+ @SmallTest
+ public void testGuessMimeType() {
+ // First check the OS
+ Assert.assertEquals("text/plain", AssetHelper.guessMimeType("aFile.txt"));
+
+ // Then check the first item in our list
+ Assert.assertEquals("video/webm", AssetHelper.guessMimeType("AVideoFile.webm"));
+ // A random list item
+ Assert.assertEquals("application/xhtml+xml", AssetHelper.guessMimeType("TestMimeFile.xht"));
+ // A file path doesn't cause issues
+ Assert.assertEquals(
+ "application/xhtml+xml", AssetHelper.guessMimeType("a/path/to/TestMimeFile.xht"));
+ // A file path doesn't cause issues
+ Assert.assertEquals(
+ "application/xhtml+xml", AssetHelper.guessMimeType("a/path/to/TestMimeFile.xht"));
+
+ // Check case insensitive
+ Assert.assertEquals("video/mpeg", AssetHelper.guessMimeType("aVideo.mPG"));
+
+ // Check a few error conditions fallback to default
+ Assert.assertEquals("text/plain", AssetHelper.guessMimeType(null));
+ Assert.assertEquals("text/plain", AssetHelper.guessMimeType("No full stop!"));
+ Assert.assertEquals("text/plain", AssetHelper.guessMimeType("file."));
+ Assert.assertEquals("text/plain", AssetHelper.guessMimeType("A.myownfiletype"));
+
+ // We added this because javascript mime types weren't being handled
+ // correctly so also adding a test for that to be safe
+ Assert.assertEquals("application/javascript", AssetHelper.guessMimeType("a js file.js"));
+
+ // Check that overridden mime map is prioritized
+ final String expectedMime = "test/mime";
+
+ URLConnection.setFileNameMap(new FileNameMap() {
+ @Override
+ public String getContentTypeFor(String fileName) {
+ return expectedMime;
+ }
+ });
+ Assert.assertEquals(expectedMime, AssetHelper.guessMimeType("aFile.txt"));
+ }
+
private InputStream assertOpen(String path) throws IOException {
InputStream stream = mAssetHelper.openAsset(path);
Assert.assertNotNull("Failed to open \"" + path + "\"", stream);
diff --git a/webkit/webkit/src/main/java/androidx/webkit/internal/AssetHelper.java b/webkit/webkit/src/main/java/androidx/webkit/internal/AssetHelper.java
index e6bbf7b..6a09ee4 100644
--- a/webkit/webkit/src/main/java/androidx/webkit/internal/AssetHelper.java
+++ b/webkit/webkit/src/main/java/androidx/webkit/internal/AssetHelper.java
@@ -30,7 +30,6 @@
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
-import java.net.URLConnection;
import java.util.zip.GZIPInputStream;
/**
@@ -192,7 +191,7 @@
}
/**
- * Use {@link URLConnection#guessContentTypeFromName} to guess MIME type or return the
+ * Use {@link MimeUtil#getMimeFromFileName} to guess MIME type or return the
* {@link DEFAULT_MIME_TYPE} if it can't guess.
*
* @param filePath path of the file to guess its MIME type.
@@ -200,8 +199,7 @@
*/
@NonNull
public static String guessMimeType(@NonNull String filePath) {
- String mimeType = URLConnection.guessContentTypeFromName(filePath);
+ String mimeType = MimeUtil.getMimeFromFileName(filePath);
return mimeType == null ? DEFAULT_MIME_TYPE : mimeType;
}
-
}
diff --git a/webkit/webkit/src/main/java/androidx/webkit/internal/MimeUtil.java b/webkit/webkit/src/main/java/androidx/webkit/internal/MimeUtil.java
new file mode 100644
index 0000000..b52a082
--- /dev/null
+++ b/webkit/webkit/src/main/java/androidx/webkit/internal/MimeUtil.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.webkit.internal;
+
+import java.net.URLConnection;
+
+class MimeUtil {
+
+ public static String getMimeFromFileName(String fileName) {
+ if (fileName == null) {
+ return null;
+ }
+
+ // Copying the logic and mapping that Chromium follows.
+ // First we check against the OS (this is a limited list by default)
+ // but app developers can extend this.
+ // We then check against a list of hardcoded mime types above if the
+ // OS didn't provide a result.
+ String mimeType = URLConnection.guessContentTypeFromName(fileName);
+
+ if (mimeType != null) {
+ return mimeType;
+ }
+
+ return guessHardcodedMime(fileName);
+ }
+
+ // We should keep this map in sync with the lists under
+ // //net/base/mime_util.cc in Chromium.
+ // A bunch of the mime types don't really apply to Android land
+ // like word docs so feel free to filter out where necessary.
+ private static String guessHardcodedMime(String fileName) {
+ int finalFullStop = fileName.lastIndexOf('.');
+ if (finalFullStop == -1) {
+ return null;
+ }
+
+ final String extension = fileName.substring(finalFullStop + 1).toLowerCase();
+
+ switch (extension) {
+ case "webm":
+ return "video/webm";
+ case "mpeg":
+ case "mpg":
+ return "video/mpeg";
+ case "mp3":
+ return "audio/mpeg";
+ case "wasm":
+ return "application/wasm";
+ case "xhtml":
+ case "xht":
+ case "xhtm":
+ return "application/xhtml+xml";
+ case "flac":
+ return "audio/flac";
+ case "ogg":
+ case "oga":
+ case "opus":
+ return "audio/ogg";
+ case "wav":
+ return "audio/wav";
+ case "m4a":
+ return "audio/x-m4a";
+ case "gif":
+ return "image/gif";
+ case "jpeg":
+ case "jpg":
+ case "jfif":
+ case "pjpeg":
+ case "pjp":
+ return "image/jpeg";
+ case "png":
+ return "image/png";
+ case "apng":
+ return "image/apng";
+ case "svg":
+ case "svgz":
+ return "image/svg+xml";
+ case "webp":
+ return "image/webp";
+ case "mht":
+ case "mhtml":
+ return "multipart/related";
+ case "css":
+ return "text/css";
+ case "html":
+ case "htm":
+ case "shtml":
+ case "shtm":
+ case "ehtml":
+ return "text/html";
+ case "js":
+ case "mjs":
+ return "application/javascript";
+ case "xml":
+ return "text/xml";
+ case "mp4":
+ case "m4v":
+ return "video/mp4";
+ case "ogv":
+ case "ogm":
+ return "video/ogg";
+ case "ico":
+ return "image/x-icon";
+ case "woff":
+ return "application/font-woff";
+ case "gz":
+ case "tgz":
+ return "application/gzip";
+ case "json":
+ return "application/json";
+ case "pdf":
+ return "application/pdf";
+ case "zip":
+ return "application/zip";
+ case "bmp":
+ return "image/bmp";
+ case "tiff":
+ case "tif":
+ return "image/tiff";
+ default:
+ return null;
+ }
+ }
+}
diff --git a/window/extensions/extensions/build.gradle b/window/extensions/extensions/build.gradle
index 5d477b7..4de2b71 100644
--- a/window/extensions/extensions/build.gradle
+++ b/window/extensions/extensions/build.gradle
@@ -23,9 +23,8 @@
}
dependencies {
- api(libs.kotlinStdlib)
implementation("androidx.annotation:annotation:1.6.0")
- implementation("androidx.annotation:annotation-experimental:1.1.0")
+ implementation("androidx.annotation:annotation-experimental:1.3.1")
implementation("androidx.window.extensions.core:core:1.0.0")
testImplementation(libs.robolectric)
diff --git a/window/window-core/api/1.2.0-beta01.txt b/window/window-core/api/1.2.0-beta01.txt
new file mode 100644
index 0000000..624b2df
--- /dev/null
+++ b/window/window-core/api/1.2.0-beta01.txt
@@ -0,0 +1,38 @@
+// Signature format: 4.0
+package androidx.window.core.layout {
+
+ public final class WindowHeightSizeClass {
+ field public static final androidx.window.core.layout.WindowHeightSizeClass COMPACT;
+ field public static final androidx.window.core.layout.WindowHeightSizeClass.Companion Companion;
+ field public static final androidx.window.core.layout.WindowHeightSizeClass EXPANDED;
+ field public static final androidx.window.core.layout.WindowHeightSizeClass MEDIUM;
+ }
+
+ public static final class WindowHeightSizeClass.Companion {
+ }
+
+ public final class WindowSizeClass {
+ method public static androidx.window.core.layout.WindowSizeClass compute(float dpWidth, float dpHeight);
+ method public androidx.window.core.layout.WindowHeightSizeClass getWindowHeightSizeClass();
+ method public androidx.window.core.layout.WindowWidthSizeClass getWindowWidthSizeClass();
+ property public final androidx.window.core.layout.WindowHeightSizeClass windowHeightSizeClass;
+ property public final androidx.window.core.layout.WindowWidthSizeClass windowWidthSizeClass;
+ field public static final androidx.window.core.layout.WindowSizeClass.Companion Companion;
+ }
+
+ public static final class WindowSizeClass.Companion {
+ method public androidx.window.core.layout.WindowSizeClass compute(float dpWidth, float dpHeight);
+ }
+
+ public final class WindowWidthSizeClass {
+ field public static final androidx.window.core.layout.WindowWidthSizeClass COMPACT;
+ field public static final androidx.window.core.layout.WindowWidthSizeClass.Companion Companion;
+ field public static final androidx.window.core.layout.WindowWidthSizeClass EXPANDED;
+ field public static final androidx.window.core.layout.WindowWidthSizeClass MEDIUM;
+ }
+
+ public static final class WindowWidthSizeClass.Companion {
+ }
+
+}
+
diff --git a/window/window-core/api/res-1.2.0-beta01.txt b/window/window-core/api/res-1.2.0-beta01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/window/window-core/api/res-1.2.0-beta01.txt
diff --git a/window/window-core/api/restricted_1.2.0-beta01.txt b/window/window-core/api/restricted_1.2.0-beta01.txt
new file mode 100644
index 0000000..624b2df
--- /dev/null
+++ b/window/window-core/api/restricted_1.2.0-beta01.txt
@@ -0,0 +1,38 @@
+// Signature format: 4.0
+package androidx.window.core.layout {
+
+ public final class WindowHeightSizeClass {
+ field public static final androidx.window.core.layout.WindowHeightSizeClass COMPACT;
+ field public static final androidx.window.core.layout.WindowHeightSizeClass.Companion Companion;
+ field public static final androidx.window.core.layout.WindowHeightSizeClass EXPANDED;
+ field public static final androidx.window.core.layout.WindowHeightSizeClass MEDIUM;
+ }
+
+ public static final class WindowHeightSizeClass.Companion {
+ }
+
+ public final class WindowSizeClass {
+ method public static androidx.window.core.layout.WindowSizeClass compute(float dpWidth, float dpHeight);
+ method public androidx.window.core.layout.WindowHeightSizeClass getWindowHeightSizeClass();
+ method public androidx.window.core.layout.WindowWidthSizeClass getWindowWidthSizeClass();
+ property public final androidx.window.core.layout.WindowHeightSizeClass windowHeightSizeClass;
+ property public final androidx.window.core.layout.WindowWidthSizeClass windowWidthSizeClass;
+ field public static final androidx.window.core.layout.WindowSizeClass.Companion Companion;
+ }
+
+ public static final class WindowSizeClass.Companion {
+ method public androidx.window.core.layout.WindowSizeClass compute(float dpWidth, float dpHeight);
+ }
+
+ public final class WindowWidthSizeClass {
+ field public static final androidx.window.core.layout.WindowWidthSizeClass COMPACT;
+ field public static final androidx.window.core.layout.WindowWidthSizeClass.Companion Companion;
+ field public static final androidx.window.core.layout.WindowWidthSizeClass EXPANDED;
+ field public static final androidx.window.core.layout.WindowWidthSizeClass MEDIUM;
+ }
+
+ public static final class WindowWidthSizeClass.Companion {
+ }
+
+}
+
diff --git a/window/window-demos/demo/src/main/AndroidManifest.xml b/window/window-demos/demo/src/main/AndroidManifest.xml
index 230cb82..597cb51 100644
--- a/window/window-demos/demo/src/main/AndroidManifest.xml
+++ b/window/window-demos/demo/src/main/AndroidManifest.xml
@@ -58,7 +58,7 @@
android:exported="false"
android:configChanges="orientation|screenSize|screenLayout|screenSize"
android:label="@string/window_metrics"/>
- <activity android:name=".RearDisplayActivityConfigChanges"
+ <activity android:name=".area.RearDisplayActivityConfigChanges"
android:exported="true"
android:configChanges=
"orientation|screenLayout|screenSize|layoutDirection|smallestScreenSize"
diff --git a/window/window-demos/demo/src/main/java/androidx/window/demo/RearDisplayActivityConfigChanges.kt b/window/window-demos/demo/src/main/java/androidx/window/demo/area/RearDisplayActivityConfigChanges.kt
similarity index 98%
rename from window/window-demos/demo/src/main/java/androidx/window/demo/RearDisplayActivityConfigChanges.kt
rename to window/window-demos/demo/src/main/java/androidx/window/demo/area/RearDisplayActivityConfigChanges.kt
index 97ab277..7e12657 100644
--- a/window/window-demos/demo/src/main/java/androidx/window/demo/RearDisplayActivityConfigChanges.kt
+++ b/window/window-demos/demo/src/main/java/androidx/window/demo/area/RearDisplayActivityConfigChanges.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package androidx.window.demo
+package androidx.window.demo.area
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
@@ -33,6 +33,7 @@
import androidx.window.area.WindowAreaInfo.Type.Companion.TYPE_REAR_FACING
import androidx.window.area.WindowAreaSession
import androidx.window.area.WindowAreaSessionCallback
+import androidx.window.core.ExperimentalWindowApi
import androidx.window.demo.common.infolog.InfoLogAdapter
import androidx.window.demo.databinding.ActivityRearDisplayBinding
import java.text.SimpleDateFormat
@@ -52,6 +53,7 @@
*
* This Activity overrides configuration changes for simplicity.
*/
+@OptIn(ExperimentalWindowApi::class)
class RearDisplayActivityConfigChanges : AppCompatActivity(), WindowAreaSessionCallback {
private lateinit var windowAreaController: WindowAreaController
diff --git a/window/window-java/api/1.2.0-beta01.txt b/window/window-java/api/1.2.0-beta01.txt
new file mode 100644
index 0000000..2e19128
--- /dev/null
+++ b/window/window-java/api/1.2.0-beta01.txt
@@ -0,0 +1,32 @@
+// Signature format: 4.0
+package androidx.window.java.area {
+
+ @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public final class WindowAreaControllerCallbackAdapter implements androidx.window.area.WindowAreaController {
+ ctor public WindowAreaControllerCallbackAdapter(androidx.window.area.WindowAreaController controller);
+ method public void addWindowAreaInfoListListener(java.util.concurrent.Executor executor, androidx.core.util.Consumer<java.util.List<androidx.window.area.WindowAreaInfo>> listener);
+ method public void removeWindowAreaInfoListListener(androidx.core.util.Consumer<java.util.List<androidx.window.area.WindowAreaInfo>> listener);
+ }
+
+}
+
+package androidx.window.java.embedding {
+
+ @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public final class SplitControllerCallbackAdapter {
+ ctor public SplitControllerCallbackAdapter(androidx.window.embedding.SplitController controller);
+ method public void addSplitListener(android.app.Activity activity, java.util.concurrent.Executor executor, androidx.core.util.Consumer<java.util.List<androidx.window.embedding.SplitInfo>> consumer);
+ method public void removeSplitListener(androidx.core.util.Consumer<java.util.List<androidx.window.embedding.SplitInfo>> consumer);
+ }
+
+}
+
+package androidx.window.java.layout {
+
+ public final class WindowInfoTrackerCallbackAdapter implements androidx.window.layout.WindowInfoTracker {
+ ctor public WindowInfoTrackerCallbackAdapter(androidx.window.layout.WindowInfoTracker tracker);
+ method public void addWindowLayoutInfoListener(android.app.Activity activity, java.util.concurrent.Executor executor, androidx.core.util.Consumer<androidx.window.layout.WindowLayoutInfo> consumer);
+ method public void addWindowLayoutInfoListener(@UiContext android.content.Context context, java.util.concurrent.Executor executor, androidx.core.util.Consumer<androidx.window.layout.WindowLayoutInfo> consumer);
+ method public void removeWindowLayoutInfoListener(androidx.core.util.Consumer<androidx.window.layout.WindowLayoutInfo> consumer);
+ }
+
+}
+
diff --git a/window/window-java/api/current.txt b/window/window-java/api/current.txt
index 886ea88..2e19128 100644
--- a/window/window-java/api/current.txt
+++ b/window/window-java/api/current.txt
@@ -1,7 +1,7 @@
// Signature format: 4.0
package androidx.window.java.area {
- public final class WindowAreaControllerCallbackAdapter implements androidx.window.area.WindowAreaController {
+ @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public final class WindowAreaControllerCallbackAdapter implements androidx.window.area.WindowAreaController {
ctor public WindowAreaControllerCallbackAdapter(androidx.window.area.WindowAreaController controller);
method public void addWindowAreaInfoListListener(java.util.concurrent.Executor executor, androidx.core.util.Consumer<java.util.List<androidx.window.area.WindowAreaInfo>> listener);
method public void removeWindowAreaInfoListListener(androidx.core.util.Consumer<java.util.List<androidx.window.area.WindowAreaInfo>> listener);
diff --git a/window/window-java/api/res-1.2.0-beta01.txt b/window/window-java/api/res-1.2.0-beta01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/window/window-java/api/res-1.2.0-beta01.txt
diff --git a/window/window-java/api/restricted_1.2.0-beta01.txt b/window/window-java/api/restricted_1.2.0-beta01.txt
new file mode 100644
index 0000000..2e19128
--- /dev/null
+++ b/window/window-java/api/restricted_1.2.0-beta01.txt
@@ -0,0 +1,32 @@
+// Signature format: 4.0
+package androidx.window.java.area {
+
+ @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public final class WindowAreaControllerCallbackAdapter implements androidx.window.area.WindowAreaController {
+ ctor public WindowAreaControllerCallbackAdapter(androidx.window.area.WindowAreaController controller);
+ method public void addWindowAreaInfoListListener(java.util.concurrent.Executor executor, androidx.core.util.Consumer<java.util.List<androidx.window.area.WindowAreaInfo>> listener);
+ method public void removeWindowAreaInfoListListener(androidx.core.util.Consumer<java.util.List<androidx.window.area.WindowAreaInfo>> listener);
+ }
+
+}
+
+package androidx.window.java.embedding {
+
+ @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public final class SplitControllerCallbackAdapter {
+ ctor public SplitControllerCallbackAdapter(androidx.window.embedding.SplitController controller);
+ method public void addSplitListener(android.app.Activity activity, java.util.concurrent.Executor executor, androidx.core.util.Consumer<java.util.List<androidx.window.embedding.SplitInfo>> consumer);
+ method public void removeSplitListener(androidx.core.util.Consumer<java.util.List<androidx.window.embedding.SplitInfo>> consumer);
+ }
+
+}
+
+package androidx.window.java.layout {
+
+ public final class WindowInfoTrackerCallbackAdapter implements androidx.window.layout.WindowInfoTracker {
+ ctor public WindowInfoTrackerCallbackAdapter(androidx.window.layout.WindowInfoTracker tracker);
+ method public void addWindowLayoutInfoListener(android.app.Activity activity, java.util.concurrent.Executor executor, androidx.core.util.Consumer<androidx.window.layout.WindowLayoutInfo> consumer);
+ method public void addWindowLayoutInfoListener(@UiContext android.content.Context context, java.util.concurrent.Executor executor, androidx.core.util.Consumer<androidx.window.layout.WindowLayoutInfo> consumer);
+ method public void removeWindowLayoutInfoListener(androidx.core.util.Consumer<androidx.window.layout.WindowLayoutInfo> consumer);
+ }
+
+}
+
diff --git a/window/window-java/api/restricted_current.txt b/window/window-java/api/restricted_current.txt
index 886ea88..2e19128 100644
--- a/window/window-java/api/restricted_current.txt
+++ b/window/window-java/api/restricted_current.txt
@@ -1,7 +1,7 @@
// Signature format: 4.0
package androidx.window.java.area {
- public final class WindowAreaControllerCallbackAdapter implements androidx.window.area.WindowAreaController {
+ @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public final class WindowAreaControllerCallbackAdapter implements androidx.window.area.WindowAreaController {
ctor public WindowAreaControllerCallbackAdapter(androidx.window.area.WindowAreaController controller);
method public void addWindowAreaInfoListListener(java.util.concurrent.Executor executor, androidx.core.util.Consumer<java.util.List<androidx.window.area.WindowAreaInfo>> listener);
method public void removeWindowAreaInfoListListener(androidx.core.util.Consumer<java.util.List<androidx.window.area.WindowAreaInfo>> listener);
diff --git a/window/window-java/src/main/java/androidx/window/java/area/WindowAreaControllerCallbackAdapter.kt b/window/window-java/src/main/java/androidx/window/java/area/WindowAreaControllerCallbackAdapter.kt
index a5697db..f461504 100644
--- a/window/window-java/src/main/java/androidx/window/java/area/WindowAreaControllerCallbackAdapter.kt
+++ b/window/window-java/src/main/java/androidx/window/java/area/WindowAreaControllerCallbackAdapter.kt
@@ -19,12 +19,14 @@
import androidx.core.util.Consumer
import androidx.window.area.WindowAreaController
import androidx.window.area.WindowAreaInfo
+import androidx.window.core.ExperimentalWindowApi
import androidx.window.java.core.CallbackToFlowAdapter
import java.util.concurrent.Executor
/**
* An adapter for [WindowAreaController] to provide callback APIs.
*/
+@ExperimentalWindowApi
class WindowAreaControllerCallbackAdapter private constructor(
private val controller: WindowAreaController,
private val callbackToFlowAdapter: CallbackToFlowAdapter
diff --git a/window/window-rxjava2/api/1.2.0-beta01.txt b/window/window-rxjava2/api/1.2.0-beta01.txt
new file mode 100644
index 0000000..5250696
--- /dev/null
+++ b/window/window-rxjava2/api/1.2.0-beta01.txt
@@ -0,0 +1,12 @@
+// Signature format: 4.0
+package androidx.window.rxjava2.layout {
+
+ public final class WindowInfoTrackerRx {
+ method public static io.reactivex.Flowable<androidx.window.layout.WindowLayoutInfo> windowLayoutInfoFlowable(androidx.window.layout.WindowInfoTracker, android.app.Activity activity);
+ method public static io.reactivex.Flowable<androidx.window.layout.WindowLayoutInfo> windowLayoutInfoFlowable(androidx.window.layout.WindowInfoTracker, @UiContext android.content.Context context);
+ method public static io.reactivex.Observable<androidx.window.layout.WindowLayoutInfo> windowLayoutInfoObservable(androidx.window.layout.WindowInfoTracker, android.app.Activity activity);
+ method public static io.reactivex.Observable<androidx.window.layout.WindowLayoutInfo> windowLayoutInfoObservable(androidx.window.layout.WindowInfoTracker, @UiContext android.content.Context context);
+ }
+
+}
+
diff --git a/window/window-rxjava2/api/res-1.2.0-beta01.txt b/window/window-rxjava2/api/res-1.2.0-beta01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/window/window-rxjava2/api/res-1.2.0-beta01.txt
diff --git a/window/window-rxjava2/api/restricted_1.2.0-beta01.txt b/window/window-rxjava2/api/restricted_1.2.0-beta01.txt
new file mode 100644
index 0000000..5250696
--- /dev/null
+++ b/window/window-rxjava2/api/restricted_1.2.0-beta01.txt
@@ -0,0 +1,12 @@
+// Signature format: 4.0
+package androidx.window.rxjava2.layout {
+
+ public final class WindowInfoTrackerRx {
+ method public static io.reactivex.Flowable<androidx.window.layout.WindowLayoutInfo> windowLayoutInfoFlowable(androidx.window.layout.WindowInfoTracker, android.app.Activity activity);
+ method public static io.reactivex.Flowable<androidx.window.layout.WindowLayoutInfo> windowLayoutInfoFlowable(androidx.window.layout.WindowInfoTracker, @UiContext android.content.Context context);
+ method public static io.reactivex.Observable<androidx.window.layout.WindowLayoutInfo> windowLayoutInfoObservable(androidx.window.layout.WindowInfoTracker, android.app.Activity activity);
+ method public static io.reactivex.Observable<androidx.window.layout.WindowLayoutInfo> windowLayoutInfoObservable(androidx.window.layout.WindowInfoTracker, @UiContext android.content.Context context);
+ }
+
+}
+
diff --git a/window/window-rxjava3/api/1.2.0-beta01.txt b/window/window-rxjava3/api/1.2.0-beta01.txt
new file mode 100644
index 0000000..23510cc
--- /dev/null
+++ b/window/window-rxjava3/api/1.2.0-beta01.txt
@@ -0,0 +1,12 @@
+// Signature format: 4.0
+package androidx.window.rxjava3.layout {
+
+ public final class WindowInfoTrackerRx {
+ method public static io.reactivex.rxjava3.core.Flowable<androidx.window.layout.WindowLayoutInfo> windowLayoutInfoFlowable(androidx.window.layout.WindowInfoTracker, android.app.Activity activity);
+ method public static io.reactivex.rxjava3.core.Flowable<androidx.window.layout.WindowLayoutInfo> windowLayoutInfoFlowable(androidx.window.layout.WindowInfoTracker, @UiContext android.content.Context context);
+ method public static io.reactivex.rxjava3.core.Observable<androidx.window.layout.WindowLayoutInfo> windowLayoutInfoObservable(androidx.window.layout.WindowInfoTracker, android.app.Activity activity);
+ method public static io.reactivex.rxjava3.core.Observable<androidx.window.layout.WindowLayoutInfo> windowLayoutInfoObservable(androidx.window.layout.WindowInfoTracker, @UiContext android.content.Context context);
+ }
+
+}
+
diff --git a/window/window-rxjava3/api/res-1.2.0-beta01.txt b/window/window-rxjava3/api/res-1.2.0-beta01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/window/window-rxjava3/api/res-1.2.0-beta01.txt
diff --git a/window/window-rxjava3/api/restricted_1.2.0-beta01.txt b/window/window-rxjava3/api/restricted_1.2.0-beta01.txt
new file mode 100644
index 0000000..23510cc
--- /dev/null
+++ b/window/window-rxjava3/api/restricted_1.2.0-beta01.txt
@@ -0,0 +1,12 @@
+// Signature format: 4.0
+package androidx.window.rxjava3.layout {
+
+ public final class WindowInfoTrackerRx {
+ method public static io.reactivex.rxjava3.core.Flowable<androidx.window.layout.WindowLayoutInfo> windowLayoutInfoFlowable(androidx.window.layout.WindowInfoTracker, android.app.Activity activity);
+ method public static io.reactivex.rxjava3.core.Flowable<androidx.window.layout.WindowLayoutInfo> windowLayoutInfoFlowable(androidx.window.layout.WindowInfoTracker, @UiContext android.content.Context context);
+ method public static io.reactivex.rxjava3.core.Observable<androidx.window.layout.WindowLayoutInfo> windowLayoutInfoObservable(androidx.window.layout.WindowInfoTracker, android.app.Activity activity);
+ method public static io.reactivex.rxjava3.core.Observable<androidx.window.layout.WindowLayoutInfo> windowLayoutInfoObservable(androidx.window.layout.WindowInfoTracker, @UiContext android.content.Context context);
+ }
+
+}
+
diff --git a/window/window-testing/api/1.2.0-beta01.txt b/window/window-testing/api/1.2.0-beta01.txt
new file mode 100644
index 0000000..847a9e8
--- /dev/null
+++ b/window/window-testing/api/1.2.0-beta01.txt
@@ -0,0 +1,73 @@
+// Signature format: 4.0
+package androidx.window.testing.embedding {
+
+ public final class ActivityEmbeddingRule implements org.junit.rules.TestRule {
+ ctor public ActivityEmbeddingRule();
+ method public org.junit.runners.model.Statement apply(org.junit.runners.model.Statement base, org.junit.runner.Description description);
+ method public void overrideIsActivityEmbedded(android.app.Activity activity, boolean isActivityEmbedded);
+ method public void overrideSplitInfo(android.app.Activity activity, java.util.List<androidx.window.embedding.SplitInfo> splitInfoList);
+ method public void overrideSplitSupportStatus(androidx.window.embedding.SplitController.SplitSupportStatus status);
+ }
+
+ public final class TestActivityStack {
+ method public static androidx.window.embedding.ActivityStack createTestActivityStack();
+ method public static androidx.window.embedding.ActivityStack createTestActivityStack(optional java.util.List<? extends android.app.Activity> activitiesInProcess);
+ method public static androidx.window.embedding.ActivityStack createTestActivityStack(optional java.util.List<? extends android.app.Activity> activitiesInProcess, optional boolean isEmpty);
+ }
+
+ public final class TestSplitAttributesCalculatorParams {
+ method public static androidx.window.embedding.SplitAttributesCalculatorParams createTestSplitAttributesCalculatorParams(androidx.window.layout.WindowMetrics parentWindowMetrics);
+ method public static androidx.window.embedding.SplitAttributesCalculatorParams createTestSplitAttributesCalculatorParams(androidx.window.layout.WindowMetrics parentWindowMetrics, optional android.content.res.Configuration parentConfiguration);
+ method public static androidx.window.embedding.SplitAttributesCalculatorParams createTestSplitAttributesCalculatorParams(androidx.window.layout.WindowMetrics parentWindowMetrics, optional android.content.res.Configuration parentConfiguration, optional androidx.window.layout.WindowLayoutInfo parentWindowLayoutInfo);
+ method public static androidx.window.embedding.SplitAttributesCalculatorParams createTestSplitAttributesCalculatorParams(androidx.window.layout.WindowMetrics parentWindowMetrics, optional android.content.res.Configuration parentConfiguration, optional androidx.window.layout.WindowLayoutInfo parentWindowLayoutInfo, optional androidx.window.embedding.SplitAttributes defaultSplitAttributes);
+ method public static androidx.window.embedding.SplitAttributesCalculatorParams createTestSplitAttributesCalculatorParams(androidx.window.layout.WindowMetrics parentWindowMetrics, optional android.content.res.Configuration parentConfiguration, optional androidx.window.layout.WindowLayoutInfo parentWindowLayoutInfo, optional androidx.window.embedding.SplitAttributes defaultSplitAttributes, optional boolean areDefaultConstraintsSatisfied);
+ method public static androidx.window.embedding.SplitAttributesCalculatorParams createTestSplitAttributesCalculatorParams(androidx.window.layout.WindowMetrics parentWindowMetrics, optional android.content.res.Configuration parentConfiguration, optional androidx.window.layout.WindowLayoutInfo parentWindowLayoutInfo, optional androidx.window.embedding.SplitAttributes defaultSplitAttributes, optional boolean areDefaultConstraintsSatisfied, optional String? splitRuleTag);
+ }
+
+ public final class TestSplitInfo {
+ method public static androidx.window.embedding.SplitInfo createTestSplitInfo();
+ method public static androidx.window.embedding.SplitInfo createTestSplitInfo(optional androidx.window.embedding.ActivityStack primaryActivityStack);
+ method public static androidx.window.embedding.SplitInfo createTestSplitInfo(optional androidx.window.embedding.ActivityStack primaryActivityStack, optional androidx.window.embedding.ActivityStack secondActivityStack);
+ method public static androidx.window.embedding.SplitInfo createTestSplitInfo(optional androidx.window.embedding.ActivityStack primaryActivityStack, optional androidx.window.embedding.ActivityStack secondActivityStack, optional androidx.window.embedding.SplitAttributes splitAttributes);
+ }
+
+}
+
+package androidx.window.testing.layout {
+
+ public final class DisplayFeatureTesting {
+ method public static androidx.window.layout.FoldingFeature createFoldingFeature(android.app.Activity activity);
+ method public static androidx.window.layout.FoldingFeature createFoldingFeature(android.app.Activity activity, optional @IntRange(from=-1L) int center);
+ method public static androidx.window.layout.FoldingFeature createFoldingFeature(android.app.Activity activity, optional @IntRange(from=-1L) int center, optional int size);
+ method public static androidx.window.layout.FoldingFeature createFoldingFeature(android.app.Activity activity, optional @IntRange(from=-1L) int center, optional int size, optional androidx.window.layout.FoldingFeature.State state);
+ method public static androidx.window.layout.FoldingFeature createFoldingFeature(android.app.Activity activity, optional @IntRange(from=-1L) int center, optional int size, optional androidx.window.layout.FoldingFeature.State state, optional androidx.window.layout.FoldingFeature.Orientation orientation);
+ method public static androidx.window.layout.FoldingFeature createFoldingFeature(android.graphics.Rect windowBounds);
+ method public static androidx.window.layout.FoldingFeature createFoldingFeature(android.graphics.Rect windowBounds, optional @IntRange(from=-1L) int center);
+ method public static androidx.window.layout.FoldingFeature createFoldingFeature(android.graphics.Rect windowBounds, optional @IntRange(from=-1L) int center, optional int size);
+ method public static androidx.window.layout.FoldingFeature createFoldingFeature(android.graphics.Rect windowBounds, optional @IntRange(from=-1L) int center, optional int size, optional androidx.window.layout.FoldingFeature.State state);
+ method public static androidx.window.layout.FoldingFeature createFoldingFeature(android.graphics.Rect windowBounds, optional @IntRange(from=-1L) int center, optional int size, optional androidx.window.layout.FoldingFeature.State state, optional androidx.window.layout.FoldingFeature.Orientation orientation);
+ }
+
+ public final class FoldingFeatureTestingConstants {
+ field public static final int FOLDING_FEATURE_CENTER_DEFAULT = -1; // 0xffffffff
+ field public static final androidx.window.testing.layout.FoldingFeatureTestingConstants INSTANCE;
+ }
+
+ public final class WindowLayoutInfoPublisherRule implements org.junit.rules.TestRule {
+ ctor public WindowLayoutInfoPublisherRule();
+ method public org.junit.runners.model.Statement apply(org.junit.runners.model.Statement base, org.junit.runner.Description description);
+ method public void overrideWindowLayoutInfo(androidx.window.layout.WindowLayoutInfo info);
+ }
+
+ public final class WindowLayoutInfoTesting {
+ method public static androidx.window.layout.WindowLayoutInfo createWindowLayoutInfo();
+ method public static androidx.window.layout.WindowLayoutInfo createWindowLayoutInfo(optional java.util.List<? extends androidx.window.layout.DisplayFeature> displayFeatures);
+ }
+
+ public final class WindowMetricsCalculatorRule implements org.junit.rules.TestRule {
+ ctor public WindowMetricsCalculatorRule();
+ method public org.junit.runners.model.Statement apply(org.junit.runners.model.Statement base, org.junit.runner.Description description);
+ }
+
+}
+
diff --git a/window/window-testing/api/res-1.2.0-beta01.txt b/window/window-testing/api/res-1.2.0-beta01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/window/window-testing/api/res-1.2.0-beta01.txt
diff --git a/window/window-testing/api/restricted_1.2.0-beta01.txt b/window/window-testing/api/restricted_1.2.0-beta01.txt
new file mode 100644
index 0000000..847a9e8
--- /dev/null
+++ b/window/window-testing/api/restricted_1.2.0-beta01.txt
@@ -0,0 +1,73 @@
+// Signature format: 4.0
+package androidx.window.testing.embedding {
+
+ public final class ActivityEmbeddingRule implements org.junit.rules.TestRule {
+ ctor public ActivityEmbeddingRule();
+ method public org.junit.runners.model.Statement apply(org.junit.runners.model.Statement base, org.junit.runner.Description description);
+ method public void overrideIsActivityEmbedded(android.app.Activity activity, boolean isActivityEmbedded);
+ method public void overrideSplitInfo(android.app.Activity activity, java.util.List<androidx.window.embedding.SplitInfo> splitInfoList);
+ method public void overrideSplitSupportStatus(androidx.window.embedding.SplitController.SplitSupportStatus status);
+ }
+
+ public final class TestActivityStack {
+ method public static androidx.window.embedding.ActivityStack createTestActivityStack();
+ method public static androidx.window.embedding.ActivityStack createTestActivityStack(optional java.util.List<? extends android.app.Activity> activitiesInProcess);
+ method public static androidx.window.embedding.ActivityStack createTestActivityStack(optional java.util.List<? extends android.app.Activity> activitiesInProcess, optional boolean isEmpty);
+ }
+
+ public final class TestSplitAttributesCalculatorParams {
+ method public static androidx.window.embedding.SplitAttributesCalculatorParams createTestSplitAttributesCalculatorParams(androidx.window.layout.WindowMetrics parentWindowMetrics);
+ method public static androidx.window.embedding.SplitAttributesCalculatorParams createTestSplitAttributesCalculatorParams(androidx.window.layout.WindowMetrics parentWindowMetrics, optional android.content.res.Configuration parentConfiguration);
+ method public static androidx.window.embedding.SplitAttributesCalculatorParams createTestSplitAttributesCalculatorParams(androidx.window.layout.WindowMetrics parentWindowMetrics, optional android.content.res.Configuration parentConfiguration, optional androidx.window.layout.WindowLayoutInfo parentWindowLayoutInfo);
+ method public static androidx.window.embedding.SplitAttributesCalculatorParams createTestSplitAttributesCalculatorParams(androidx.window.layout.WindowMetrics parentWindowMetrics, optional android.content.res.Configuration parentConfiguration, optional androidx.window.layout.WindowLayoutInfo parentWindowLayoutInfo, optional androidx.window.embedding.SplitAttributes defaultSplitAttributes);
+ method public static androidx.window.embedding.SplitAttributesCalculatorParams createTestSplitAttributesCalculatorParams(androidx.window.layout.WindowMetrics parentWindowMetrics, optional android.content.res.Configuration parentConfiguration, optional androidx.window.layout.WindowLayoutInfo parentWindowLayoutInfo, optional androidx.window.embedding.SplitAttributes defaultSplitAttributes, optional boolean areDefaultConstraintsSatisfied);
+ method public static androidx.window.embedding.SplitAttributesCalculatorParams createTestSplitAttributesCalculatorParams(androidx.window.layout.WindowMetrics parentWindowMetrics, optional android.content.res.Configuration parentConfiguration, optional androidx.window.layout.WindowLayoutInfo parentWindowLayoutInfo, optional androidx.window.embedding.SplitAttributes defaultSplitAttributes, optional boolean areDefaultConstraintsSatisfied, optional String? splitRuleTag);
+ }
+
+ public final class TestSplitInfo {
+ method public static androidx.window.embedding.SplitInfo createTestSplitInfo();
+ method public static androidx.window.embedding.SplitInfo createTestSplitInfo(optional androidx.window.embedding.ActivityStack primaryActivityStack);
+ method public static androidx.window.embedding.SplitInfo createTestSplitInfo(optional androidx.window.embedding.ActivityStack primaryActivityStack, optional androidx.window.embedding.ActivityStack secondActivityStack);
+ method public static androidx.window.embedding.SplitInfo createTestSplitInfo(optional androidx.window.embedding.ActivityStack primaryActivityStack, optional androidx.window.embedding.ActivityStack secondActivityStack, optional androidx.window.embedding.SplitAttributes splitAttributes);
+ }
+
+}
+
+package androidx.window.testing.layout {
+
+ public final class DisplayFeatureTesting {
+ method public static androidx.window.layout.FoldingFeature createFoldingFeature(android.app.Activity activity);
+ method public static androidx.window.layout.FoldingFeature createFoldingFeature(android.app.Activity activity, optional @IntRange(from=-1L) int center);
+ method public static androidx.window.layout.FoldingFeature createFoldingFeature(android.app.Activity activity, optional @IntRange(from=-1L) int center, optional int size);
+ method public static androidx.window.layout.FoldingFeature createFoldingFeature(android.app.Activity activity, optional @IntRange(from=-1L) int center, optional int size, optional androidx.window.layout.FoldingFeature.State state);
+ method public static androidx.window.layout.FoldingFeature createFoldingFeature(android.app.Activity activity, optional @IntRange(from=-1L) int center, optional int size, optional androidx.window.layout.FoldingFeature.State state, optional androidx.window.layout.FoldingFeature.Orientation orientation);
+ method public static androidx.window.layout.FoldingFeature createFoldingFeature(android.graphics.Rect windowBounds);
+ method public static androidx.window.layout.FoldingFeature createFoldingFeature(android.graphics.Rect windowBounds, optional @IntRange(from=-1L) int center);
+ method public static androidx.window.layout.FoldingFeature createFoldingFeature(android.graphics.Rect windowBounds, optional @IntRange(from=-1L) int center, optional int size);
+ method public static androidx.window.layout.FoldingFeature createFoldingFeature(android.graphics.Rect windowBounds, optional @IntRange(from=-1L) int center, optional int size, optional androidx.window.layout.FoldingFeature.State state);
+ method public static androidx.window.layout.FoldingFeature createFoldingFeature(android.graphics.Rect windowBounds, optional @IntRange(from=-1L) int center, optional int size, optional androidx.window.layout.FoldingFeature.State state, optional androidx.window.layout.FoldingFeature.Orientation orientation);
+ }
+
+ public final class FoldingFeatureTestingConstants {
+ field public static final int FOLDING_FEATURE_CENTER_DEFAULT = -1; // 0xffffffff
+ field public static final androidx.window.testing.layout.FoldingFeatureTestingConstants INSTANCE;
+ }
+
+ public final class WindowLayoutInfoPublisherRule implements org.junit.rules.TestRule {
+ ctor public WindowLayoutInfoPublisherRule();
+ method public org.junit.runners.model.Statement apply(org.junit.runners.model.Statement base, org.junit.runner.Description description);
+ method public void overrideWindowLayoutInfo(androidx.window.layout.WindowLayoutInfo info);
+ }
+
+ public final class WindowLayoutInfoTesting {
+ method public static androidx.window.layout.WindowLayoutInfo createWindowLayoutInfo();
+ method public static androidx.window.layout.WindowLayoutInfo createWindowLayoutInfo(optional java.util.List<? extends androidx.window.layout.DisplayFeature> displayFeatures);
+ }
+
+ public final class WindowMetricsCalculatorRule implements org.junit.rules.TestRule {
+ ctor public WindowMetricsCalculatorRule();
+ method public org.junit.runners.model.Statement apply(org.junit.runners.model.Statement base, org.junit.runner.Description description);
+ }
+
+}
+
diff --git a/window/window/api/1.2.0-beta01.txt b/window/window/api/1.2.0-beta01.txt
new file mode 100644
index 0000000..7c79bde
--- /dev/null
+++ b/window/window/api/1.2.0-beta01.txt
@@ -0,0 +1,465 @@
+// Signature format: 4.0
+package androidx.window {
+
+ public final class WindowProperties {
+ field public static final androidx.window.WindowProperties INSTANCE;
+ field public static final String PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE = "android.window.PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE";
+ field public static final String PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED = "android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED";
+ field public static final String PROPERTY_COMPAT_ALLOW_IGNORING_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED = "android.window.PROPERTY_COMPAT_ALLOW_IGNORING_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED";
+ field public static final String PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE = "android.window.PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE";
+ field public static final String PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES = "android.window.PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES";
+ }
+
+}
+
+package androidx.window.area {
+
+ @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public final class WindowAreaCapability {
+ method public androidx.window.area.WindowAreaCapability.Operation getOperation();
+ method public androidx.window.area.WindowAreaCapability.Status getStatus();
+ property public final androidx.window.area.WindowAreaCapability.Operation operation;
+ property public final androidx.window.area.WindowAreaCapability.Status status;
+ }
+
+ @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public static final class WindowAreaCapability.Operation {
+ field public static final androidx.window.area.WindowAreaCapability.Operation.Companion Companion;
+ field public static final androidx.window.area.WindowAreaCapability.Operation OPERATION_PRESENT_ON_AREA;
+ field public static final androidx.window.area.WindowAreaCapability.Operation OPERATION_TRANSFER_ACTIVITY_TO_AREA;
+ }
+
+ public static final class WindowAreaCapability.Operation.Companion {
+ }
+
+ @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public static final class WindowAreaCapability.Status {
+ field public static final androidx.window.area.WindowAreaCapability.Status.Companion Companion;
+ field public static final androidx.window.area.WindowAreaCapability.Status WINDOW_AREA_STATUS_ACTIVE;
+ field public static final androidx.window.area.WindowAreaCapability.Status WINDOW_AREA_STATUS_AVAILABLE;
+ field public static final androidx.window.area.WindowAreaCapability.Status WINDOW_AREA_STATUS_UNAVAILABLE;
+ field public static final androidx.window.area.WindowAreaCapability.Status WINDOW_AREA_STATUS_UNSUPPORTED;
+ }
+
+ public static final class WindowAreaCapability.Status.Companion {
+ }
+
+ @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public interface WindowAreaController {
+ method public static androidx.window.area.WindowAreaController getOrCreate();
+ method public kotlinx.coroutines.flow.Flow<java.util.List<androidx.window.area.WindowAreaInfo>> getWindowAreaInfos();
+ method public void presentContentOnWindowArea(android.os.Binder token, android.app.Activity activity, java.util.concurrent.Executor executor, androidx.window.area.WindowAreaPresentationSessionCallback windowAreaPresentationSessionCallback);
+ method public void transferActivityToWindowArea(android.os.Binder token, android.app.Activity activity, java.util.concurrent.Executor executor, androidx.window.area.WindowAreaSessionCallback windowAreaSessionCallback);
+ property public abstract kotlinx.coroutines.flow.Flow<java.util.List<androidx.window.area.WindowAreaInfo>> windowAreaInfos;
+ field public static final androidx.window.area.WindowAreaController.Companion Companion;
+ }
+
+ public static final class WindowAreaController.Companion {
+ method public androidx.window.area.WindowAreaController getOrCreate();
+ }
+
+ @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public final class WindowAreaInfo {
+ method public androidx.window.area.WindowAreaSession? getActiveSession(androidx.window.area.WindowAreaCapability.Operation operation);
+ method public androidx.window.area.WindowAreaCapability? getCapability(androidx.window.area.WindowAreaCapability.Operation operation);
+ method public androidx.window.layout.WindowMetrics getMetrics();
+ method public android.os.Binder getToken();
+ method public androidx.window.area.WindowAreaInfo.Type getType();
+ method public void setMetrics(androidx.window.layout.WindowMetrics);
+ property public final androidx.window.layout.WindowMetrics metrics;
+ property public final android.os.Binder token;
+ property public final androidx.window.area.WindowAreaInfo.Type type;
+ }
+
+ @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public static final class WindowAreaInfo.Type {
+ field public static final androidx.window.area.WindowAreaInfo.Type.Companion Companion;
+ field public static final androidx.window.area.WindowAreaInfo.Type TYPE_REAR_FACING;
+ }
+
+ public static final class WindowAreaInfo.Type.Companion {
+ }
+
+ @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public interface WindowAreaPresentationSessionCallback {
+ method public void onContainerVisibilityChanged(boolean isVisible);
+ method public void onSessionEnded(Throwable? t);
+ method public void onSessionStarted(androidx.window.area.WindowAreaSessionPresenter session);
+ }
+
+ @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public interface WindowAreaSession {
+ method public void close();
+ }
+
+ @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public interface WindowAreaSessionCallback {
+ method public void onSessionEnded(Throwable? t);
+ method public void onSessionStarted(androidx.window.area.WindowAreaSession session);
+ }
+
+ @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public interface WindowAreaSessionPresenter extends androidx.window.area.WindowAreaSession {
+ method public android.content.Context getContext();
+ method public void setContentView(android.view.View view);
+ property public abstract android.content.Context context;
+ }
+
+}
+
+package androidx.window.core {
+
+ @SuppressCompatibility @kotlin.RequiresOptIn(level=kotlin.RequiresOptIn.Level.WARNING) @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) public @interface ExperimentalWindowApi {
+ }
+
+}
+
+package androidx.window.embedding {
+
+ public final class ActivityEmbeddingController {
+ method @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public void finishActivityStacks(java.util.Set<androidx.window.embedding.ActivityStack> activityStacks);
+ method @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public androidx.window.embedding.ActivityStack? getActivityStack(android.app.Activity activity);
+ method public static androidx.window.embedding.ActivityEmbeddingController getInstance(android.content.Context context);
+ method public boolean isActivityEmbedded(android.app.Activity activity);
+ method @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public boolean isFinishingActivityStacksSupported();
+ field public static final androidx.window.embedding.ActivityEmbeddingController.Companion Companion;
+ }
+
+ public static final class ActivityEmbeddingController.Companion {
+ method public androidx.window.embedding.ActivityEmbeddingController getInstance(android.content.Context context);
+ }
+
+ public final class ActivityEmbeddingOptions {
+ method @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public static boolean isSetLaunchingActivityStackSupported(android.app.ActivityOptions);
+ method @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public static android.app.ActivityOptions setLaunchingActivityStack(android.app.ActivityOptions, android.app.Activity activity);
+ method @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public static android.app.ActivityOptions setLaunchingActivityStack(android.app.ActivityOptions, android.content.Context context, androidx.window.embedding.ActivityStack activityStack);
+ }
+
+ public final class ActivityFilter {
+ ctor public ActivityFilter(android.content.ComponentName componentName, String? intentAction);
+ method public android.content.ComponentName getComponentName();
+ method public String? getIntentAction();
+ method public boolean matchesActivity(android.app.Activity activity);
+ method public boolean matchesIntent(android.content.Intent intent);
+ property public final android.content.ComponentName componentName;
+ property public final String? intentAction;
+ }
+
+ public final class ActivityRule extends androidx.window.embedding.EmbeddingRule {
+ method public boolean getAlwaysExpand();
+ method public java.util.Set<androidx.window.embedding.ActivityFilter> getFilters();
+ property public final boolean alwaysExpand;
+ property public final java.util.Set<androidx.window.embedding.ActivityFilter> filters;
+ }
+
+ public static final class ActivityRule.Builder {
+ ctor public ActivityRule.Builder(java.util.Set<androidx.window.embedding.ActivityFilter> filters);
+ method public androidx.window.embedding.ActivityRule build();
+ method public androidx.window.embedding.ActivityRule.Builder setAlwaysExpand(boolean alwaysExpand);
+ method public androidx.window.embedding.ActivityRule.Builder setTag(String? tag);
+ }
+
+ public final class ActivityStack {
+ method public operator boolean contains(android.app.Activity activity);
+ method public boolean isEmpty();
+ property public final boolean isEmpty;
+ }
+
+ public final class EmbeddingAspectRatio {
+ method public static androidx.window.embedding.EmbeddingAspectRatio ratio(@FloatRange(from=1.0, fromInclusive=false) float ratio);
+ field public static final androidx.window.embedding.EmbeddingAspectRatio ALWAYS_ALLOW;
+ field public static final androidx.window.embedding.EmbeddingAspectRatio ALWAYS_DISALLOW;
+ field public static final androidx.window.embedding.EmbeddingAspectRatio.Companion Companion;
+ }
+
+ public static final class EmbeddingAspectRatio.Companion {
+ method public androidx.window.embedding.EmbeddingAspectRatio ratio(@FloatRange(from=1.0, fromInclusive=false) float ratio);
+ }
+
+ public abstract class EmbeddingRule {
+ method public final String? getTag();
+ property public final String? tag;
+ }
+
+ public final class RuleController {
+ method public void addRule(androidx.window.embedding.EmbeddingRule rule);
+ method public void clearRules();
+ method public static androidx.window.embedding.RuleController getInstance(android.content.Context context);
+ method public java.util.Set<androidx.window.embedding.EmbeddingRule> getRules();
+ method public static java.util.Set<androidx.window.embedding.EmbeddingRule> parseRules(android.content.Context context, @XmlRes int staticRuleResourceId);
+ method public void removeRule(androidx.window.embedding.EmbeddingRule rule);
+ method public void setRules(java.util.Set<? extends androidx.window.embedding.EmbeddingRule> rules);
+ field public static final androidx.window.embedding.RuleController.Companion Companion;
+ }
+
+ public static final class RuleController.Companion {
+ method public androidx.window.embedding.RuleController getInstance(android.content.Context context);
+ method public java.util.Set<androidx.window.embedding.EmbeddingRule> parseRules(android.content.Context context, @XmlRes int staticRuleResourceId);
+ }
+
+ public final class SplitAttributes {
+ method public androidx.window.embedding.SplitAttributes.LayoutDirection getLayoutDirection();
+ method public androidx.window.embedding.SplitAttributes.SplitType getSplitType();
+ property public final androidx.window.embedding.SplitAttributes.LayoutDirection layoutDirection;
+ property public final androidx.window.embedding.SplitAttributes.SplitType splitType;
+ field public static final androidx.window.embedding.SplitAttributes.Companion Companion;
+ }
+
+ public static final class SplitAttributes.Builder {
+ ctor public SplitAttributes.Builder();
+ method public androidx.window.embedding.SplitAttributes build();
+ method public androidx.window.embedding.SplitAttributes.Builder setLayoutDirection(androidx.window.embedding.SplitAttributes.LayoutDirection layoutDirection);
+ method public androidx.window.embedding.SplitAttributes.Builder setSplitType(androidx.window.embedding.SplitAttributes.SplitType type);
+ }
+
+ public static final class SplitAttributes.Companion {
+ }
+
+ public static final class SplitAttributes.LayoutDirection {
+ field public static final androidx.window.embedding.SplitAttributes.LayoutDirection BOTTOM_TO_TOP;
+ field public static final androidx.window.embedding.SplitAttributes.LayoutDirection.Companion Companion;
+ field public static final androidx.window.embedding.SplitAttributes.LayoutDirection LEFT_TO_RIGHT;
+ field public static final androidx.window.embedding.SplitAttributes.LayoutDirection LOCALE;
+ field public static final androidx.window.embedding.SplitAttributes.LayoutDirection RIGHT_TO_LEFT;
+ field public static final androidx.window.embedding.SplitAttributes.LayoutDirection TOP_TO_BOTTOM;
+ }
+
+ public static final class SplitAttributes.LayoutDirection.Companion {
+ }
+
+ public static final class SplitAttributes.SplitType {
+ method public static androidx.window.embedding.SplitAttributes.SplitType ratio(@FloatRange(from=0.0, to=1.0, fromInclusive=false, toInclusive=false) float ratio);
+ field public static final androidx.window.embedding.SplitAttributes.SplitType.Companion Companion;
+ field public static final androidx.window.embedding.SplitAttributes.SplitType SPLIT_TYPE_EQUAL;
+ field public static final androidx.window.embedding.SplitAttributes.SplitType SPLIT_TYPE_EXPAND;
+ field public static final androidx.window.embedding.SplitAttributes.SplitType SPLIT_TYPE_HINGE;
+ }
+
+ public static final class SplitAttributes.SplitType.Companion {
+ method public androidx.window.embedding.SplitAttributes.SplitType ratio(@FloatRange(from=0.0, to=1.0, fromInclusive=false, toInclusive=false) float ratio);
+ }
+
+ public final class SplitAttributesCalculatorParams {
+ method public boolean getAreDefaultConstraintsSatisfied();
+ method public androidx.window.embedding.SplitAttributes getDefaultSplitAttributes();
+ method public android.content.res.Configuration getParentConfiguration();
+ method public androidx.window.layout.WindowLayoutInfo getParentWindowLayoutInfo();
+ method public androidx.window.layout.WindowMetrics getParentWindowMetrics();
+ method public String? getSplitRuleTag();
+ property public final boolean areDefaultConstraintsSatisfied;
+ property public final androidx.window.embedding.SplitAttributes defaultSplitAttributes;
+ property public final android.content.res.Configuration parentConfiguration;
+ property public final androidx.window.layout.WindowLayoutInfo parentWindowLayoutInfo;
+ property public final androidx.window.layout.WindowMetrics parentWindowMetrics;
+ property public final String? splitRuleTag;
+ }
+
+ public final class SplitController {
+ method public void clearSplitAttributesCalculator();
+ method public static androidx.window.embedding.SplitController getInstance(android.content.Context context);
+ method public androidx.window.embedding.SplitController.SplitSupportStatus getSplitSupportStatus();
+ method @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public void invalidateTopVisibleSplitAttributes();
+ method @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public boolean isInvalidatingTopVisibleSplitAttributesSupported();
+ method public boolean isSplitAttributesCalculatorSupported();
+ method @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public boolean isUpdatingSplitAttributesSupported();
+ method public void setSplitAttributesCalculator(kotlin.jvm.functions.Function1<? super androidx.window.embedding.SplitAttributesCalculatorParams,androidx.window.embedding.SplitAttributes> calculator);
+ method public kotlinx.coroutines.flow.Flow<java.util.List<androidx.window.embedding.SplitInfo>> splitInfoList(android.app.Activity activity);
+ method @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public void updateSplitAttributes(androidx.window.embedding.SplitInfo splitInfo, androidx.window.embedding.SplitAttributes splitAttributes);
+ property public final androidx.window.embedding.SplitController.SplitSupportStatus splitSupportStatus;
+ field public static final androidx.window.embedding.SplitController.Companion Companion;
+ }
+
+ public static final class SplitController.Companion {
+ method public androidx.window.embedding.SplitController getInstance(android.content.Context context);
+ }
+
+ public static final class SplitController.SplitSupportStatus {
+ field public static final androidx.window.embedding.SplitController.SplitSupportStatus.Companion Companion;
+ field public static final androidx.window.embedding.SplitController.SplitSupportStatus SPLIT_AVAILABLE;
+ field public static final androidx.window.embedding.SplitController.SplitSupportStatus SPLIT_ERROR_PROPERTY_NOT_DECLARED;
+ field public static final androidx.window.embedding.SplitController.SplitSupportStatus SPLIT_UNAVAILABLE;
+ }
+
+ public static final class SplitController.SplitSupportStatus.Companion {
+ }
+
+ public final class SplitInfo {
+ method public operator boolean contains(android.app.Activity activity);
+ method public androidx.window.embedding.ActivityStack getPrimaryActivityStack();
+ method public androidx.window.embedding.ActivityStack getSecondaryActivityStack();
+ method public androidx.window.embedding.SplitAttributes getSplitAttributes();
+ property public final androidx.window.embedding.ActivityStack primaryActivityStack;
+ property public final androidx.window.embedding.ActivityStack secondaryActivityStack;
+ property public final androidx.window.embedding.SplitAttributes splitAttributes;
+ }
+
+ public final class SplitPairFilter {
+ ctor public SplitPairFilter(android.content.ComponentName primaryActivityName, android.content.ComponentName secondaryActivityName, String? secondaryActivityIntentAction);
+ method public android.content.ComponentName getPrimaryActivityName();
+ method public String? getSecondaryActivityIntentAction();
+ method public android.content.ComponentName getSecondaryActivityName();
+ method public boolean matchesActivityIntentPair(android.app.Activity primaryActivity, android.content.Intent secondaryActivityIntent);
+ method public boolean matchesActivityPair(android.app.Activity primaryActivity, android.app.Activity secondaryActivity);
+ property public final android.content.ComponentName primaryActivityName;
+ property public final String? secondaryActivityIntentAction;
+ property public final android.content.ComponentName secondaryActivityName;
+ }
+
+ public final class SplitPairRule extends androidx.window.embedding.SplitRule {
+ method public boolean getClearTop();
+ method public java.util.Set<androidx.window.embedding.SplitPairFilter> getFilters();
+ method public androidx.window.embedding.SplitRule.FinishBehavior getFinishPrimaryWithSecondary();
+ method public androidx.window.embedding.SplitRule.FinishBehavior getFinishSecondaryWithPrimary();
+ property public final boolean clearTop;
+ property public final java.util.Set<androidx.window.embedding.SplitPairFilter> filters;
+ property public final androidx.window.embedding.SplitRule.FinishBehavior finishPrimaryWithSecondary;
+ property public final androidx.window.embedding.SplitRule.FinishBehavior finishSecondaryWithPrimary;
+ }
+
+ public static final class SplitPairRule.Builder {
+ ctor public SplitPairRule.Builder(java.util.Set<androidx.window.embedding.SplitPairFilter> filters);
+ method public androidx.window.embedding.SplitPairRule build();
+ method public androidx.window.embedding.SplitPairRule.Builder setClearTop(boolean clearTop);
+ method public androidx.window.embedding.SplitPairRule.Builder setDefaultSplitAttributes(androidx.window.embedding.SplitAttributes defaultSplitAttributes);
+ method public androidx.window.embedding.SplitPairRule.Builder setFinishPrimaryWithSecondary(androidx.window.embedding.SplitRule.FinishBehavior finishPrimaryWithSecondary);
+ method public androidx.window.embedding.SplitPairRule.Builder setFinishSecondaryWithPrimary(androidx.window.embedding.SplitRule.FinishBehavior finishSecondaryWithPrimary);
+ method public androidx.window.embedding.SplitPairRule.Builder setMaxAspectRatioInLandscape(androidx.window.embedding.EmbeddingAspectRatio aspectRatio);
+ method public androidx.window.embedding.SplitPairRule.Builder setMaxAspectRatioInPortrait(androidx.window.embedding.EmbeddingAspectRatio aspectRatio);
+ method public androidx.window.embedding.SplitPairRule.Builder setMinHeightDp(@IntRange(from=0L) int minHeightDp);
+ method public androidx.window.embedding.SplitPairRule.Builder setMinSmallestWidthDp(@IntRange(from=0L) int minSmallestWidthDp);
+ method public androidx.window.embedding.SplitPairRule.Builder setMinWidthDp(@IntRange(from=0L) int minWidthDp);
+ method public androidx.window.embedding.SplitPairRule.Builder setTag(String? tag);
+ }
+
+ public final class SplitPlaceholderRule extends androidx.window.embedding.SplitRule {
+ method public java.util.Set<androidx.window.embedding.ActivityFilter> getFilters();
+ method public androidx.window.embedding.SplitRule.FinishBehavior getFinishPrimaryWithPlaceholder();
+ method public android.content.Intent getPlaceholderIntent();
+ method public boolean isSticky();
+ property public final java.util.Set<androidx.window.embedding.ActivityFilter> filters;
+ property public final androidx.window.embedding.SplitRule.FinishBehavior finishPrimaryWithPlaceholder;
+ property public final boolean isSticky;
+ property public final android.content.Intent placeholderIntent;
+ }
+
+ public static final class SplitPlaceholderRule.Builder {
+ ctor public SplitPlaceholderRule.Builder(java.util.Set<androidx.window.embedding.ActivityFilter> filters, android.content.Intent placeholderIntent);
+ method public androidx.window.embedding.SplitPlaceholderRule build();
+ method public androidx.window.embedding.SplitPlaceholderRule.Builder setDefaultSplitAttributes(androidx.window.embedding.SplitAttributes defaultSplitAttributes);
+ method public androidx.window.embedding.SplitPlaceholderRule.Builder setFinishPrimaryWithPlaceholder(androidx.window.embedding.SplitRule.FinishBehavior finishPrimaryWithPlaceholder);
+ method public androidx.window.embedding.SplitPlaceholderRule.Builder setMaxAspectRatioInLandscape(androidx.window.embedding.EmbeddingAspectRatio aspectRatio);
+ method public androidx.window.embedding.SplitPlaceholderRule.Builder setMaxAspectRatioInPortrait(androidx.window.embedding.EmbeddingAspectRatio aspectRatio);
+ method public androidx.window.embedding.SplitPlaceholderRule.Builder setMinHeightDp(@IntRange(from=0L) int minHeightDp);
+ method public androidx.window.embedding.SplitPlaceholderRule.Builder setMinSmallestWidthDp(@IntRange(from=0L) int minSmallestWidthDp);
+ method public androidx.window.embedding.SplitPlaceholderRule.Builder setMinWidthDp(@IntRange(from=0L) int minWidthDp);
+ method public androidx.window.embedding.SplitPlaceholderRule.Builder setSticky(boolean isSticky);
+ method public androidx.window.embedding.SplitPlaceholderRule.Builder setTag(String? tag);
+ }
+
+ public class SplitRule extends androidx.window.embedding.EmbeddingRule {
+ method public final androidx.window.embedding.SplitAttributes getDefaultSplitAttributes();
+ method public final androidx.window.embedding.EmbeddingAspectRatio getMaxAspectRatioInLandscape();
+ method public final androidx.window.embedding.EmbeddingAspectRatio getMaxAspectRatioInPortrait();
+ method public final int getMinHeightDp();
+ method public final int getMinSmallestWidthDp();
+ method public final int getMinWidthDp();
+ property public final androidx.window.embedding.SplitAttributes defaultSplitAttributes;
+ property public final androidx.window.embedding.EmbeddingAspectRatio maxAspectRatioInLandscape;
+ property public final androidx.window.embedding.EmbeddingAspectRatio maxAspectRatioInPortrait;
+ property public final int minHeightDp;
+ property public final int minSmallestWidthDp;
+ property public final int minWidthDp;
+ field public static final androidx.window.embedding.SplitRule.Companion Companion;
+ field public static final androidx.window.embedding.EmbeddingAspectRatio SPLIT_MAX_ASPECT_RATIO_LANDSCAPE_DEFAULT;
+ field public static final androidx.window.embedding.EmbeddingAspectRatio SPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULT;
+ field public static final int SPLIT_MIN_DIMENSION_ALWAYS_ALLOW = 0; // 0x0
+ field public static final int SPLIT_MIN_DIMENSION_DP_DEFAULT = 600; // 0x258
+ }
+
+ public static final class SplitRule.Companion {
+ }
+
+ public static final class SplitRule.FinishBehavior {
+ field public static final androidx.window.embedding.SplitRule.FinishBehavior ADJACENT;
+ field public static final androidx.window.embedding.SplitRule.FinishBehavior ALWAYS;
+ field public static final androidx.window.embedding.SplitRule.FinishBehavior.Companion Companion;
+ field public static final androidx.window.embedding.SplitRule.FinishBehavior NEVER;
+ }
+
+ public static final class SplitRule.FinishBehavior.Companion {
+ }
+
+}
+
+package androidx.window.layout {
+
+ public interface DisplayFeature {
+ method public android.graphics.Rect getBounds();
+ property public abstract android.graphics.Rect bounds;
+ }
+
+ public interface FoldingFeature extends androidx.window.layout.DisplayFeature {
+ method public androidx.window.layout.FoldingFeature.OcclusionType getOcclusionType();
+ method public androidx.window.layout.FoldingFeature.Orientation getOrientation();
+ method public androidx.window.layout.FoldingFeature.State getState();
+ method public boolean isSeparating();
+ property public abstract boolean isSeparating;
+ property public abstract androidx.window.layout.FoldingFeature.OcclusionType occlusionType;
+ property public abstract androidx.window.layout.FoldingFeature.Orientation orientation;
+ property public abstract androidx.window.layout.FoldingFeature.State state;
+ }
+
+ public static final class FoldingFeature.OcclusionType {
+ field public static final androidx.window.layout.FoldingFeature.OcclusionType.Companion Companion;
+ field public static final androidx.window.layout.FoldingFeature.OcclusionType FULL;
+ field public static final androidx.window.layout.FoldingFeature.OcclusionType NONE;
+ }
+
+ public static final class FoldingFeature.OcclusionType.Companion {
+ }
+
+ public static final class FoldingFeature.Orientation {
+ field public static final androidx.window.layout.FoldingFeature.Orientation.Companion Companion;
+ field public static final androidx.window.layout.FoldingFeature.Orientation HORIZONTAL;
+ field public static final androidx.window.layout.FoldingFeature.Orientation VERTICAL;
+ }
+
+ public static final class FoldingFeature.Orientation.Companion {
+ }
+
+ public static final class FoldingFeature.State {
+ field public static final androidx.window.layout.FoldingFeature.State.Companion Companion;
+ field public static final androidx.window.layout.FoldingFeature.State FLAT;
+ field public static final androidx.window.layout.FoldingFeature.State HALF_OPENED;
+ }
+
+ public static final class FoldingFeature.State.Companion {
+ }
+
+ public interface WindowInfoTracker {
+ method public static androidx.window.layout.WindowInfoTracker getOrCreate(android.content.Context context);
+ method public kotlinx.coroutines.flow.Flow<androidx.window.layout.WindowLayoutInfo> windowLayoutInfo(android.app.Activity activity);
+ method public default kotlinx.coroutines.flow.Flow<androidx.window.layout.WindowLayoutInfo> windowLayoutInfo(@UiContext android.content.Context context);
+ field public static final androidx.window.layout.WindowInfoTracker.Companion Companion;
+ }
+
+ public static final class WindowInfoTracker.Companion {
+ method public androidx.window.layout.WindowInfoTracker getOrCreate(android.content.Context context);
+ }
+
+ public final class WindowLayoutInfo {
+ method public java.util.List<androidx.window.layout.DisplayFeature> getDisplayFeatures();
+ property public final java.util.List<androidx.window.layout.DisplayFeature> displayFeatures;
+ }
+
+ public final class WindowMetrics {
+ method public android.graphics.Rect getBounds();
+ method @SuppressCompatibility @RequiresApi(android.os.Build.VERSION_CODES.R) @androidx.window.core.ExperimentalWindowApi public androidx.core.view.WindowInsetsCompat getWindowInsets();
+ property public final android.graphics.Rect bounds;
+ }
+
+ public interface WindowMetricsCalculator {
+ method public androidx.window.layout.WindowMetrics computeCurrentWindowMetrics(android.app.Activity activity);
+ method public default androidx.window.layout.WindowMetrics computeCurrentWindowMetrics(@UiContext android.content.Context context);
+ method public androidx.window.layout.WindowMetrics computeMaximumWindowMetrics(android.app.Activity activity);
+ method public default androidx.window.layout.WindowMetrics computeMaximumWindowMetrics(@UiContext android.content.Context context);
+ method public static androidx.window.layout.WindowMetricsCalculator getOrCreate();
+ field public static final androidx.window.layout.WindowMetricsCalculator.Companion Companion;
+ }
+
+ public static final class WindowMetricsCalculator.Companion {
+ method public androidx.window.layout.WindowMetricsCalculator getOrCreate();
+ }
+
+}
+
diff --git a/window/window/api/current.txt b/window/window/api/current.txt
index 2c51c8d..7c79bde 100644
--- a/window/window/api/current.txt
+++ b/window/window/api/current.txt
@@ -14,14 +14,14 @@
package androidx.window.area {
- public final class WindowAreaCapability {
+ @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public final class WindowAreaCapability {
method public androidx.window.area.WindowAreaCapability.Operation getOperation();
method public androidx.window.area.WindowAreaCapability.Status getStatus();
property public final androidx.window.area.WindowAreaCapability.Operation operation;
property public final androidx.window.area.WindowAreaCapability.Status status;
}
- public static final class WindowAreaCapability.Operation {
+ @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public static final class WindowAreaCapability.Operation {
field public static final androidx.window.area.WindowAreaCapability.Operation.Companion Companion;
field public static final androidx.window.area.WindowAreaCapability.Operation OPERATION_PRESENT_ON_AREA;
field public static final androidx.window.area.WindowAreaCapability.Operation OPERATION_TRANSFER_ACTIVITY_TO_AREA;
@@ -30,7 +30,7 @@
public static final class WindowAreaCapability.Operation.Companion {
}
- public static final class WindowAreaCapability.Status {
+ @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public static final class WindowAreaCapability.Status {
field public static final androidx.window.area.WindowAreaCapability.Status.Companion Companion;
field public static final androidx.window.area.WindowAreaCapability.Status WINDOW_AREA_STATUS_ACTIVE;
field public static final androidx.window.area.WindowAreaCapability.Status WINDOW_AREA_STATUS_AVAILABLE;
@@ -41,7 +41,7 @@
public static final class WindowAreaCapability.Status.Companion {
}
- public interface WindowAreaController {
+ @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public interface WindowAreaController {
method public static androidx.window.area.WindowAreaController getOrCreate();
method public kotlinx.coroutines.flow.Flow<java.util.List<androidx.window.area.WindowAreaInfo>> getWindowAreaInfos();
method public void presentContentOnWindowArea(android.os.Binder token, android.app.Activity activity, java.util.concurrent.Executor executor, androidx.window.area.WindowAreaPresentationSessionCallback windowAreaPresentationSessionCallback);
@@ -54,7 +54,7 @@
method public androidx.window.area.WindowAreaController getOrCreate();
}
- public final class WindowAreaInfo {
+ @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public final class WindowAreaInfo {
method public androidx.window.area.WindowAreaSession? getActiveSession(androidx.window.area.WindowAreaCapability.Operation operation);
method public androidx.window.area.WindowAreaCapability? getCapability(androidx.window.area.WindowAreaCapability.Operation operation);
method public androidx.window.layout.WindowMetrics getMetrics();
@@ -66,7 +66,7 @@
property public final androidx.window.area.WindowAreaInfo.Type type;
}
- public static final class WindowAreaInfo.Type {
+ @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public static final class WindowAreaInfo.Type {
field public static final androidx.window.area.WindowAreaInfo.Type.Companion Companion;
field public static final androidx.window.area.WindowAreaInfo.Type TYPE_REAR_FACING;
}
@@ -74,22 +74,22 @@
public static final class WindowAreaInfo.Type.Companion {
}
- public interface WindowAreaPresentationSessionCallback {
+ @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public interface WindowAreaPresentationSessionCallback {
method public void onContainerVisibilityChanged(boolean isVisible);
method public void onSessionEnded(Throwable? t);
method public void onSessionStarted(androidx.window.area.WindowAreaSessionPresenter session);
}
- public interface WindowAreaSession {
+ @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public interface WindowAreaSession {
method public void close();
}
- public interface WindowAreaSessionCallback {
+ @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public interface WindowAreaSessionCallback {
method public void onSessionEnded(Throwable? t);
method public void onSessionStarted(androidx.window.area.WindowAreaSession session);
}
- public interface WindowAreaSessionPresenter extends androidx.window.area.WindowAreaSession {
+ @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public interface WindowAreaSessionPresenter extends androidx.window.area.WindowAreaSession {
method public android.content.Context getContext();
method public void setContentView(android.view.View view);
property public abstract android.content.Context context;
diff --git a/window/window/api/res-1.2.0-beta01.txt b/window/window/api/res-1.2.0-beta01.txt
new file mode 100644
index 0000000..185352b
--- /dev/null
+++ b/window/window/api/res-1.2.0-beta01.txt
@@ -0,0 +1,21 @@
+attr activityAction
+attr activityName
+attr alwaysExpand
+attr animationBackgroundColor
+attr clearTop
+attr finishPrimaryWithPlaceholder
+attr finishPrimaryWithSecondary
+attr finishSecondaryWithPrimary
+attr placeholderActivityName
+attr primaryActivityName
+attr secondaryActivityAction
+attr secondaryActivityName
+attr splitLayoutDirection
+attr splitMaxAspectRatioInLandscape
+attr splitMaxAspectRatioInPortrait
+attr splitMinHeightDp
+attr splitMinSmallestWidthDp
+attr splitMinWidthDp
+attr splitRatio
+attr stickyPlaceholder
+attr tag
diff --git a/window/window/api/restricted_1.2.0-beta01.txt b/window/window/api/restricted_1.2.0-beta01.txt
new file mode 100644
index 0000000..7c79bde
--- /dev/null
+++ b/window/window/api/restricted_1.2.0-beta01.txt
@@ -0,0 +1,465 @@
+// Signature format: 4.0
+package androidx.window {
+
+ public final class WindowProperties {
+ field public static final androidx.window.WindowProperties INSTANCE;
+ field public static final String PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE = "android.window.PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE";
+ field public static final String PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED = "android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED";
+ field public static final String PROPERTY_COMPAT_ALLOW_IGNORING_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED = "android.window.PROPERTY_COMPAT_ALLOW_IGNORING_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED";
+ field public static final String PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE = "android.window.PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE";
+ field public static final String PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES = "android.window.PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES";
+ }
+
+}
+
+package androidx.window.area {
+
+ @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public final class WindowAreaCapability {
+ method public androidx.window.area.WindowAreaCapability.Operation getOperation();
+ method public androidx.window.area.WindowAreaCapability.Status getStatus();
+ property public final androidx.window.area.WindowAreaCapability.Operation operation;
+ property public final androidx.window.area.WindowAreaCapability.Status status;
+ }
+
+ @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public static final class WindowAreaCapability.Operation {
+ field public static final androidx.window.area.WindowAreaCapability.Operation.Companion Companion;
+ field public static final androidx.window.area.WindowAreaCapability.Operation OPERATION_PRESENT_ON_AREA;
+ field public static final androidx.window.area.WindowAreaCapability.Operation OPERATION_TRANSFER_ACTIVITY_TO_AREA;
+ }
+
+ public static final class WindowAreaCapability.Operation.Companion {
+ }
+
+ @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public static final class WindowAreaCapability.Status {
+ field public static final androidx.window.area.WindowAreaCapability.Status.Companion Companion;
+ field public static final androidx.window.area.WindowAreaCapability.Status WINDOW_AREA_STATUS_ACTIVE;
+ field public static final androidx.window.area.WindowAreaCapability.Status WINDOW_AREA_STATUS_AVAILABLE;
+ field public static final androidx.window.area.WindowAreaCapability.Status WINDOW_AREA_STATUS_UNAVAILABLE;
+ field public static final androidx.window.area.WindowAreaCapability.Status WINDOW_AREA_STATUS_UNSUPPORTED;
+ }
+
+ public static final class WindowAreaCapability.Status.Companion {
+ }
+
+ @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public interface WindowAreaController {
+ method public static androidx.window.area.WindowAreaController getOrCreate();
+ method public kotlinx.coroutines.flow.Flow<java.util.List<androidx.window.area.WindowAreaInfo>> getWindowAreaInfos();
+ method public void presentContentOnWindowArea(android.os.Binder token, android.app.Activity activity, java.util.concurrent.Executor executor, androidx.window.area.WindowAreaPresentationSessionCallback windowAreaPresentationSessionCallback);
+ method public void transferActivityToWindowArea(android.os.Binder token, android.app.Activity activity, java.util.concurrent.Executor executor, androidx.window.area.WindowAreaSessionCallback windowAreaSessionCallback);
+ property public abstract kotlinx.coroutines.flow.Flow<java.util.List<androidx.window.area.WindowAreaInfo>> windowAreaInfos;
+ field public static final androidx.window.area.WindowAreaController.Companion Companion;
+ }
+
+ public static final class WindowAreaController.Companion {
+ method public androidx.window.area.WindowAreaController getOrCreate();
+ }
+
+ @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public final class WindowAreaInfo {
+ method public androidx.window.area.WindowAreaSession? getActiveSession(androidx.window.area.WindowAreaCapability.Operation operation);
+ method public androidx.window.area.WindowAreaCapability? getCapability(androidx.window.area.WindowAreaCapability.Operation operation);
+ method public androidx.window.layout.WindowMetrics getMetrics();
+ method public android.os.Binder getToken();
+ method public androidx.window.area.WindowAreaInfo.Type getType();
+ method public void setMetrics(androidx.window.layout.WindowMetrics);
+ property public final androidx.window.layout.WindowMetrics metrics;
+ property public final android.os.Binder token;
+ property public final androidx.window.area.WindowAreaInfo.Type type;
+ }
+
+ @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public static final class WindowAreaInfo.Type {
+ field public static final androidx.window.area.WindowAreaInfo.Type.Companion Companion;
+ field public static final androidx.window.area.WindowAreaInfo.Type TYPE_REAR_FACING;
+ }
+
+ public static final class WindowAreaInfo.Type.Companion {
+ }
+
+ @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public interface WindowAreaPresentationSessionCallback {
+ method public void onContainerVisibilityChanged(boolean isVisible);
+ method public void onSessionEnded(Throwable? t);
+ method public void onSessionStarted(androidx.window.area.WindowAreaSessionPresenter session);
+ }
+
+ @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public interface WindowAreaSession {
+ method public void close();
+ }
+
+ @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public interface WindowAreaSessionCallback {
+ method public void onSessionEnded(Throwable? t);
+ method public void onSessionStarted(androidx.window.area.WindowAreaSession session);
+ }
+
+ @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public interface WindowAreaSessionPresenter extends androidx.window.area.WindowAreaSession {
+ method public android.content.Context getContext();
+ method public void setContentView(android.view.View view);
+ property public abstract android.content.Context context;
+ }
+
+}
+
+package androidx.window.core {
+
+ @SuppressCompatibility @kotlin.RequiresOptIn(level=kotlin.RequiresOptIn.Level.WARNING) @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) public @interface ExperimentalWindowApi {
+ }
+
+}
+
+package androidx.window.embedding {
+
+ public final class ActivityEmbeddingController {
+ method @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public void finishActivityStacks(java.util.Set<androidx.window.embedding.ActivityStack> activityStacks);
+ method @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public androidx.window.embedding.ActivityStack? getActivityStack(android.app.Activity activity);
+ method public static androidx.window.embedding.ActivityEmbeddingController getInstance(android.content.Context context);
+ method public boolean isActivityEmbedded(android.app.Activity activity);
+ method @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public boolean isFinishingActivityStacksSupported();
+ field public static final androidx.window.embedding.ActivityEmbeddingController.Companion Companion;
+ }
+
+ public static final class ActivityEmbeddingController.Companion {
+ method public androidx.window.embedding.ActivityEmbeddingController getInstance(android.content.Context context);
+ }
+
+ public final class ActivityEmbeddingOptions {
+ method @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public static boolean isSetLaunchingActivityStackSupported(android.app.ActivityOptions);
+ method @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public static android.app.ActivityOptions setLaunchingActivityStack(android.app.ActivityOptions, android.app.Activity activity);
+ method @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public static android.app.ActivityOptions setLaunchingActivityStack(android.app.ActivityOptions, android.content.Context context, androidx.window.embedding.ActivityStack activityStack);
+ }
+
+ public final class ActivityFilter {
+ ctor public ActivityFilter(android.content.ComponentName componentName, String? intentAction);
+ method public android.content.ComponentName getComponentName();
+ method public String? getIntentAction();
+ method public boolean matchesActivity(android.app.Activity activity);
+ method public boolean matchesIntent(android.content.Intent intent);
+ property public final android.content.ComponentName componentName;
+ property public final String? intentAction;
+ }
+
+ public final class ActivityRule extends androidx.window.embedding.EmbeddingRule {
+ method public boolean getAlwaysExpand();
+ method public java.util.Set<androidx.window.embedding.ActivityFilter> getFilters();
+ property public final boolean alwaysExpand;
+ property public final java.util.Set<androidx.window.embedding.ActivityFilter> filters;
+ }
+
+ public static final class ActivityRule.Builder {
+ ctor public ActivityRule.Builder(java.util.Set<androidx.window.embedding.ActivityFilter> filters);
+ method public androidx.window.embedding.ActivityRule build();
+ method public androidx.window.embedding.ActivityRule.Builder setAlwaysExpand(boolean alwaysExpand);
+ method public androidx.window.embedding.ActivityRule.Builder setTag(String? tag);
+ }
+
+ public final class ActivityStack {
+ method public operator boolean contains(android.app.Activity activity);
+ method public boolean isEmpty();
+ property public final boolean isEmpty;
+ }
+
+ public final class EmbeddingAspectRatio {
+ method public static androidx.window.embedding.EmbeddingAspectRatio ratio(@FloatRange(from=1.0, fromInclusive=false) float ratio);
+ field public static final androidx.window.embedding.EmbeddingAspectRatio ALWAYS_ALLOW;
+ field public static final androidx.window.embedding.EmbeddingAspectRatio ALWAYS_DISALLOW;
+ field public static final androidx.window.embedding.EmbeddingAspectRatio.Companion Companion;
+ }
+
+ public static final class EmbeddingAspectRatio.Companion {
+ method public androidx.window.embedding.EmbeddingAspectRatio ratio(@FloatRange(from=1.0, fromInclusive=false) float ratio);
+ }
+
+ public abstract class EmbeddingRule {
+ method public final String? getTag();
+ property public final String? tag;
+ }
+
+ public final class RuleController {
+ method public void addRule(androidx.window.embedding.EmbeddingRule rule);
+ method public void clearRules();
+ method public static androidx.window.embedding.RuleController getInstance(android.content.Context context);
+ method public java.util.Set<androidx.window.embedding.EmbeddingRule> getRules();
+ method public static java.util.Set<androidx.window.embedding.EmbeddingRule> parseRules(android.content.Context context, @XmlRes int staticRuleResourceId);
+ method public void removeRule(androidx.window.embedding.EmbeddingRule rule);
+ method public void setRules(java.util.Set<? extends androidx.window.embedding.EmbeddingRule> rules);
+ field public static final androidx.window.embedding.RuleController.Companion Companion;
+ }
+
+ public static final class RuleController.Companion {
+ method public androidx.window.embedding.RuleController getInstance(android.content.Context context);
+ method public java.util.Set<androidx.window.embedding.EmbeddingRule> parseRules(android.content.Context context, @XmlRes int staticRuleResourceId);
+ }
+
+ public final class SplitAttributes {
+ method public androidx.window.embedding.SplitAttributes.LayoutDirection getLayoutDirection();
+ method public androidx.window.embedding.SplitAttributes.SplitType getSplitType();
+ property public final androidx.window.embedding.SplitAttributes.LayoutDirection layoutDirection;
+ property public final androidx.window.embedding.SplitAttributes.SplitType splitType;
+ field public static final androidx.window.embedding.SplitAttributes.Companion Companion;
+ }
+
+ public static final class SplitAttributes.Builder {
+ ctor public SplitAttributes.Builder();
+ method public androidx.window.embedding.SplitAttributes build();
+ method public androidx.window.embedding.SplitAttributes.Builder setLayoutDirection(androidx.window.embedding.SplitAttributes.LayoutDirection layoutDirection);
+ method public androidx.window.embedding.SplitAttributes.Builder setSplitType(androidx.window.embedding.SplitAttributes.SplitType type);
+ }
+
+ public static final class SplitAttributes.Companion {
+ }
+
+ public static final class SplitAttributes.LayoutDirection {
+ field public static final androidx.window.embedding.SplitAttributes.LayoutDirection BOTTOM_TO_TOP;
+ field public static final androidx.window.embedding.SplitAttributes.LayoutDirection.Companion Companion;
+ field public static final androidx.window.embedding.SplitAttributes.LayoutDirection LEFT_TO_RIGHT;
+ field public static final androidx.window.embedding.SplitAttributes.LayoutDirection LOCALE;
+ field public static final androidx.window.embedding.SplitAttributes.LayoutDirection RIGHT_TO_LEFT;
+ field public static final androidx.window.embedding.SplitAttributes.LayoutDirection TOP_TO_BOTTOM;
+ }
+
+ public static final class SplitAttributes.LayoutDirection.Companion {
+ }
+
+ public static final class SplitAttributes.SplitType {
+ method public static androidx.window.embedding.SplitAttributes.SplitType ratio(@FloatRange(from=0.0, to=1.0, fromInclusive=false, toInclusive=false) float ratio);
+ field public static final androidx.window.embedding.SplitAttributes.SplitType.Companion Companion;
+ field public static final androidx.window.embedding.SplitAttributes.SplitType SPLIT_TYPE_EQUAL;
+ field public static final androidx.window.embedding.SplitAttributes.SplitType SPLIT_TYPE_EXPAND;
+ field public static final androidx.window.embedding.SplitAttributes.SplitType SPLIT_TYPE_HINGE;
+ }
+
+ public static final class SplitAttributes.SplitType.Companion {
+ method public androidx.window.embedding.SplitAttributes.SplitType ratio(@FloatRange(from=0.0, to=1.0, fromInclusive=false, toInclusive=false) float ratio);
+ }
+
+ public final class SplitAttributesCalculatorParams {
+ method public boolean getAreDefaultConstraintsSatisfied();
+ method public androidx.window.embedding.SplitAttributes getDefaultSplitAttributes();
+ method public android.content.res.Configuration getParentConfiguration();
+ method public androidx.window.layout.WindowLayoutInfo getParentWindowLayoutInfo();
+ method public androidx.window.layout.WindowMetrics getParentWindowMetrics();
+ method public String? getSplitRuleTag();
+ property public final boolean areDefaultConstraintsSatisfied;
+ property public final androidx.window.embedding.SplitAttributes defaultSplitAttributes;
+ property public final android.content.res.Configuration parentConfiguration;
+ property public final androidx.window.layout.WindowLayoutInfo parentWindowLayoutInfo;
+ property public final androidx.window.layout.WindowMetrics parentWindowMetrics;
+ property public final String? splitRuleTag;
+ }
+
+ public final class SplitController {
+ method public void clearSplitAttributesCalculator();
+ method public static androidx.window.embedding.SplitController getInstance(android.content.Context context);
+ method public androidx.window.embedding.SplitController.SplitSupportStatus getSplitSupportStatus();
+ method @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public void invalidateTopVisibleSplitAttributes();
+ method @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public boolean isInvalidatingTopVisibleSplitAttributesSupported();
+ method public boolean isSplitAttributesCalculatorSupported();
+ method @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public boolean isUpdatingSplitAttributesSupported();
+ method public void setSplitAttributesCalculator(kotlin.jvm.functions.Function1<? super androidx.window.embedding.SplitAttributesCalculatorParams,androidx.window.embedding.SplitAttributes> calculator);
+ method public kotlinx.coroutines.flow.Flow<java.util.List<androidx.window.embedding.SplitInfo>> splitInfoList(android.app.Activity activity);
+ method @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public void updateSplitAttributes(androidx.window.embedding.SplitInfo splitInfo, androidx.window.embedding.SplitAttributes splitAttributes);
+ property public final androidx.window.embedding.SplitController.SplitSupportStatus splitSupportStatus;
+ field public static final androidx.window.embedding.SplitController.Companion Companion;
+ }
+
+ public static final class SplitController.Companion {
+ method public androidx.window.embedding.SplitController getInstance(android.content.Context context);
+ }
+
+ public static final class SplitController.SplitSupportStatus {
+ field public static final androidx.window.embedding.SplitController.SplitSupportStatus.Companion Companion;
+ field public static final androidx.window.embedding.SplitController.SplitSupportStatus SPLIT_AVAILABLE;
+ field public static final androidx.window.embedding.SplitController.SplitSupportStatus SPLIT_ERROR_PROPERTY_NOT_DECLARED;
+ field public static final androidx.window.embedding.SplitController.SplitSupportStatus SPLIT_UNAVAILABLE;
+ }
+
+ public static final class SplitController.SplitSupportStatus.Companion {
+ }
+
+ public final class SplitInfo {
+ method public operator boolean contains(android.app.Activity activity);
+ method public androidx.window.embedding.ActivityStack getPrimaryActivityStack();
+ method public androidx.window.embedding.ActivityStack getSecondaryActivityStack();
+ method public androidx.window.embedding.SplitAttributes getSplitAttributes();
+ property public final androidx.window.embedding.ActivityStack primaryActivityStack;
+ property public final androidx.window.embedding.ActivityStack secondaryActivityStack;
+ property public final androidx.window.embedding.SplitAttributes splitAttributes;
+ }
+
+ public final class SplitPairFilter {
+ ctor public SplitPairFilter(android.content.ComponentName primaryActivityName, android.content.ComponentName secondaryActivityName, String? secondaryActivityIntentAction);
+ method public android.content.ComponentName getPrimaryActivityName();
+ method public String? getSecondaryActivityIntentAction();
+ method public android.content.ComponentName getSecondaryActivityName();
+ method public boolean matchesActivityIntentPair(android.app.Activity primaryActivity, android.content.Intent secondaryActivityIntent);
+ method public boolean matchesActivityPair(android.app.Activity primaryActivity, android.app.Activity secondaryActivity);
+ property public final android.content.ComponentName primaryActivityName;
+ property public final String? secondaryActivityIntentAction;
+ property public final android.content.ComponentName secondaryActivityName;
+ }
+
+ public final class SplitPairRule extends androidx.window.embedding.SplitRule {
+ method public boolean getClearTop();
+ method public java.util.Set<androidx.window.embedding.SplitPairFilter> getFilters();
+ method public androidx.window.embedding.SplitRule.FinishBehavior getFinishPrimaryWithSecondary();
+ method public androidx.window.embedding.SplitRule.FinishBehavior getFinishSecondaryWithPrimary();
+ property public final boolean clearTop;
+ property public final java.util.Set<androidx.window.embedding.SplitPairFilter> filters;
+ property public final androidx.window.embedding.SplitRule.FinishBehavior finishPrimaryWithSecondary;
+ property public final androidx.window.embedding.SplitRule.FinishBehavior finishSecondaryWithPrimary;
+ }
+
+ public static final class SplitPairRule.Builder {
+ ctor public SplitPairRule.Builder(java.util.Set<androidx.window.embedding.SplitPairFilter> filters);
+ method public androidx.window.embedding.SplitPairRule build();
+ method public androidx.window.embedding.SplitPairRule.Builder setClearTop(boolean clearTop);
+ method public androidx.window.embedding.SplitPairRule.Builder setDefaultSplitAttributes(androidx.window.embedding.SplitAttributes defaultSplitAttributes);
+ method public androidx.window.embedding.SplitPairRule.Builder setFinishPrimaryWithSecondary(androidx.window.embedding.SplitRule.FinishBehavior finishPrimaryWithSecondary);
+ method public androidx.window.embedding.SplitPairRule.Builder setFinishSecondaryWithPrimary(androidx.window.embedding.SplitRule.FinishBehavior finishSecondaryWithPrimary);
+ method public androidx.window.embedding.SplitPairRule.Builder setMaxAspectRatioInLandscape(androidx.window.embedding.EmbeddingAspectRatio aspectRatio);
+ method public androidx.window.embedding.SplitPairRule.Builder setMaxAspectRatioInPortrait(androidx.window.embedding.EmbeddingAspectRatio aspectRatio);
+ method public androidx.window.embedding.SplitPairRule.Builder setMinHeightDp(@IntRange(from=0L) int minHeightDp);
+ method public androidx.window.embedding.SplitPairRule.Builder setMinSmallestWidthDp(@IntRange(from=0L) int minSmallestWidthDp);
+ method public androidx.window.embedding.SplitPairRule.Builder setMinWidthDp(@IntRange(from=0L) int minWidthDp);
+ method public androidx.window.embedding.SplitPairRule.Builder setTag(String? tag);
+ }
+
+ public final class SplitPlaceholderRule extends androidx.window.embedding.SplitRule {
+ method public java.util.Set<androidx.window.embedding.ActivityFilter> getFilters();
+ method public androidx.window.embedding.SplitRule.FinishBehavior getFinishPrimaryWithPlaceholder();
+ method public android.content.Intent getPlaceholderIntent();
+ method public boolean isSticky();
+ property public final java.util.Set<androidx.window.embedding.ActivityFilter> filters;
+ property public final androidx.window.embedding.SplitRule.FinishBehavior finishPrimaryWithPlaceholder;
+ property public final boolean isSticky;
+ property public final android.content.Intent placeholderIntent;
+ }
+
+ public static final class SplitPlaceholderRule.Builder {
+ ctor public SplitPlaceholderRule.Builder(java.util.Set<androidx.window.embedding.ActivityFilter> filters, android.content.Intent placeholderIntent);
+ method public androidx.window.embedding.SplitPlaceholderRule build();
+ method public androidx.window.embedding.SplitPlaceholderRule.Builder setDefaultSplitAttributes(androidx.window.embedding.SplitAttributes defaultSplitAttributes);
+ method public androidx.window.embedding.SplitPlaceholderRule.Builder setFinishPrimaryWithPlaceholder(androidx.window.embedding.SplitRule.FinishBehavior finishPrimaryWithPlaceholder);
+ method public androidx.window.embedding.SplitPlaceholderRule.Builder setMaxAspectRatioInLandscape(androidx.window.embedding.EmbeddingAspectRatio aspectRatio);
+ method public androidx.window.embedding.SplitPlaceholderRule.Builder setMaxAspectRatioInPortrait(androidx.window.embedding.EmbeddingAspectRatio aspectRatio);
+ method public androidx.window.embedding.SplitPlaceholderRule.Builder setMinHeightDp(@IntRange(from=0L) int minHeightDp);
+ method public androidx.window.embedding.SplitPlaceholderRule.Builder setMinSmallestWidthDp(@IntRange(from=0L) int minSmallestWidthDp);
+ method public androidx.window.embedding.SplitPlaceholderRule.Builder setMinWidthDp(@IntRange(from=0L) int minWidthDp);
+ method public androidx.window.embedding.SplitPlaceholderRule.Builder setSticky(boolean isSticky);
+ method public androidx.window.embedding.SplitPlaceholderRule.Builder setTag(String? tag);
+ }
+
+ public class SplitRule extends androidx.window.embedding.EmbeddingRule {
+ method public final androidx.window.embedding.SplitAttributes getDefaultSplitAttributes();
+ method public final androidx.window.embedding.EmbeddingAspectRatio getMaxAspectRatioInLandscape();
+ method public final androidx.window.embedding.EmbeddingAspectRatio getMaxAspectRatioInPortrait();
+ method public final int getMinHeightDp();
+ method public final int getMinSmallestWidthDp();
+ method public final int getMinWidthDp();
+ property public final androidx.window.embedding.SplitAttributes defaultSplitAttributes;
+ property public final androidx.window.embedding.EmbeddingAspectRatio maxAspectRatioInLandscape;
+ property public final androidx.window.embedding.EmbeddingAspectRatio maxAspectRatioInPortrait;
+ property public final int minHeightDp;
+ property public final int minSmallestWidthDp;
+ property public final int minWidthDp;
+ field public static final androidx.window.embedding.SplitRule.Companion Companion;
+ field public static final androidx.window.embedding.EmbeddingAspectRatio SPLIT_MAX_ASPECT_RATIO_LANDSCAPE_DEFAULT;
+ field public static final androidx.window.embedding.EmbeddingAspectRatio SPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULT;
+ field public static final int SPLIT_MIN_DIMENSION_ALWAYS_ALLOW = 0; // 0x0
+ field public static final int SPLIT_MIN_DIMENSION_DP_DEFAULT = 600; // 0x258
+ }
+
+ public static final class SplitRule.Companion {
+ }
+
+ public static final class SplitRule.FinishBehavior {
+ field public static final androidx.window.embedding.SplitRule.FinishBehavior ADJACENT;
+ field public static final androidx.window.embedding.SplitRule.FinishBehavior ALWAYS;
+ field public static final androidx.window.embedding.SplitRule.FinishBehavior.Companion Companion;
+ field public static final androidx.window.embedding.SplitRule.FinishBehavior NEVER;
+ }
+
+ public static final class SplitRule.FinishBehavior.Companion {
+ }
+
+}
+
+package androidx.window.layout {
+
+ public interface DisplayFeature {
+ method public android.graphics.Rect getBounds();
+ property public abstract android.graphics.Rect bounds;
+ }
+
+ public interface FoldingFeature extends androidx.window.layout.DisplayFeature {
+ method public androidx.window.layout.FoldingFeature.OcclusionType getOcclusionType();
+ method public androidx.window.layout.FoldingFeature.Orientation getOrientation();
+ method public androidx.window.layout.FoldingFeature.State getState();
+ method public boolean isSeparating();
+ property public abstract boolean isSeparating;
+ property public abstract androidx.window.layout.FoldingFeature.OcclusionType occlusionType;
+ property public abstract androidx.window.layout.FoldingFeature.Orientation orientation;
+ property public abstract androidx.window.layout.FoldingFeature.State state;
+ }
+
+ public static final class FoldingFeature.OcclusionType {
+ field public static final androidx.window.layout.FoldingFeature.OcclusionType.Companion Companion;
+ field public static final androidx.window.layout.FoldingFeature.OcclusionType FULL;
+ field public static final androidx.window.layout.FoldingFeature.OcclusionType NONE;
+ }
+
+ public static final class FoldingFeature.OcclusionType.Companion {
+ }
+
+ public static final class FoldingFeature.Orientation {
+ field public static final androidx.window.layout.FoldingFeature.Orientation.Companion Companion;
+ field public static final androidx.window.layout.FoldingFeature.Orientation HORIZONTAL;
+ field public static final androidx.window.layout.FoldingFeature.Orientation VERTICAL;
+ }
+
+ public static final class FoldingFeature.Orientation.Companion {
+ }
+
+ public static final class FoldingFeature.State {
+ field public static final androidx.window.layout.FoldingFeature.State.Companion Companion;
+ field public static final androidx.window.layout.FoldingFeature.State FLAT;
+ field public static final androidx.window.layout.FoldingFeature.State HALF_OPENED;
+ }
+
+ public static final class FoldingFeature.State.Companion {
+ }
+
+ public interface WindowInfoTracker {
+ method public static androidx.window.layout.WindowInfoTracker getOrCreate(android.content.Context context);
+ method public kotlinx.coroutines.flow.Flow<androidx.window.layout.WindowLayoutInfo> windowLayoutInfo(android.app.Activity activity);
+ method public default kotlinx.coroutines.flow.Flow<androidx.window.layout.WindowLayoutInfo> windowLayoutInfo(@UiContext android.content.Context context);
+ field public static final androidx.window.layout.WindowInfoTracker.Companion Companion;
+ }
+
+ public static final class WindowInfoTracker.Companion {
+ method public androidx.window.layout.WindowInfoTracker getOrCreate(android.content.Context context);
+ }
+
+ public final class WindowLayoutInfo {
+ method public java.util.List<androidx.window.layout.DisplayFeature> getDisplayFeatures();
+ property public final java.util.List<androidx.window.layout.DisplayFeature> displayFeatures;
+ }
+
+ public final class WindowMetrics {
+ method public android.graphics.Rect getBounds();
+ method @SuppressCompatibility @RequiresApi(android.os.Build.VERSION_CODES.R) @androidx.window.core.ExperimentalWindowApi public androidx.core.view.WindowInsetsCompat getWindowInsets();
+ property public final android.graphics.Rect bounds;
+ }
+
+ public interface WindowMetricsCalculator {
+ method public androidx.window.layout.WindowMetrics computeCurrentWindowMetrics(android.app.Activity activity);
+ method public default androidx.window.layout.WindowMetrics computeCurrentWindowMetrics(@UiContext android.content.Context context);
+ method public androidx.window.layout.WindowMetrics computeMaximumWindowMetrics(android.app.Activity activity);
+ method public default androidx.window.layout.WindowMetrics computeMaximumWindowMetrics(@UiContext android.content.Context context);
+ method public static androidx.window.layout.WindowMetricsCalculator getOrCreate();
+ field public static final androidx.window.layout.WindowMetricsCalculator.Companion Companion;
+ }
+
+ public static final class WindowMetricsCalculator.Companion {
+ method public androidx.window.layout.WindowMetricsCalculator getOrCreate();
+ }
+
+}
+
diff --git a/window/window/api/restricted_current.txt b/window/window/api/restricted_current.txt
index 2c51c8d..7c79bde 100644
--- a/window/window/api/restricted_current.txt
+++ b/window/window/api/restricted_current.txt
@@ -14,14 +14,14 @@
package androidx.window.area {
- public final class WindowAreaCapability {
+ @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public final class WindowAreaCapability {
method public androidx.window.area.WindowAreaCapability.Operation getOperation();
method public androidx.window.area.WindowAreaCapability.Status getStatus();
property public final androidx.window.area.WindowAreaCapability.Operation operation;
property public final androidx.window.area.WindowAreaCapability.Status status;
}
- public static final class WindowAreaCapability.Operation {
+ @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public static final class WindowAreaCapability.Operation {
field public static final androidx.window.area.WindowAreaCapability.Operation.Companion Companion;
field public static final androidx.window.area.WindowAreaCapability.Operation OPERATION_PRESENT_ON_AREA;
field public static final androidx.window.area.WindowAreaCapability.Operation OPERATION_TRANSFER_ACTIVITY_TO_AREA;
@@ -30,7 +30,7 @@
public static final class WindowAreaCapability.Operation.Companion {
}
- public static final class WindowAreaCapability.Status {
+ @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public static final class WindowAreaCapability.Status {
field public static final androidx.window.area.WindowAreaCapability.Status.Companion Companion;
field public static final androidx.window.area.WindowAreaCapability.Status WINDOW_AREA_STATUS_ACTIVE;
field public static final androidx.window.area.WindowAreaCapability.Status WINDOW_AREA_STATUS_AVAILABLE;
@@ -41,7 +41,7 @@
public static final class WindowAreaCapability.Status.Companion {
}
- public interface WindowAreaController {
+ @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public interface WindowAreaController {
method public static androidx.window.area.WindowAreaController getOrCreate();
method public kotlinx.coroutines.flow.Flow<java.util.List<androidx.window.area.WindowAreaInfo>> getWindowAreaInfos();
method public void presentContentOnWindowArea(android.os.Binder token, android.app.Activity activity, java.util.concurrent.Executor executor, androidx.window.area.WindowAreaPresentationSessionCallback windowAreaPresentationSessionCallback);
@@ -54,7 +54,7 @@
method public androidx.window.area.WindowAreaController getOrCreate();
}
- public final class WindowAreaInfo {
+ @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public final class WindowAreaInfo {
method public androidx.window.area.WindowAreaSession? getActiveSession(androidx.window.area.WindowAreaCapability.Operation operation);
method public androidx.window.area.WindowAreaCapability? getCapability(androidx.window.area.WindowAreaCapability.Operation operation);
method public androidx.window.layout.WindowMetrics getMetrics();
@@ -66,7 +66,7 @@
property public final androidx.window.area.WindowAreaInfo.Type type;
}
- public static final class WindowAreaInfo.Type {
+ @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public static final class WindowAreaInfo.Type {
field public static final androidx.window.area.WindowAreaInfo.Type.Companion Companion;
field public static final androidx.window.area.WindowAreaInfo.Type TYPE_REAR_FACING;
}
@@ -74,22 +74,22 @@
public static final class WindowAreaInfo.Type.Companion {
}
- public interface WindowAreaPresentationSessionCallback {
+ @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public interface WindowAreaPresentationSessionCallback {
method public void onContainerVisibilityChanged(boolean isVisible);
method public void onSessionEnded(Throwable? t);
method public void onSessionStarted(androidx.window.area.WindowAreaSessionPresenter session);
}
- public interface WindowAreaSession {
+ @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public interface WindowAreaSession {
method public void close();
}
- public interface WindowAreaSessionCallback {
+ @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public interface WindowAreaSessionCallback {
method public void onSessionEnded(Throwable? t);
method public void onSessionStarted(androidx.window.area.WindowAreaSession session);
}
- public interface WindowAreaSessionPresenter extends androidx.window.area.WindowAreaSession {
+ @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public interface WindowAreaSessionPresenter extends androidx.window.area.WindowAreaSession {
method public android.content.Context getContext();
method public void setContentView(android.view.View view);
property public abstract android.content.Context context;
diff --git a/window/window/src/androidTest/java/androidx/window/area/WindowAreaControllerImplTest.kt b/window/window/src/androidTest/java/androidx/window/area/WindowAreaControllerImplTest.kt
index 4ae7871..92b327a 100644
--- a/window/window/src/androidTest/java/androidx/window/area/WindowAreaControllerImplTest.kt
+++ b/window/window/src/androidTest/java/androidx/window/area/WindowAreaControllerImplTest.kt
@@ -32,6 +32,7 @@
import androidx.window.area.WindowAreaCapability.Operation.Companion.OPERATION_TRANSFER_ACTIVITY_TO_AREA
import androidx.window.area.WindowAreaCapability.Status.Companion.WINDOW_AREA_STATUS_AVAILABLE
import androidx.window.area.WindowAreaCapability.Status.Companion.WINDOW_AREA_STATUS_UNAVAILABLE
+import androidx.window.core.ExperimentalWindowApi
import androidx.window.extensions.area.ExtensionWindowAreaPresentation
import androidx.window.extensions.area.ExtensionWindowAreaStatus
import androidx.window.extensions.area.WindowAreaComponent
@@ -59,7 +60,7 @@
import org.junit.Rule
import org.junit.Test
-@OptIn(ExperimentalCoroutinesApi::class)
+@OptIn(ExperimentalCoroutinesApi::class, ExperimentalWindowApi::class)
class WindowAreaControllerImplTest {
@get:Rule
diff --git a/window/window/src/main/java/androidx/window/area/EmptyWindowAreaControllerImpl.kt b/window/window/src/main/java/androidx/window/area/EmptyWindowAreaControllerImpl.kt
index 07fcfd5..7c0cb61 100644
--- a/window/window/src/main/java/androidx/window/area/EmptyWindowAreaControllerImpl.kt
+++ b/window/window/src/main/java/androidx/window/area/EmptyWindowAreaControllerImpl.kt
@@ -18,6 +18,7 @@
import android.app.Activity
import android.os.Binder
+import androidx.window.core.ExperimentalWindowApi
import java.util.concurrent.Executor
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf
@@ -25,6 +26,7 @@
/**
* Empty Implementation for devices that do not support the [WindowAreaController] functionality
*/
+@ExperimentalWindowApi
internal class EmptyWindowAreaControllerImpl : WindowAreaController {
override val windowAreaInfos: Flow<List<WindowAreaInfo>>
diff --git a/window/window/src/main/java/androidx/window/area/RearDisplayPresentationSessionPresenterImpl.kt b/window/window/src/main/java/androidx/window/area/RearDisplayPresentationSessionPresenterImpl.kt
index 60aabc8..e0e9574 100644
--- a/window/window/src/main/java/androidx/window/area/RearDisplayPresentationSessionPresenterImpl.kt
+++ b/window/window/src/main/java/androidx/window/area/RearDisplayPresentationSessionPresenterImpl.kt
@@ -18,9 +18,11 @@
import android.content.Context
import android.view.View
+import androidx.window.core.ExperimentalWindowApi
import androidx.window.extensions.area.ExtensionWindowAreaPresentation
import androidx.window.extensions.area.WindowAreaComponent
+@ExperimentalWindowApi
internal class RearDisplayPresentationSessionPresenterImpl(
private val windowAreaComponent: WindowAreaComponent,
private val presentation: ExtensionWindowAreaPresentation
diff --git a/window/window/src/main/java/androidx/window/area/RearDisplaySessionImpl.kt b/window/window/src/main/java/androidx/window/area/RearDisplaySessionImpl.kt
index 5a4a9a3..9a5bbd3 100644
--- a/window/window/src/main/java/androidx/window/area/RearDisplaySessionImpl.kt
+++ b/window/window/src/main/java/androidx/window/area/RearDisplaySessionImpl.kt
@@ -16,8 +16,10 @@
package androidx.window.area
+import androidx.window.core.ExperimentalWindowApi
import androidx.window.extensions.area.WindowAreaComponent
+@ExperimentalWindowApi
internal class RearDisplaySessionImpl(
private val windowAreaComponent: WindowAreaComponent
) : WindowAreaSession {
diff --git a/window/window/src/main/java/androidx/window/area/WindowAreaAdapter.kt b/window/window/src/main/java/androidx/window/area/WindowAreaAdapter.kt
index c884dc6..ba76dfd 100644
--- a/window/window/src/main/java/androidx/window/area/WindowAreaAdapter.kt
+++ b/window/window/src/main/java/androidx/window/area/WindowAreaAdapter.kt
@@ -20,6 +20,7 @@
import androidx.window.area.WindowAreaCapability.Status.Companion.WINDOW_AREA_STATUS_AVAILABLE
import androidx.window.area.WindowAreaCapability.Status.Companion.WINDOW_AREA_STATUS_UNAVAILABLE
import androidx.window.area.WindowAreaCapability.Status.Companion.WINDOW_AREA_STATUS_UNSUPPORTED
+import androidx.window.core.ExperimentalWindowApi
import androidx.window.extensions.area.WindowAreaComponent
import androidx.window.extensions.area.WindowAreaComponent.STATUS_ACTIVE
import androidx.window.extensions.area.WindowAreaComponent.STATUS_AVAILABLE
@@ -30,6 +31,7 @@
* Adapter object to assist in translating values received from [WindowAreaComponent]
* to developer friendly values in [WindowAreaController]
*/
+@ExperimentalWindowApi
internal object WindowAreaAdapter {
internal fun translate(
diff --git a/window/window/src/main/java/androidx/window/area/WindowAreaCapability.kt b/window/window/src/main/java/androidx/window/area/WindowAreaCapability.kt
index 7992fbd..cc0cde3 100644
--- a/window/window/src/main/java/androidx/window/area/WindowAreaCapability.kt
+++ b/window/window/src/main/java/androidx/window/area/WindowAreaCapability.kt
@@ -17,10 +17,12 @@
package androidx.window.area
import android.app.Activity
+import androidx.window.core.ExperimentalWindowApi
/**
* Represents a capability for a [WindowAreaInfo].
*/
+@ExperimentalWindowApi
class WindowAreaCapability internal constructor(val operation: Operation, val status: Status) {
override fun toString(): String {
return "Operation: $operation: Status: $status"
@@ -29,6 +31,7 @@
/**
* Represents the status of availability for a specific [WindowAreaCapability]
*/
+ @ExperimentalWindowApi
class Status private constructor(private val description: String) {
override fun toString(): String {
return description
@@ -75,6 +78,7 @@
/**
* Represents an operation that a [WindowAreaInfo] may support.
*/
+ @ExperimentalWindowApi
class Operation private constructor(private val description: String) {
override fun toString(): String {
return description
diff --git a/window/window/src/main/java/androidx/window/area/WindowAreaController.kt b/window/window/src/main/java/androidx/window/area/WindowAreaController.kt
index e02ab52..ae2500c 100644
--- a/window/window/src/main/java/androidx/window/area/WindowAreaController.kt
+++ b/window/window/src/main/java/androidx/window/area/WindowAreaController.kt
@@ -24,6 +24,7 @@
import androidx.window.area.WindowAreaInfo.Type.Companion.TYPE_REAR_FACING
import androidx.window.area.utils.DeviceUtils
import androidx.window.core.BuildConfig
+import androidx.window.core.ExperimentalWindowApi
import androidx.window.core.ExtensionsUtil
import androidx.window.core.VerificationMode
import java.util.concurrent.Executor
@@ -34,6 +35,7 @@
* displays or display areas on a device.
*
*/
+@ExperimentalWindowApi
interface WindowAreaController {
/**
@@ -181,6 +183,7 @@
* Decorator that allows us to provide different functionality
* in our window-testing artifact.
*/
+@ExperimentalWindowApi
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
interface WindowAreaControllerDecorator {
/**
@@ -190,6 +193,7 @@
public fun decorate(controller: WindowAreaController): WindowAreaController
}
+@ExperimentalWindowApi
private object EmptyDecorator : WindowAreaControllerDecorator {
override fun decorate(controller: WindowAreaController): WindowAreaController {
return controller
diff --git a/window/window/src/main/java/androidx/window/area/WindowAreaControllerImpl.kt b/window/window/src/main/java/androidx/window/area/WindowAreaControllerImpl.kt
index 641a277..c815a24 100644
--- a/window/window/src/main/java/androidx/window/area/WindowAreaControllerImpl.kt
+++ b/window/window/src/main/java/androidx/window/area/WindowAreaControllerImpl.kt
@@ -27,6 +27,7 @@
import androidx.window.area.WindowAreaCapability.Status.Companion.WINDOW_AREA_STATUS_UNSUPPORTED
import androidx.window.area.utils.DeviceUtils
import androidx.window.core.BuildConfig
+import androidx.window.core.ExperimentalWindowApi
import androidx.window.core.VerificationMode
import androidx.window.extensions.area.ExtensionWindowAreaStatus
import androidx.window.extensions.area.WindowAreaComponent
@@ -55,6 +56,7 @@
* [Build.VERSION_CODES.S] as that's the min level of support for
* this functionality.
*/
+@ExperimentalWindowApi
@RequiresApi(Build.VERSION_CODES.Q)
internal class WindowAreaControllerImpl(
private val windowAreaComponent: WindowAreaComponent,
diff --git a/window/window/src/main/java/androidx/window/area/WindowAreaInfo.kt b/window/window/src/main/java/androidx/window/area/WindowAreaInfo.kt
index dcfd01f..4aa63a1b 100644
--- a/window/window/src/main/java/androidx/window/area/WindowAreaInfo.kt
+++ b/window/window/src/main/java/androidx/window/area/WindowAreaInfo.kt
@@ -20,6 +20,7 @@
import androidx.window.area.WindowAreaCapability.Operation.Companion.OPERATION_PRESENT_ON_AREA
import androidx.window.area.WindowAreaCapability.Operation.Companion.OPERATION_TRANSFER_ACTIVITY_TO_AREA
import androidx.window.area.WindowAreaCapability.Status.Companion.WINDOW_AREA_STATUS_ACTIVE
+import androidx.window.core.ExperimentalWindowApi
import androidx.window.extensions.area.WindowAreaComponent
import androidx.window.layout.WindowMetrics
@@ -28,6 +29,7 @@
* display in the system. These values can be used to modify the UI to show/hide controls and
* determine when features can be enabled.
*/
+@ExperimentalWindowApi
class WindowAreaInfo internal constructor(
/**
@@ -96,6 +98,7 @@
/**
* Represents a type of [WindowAreaInfo]
*/
+ @ExperimentalWindowApi
class Type private constructor(private val description: String) {
override fun toString(): String {
return description
diff --git a/window/window/src/main/java/androidx/window/area/WindowAreaPresentationSessionCallback.kt b/window/window/src/main/java/androidx/window/area/WindowAreaPresentationSessionCallback.kt
index 05b6d2d..6cd548a 100644
--- a/window/window/src/main/java/androidx/window/area/WindowAreaPresentationSessionCallback.kt
+++ b/window/window/src/main/java/androidx/window/area/WindowAreaPresentationSessionCallback.kt
@@ -18,12 +18,14 @@
import android.content.Context
import android.view.View
+import androidx.window.core.ExperimentalWindowApi
/**
* A callback to notify about the lifecycle of a window area presentation session.
*
* @see WindowAreaController.presentContentOnWindowArea
*/
+@ExperimentalWindowApi
interface WindowAreaPresentationSessionCallback {
/**
diff --git a/window/window/src/main/java/androidx/window/area/WindowAreaSession.kt b/window/window/src/main/java/androidx/window/area/WindowAreaSession.kt
index 7743ef0..05b659c 100644
--- a/window/window/src/main/java/androidx/window/area/WindowAreaSession.kt
+++ b/window/window/src/main/java/androidx/window/area/WindowAreaSession.kt
@@ -16,11 +16,14 @@
package androidx.window.area
+import androidx.window.core.ExperimentalWindowApi
+
/**
* Session interface to represent an active window area feature.
*
* @see WindowAreaSessionCallback.onSessionStarted
*/
+@ExperimentalWindowApi
interface WindowAreaSession {
/**
diff --git a/window/window/src/main/java/androidx/window/area/WindowAreaSessionCallback.kt b/window/window/src/main/java/androidx/window/area/WindowAreaSessionCallback.kt
index e551660..d02083e 100644
--- a/window/window/src/main/java/androidx/window/area/WindowAreaSessionCallback.kt
+++ b/window/window/src/main/java/androidx/window/area/WindowAreaSessionCallback.kt
@@ -16,11 +16,14 @@
package androidx.window.area
+import androidx.window.core.ExperimentalWindowApi
+
/**
* Callback to update the client on the WindowArea Session being
* started and ended.
* TODO(b/207720511) Move to window-java module when Kotlin API Finalized
*/
+@ExperimentalWindowApi
interface WindowAreaSessionCallback {
/**
diff --git a/window/window/src/main/java/androidx/window/area/WindowAreaSessionPresenter.kt b/window/window/src/main/java/androidx/window/area/WindowAreaSessionPresenter.kt
index b06cbbd..4cc05a9 100644
--- a/window/window/src/main/java/androidx/window/area/WindowAreaSessionPresenter.kt
+++ b/window/window/src/main/java/androidx/window/area/WindowAreaSessionPresenter.kt
@@ -18,6 +18,7 @@
import android.content.Context
import android.view.View
+import androidx.window.core.ExperimentalWindowApi
/**
* A container that allows getting access to and showing content on a window area. The container is
@@ -26,6 +27,7 @@
* application window, or can be closed by calling [WindowAreaSessionPresenter.close].
* @see WindowAreaController.presentContentOnWindowArea
*/
+@ExperimentalWindowApi
interface WindowAreaSessionPresenter : WindowAreaSession {
/**
* Returns the [Context] associated with the window area.