Merge "Bump wear integration test demo version to 1.24" into androidx-main
diff --git a/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/utils/Constants.kt b/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/utils/Constants.kt
index c82ead6..d184d2f 100644
--- a/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/utils/Constants.kt
+++ b/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/utils/Constants.kt
@@ -20,7 +20,7 @@
// Minimum AGP version required
internal val MIN_AGP_VERSION_REQUIRED_INCLUSIVE = AndroidPluginVersion(8, 0, 0)
-internal val MAX_AGP_VERSION_RECOMMENDED_EXCLUSIVE = AndroidPluginVersion(8, 5, 0).alpha(1)
+internal val MAX_AGP_VERSION_RECOMMENDED_EXCLUSIVE = AndroidPluginVersion(8, 6, 0).alpha(1)
// Prefix for the build type baseline profile
internal const val BUILD_TYPE_BASELINE_PROFILE_PREFIX = "nonMinified"
diff --git a/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridScrollTest.kt b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridScrollTest.kt
index 34493db..fa4a65e 100644
--- a/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridScrollTest.kt
+++ b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridScrollTest.kt
@@ -17,10 +17,11 @@
package androidx.compose.foundation.lazy.staggeredgrid
import androidx.compose.foundation.AutoTestFrameClock
-import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.scrollBy
+import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.size
import androidx.compose.foundation.text.BasicText
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
@@ -34,11 +35,11 @@
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
+import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
-@OptIn(ExperimentalFoundationApi::class)
@MediumTest
@RunWith(Parameterized::class)
class LazyStaggeredGridScrollTest(
@@ -59,20 +60,23 @@
private var itemSizeDp = Dp.Unspecified
private val itemCount = 100
+ @Before
+ fun initSizes() {
+ itemSizeDp = with(rule.density) {
+ itemSizePx.toDp()
+ }
+ }
+
fun setContent(
containerSizePx: Int = itemSizePx * 5,
afterContentPaddingPx: Int = 0
) {
- itemSizeDp = with(rule.density) {
- itemSizePx.toDp()
- }
rule.setContent {
state = rememberLazyStaggeredGridState()
with(rule.density) {
TestContent(containerSizePx.toDp(), afterContentPaddingPx.toDp())
}
}
- rule.waitForIdle()
}
@Test
@@ -353,6 +357,41 @@
}
}
+ @Test
+ fun scrollBy_emptyItemWithSpacing() {
+ val state = LazyStaggeredGridState()
+ val spacingDp = with(rule.density) { 10.toDp() }
+ rule.setContent {
+ LazyStaggeredGrid(
+ lanes = 1,
+ state = state,
+ mainAxisSpacing = spacingDp,
+ modifier = Modifier.size(itemSizeDp),
+ ) {
+ item {
+ Box(Modifier.size(itemSizeDp).testTag("0").debugBorder())
+ }
+ item { } // empty item surrounded by spacings
+ item {
+ Box(Modifier.size(itemSizeDp).testTag("2").debugBorder())
+ }
+ }
+ }
+ rule.runOnIdle {
+ runBlocking(AutoTestFrameClock()) {
+ // empty item introduces two spacings 10 pixels each
+ // so after this operation item 2 is right at the edge of the viewport
+ state.scrollBy(20f)
+ // then we do some extra scrolling to make item 2 visible
+ state.scrollBy(20f)
+ }
+ }
+ rule.onNodeWithTag("2")
+ .assertMainAxisStartPositionInRootIsEqualTo(
+ itemSizeDp - with(rule.density) { 20.toDp() }
+ )
+ }
+
@Composable
private fun TestContent(containerSizeDp: Dp, afterContentPaddingDp: Dp) {
// |-|-|
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/AndroidExternalSurfaceTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/AndroidExternalSurfaceTest.kt
index d1097a6..4d0efc2 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/AndroidExternalSurfaceTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/AndroidExternalSurfaceTest.kt
@@ -273,6 +273,13 @@
}
@Test
+ @Ignore(
+ """Despite best efforts in screenshotToImage(), this test is too flaky currently.
+ |Since this test only tests that the `zOrder` parameter is properly passed
+ |to the underlying SurfaceView, we don't lose much by disabling it.
+ |This test should be more robust on API level 34 using the window
+ |screenshot API."""
+ )
fun testZOrderDefault() {
val latch = CountDownLatch(FrameCount)
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/HandwritingDetectorTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/HandwritingDetectorTest.kt
index 79686c2..3de2da2 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/HandwritingDetectorTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/HandwritingDetectorTest.kt
@@ -16,8 +16,12 @@
package androidx.compose.foundation.text.input
+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.safeContentPadding
+import androidx.compose.foundation.text.handwriting.HandwritingBoundsVerticalOffset
import androidx.compose.foundation.text.handwriting.handwritingDetector
import androidx.compose.foundation.text.handwriting.isStylusHandwritingSupported
import androidx.compose.foundation.text.performStylusClick
@@ -48,7 +52,9 @@
private val imm = FakeInputMethodManager()
- private val tag = "detector"
+ private val detectorTag = "detector"
+ private val insideSpacerTag = "inside"
+ private val outsideSpacerTag = "outside"
private var callbackCount = 0
@@ -62,39 +68,72 @@
callbackCount = 0
rule.setContent {
- Spacer(
- modifier = Modifier
- .fillMaxSize()
- .handwritingDetector { callbackCount++ }
- .testTag(tag)
- )
+ Column(Modifier.safeContentPadding()) {
+ Spacer(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(HandwritingBoundsVerticalOffset)
+ .handwritingDetector { callbackCount++ }
+ .testTag(detectorTag)
+ )
+ // This spacer is within the extended handwriting bounds of the detector
+ Spacer(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(HandwritingBoundsVerticalOffset)
+ .testTag(insideSpacerTag)
+ )
+ // This spacer is outside the extended handwriting bounds of the detector
+ Spacer(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(HandwritingBoundsVerticalOffset)
+ .testTag(outsideSpacerTag)
+ )
+ }
}
}
@Test
fun detector_handwriting_preparesDelegation() {
- rule.onNodeWithTag(tag).performStylusHandwriting()
+ rule.onNodeWithTag(detectorTag).performStylusHandwriting()
assertHandwritingDelegationPrepared()
}
@Test
+ fun detector_handwritingInExtendedBounds_preparesDelegation() {
+ // This spacer is within the extended handwriting bounds of the detector
+ rule.onNodeWithTag(insideSpacerTag).performStylusHandwriting()
+
+ assertHandwritingDelegationPrepared()
+ }
+
+ @Test
+ fun detector_handwritingOutsideExtendedBounds_notPreparesDelegation() {
+ // This spacer is outside the extended handwriting bounds of the detector
+ rule.onNodeWithTag(outsideSpacerTag).performStylusHandwriting()
+
+ assertHandwritingDelegationNotPrepared()
+ }
+
+ @Test
fun detector_click_notPreparesDelegation() {
- rule.onNodeWithTag(tag).performStylusClick()
+ rule.onNodeWithTag(detectorTag).performStylusClick()
assertHandwritingDelegationNotPrepared()
}
@Test
fun detector_longClick_notPreparesDelegation() {
- rule.onNodeWithTag(tag).performStylusLongClick()
+ rule.onNodeWithTag(detectorTag).performStylusLongClick()
assertHandwritingDelegationNotPrepared()
}
@Test
fun detector_longPressAndDrag_notPreparesDelegation() {
- rule.onNodeWithTag(tag).performStylusLongPressAndDrag()
+ rule.onNodeWithTag(detectorTag).performStylusLongPressAndDrag()
assertHandwritingDelegationNotPrepared()
}
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/handwriting/HandwritingDetector.android.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/handwriting/HandwritingDetector.android.kt
index a6ac8fc..ed44b5e 100644
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/handwriting/HandwritingDetector.android.kt
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/handwriting/HandwritingDetector.android.kt
@@ -17,6 +17,7 @@
package androidx.compose.foundation.text.handwriting
import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.text.input.internal.ComposeInputMethodManager
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.pointer.PointerEvent
@@ -52,7 +53,15 @@
* @sample androidx.compose.foundation.samples.HandwritingDetectorSample
*/
fun Modifier.handwritingDetector(callback: () -> Unit) =
- if (isStylusHandwritingSupported) then(HandwritingDetectorElement(callback)) else this
+ if (isStylusHandwritingSupported) {
+ then(HandwritingDetectorElement(callback))
+ .padding(
+ horizontal = HandwritingBoundsHorizontalOffset,
+ vertical = HandwritingBoundsVerticalOffset
+ )
+ } else {
+ this
+ }
private class HandwritingDetectorElement(
private val callback: () -> Unit
@@ -92,9 +101,9 @@
pointerInputNode.onCancelPointerInput()
}
- val pointerInputNode = delegate(StylusHandwritingNode {
+ val pointerInputNode = delegate(StylusHandwritingNodeWithNegativePadding {
callback()
composeImm.prepareStylusHandwritingDelegation()
- return@StylusHandwritingNode true
+ return@StylusHandwritingNodeWithNegativePadding true
})
}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridMeasure.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridMeasure.kt
index 5928a6e..886f376 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridMeasure.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridMeasure.kt
@@ -960,10 +960,6 @@
val mainAxisOffset = itemScrollOffsets.maxInRange(spanRange)
val crossAxisOffset = resolvedSlots.positions[laneIndex]
- if (item.placeablesCount == 0) {
- // nothing to place, ignore spacings
- continue
- }
item.position(
mainAxis = mainAxisOffset,
crossAxis = crossAxisOffset,
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/handwriting/StylusHandwriting.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/handwriting/StylusHandwriting.kt
index 381dbbf..4909b8f 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/handwriting/StylusHandwriting.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/handwriting/StylusHandwriting.kt
@@ -89,7 +89,7 @@
* same.
* Note: this node is a temporary solution, ideally we don't need it.
*/
-private class StylusHandwritingNodeWithNegativePadding(
+internal class StylusHandwritingNodeWithNegativePadding(
onHandwritingSlopExceeded: () -> Boolean
) : StylusHandwritingNode(onHandwritingSlopExceeded), LayoutModifierNode {
override fun MeasureScope.measure(
diff --git a/compose/integration-tests/macrobenchmark-target/src/main/AndroidManifest.xml b/compose/integration-tests/macrobenchmark-target/src/main/AndroidManifest.xml
index cccbd34..0eadc27 100644
--- a/compose/integration-tests/macrobenchmark-target/src/main/AndroidManifest.xml
+++ b/compose/integration-tests/macrobenchmark-target/src/main/AndroidManifest.xml
@@ -266,6 +266,7 @@
</intent-filter>
<intent-filter>
<action android:name="androidx.compose.integration.macrobenchmark.target.CROSSFADE_ACTIVITY" />
+ <category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
diff --git a/compose/material/material-icons-extended/build.gradle b/compose/material/material-icons-extended/build.gradle
index 324023b..01a08c0 100644
--- a/compose/material/material-icons-extended/build.gradle
+++ b/compose/material/material-icons-extended/build.gradle
@@ -136,3 +136,15 @@
android {
namespace "androidx.compose.material.icons.extended"
}
+
+afterEvaluate {
+ // Workaround for b/337776938
+ if (!project.hasProperty("android.injected.invoked.from.ide")) {
+ tasks.named("lintAnalyzeDebugAndroidTest").configure {
+ it.dependsOn("generateTestFilesDebugAndroidTest")
+ }
+ tasks.named("generateDebugAndroidTestLintModel").configure {
+ it.dependsOn("generateTestFilesDebugAndroidTest")
+ }
+ }
+}
diff --git a/compose/material/material/src/androidInstrumentedTest/kotlin/androidx/compose/material/SnackbarTest.kt b/compose/material/material/src/androidInstrumentedTest/kotlin/androidx/compose/material/SnackbarTest.kt
index 0f50354..0df30f3 100644
--- a/compose/material/material/src/androidInstrumentedTest/kotlin/androidx/compose/material/SnackbarTest.kt
+++ b/compose/material/material/src/androidInstrumentedTest/kotlin/androidx/compose/material/SnackbarTest.kt
@@ -18,6 +18,7 @@
import android.os.Build
import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.sizeIn
import androidx.compose.foundation.shape.CutCornerShape
import androidx.compose.runtime.CompositionLocalProvider
@@ -95,6 +96,29 @@
}
@Test
+ fun snackbar_emptyContent() {
+ rule.setMaterialContent {
+ Snackbar {
+ // empty content should not crash
+ }
+ }
+ }
+
+ @Test
+ fun snackbar_noTextInContent() {
+ val snackbarHeight = 48.dp
+ val contentSize = 20.dp
+ rule.setMaterialContent {
+ Snackbar {
+ // non-text content should not crash
+ Box(Modifier.testTag("content").size(contentSize))
+ }
+ }
+ rule.onNodeWithTag("content")
+ .assertTopPositionInRootIsEqualTo((snackbarHeight - contentSize) / 2)
+ }
+
+ @Test
fun snackbar_shortTextOnly_defaultSizes() {
val snackbar = rule.setMaterialContentForSizeAssertions(
parentMaxWidth = 300.dp
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Snackbar.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Snackbar.kt
index 29673c2..2e490ec 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Snackbar.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Snackbar.kt
@@ -32,10 +32,12 @@
import androidx.compose.ui.layout.FirstBaseline
import androidx.compose.ui.layout.LastBaseline
import androidx.compose.ui.layout.Layout
+import androidx.compose.ui.layout.Placeable
import androidx.compose.ui.layout.layoutId
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastFirst
+import androidx.compose.ui.util.fastForEach
import kotlin.math.max
/**
@@ -245,25 +247,42 @@
content()
}
}) { measurables, constraints ->
- require(measurables.size == 1) {
- "text for Snackbar expected to have exactly only one child"
+ val textPlaceables = ArrayList<Placeable>(measurables.size)
+ var firstBaseline = AlignmentLine.Unspecified
+ var lastBaseline = AlignmentLine.Unspecified
+ var height = 0
+
+ measurables.fastForEach {
+ val placeable = it.measure(constraints)
+ textPlaceables.add(placeable)
+ if (placeable[FirstBaseline] != AlignmentLine.Unspecified &&
+ (firstBaseline == AlignmentLine.Unspecified ||
+ placeable[FirstBaseline] < firstBaseline)) {
+ firstBaseline = placeable[FirstBaseline]
+ }
+ if (placeable[LastBaseline] != AlignmentLine.Unspecified &&
+ (lastBaseline == AlignmentLine.Unspecified ||
+ placeable[LastBaseline] > lastBaseline)) {
+ lastBaseline = placeable[LastBaseline]
+ }
+ height = max(height, placeable.height)
}
- val textPlaceable = measurables.first().measure(constraints)
- val firstBaseline = textPlaceable[FirstBaseline]
- val lastBaseline = textPlaceable[LastBaseline]
- require(firstBaseline != AlignmentLine.Unspecified) { "No baselines for text" }
- require(lastBaseline != AlignmentLine.Unspecified) { "No baselines for text" }
+
+ val hasText = firstBaseline != AlignmentLine.Unspecified &&
+ lastBaseline != AlignmentLine.Unspecified
val minHeight =
- if (firstBaseline == lastBaseline) {
+ if (firstBaseline == lastBaseline || !hasText) {
SnackbarMinHeightOneLine
} else {
SnackbarMinHeightTwoLines
}
- val containerHeight = max(minHeight.roundToPx(), textPlaceable.height)
+ val containerHeight = max(minHeight.roundToPx(), height)
layout(constraints.maxWidth, containerHeight) {
- val textPlaceY = (containerHeight - textPlaceable.height) / 2
- textPlaceable.placeRelative(0, textPlaceY)
+ textPlaceables.fastForEach {
+ val textPlaceY = (containerHeight - it.height) / 2
+ it.placeRelative(0, textPlaceY)
+ }
}
}
}
@@ -316,10 +335,10 @@
)
val firstTextBaseline = textPlaceable[FirstBaseline]
- require(firstTextBaseline != AlignmentLine.Unspecified) { "No baselines for text" }
val lastTextBaseline = textPlaceable[LastBaseline]
- require(lastTextBaseline != AlignmentLine.Unspecified) { "No baselines for text" }
- val isOneLine = firstTextBaseline == lastTextBaseline
+ val hasText = firstTextBaseline != AlignmentLine.Unspecified &&
+ lastTextBaseline != AlignmentLine.Unspecified
+ val isOneLine = firstTextBaseline == lastTextBaseline || !hasText
val buttonPlaceX = constraints.maxWidth - buttonPlaceable.width
val textPlaceY: Int
diff --git a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/DismissibleNavigationDrawerTest.kt b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/DismissibleNavigationDrawerTest.kt
index 9aa622d..571508f 100644
--- a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/DismissibleNavigationDrawerTest.kt
+++ b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/DismissibleNavigationDrawerTest.kt
@@ -29,6 +29,10 @@
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.layout.LayoutCoordinates
+import androidx.compose.ui.layout.onGloballyPositioned
+import androidx.compose.ui.layout.positionOnScreen
+import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.semantics.SemanticsActions
@@ -47,6 +51,7 @@
import androidx.compose.ui.test.performTouchInput
import androidx.compose.ui.test.swipeLeft
import androidx.compose.ui.test.swipeRight
+import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -188,50 +193,68 @@
@Test
fun dismissibleNavigationDrawer_testOffset_customWidthLarger_whenOpen() {
- val customWidth = NavigationDrawerWidth + 5.dp
+ val customWidth = NavigationDrawerWidth + 20.dp
+ val density = Density(0.5f)
+ lateinit var coords: LayoutCoordinates
rule.setMaterialContent(lightColorScheme()) {
- val drawerState = rememberDrawerState(DrawerValue.Open)
- DismissibleNavigationDrawer(
- drawerState = drawerState,
- drawerContent = {
- DismissibleDrawerSheet(Modifier.width(customWidth)) {
- Box(
- Modifier
- .fillMaxSize()
- .testTag("content")
- )
- }
- },
- content = {}
- )
+ // Reduce density to ensure wide drawer fits on screen
+ CompositionLocalProvider(LocalDensity provides density) {
+ val drawerState = rememberDrawerState(DrawerValue.Open)
+ DismissibleNavigationDrawer(
+ drawerState = drawerState,
+ drawerContent = {
+ DismissibleDrawerSheet(Modifier.width(customWidth)) {
+ Box(
+ Modifier
+ .fillMaxSize()
+ .onGloballyPositioned {
+ coords = it
+ }
+ )
+ }
+ },
+ content = {}
+ )
+ }
}
- rule.onNodeWithTag("content")
- .assertLeftPositionInRootIsEqualTo(0.dp)
+ rule.runOnIdle {
+ assertThat(coords.positionOnScreen().x).isEqualTo(0f)
+ }
}
@Test
fun dismissibleNavigationDrawer_testOffset_customWidthLarger_whenClosed() {
- val customWidth = NavigationDrawerWidth + 5.dp
+ val customWidth = NavigationDrawerWidth + 20.dp
+ val density = Density(0.5f)
+ lateinit var coords: LayoutCoordinates
rule.setMaterialContent(lightColorScheme()) {
- val drawerState = rememberDrawerState(DrawerValue.Closed)
- DismissibleNavigationDrawer(
- drawerState = drawerState,
- drawerContent = {
- DismissibleDrawerSheet(Modifier.width(customWidth)) {
- Box(
- Modifier
- .fillMaxSize()
- .testTag("content")
- )
- }
- },
- content = {}
- )
+ // Reduce density to ensure wide drawer fits on screen
+ CompositionLocalProvider(LocalDensity provides density) {
+ val drawerState = rememberDrawerState(DrawerValue.Closed)
+ DismissibleNavigationDrawer(
+ drawerState = drawerState,
+ drawerContent = {
+ DismissibleDrawerSheet(Modifier.width(customWidth)) {
+ Box(
+ Modifier
+ .fillMaxSize()
+ .onGloballyPositioned {
+ coords = it
+ }
+ )
+ }
+ },
+ content = {}
+ )
+ }
}
- rule.onNodeWithTag("content")
- .assertLeftPositionInRootIsEqualTo(-customWidth)
+ rule.runOnIdle {
+ with(density) {
+ assertThat(coords.positionOnScreen().x).isWithin(1f).of(-customWidth.toPx())
+ }
+ }
}
@Test
@@ -416,12 +439,16 @@
// When the drawer state is set to Opened
drawerState.snapTo(DrawerValue.Open)
// Then the drawer should be opened
- assertThat(drawerState.currentValue).isEqualTo(DrawerValue.Open)
+ rule.runOnIdle {
+ assertThat(drawerState.currentValue).isEqualTo(DrawerValue.Open)
+ }
// When the drawer state is set to Closed
drawerState.snapTo(DrawerValue.Closed)
// Then the drawer should be closed
- assertThat(drawerState.currentValue).isEqualTo(DrawerValue.Closed)
+ rule.runOnIdle {
+ assertThat(drawerState.currentValue).isEqualTo(DrawerValue.Closed)
+ }
}
@Test
diff --git a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ModalNavigationDrawerTest.kt b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ModalNavigationDrawerTest.kt
index 587382f..97f3407 100644
--- a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ModalNavigationDrawerTest.kt
+++ b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ModalNavigationDrawerTest.kt
@@ -29,6 +29,10 @@
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.layout.LayoutCoordinates
+import androidx.compose.ui.layout.onGloballyPositioned
+import androidx.compose.ui.layout.positionOnScreen
+import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.semantics.SemanticsActions
@@ -49,6 +53,7 @@
import androidx.compose.ui.test.performTouchInput
import androidx.compose.ui.test.swipeLeft
import androidx.compose.ui.test.swipeRight
+import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -167,50 +172,68 @@
@Test
fun navigationDrawer_testOffset_customWidthLarger_whenOpen() {
- val customWidth = NavigationDrawerWidth + 5.dp
+ val customWidth = NavigationDrawerWidth + 20.dp
+ val density = Density(0.5f)
+ lateinit var coords: LayoutCoordinates
rule.setMaterialContent(lightColorScheme()) {
- val drawerState = rememberDrawerState(DrawerValue.Open)
- ModalNavigationDrawer(
- drawerState = drawerState,
- drawerContent = {
- ModalDrawerSheet(Modifier.width(customWidth)) {
- Box(
- Modifier
- .fillMaxSize()
- .testTag("content")
- )
- }
- },
- content = {}
- )
+ // Reduce density to ensure wide drawer fits on screen
+ CompositionLocalProvider(LocalDensity provides density) {
+ val drawerState = rememberDrawerState(DrawerValue.Open)
+ ModalNavigationDrawer(
+ drawerState = drawerState,
+ drawerContent = {
+ ModalDrawerSheet(Modifier.width(customWidth)) {
+ Box(
+ Modifier
+ .fillMaxSize()
+ .onGloballyPositioned {
+ coords = it
+ }
+ )
+ }
+ },
+ content = {}
+ )
+ }
}
- rule.onNodeWithTag("content")
- .assertLeftPositionInRootIsEqualTo(0.dp)
+ rule.runOnIdle {
+ assertThat(coords.positionOnScreen().x).isEqualTo(0f)
+ }
}
@Test
fun navigationDrawer_testOffset_customWidthLarger_whenClosed() {
- val customWidth = NavigationDrawerWidth + 5.dp
+ val customWidth = NavigationDrawerWidth + 20.dp
+ val density = Density(0.5f)
+ lateinit var coords: LayoutCoordinates
rule.setMaterialContent(lightColorScheme()) {
- val drawerState = rememberDrawerState(DrawerValue.Closed)
- ModalNavigationDrawer(
- drawerState = drawerState,
- drawerContent = {
- ModalDrawerSheet(Modifier.width(customWidth)) {
- Box(
- Modifier
- .fillMaxSize()
- .testTag("content")
- )
- }
- },
- content = {}
- )
+ // Reduce density to ensure wide drawer fits on screen
+ CompositionLocalProvider(LocalDensity provides density) {
+ val drawerState = rememberDrawerState(DrawerValue.Closed)
+ ModalNavigationDrawer(
+ drawerState = drawerState,
+ drawerContent = {
+ ModalDrawerSheet(Modifier.width(customWidth)) {
+ Box(
+ Modifier
+ .fillMaxSize()
+ .onGloballyPositioned {
+ coords = it
+ }
+ )
+ }
+ },
+ content = {}
+ )
+ }
}
- rule.onNodeWithTag("content")
- .assertLeftPositionInRootIsEqualTo(-customWidth)
+ rule.runOnIdle {
+ with(density) {
+ assertThat(coords.positionOnScreen().x).isWithin(1f).of(-customWidth.toPx())
+ }
+ }
}
@Test
@@ -419,12 +442,16 @@
// When the drawer state is set to Opened
drawerState.snapTo(DrawerValue.Open)
// Then the drawer should be opened
- assertThat(drawerState.currentValue).isEqualTo(DrawerValue.Open)
+ rule.runOnIdle {
+ assertThat(drawerState.currentValue).isEqualTo(DrawerValue.Open)
+ }
// When the drawer state is set to Closed
drawerState.snapTo(DrawerValue.Closed)
// Then the drawer should be closed
- assertThat(drawerState.currentValue).isEqualTo(DrawerValue.Closed)
+ rule.runOnIdle {
+ assertThat(drawerState.currentValue).isEqualTo(DrawerValue.Closed)
+ }
}
@Test
diff --git a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/SnackbarTest.kt b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/SnackbarTest.kt
index 50e761f..38861c1 100644
--- a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/SnackbarTest.kt
+++ b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/SnackbarTest.kt
@@ -17,6 +17,7 @@
package androidx.compose.material3
import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.sizeIn
import androidx.compose.material3.internal.Strings
import androidx.compose.material3.internal.getString
@@ -88,6 +89,29 @@
}
@Test
+ fun snackbar_emptyContent() {
+ rule.setMaterialContent(lightColorScheme()) {
+ Snackbar {
+ // empty content should not crash
+ }
+ }
+ }
+
+ @Test
+ fun snackbar_noTextInContent() {
+ val snackbarHeight = 48.dp
+ val contentSize = 20.dp
+ rule.setMaterialContent(lightColorScheme()) {
+ Snackbar {
+ // non-text content should not crash
+ Box(Modifier.testTag("content").size(contentSize))
+ }
+ }
+ rule.onNodeWithTag("content")
+ .assertTopPositionInRootIsEqualTo((snackbarHeight - contentSize) / 2)
+ }
+
+ @Test
fun snackbar_withDismiss_semantics() {
var clicked = false
val snackbarVisuals =
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Snackbar.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Snackbar.kt
index 644850d5..ed987ae 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Snackbar.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Snackbar.kt
@@ -119,29 +119,21 @@
val actionTextStyle = SnackbarTokens.ActionLabelTextFont.value
CompositionLocalProvider(LocalTextStyle provides textStyle) {
when {
- action == null -> OneRowSnackbar(
+ actionOnNewLine && action != null -> NewLineButtonSnackbar(
text = content,
- action = null,
+ action = action,
dismissAction = dismissAction,
- actionTextStyle,
- actionContentColor,
- dismissActionContentColor
- )
- actionOnNewLine -> NewLineButtonSnackbar(
- content,
- action,
- dismissAction,
- actionTextStyle,
- actionContentColor,
- dismissActionContentColor
+ actionTextStyle = actionTextStyle,
+ actionContentColor = actionContentColor,
+ dismissActionContentColor = dismissActionContentColor,
)
else -> OneRowSnackbar(
text = content,
action = action,
dismissAction = dismissAction,
- actionTextStyle,
- actionContentColor,
- dismissActionContentColor
+ actionTextStyle = actionTextStyle,
+ actionTextColor = actionContentColor,
+ dismissActionColor = dismissActionContentColor,
)
}
}
@@ -353,10 +345,10 @@
)
val firstTextBaseline = textPlaceable[FirstBaseline]
- require(firstTextBaseline != AlignmentLine.Unspecified) { "No baselines for text" }
val lastTextBaseline = textPlaceable[LastBaseline]
- require(lastTextBaseline != AlignmentLine.Unspecified) { "No baselines for text" }
- val isOneLine = firstTextBaseline == lastTextBaseline
+ val hasText = firstTextBaseline != AlignmentLine.Unspecified &&
+ lastTextBaseline != AlignmentLine.Unspecified
+ val isOneLine = firstTextBaseline == lastTextBaseline || !hasText
val dismissButtonPlaceX = containerWidth - dismissButtonWidth
val actionButtonPlaceX = dismissButtonPlaceX - actionButtonWidth
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/BroadcastFrameClock.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/BroadcastFrameClock.kt
index df5f37a..95ea8e8 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/BroadcastFrameClock.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/BroadcastFrameClock.kt
@@ -82,14 +82,13 @@
override suspend fun <R> withFrameNanos(
onFrame: (Long) -> R
): R = suspendCancellableCoroutine { co ->
- lateinit var awaiter: FrameAwaiter<R>
+ val awaiter = FrameAwaiter(onFrame, co)
val hasNewAwaiters = synchronized(lock) {
val cause = failureCause
if (cause != null) {
co.resumeWithException(cause)
return@suspendCancellableCoroutine
}
- awaiter = FrameAwaiter(onFrame, co)
val hadAwaiters = awaiters.isNotEmpty()
awaiters.add(awaiter)
if (!hadAwaiters) hasAwaitersUnlocked.set(1)
diff --git a/compose/ui/ui-text/lint-baseline.xml b/compose/ui/ui-text/lint-baseline.xml
index e34eca2..d8802c0 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.3.0-beta01" type="baseline" client="gradle" dependencies="false" name="AGP (8.3.0-beta01)" variant="all" version="8.3.0-beta01">
+<issues format="6" by="lint 8.5.0-alpha03" type="baseline" client="gradle" dependencies="false" name="AGP (8.5.0-alpha03)" variant="all" version="8.5.0-alpha03">
<issue
id="NewApi"
@@ -12,7 +12,7 @@
<issue
id="NewApi"
- message="Cast from `BlendMode` to `Comparable` requires API level 29 (current min is 21)"
+ message="Implicit cast from `BlendMode` to `Comparable` requires API level 29 (current min is 21)"
errorLine1=" assertThat(paragraph.textPaint.blendMode).isEqualTo(BlendMode.SrcOver)"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
@@ -30,7 +30,7 @@
<issue
id="NewApi"
- message="Cast from `BlendMode` to `Comparable` requires API level 29 (current min is 21)"
+ message="Implicit cast from `BlendMode` to `Comparable` requires API level 29 (current min is 21)"
errorLine1=" assertThat(paragraph.textPaint.blendMode).isEqualTo(BlendMode.SrcOver)"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
@@ -48,7 +48,7 @@
<issue
id="NewApi"
- message="Cast from `BlendMode` to `Comparable` requires API level 29 (current min is 21)"
+ message="Implicit cast from `BlendMode` to `Comparable` requires API level 29 (current min is 21)"
errorLine1=" assertThat(textPaint.blendMode).isEqualTo(BlendMode.SrcOver)"
errorLine2=" ~~~~~~~~~~~~~~~~~~~">
<location
@@ -75,7 +75,7 @@
<issue
id="NewApi"
- message="Cast from `BlendMode` to `Comparable` requires API level 29 (current min is 21)"
+ message="Implicit cast from `BlendMode` to `Comparable` requires API level 29 (current min is 21)"
errorLine1=" assertThat(textPaint.blendMode).isEqualTo(BlendMode.DstOver)"
errorLine2=" ~~~~~~~~~~~~~~~~~~~">
<location
diff --git a/compose/ui/ui-unit/api/current.txt b/compose/ui/ui-unit/api/current.txt
index 8af3326..3e20eda 100644
--- a/compose/ui/ui-unit/api/current.txt
+++ b/compose/ui/ui-unit/api/current.txt
@@ -132,8 +132,8 @@
method public long copy(optional float x, optional float y);
method public float getX();
method public float getY();
- method @androidx.compose.runtime.Stable public inline operator long minus(long other);
- method @androidx.compose.runtime.Stable public inline operator long plus(long other);
+ method @androidx.compose.runtime.Stable public operator long minus(long other);
+ method @androidx.compose.runtime.Stable public operator long plus(long other);
property @androidx.compose.runtime.Stable public final float x;
property @androidx.compose.runtime.Stable public final float y;
field public static final androidx.compose.ui.unit.DpOffset.Companion Companion;
@@ -176,8 +176,8 @@
method @androidx.compose.runtime.Stable public operator long div(int other);
method public float getHeight();
method public float getWidth();
- method @androidx.compose.runtime.Stable public inline operator long minus(long other);
- method @androidx.compose.runtime.Stable public inline operator long plus(long other);
+ method @androidx.compose.runtime.Stable public operator long minus(long other);
+ method @androidx.compose.runtime.Stable public operator long plus(long other);
method @androidx.compose.runtime.Stable public operator long times(float other);
method @androidx.compose.runtime.Stable public operator long times(int other);
property @androidx.compose.runtime.Stable public final float height;
diff --git a/compose/ui/ui-unit/api/restricted_current.txt b/compose/ui/ui-unit/api/restricted_current.txt
index d3e260a..7d41176 100644
--- a/compose/ui/ui-unit/api/restricted_current.txt
+++ b/compose/ui/ui-unit/api/restricted_current.txt
@@ -132,8 +132,8 @@
method public long copy(optional float x, optional float y);
method public float getX();
method public float getY();
- method @androidx.compose.runtime.Stable public inline operator long minus(long other);
- method @androidx.compose.runtime.Stable public inline operator long plus(long other);
+ method @androidx.compose.runtime.Stable public operator long minus(long other);
+ method @androidx.compose.runtime.Stable public operator long plus(long other);
property @androidx.compose.runtime.Stable public final float x;
property @androidx.compose.runtime.Stable public final float y;
field public static final androidx.compose.ui.unit.DpOffset.Companion Companion;
@@ -176,8 +176,8 @@
method @androidx.compose.runtime.Stable public operator long div(int other);
method public float getHeight();
method public float getWidth();
- method @androidx.compose.runtime.Stable public inline operator long minus(long other);
- method @androidx.compose.runtime.Stable public inline operator long plus(long other);
+ method @androidx.compose.runtime.Stable public operator long minus(long other);
+ method @androidx.compose.runtime.Stable public operator long plus(long other);
method @androidx.compose.runtime.Stable public operator long times(float other);
method @androidx.compose.runtime.Stable public operator long times(int other);
property @androidx.compose.runtime.Stable public final float height;
diff --git a/compose/ui/ui-unit/src/commonMain/kotlin/androidx/compose/ui/unit/Dp.kt b/compose/ui/ui-unit/src/commonMain/kotlin/androidx/compose/ui/unit/Dp.kt
index 5c5102f..e2d9797 100644
--- a/compose/ui/ui-unit/src/commonMain/kotlin/androidx/compose/ui/unit/Dp.kt
+++ b/compose/ui/ui-unit/src/commonMain/kotlin/androidx/compose/ui/unit/Dp.kt
@@ -269,21 +269,29 @@
* Returns a copy of this [DpOffset] instance optionally overriding the
* x or y parameter
*/
- fun copy(x: Dp = this.x, y: Dp = this.y): DpOffset = DpOffset(x, y)
+ fun copy(x: Dp = this.x, y: Dp = this.y): DpOffset = DpOffset(packFloats(x.value, y.value))
/**
* Subtract a [DpOffset] from another one.
*/
@Stable
- inline operator fun minus(other: DpOffset) =
- DpOffset(x - other.x, y - other.y)
+ operator fun minus(other: DpOffset) = DpOffset(
+ packFloats(
+ (x - other.x).value,
+ (y - other.y).value
+ )
+ )
/**
* Add a [DpOffset] to another one.
*/
@Stable
- inline operator fun plus(other: DpOffset) =
- DpOffset(x + other.x, y + other.y)
+ operator fun plus(other: DpOffset) = DpOffset(
+ packFloats(
+ (x + other.x).value,
+ (y + other.y).value
+ )
+ )
@Stable
override fun toString(): String =
@@ -297,14 +305,14 @@
/**
* A [DpOffset] with 0 DP [x] and 0 DP [y] values.
*/
- val Zero = DpOffset(0.dp, 0.dp)
+ val Zero = DpOffset(0x0L)
/**
* Represents an offset whose [x] and [y] are unspecified. This is usually a replacement for
* `null` when a primitive value is desired.
* Access to [x] or [y] on an unspecified offset is not allowed.
*/
- val Unspecified = DpOffset(Dp.Unspecified, Dp.Unspecified)
+ val Unspecified = DpOffset(0x7fc00000_7fc00000L)
}
}
@@ -342,7 +350,12 @@
*/
@Stable
fun lerp(start: DpOffset, stop: DpOffset, fraction: Float): DpOffset =
- DpOffset(lerp(start.x, stop.x, fraction), lerp(start.y, stop.y, fraction))
+ DpOffset(
+ packFloats(
+ lerp(start.x.value, stop.x.value, fraction),
+ lerp(start.y.value, stop.y.value, fraction)
+ )
+ )
/**
* Constructs a [DpSize] from [width] and [height] [Dp] values.
@@ -372,21 +385,33 @@
* Returns a copy of this [DpSize] instance optionally overriding the
* width or height parameter
*/
- fun copy(width: Dp = this.width, height: Dp = this.height): DpSize = DpSize(width, height)
+ fun copy(width: Dp = this.width, height: Dp = this.height): DpSize = DpSize(
+ packFloats(width.value, height.value)
+ )
/**
* Subtract a [DpSize] from another one.
*/
@Stable
- inline operator fun minus(other: DpSize) =
- DpSize(width - other.width, height - other.height)
+ operator fun minus(other: DpSize) =
+ DpSize(
+ packFloats(
+ (width - other.width).value,
+ (height - other.height).value
+ )
+ )
/**
* Add a [DpSize] to another one.
*/
@Stable
- inline operator fun plus(other: DpSize) =
- DpSize(width + other.width, height + other.height)
+ operator fun plus(other: DpSize) =
+ DpSize(
+ packFloats(
+ (width + other.width).value,
+ (height + other.height).value
+ )
+ )
@Stable
inline operator fun component1(): Dp = width
@@ -395,16 +420,36 @@
inline operator fun component2(): Dp = height
@Stable
- operator fun times(other: Int): DpSize = DpSize(width * other, height * other)
+ operator fun times(other: Int): DpSize = DpSize(
+ packFloats(
+ (width * other).value,
+ (height * other).value
+ )
+ )
@Stable
- operator fun times(other: Float): DpSize = DpSize(width * other, height * other)
+ operator fun times(other: Float): DpSize = DpSize(
+ packFloats(
+ (width * other).value,
+ (height * other).value
+ )
+ )
@Stable
- operator fun div(other: Int): DpSize = DpSize(width / other, height / other)
+ operator fun div(other: Int): DpSize = DpSize(
+ packFloats(
+ (width / other).value,
+ (height / other).value
+ )
+ )
@Stable
- operator fun div(other: Float): DpSize = DpSize(width / other, height / other)
+ operator fun div(other: Float): DpSize = DpSize(
+ packFloats(
+ (width / other).value,
+ (height / other).value
+ )
+ )
@Stable
override fun toString(): String =
@@ -418,14 +463,14 @@
/**
* A [DpSize] with 0 DP [width] and 0 DP [height] values.
*/
- val Zero = DpSize(0.dp, 0.dp)
+ val Zero = DpSize(0x0L)
/**
* A size whose [width] and [height] are unspecified. This is usually a replacement for
* `null` when a primitive value is desired.
* Access to [width] or [height] on an unspecified size is not allowed.
*/
- val Unspecified = DpSize(Dp.Unspecified, Dp.Unspecified)
+ val Unspecified = DpSize(0x7fc00000_7fc00000L)
}
}
@@ -456,7 +501,12 @@
*/
@Stable
val DpSize.center: DpOffset
- get() = DpOffset(width / 2f, height / 2f)
+ get() = DpOffset(
+ packFloats(
+ (width / 2f).value,
+ (height / 2f).value
+ )
+ )
@Stable
inline operator fun Int.times(size: DpSize) = size * this
@@ -476,7 +526,12 @@
*/
@Stable
fun lerp(start: DpSize, stop: DpSize, fraction: Float): DpSize =
- DpSize(lerp(start.width, stop.width, fraction), lerp(start.height, stop.height, fraction))
+ DpSize(
+ packFloats(
+ lerp(start.width, stop.width, fraction).value,
+ lerp(start.height, stop.height, fraction).value
+ )
+ )
/**
* A four dimensional bounds using [Dp] for units
diff --git a/compose/ui/ui/src/androidMain/res/values-hi/strings.xml b/compose/ui/ui/src/androidMain/res/values-hi/strings.xml
index be2db07..103ecfa 100644
--- a/compose/ui/ui/src/androidMain/res/values-hi/strings.xml
+++ b/compose/ui/ui/src/androidMain/res/values-hi/strings.xml
@@ -31,8 +31,7 @@
<string name="close_drawer" msgid="406453423630273620">"नेविगेशन मेन्यू बंद करें"</string>
<string name="close_sheet" msgid="7573152094250666567">"शीट बंद करें"</string>
<string name="default_error_message" msgid="8038256446254964252">"अमान्य इनपुट"</string>
- <!-- no translation found for state_empty (4139871816613051306) -->
- <skip />
+ <string name="state_empty" msgid="4139871816613051306">"कोई भी तार नहीं लगा है"</string>
<string name="default_popup_window_title" msgid="6312721426453364202">"पॉप-अप विंडो"</string>
<string name="range_start" msgid="7097486360902471446">"रेंज की शुरुआत"</string>
<string name="range_end" msgid="5941395253238309765">"रेंज की सीमा"</string>
diff --git a/compose/ui/ui/src/androidMain/res/values-km/strings.xml b/compose/ui/ui/src/androidMain/res/values-km/strings.xml
index 207a067..e10e501 100644
--- a/compose/ui/ui/src/androidMain/res/values-km/strings.xml
+++ b/compose/ui/ui/src/androidMain/res/values-km/strings.xml
@@ -31,8 +31,7 @@
<string name="close_drawer" msgid="406453423630273620">"បិទម៉ឺនុយរុករក"</string>
<string name="close_sheet" msgid="7573152094250666567">"បិទសន្លឹក"</string>
<string name="default_error_message" msgid="8038256446254964252">"ការបញ្ចូលមិនត្រឹមត្រូវ"</string>
- <!-- no translation found for state_empty (4139871816613051306) -->
- <skip />
+ <string name="state_empty" msgid="4139871816613051306">"ទទេ"</string>
<string name="default_popup_window_title" msgid="6312721426453364202">"វិនដូលោតឡើង"</string>
<string name="range_start" msgid="7097486360902471446">"ចំណុចចាប់ផ្ដើម"</string>
<string name="range_end" msgid="5941395253238309765">"ចំណុចបញ្ចប់"</string>
diff --git a/compose/ui/ui/src/androidMain/res/values-lo/strings.xml b/compose/ui/ui/src/androidMain/res/values-lo/strings.xml
index 95bccae..c201f11 100644
--- a/compose/ui/ui/src/androidMain/res/values-lo/strings.xml
+++ b/compose/ui/ui/src/androidMain/res/values-lo/strings.xml
@@ -31,8 +31,7 @@
<string name="close_drawer" msgid="406453423630273620">"ປິດເມນູການນຳທາງ"</string>
<string name="close_sheet" msgid="7573152094250666567">"ປິດຊີດ"</string>
<string name="default_error_message" msgid="8038256446254964252">"ຂໍ້ມູນທີ່ປ້ອນເຂົ້າບໍ່ຖືກຕ້ອງ"</string>
- <!-- no translation found for state_empty (4139871816613051306) -->
- <skip />
+ <string name="state_empty" msgid="4139871816613051306">"ຫວ່າງເປົ່າ"</string>
<string name="default_popup_window_title" msgid="6312721426453364202">"ໜ້າຈໍປັອບອັບ"</string>
<string name="range_start" msgid="7097486360902471446">"ເລີ່ມຕົ້ນໄລຍະ"</string>
<string name="range_end" msgid="5941395253238309765">"ສິ້ນສຸດໄລຍະ"</string>
diff --git a/compose/ui/ui/src/androidMain/res/values-my/strings.xml b/compose/ui/ui/src/androidMain/res/values-my/strings.xml
index 074defe..a44f8ed 100644
--- a/compose/ui/ui/src/androidMain/res/values-my/strings.xml
+++ b/compose/ui/ui/src/androidMain/res/values-my/strings.xml
@@ -31,8 +31,7 @@
<string name="close_drawer" msgid="406453423630273620">"လမ်းညွှန် မီနူး ပိတ်ရန်"</string>
<string name="close_sheet" msgid="7573152094250666567">"စာမျက်နှာ ပိတ်ရန်"</string>
<string name="default_error_message" msgid="8038256446254964252">"ထည့်သွင်းမှု မမှန်ကန်ပါ"</string>
- <!-- no translation found for state_empty (4139871816613051306) -->
- <skip />
+ <string name="state_empty" msgid="4139871816613051306">"မရှိပါ"</string>
<string name="default_popup_window_title" msgid="6312721426453364202">"ပေါ့ပ်အပ် ဝင်းဒိုး"</string>
<string name="range_start" msgid="7097486360902471446">"အပိုင်းအခြား အစ"</string>
<string name="range_end" msgid="5941395253238309765">"အပိုင်းအခြား အဆုံး"</string>
diff --git a/compose/ui/ui/src/androidMain/res/values-ne/strings.xml b/compose/ui/ui/src/androidMain/res/values-ne/strings.xml
index 7078d5c..f8a9002 100644
--- a/compose/ui/ui/src/androidMain/res/values-ne/strings.xml
+++ b/compose/ui/ui/src/androidMain/res/values-ne/strings.xml
@@ -31,8 +31,7 @@
<string name="close_drawer" msgid="406453423630273620">"नेभिगेसन मेनु बन्द गर्नुहोस्"</string>
<string name="close_sheet" msgid="7573152094250666567">"पाना बन्द गर्नुहोस्"</string>
<string name="default_error_message" msgid="8038256446254964252">"अवैद्य इन्पुट"</string>
- <!-- no translation found for state_empty (4139871816613051306) -->
- <skip />
+ <string name="state_empty" msgid="4139871816613051306">"खाली"</string>
<string name="default_popup_window_title" msgid="6312721426453364202">"पपअप विन्डो"</string>
<string name="range_start" msgid="7097486360902471446">"दायराको सुरुवात बिन्दु"</string>
<string name="range_end" msgid="5941395253238309765">"दायराको अन्तिम बिन्दु"</string>
diff --git a/compose/ui/ui/src/androidMain/res/values-pt-rPT/strings.xml b/compose/ui/ui/src/androidMain/res/values-pt-rPT/strings.xml
index e9061ec..be24761 100644
--- a/compose/ui/ui/src/androidMain/res/values-pt-rPT/strings.xml
+++ b/compose/ui/ui/src/androidMain/res/values-pt-rPT/strings.xml
@@ -31,8 +31,7 @@
<string name="close_drawer" msgid="406453423630273620">"Fechar menu de navegação"</string>
<string name="close_sheet" msgid="7573152094250666567">"Fechar folha"</string>
<string name="default_error_message" msgid="8038256446254964252">"Entrada inválida"</string>
- <!-- no translation found for state_empty (4139871816613051306) -->
- <skip />
+ <string name="state_empty" msgid="4139871816613051306">"Vazio"</string>
<string name="default_popup_window_title" msgid="6312721426453364202">"Janela pop-up"</string>
<string name="range_start" msgid="7097486360902471446">"Início do intervalo"</string>
<string name="range_end" msgid="5941395253238309765">"Fim do intervalo"</string>
diff --git a/compose/ui/ui/src/androidMain/res/values-sw/strings.xml b/compose/ui/ui/src/androidMain/res/values-sw/strings.xml
index a213605..98421b2 100644
--- a/compose/ui/ui/src/androidMain/res/values-sw/strings.xml
+++ b/compose/ui/ui/src/androidMain/res/values-sw/strings.xml
@@ -31,8 +31,7 @@
<string name="close_drawer" msgid="406453423630273620">"Funga menyu ya kusogeza"</string>
<string name="close_sheet" msgid="7573152094250666567">"Funga laha"</string>
<string name="default_error_message" msgid="8038256446254964252">"Ulichoweka si sahihi"</string>
- <!-- no translation found for state_empty (4139871816613051306) -->
- <skip />
+ <string name="state_empty" msgid="4139871816613051306">"Tupu"</string>
<string name="default_popup_window_title" msgid="6312721426453364202">"Dirisha Ibukizi"</string>
<string name="range_start" msgid="7097486360902471446">"Mwanzo wa masafa"</string>
<string name="range_end" msgid="5941395253238309765">"Mwisho wa masafa"</string>
diff --git a/development/update_studio.sh b/development/update_studio.sh
index ef74a284..324322f 100755
--- a/development/update_studio.sh
+++ b/development/update_studio.sh
@@ -7,8 +7,8 @@
# Versions that the user should update when running this script
echo Getting Studio version and link
-AGP_VERSION=${1:-8.4.0-alpha12}
-STUDIO_VERSION_STRING=${2:-"Android Studio Jellyfish | 2023.3.1 Canary 12"}
+AGP_VERSION=${1:-8.5.0-alpha06}
+STUDIO_VERSION_STRING=${2:-"Android Studio Koala | 2024.1.1 Canary 6"}
# Get studio version number from version name
STUDIO_IFRAME_LINK=`curl "https://developer.android.com/studio/archive.html" | grep "<iframe " | sed "s/.* src=\"\([^\"]*\)\".*/\1/g"`
diff --git a/gradle.properties b/gradle.properties
index 7a42aa7..68fc0ca 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -21,6 +21,7 @@
# fullsdk-linux/**/package.xml -> b/291331139
org.gradle.configuration-cache.inputs.unsafe.ignore.file-system-checks=**/prebuilts/fullsdk-linux;**/prebuilts/fullsdk-linux/platforms/android-*/package.xml
+android.javaCompile.suppressSourceTargetDeprecationWarning=true
android.lint.baselineOmitLineNumbers=true
android.lint.printStackTrace=true
android.builder.sdkDownload=false
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 9c01869..da3f63c 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -2,13 +2,13 @@
# -----------------------------------------------------------------------------
# All of the following should be updated in sync.
# -----------------------------------------------------------------------------
-androidGradlePlugin = "8.4.0-alpha12"
+androidGradlePlugin = "8.5.0-alpha06"
# NOTE: When updating the lint version we also need to update the `api` version
# supported by `IssueRegistry`'s.' For e.g. r.android.com/1331903
-androidLint = "31.4.0-alpha12"
+androidLint = "31.5.0-alpha06"
# Once you have a chosen version of AGP to upgrade to, go to
# https://developer.android.com/studio/archive and find the matching version of Studio.
-androidStudio = "2023.3.1.12"
+androidStudio = "2024.1.1.4"
# -----------------------------------------------------------------------------
androidGradlePluginMin = "7.0.4"
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index e5c45a0..1d107cd 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=../../../../tools/external/gradle/gradle-8.7-bin.zip
-distributionSha256Sum=544c35d6bd849ae8a5ed0bcea39ba677dc40f49df7d1835561582da2009b961d
+distributionUrl=../../../../tools/external/gradle/gradle-8.8-rc-1-bin.zip
+distributionSha256Sum=a2e1cfee7ffdeee86015b85b2dd2a435032c40eedc01d8172285556c7d8fea13
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
index e13d44f..dce5e38 100755
--- a/gradlew
+++ b/gradlew
@@ -16,7 +16,6 @@
OUT_DIR="$(cd $OUT_DIR && pwd -P)"
export GRADLE_USER_HOME="$OUT_DIR/.gradle"
export TMPDIR="$OUT_DIR/tmp"
- mkdir -p "$TMPDIR"
else
CHECKOUT_ROOT="$(cd $SCRIPT_PATH/../.. && pwd -P)"
export OUT_DIR="$CHECKOUT_ROOT/out"
@@ -230,9 +229,6 @@
if [ "$GRADLE_USER_HOME" != "" ]; then
HOME_SYSTEM_PROPERTY_ARGUMENT="-Duser.home=$GRADLE_USER_HOME"
fi
-if [ "$TMPDIR" != "" ]; then
- TMPDIR_ARG="-Djava.io.tmpdir=$TMPDIR"
-fi
if [[ " ${@} " =~ " --clean " ]]; then
cleanCaches=true
@@ -407,6 +403,11 @@
}
function runGradle() {
+ if [ "$TMPDIR" != "" ]; then
+ mkdir -p "$TMPDIR"
+ TMPDIR_ARG="-Djava.io.tmpdir=$TMPDIR"
+ fi
+
processOutput=false
if [[ " ${@} " =~ " -Pandroidx.validateNoUnrecognizedMessages " ]]; then
processOutput=true
diff --git a/kruth/kruth/api/current.ignore b/kruth/kruth/api/current.ignore
index 2fc7dd5..5589f1d 100644
--- a/kruth/kruth/api/current.ignore
+++ b/kruth/kruth/api/current.ignore
@@ -73,8 +73,6 @@
Removed class androidx.kruth.PrimitiveDoubleArraySubject.DoubleArrayAsIterable
RemovedClass: androidx.kruth.PrimitiveFloatArraySubject.FloatArrayAsIterable:
Removed class androidx.kruth.PrimitiveFloatArraySubject.FloatArrayAsIterable
-RemovedClass: androidx.kruth.TableSubject:
- Removed class androidx.kruth.TableSubject
RemovedClass: androidx.kruth.Truth:
Removed class androidx.kruth.Truth
RemovedClass: androidx.kruth.TruthJUnit:
diff --git a/kruth/kruth/api/current.txt b/kruth/kruth/api/current.txt
index 857e856..26a1b93 100644
--- a/kruth/kruth/api/current.txt
+++ b/kruth/kruth/api/current.txt
@@ -201,6 +201,7 @@
method public static <T> androidx.kruth.GuavaOptionalSubject<T> assertThat(com.google.common.base.Optional<T>? actual);
method public static <K, V> androidx.kruth.MultimapSubject<K,V> assertThat(com.google.common.collect.Multimap<K,V> actual);
method public static <T> androidx.kruth.MultisetSubject<T> assertThat(com.google.common.collect.Multiset<T> actual);
+ method public static <R, C, V> androidx.kruth.TableSubject<R,C,V> assertThat(com.google.common.collect.Table<R,C,V> actual);
method public static androidx.kruth.ClassSubject assertThat(Class<?> actual);
method public static androidx.kruth.BigDecimalSubject assertThat(java.math.BigDecimal actual);
}
@@ -418,6 +419,21 @@
method public SubjectT createSubject(androidx.kruth.FailureMetadata metadata, ActualT? actual);
}
+ public final class TableSubject<R, C, V> extends androidx.kruth.Subject<com.google.common.collect.Table<R,C,V>> {
+ method public void contains(R rowKey, C columnKey);
+ method public void containsCell(com.google.common.collect.Table.Cell<R,C,V>? cell);
+ method public void containsCell(R rowKey, C colKey, V value);
+ method public void containsColumn(C columnKey);
+ method public void containsRow(R rowKey);
+ method public void containsValue(V value);
+ method public void doesNotContain(R rowKey, C columnKey);
+ method public void doesNotContainCell(com.google.common.collect.Table.Cell<R,C,V>? cell);
+ method public void doesNotContainCell(R rowKey, C colKey, V value);
+ method public void hasSize(int expectedSize);
+ method public void isEmpty();
+ method public void isNotEmpty();
+ }
+
public class ThrowableSubject<T extends java.lang.Throwable> extends androidx.kruth.Subject<T> {
ctor protected ThrowableSubject(androidx.kruth.FailureMetadata metadata, T? actual);
method public final androidx.kruth.ThrowableSubject<java.lang.Throwable> hasCauseThat();
diff --git a/kruth/kruth/api/restricted_current.ignore b/kruth/kruth/api/restricted_current.ignore
index 2fc7dd5..5589f1d 100644
--- a/kruth/kruth/api/restricted_current.ignore
+++ b/kruth/kruth/api/restricted_current.ignore
@@ -73,8 +73,6 @@
Removed class androidx.kruth.PrimitiveDoubleArraySubject.DoubleArrayAsIterable
RemovedClass: androidx.kruth.PrimitiveFloatArraySubject.FloatArrayAsIterable:
Removed class androidx.kruth.PrimitiveFloatArraySubject.FloatArrayAsIterable
-RemovedClass: androidx.kruth.TableSubject:
- Removed class androidx.kruth.TableSubject
RemovedClass: androidx.kruth.Truth:
Removed class androidx.kruth.Truth
RemovedClass: androidx.kruth.TruthJUnit:
diff --git a/kruth/kruth/api/restricted_current.txt b/kruth/kruth/api/restricted_current.txt
index 90ec259..4d8c416 100644
--- a/kruth/kruth/api/restricted_current.txt
+++ b/kruth/kruth/api/restricted_current.txt
@@ -201,6 +201,7 @@
method public static <T> androidx.kruth.GuavaOptionalSubject<T> assertThat(com.google.common.base.Optional<T>? actual);
method public static <K, V> androidx.kruth.MultimapSubject<K,V> assertThat(com.google.common.collect.Multimap<K,V> actual);
method public static <T> androidx.kruth.MultisetSubject<T> assertThat(com.google.common.collect.Multiset<T> actual);
+ method public static <R, C, V> androidx.kruth.TableSubject<R,C,V> assertThat(com.google.common.collect.Table<R,C,V> actual);
method public static androidx.kruth.ClassSubject assertThat(Class<?> actual);
method public static androidx.kruth.BigDecimalSubject assertThat(java.math.BigDecimal actual);
}
@@ -419,6 +420,21 @@
method public SubjectT createSubject(androidx.kruth.FailureMetadata metadata, ActualT? actual);
}
+ public final class TableSubject<R, C, V> extends androidx.kruth.Subject<com.google.common.collect.Table<R,C,V>> {
+ method public void contains(R rowKey, C columnKey);
+ method public void containsCell(com.google.common.collect.Table.Cell<R,C,V>? cell);
+ method public void containsCell(R rowKey, C colKey, V value);
+ method public void containsColumn(C columnKey);
+ method public void containsRow(R rowKey);
+ method public void containsValue(V value);
+ method public void doesNotContain(R rowKey, C columnKey);
+ method public void doesNotContainCell(com.google.common.collect.Table.Cell<R,C,V>? cell);
+ method public void doesNotContainCell(R rowKey, C colKey, V value);
+ method public void hasSize(int expectedSize);
+ method public void isEmpty();
+ method public void isNotEmpty();
+ }
+
public class ThrowableSubject<T extends java.lang.Throwable> extends androidx.kruth.Subject<T> {
ctor protected ThrowableSubject(androidx.kruth.FailureMetadata metadata, T? actual);
method public final androidx.kruth.ThrowableSubject<java.lang.Throwable> hasCauseThat();
diff --git a/kruth/kruth/src/jvmMain/kotlin/androidx/kruth/Kruth.jvm.kt b/kruth/kruth/src/jvmMain/kotlin/androidx/kruth/Kruth.jvm.kt
index ea1fbc7..9b8c123 100644
--- a/kruth/kruth/src/jvmMain/kotlin/androidx/kruth/Kruth.jvm.kt
+++ b/kruth/kruth/src/jvmMain/kotlin/androidx/kruth/Kruth.jvm.kt
@@ -19,6 +19,7 @@
import com.google.common.base.Optional
import com.google.common.collect.Multimap
import com.google.common.collect.Multiset
+import com.google.common.collect.Table
import java.math.BigDecimal
fun assertThat(actual: Class<*>): ClassSubject =
@@ -35,3 +36,6 @@
fun <K, V> assertThat(actual: Multimap<K, V>): MultimapSubject<K, V> =
MultimapSubject(actual = actual)
+
+fun <R, C, V> assertThat(actual: Table<R, C, V>): TableSubject<R, C, V> =
+ TableSubject(actual = actual)
diff --git a/kruth/kruth/src/jvmMain/kotlin/androidx/kruth/PlatformStandardSubjectBuilder.jvm.kt b/kruth/kruth/src/jvmMain/kotlin/androidx/kruth/PlatformStandardSubjectBuilder.jvm.kt
index 891e0a2..e4030e6 100644
--- a/kruth/kruth/src/jvmMain/kotlin/androidx/kruth/PlatformStandardSubjectBuilder.jvm.kt
+++ b/kruth/kruth/src/jvmMain/kotlin/androidx/kruth/PlatformStandardSubjectBuilder.jvm.kt
@@ -19,6 +19,7 @@
import com.google.common.base.Optional
import com.google.common.collect.Multimap
import com.google.common.collect.Multiset
+import com.google.common.collect.Table
import java.math.BigDecimal
internal actual interface PlatformStandardSubjectBuilder {
@@ -28,6 +29,7 @@
fun that(actual: BigDecimal): BigDecimalSubject
fun <T> that(actual: Multiset<T>): MultisetSubject<T>
fun <K, V> that(actual: Multimap<K, V>): MultimapSubject<K, V>
+ fun <R, C, V> that(actual: Table<R, C, V>): TableSubject<R, C, V>
}
internal actual class PlatformStandardSubjectBuilderImpl actual constructor(
@@ -48,4 +50,7 @@
override fun <K, V> that(actual: Multimap<K, V>): MultimapSubject<K, V> =
MultimapSubject(actual = actual, metadata = metadata)
+
+ override fun <R, C, V> that(actual: Table<R, C, V>): TableSubject<R, C, V> =
+ TableSubject(actual = actual, metadata = metadata)
}
diff --git a/kruth/kruth/src/jvmMain/kotlin/androidx/kruth/TableSubject.jvm.kt b/kruth/kruth/src/jvmMain/kotlin/androidx/kruth/TableSubject.jvm.kt
new file mode 100644
index 0000000..80f873e
--- /dev/null
+++ b/kruth/kruth/src/jvmMain/kotlin/androidx/kruth/TableSubject.jvm.kt
@@ -0,0 +1,134 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.kruth
+
+import androidx.kruth.Fact.Companion.fact
+import androidx.kruth.Fact.Companion.simpleFact
+import com.google.common.collect.Table
+import com.google.common.collect.Table.Cell
+import com.google.common.collect.Tables.immutableCell
+
+class TableSubject<R, C, V> internal constructor(
+ actual: Table<R, C, V>,
+ metadata: FailureMetadata = FailureMetadata(),
+) : Subject<Table<R, C, V>>(actual, metadata, typeDescriptionOverride = null) {
+
+ /** Fails if the table is not empty. */
+ fun isEmpty() {
+ requireNonNull(actual)
+
+ if (!actual.isEmpty) {
+ failWithActual(simpleFact("expected to be empty"))
+ }
+ }
+
+ /** Fails if the table is empty. */
+ fun isNotEmpty() {
+ requireNonNull(actual)
+
+ if (actual.isEmpty) {
+ failWithoutActual(simpleFact("expected not to be empty"))
+ }
+ }
+
+ /** Fails if the table does not have the given size. */
+ fun hasSize(expectedSize: Int) {
+ require(expectedSize >= 0) { "expectedSize($expectedSize) must be >= 0" }
+ requireNonNull(actual)
+
+ check("size()").that(actual.size()).isEqualTo(expectedSize)
+ }
+
+ /** Fails if the table does not contain a mapping for the given row key and column key. */
+ fun contains(rowKey: R, columnKey: C) {
+ requireNonNull(actual)
+
+ if (!actual.contains(rowKey, columnKey)) {
+ failWithActual(
+ simpleFact("expected to contain mapping for row-column key pair"),
+ fact("row key", rowKey),
+ fact("column key", columnKey),
+ )
+ }
+ }
+
+ /** Fails if the table contains a mapping for the given row key and column key. */
+ fun doesNotContain(rowKey: R, columnKey: C) {
+ requireNonNull(actual)
+
+ if (actual.contains(rowKey, columnKey)) {
+ failWithoutActual(
+ simpleFact("expected not to contain mapping for row-column key pair"),
+ fact("row key", rowKey),
+ fact("column key", columnKey),
+ fact("but contained value", actual[rowKey, columnKey]),
+ fact("full contents", actual),
+ )
+ }
+ }
+
+ /** Fails if the table does not contain the given cell. */
+ fun containsCell(rowKey: R, colKey: C, value: V) {
+ containsCell(immutableCell(rowKey, colKey, value))
+ }
+
+ /** Fails if the table does not contain the given cell. */
+ fun containsCell(cell: Cell<R, C, V>?) {
+ requireNonNull(cell)
+ requireNonNull(actual)
+
+ checkNoNeedToDisplayBothValues("cellSet()")
+ .that(actual.cellSet())
+ .contains(cell)
+ }
+
+ /** Fails if the table contains the given cell. */
+ fun doesNotContainCell(rowKey: R, colKey: C, value: V) {
+ doesNotContainCell(immutableCell(rowKey, colKey, value))
+ }
+
+ /** Fails if the table contains the given cell. */
+ fun doesNotContainCell(cell: Cell<R, C, V>?) {
+ requireNonNull(cell)
+ requireNonNull(actual)
+
+ checkNoNeedToDisplayBothValues("cellSet()")
+ .that(actual.cellSet())
+ .doesNotContain(cell)
+ }
+
+ /** Fails if the table does not contain the given row key. */
+ fun containsRow(rowKey: R) {
+ requireNonNull(actual)
+
+ check("rowKeySet()").that(actual.rowKeySet()).contains(rowKey)
+ }
+
+ /** Fails if the table does not contain the given column key. */
+ fun containsColumn(columnKey: C) {
+ requireNonNull(actual)
+
+ check("columnKeySet()").that(actual.columnKeySet()).contains(columnKey)
+ }
+
+ /** Fails if the table does not contain the given value. */
+ fun containsValue(value: V) {
+ requireNonNull(actual)
+
+ check("values()").that(actual.values()).contains(value)
+ }
+}
diff --git a/kruth/kruth/src/jvmTest/kotlin/androidx/kruth/TableSubjectTest.jvm.kt b/kruth/kruth/src/jvmTest/kotlin/androidx/kruth/TableSubjectTest.jvm.kt
new file mode 100644
index 0000000..a2a1157
--- /dev/null
+++ b/kruth/kruth/src/jvmTest/kotlin/androidx/kruth/TableSubjectTest.jvm.kt
@@ -0,0 +1,138 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.kruth
+
+import com.google.common.collect.ImmutableTable
+import com.google.common.collect.Tables.immutableCell
+import kotlin.test.Test
+import kotlin.test.assertFailsWith
+
+class TableSubjectTest {
+
+ @Test
+ fun tableIsEmpty() {
+ val table = ImmutableTable.of<String, String, String>()
+ assertThat(table).isEmpty()
+ }
+
+ @Test
+ fun tableIsEmptyWithFailure() {
+ val table = ImmutableTable.of(1, 5, 7)
+ assertFailsWith<AssertionError> {
+ assertThat(table).isEmpty()
+ }
+ }
+
+ @Test
+ fun tableIsNotEmpty() {
+ val table = ImmutableTable.of(1, 5, 7)
+ assertThat(table).isNotEmpty()
+ }
+
+ @Test
+ fun tableIsNotEmptyWithFailure() {
+ val table = ImmutableTable.of<Int, Int, Int>()
+ assertFailsWith<AssertionError> {
+ assertThat(table).isNotEmpty()
+ }
+ }
+
+ @Test
+ fun hasSize() {
+ assertThat(ImmutableTable.of(1, 2, 3)).hasSize(1)
+ }
+
+ @Test
+ fun hasSizeZero() {
+ assertThat(ImmutableTable.of<Any, Any, Any>()).hasSize(0)
+ }
+
+ @Test
+ fun hasSizeNegative() {
+ assertFailsWith<IllegalArgumentException> {
+ assertThat(ImmutableTable.of(1, 2, 3)).hasSize(-1)
+ }
+ }
+
+ @Test
+ fun contains() {
+ val table = ImmutableTable.of("row", "col", "val")
+ assertThat(table).contains("row", "col")
+ }
+
+ @Test
+ fun containsFailure() {
+ val table = ImmutableTable.of("row", "col", "val")
+
+ assertFailsWith<AssertionError> {
+ assertThat(table).contains("row", "otherCol")
+ }
+ }
+
+ @Test
+ fun doesNotContain() {
+ val table = ImmutableTable.of("row", "col", "val")
+ assertThat(table).doesNotContain("row", "row")
+ assertThat(table).doesNotContain("col", "row")
+ assertThat(table).doesNotContain("col", "col")
+ assertThat(table).doesNotContain(null, null)
+ }
+
+ @Test
+ fun doesNotContainFailure() {
+ val table = ImmutableTable.of("row", "col", "val")
+ assertFailsWith<AssertionError> {
+ assertThat(table).doesNotContain("row", "col")
+ }
+ }
+
+ @Test
+ fun containsCell() {
+ val table = ImmutableTable.of("row", "col", "val")
+ assertThat(table).containsCell("row", "col", "val")
+ assertThat(table).containsCell(immutableCell("row", "col", "val"))
+ }
+
+ @Test
+ fun containsCellFailure() {
+ val table = ImmutableTable.of("row", "col", "val")
+ assertFailsWith<AssertionError> {
+ assertThat(table).containsCell("row", "row", "val")
+ }
+ }
+
+ @Test
+ fun doesNotContainCell() {
+ val table = ImmutableTable.of("row", "col", "val")
+ assertThat(table).doesNotContainCell("row", "row", "val")
+ assertThat(table).doesNotContainCell("col", "row", "val")
+ assertThat(table).doesNotContainCell("col", "col", "val")
+ assertThat(table).doesNotContainCell(null, null, null)
+ assertThat(table).doesNotContainCell(immutableCell("row", "row", "val"))
+ assertThat(table).doesNotContainCell(immutableCell("col", "row", "val"))
+ assertThat(table).doesNotContainCell(immutableCell("col", "col", "val"))
+ assertThat(table).doesNotContainCell(immutableCell(null, null, null))
+ }
+
+ @Test
+ fun doesNotContainCellFailure() {
+ val table = ImmutableTable.of("row", "col", "val")
+ assertFailsWith<AssertionError> {
+ assertThat(table).doesNotContainCell("row", "col", "val")
+ }
+ }
+}
diff --git a/lifecycle/lifecycle-viewmodel-compose/src/androidInstrumentedTest/kotlin/androidx/lifecycle/viewmodel/compose/SavedStateHandleSaverTest.android.kt b/lifecycle/lifecycle-viewmodel-compose/src/androidInstrumentedTest/kotlin/androidx/lifecycle/viewmodel/compose/SavedStateHandleSaverTest.android.kt
index 8bcf5f0..a42b7d8 100644
--- a/lifecycle/lifecycle-viewmodel-compose/src/androidInstrumentedTest/kotlin/androidx/lifecycle/viewmodel/compose/SavedStateHandleSaverTest.android.kt
+++ b/lifecycle/lifecycle-viewmodel-compose/src/androidInstrumentedTest/kotlin/androidx/lifecycle/viewmodel/compose/SavedStateHandleSaverTest.android.kt
@@ -359,6 +359,76 @@
assertThat(getOptionalCount!!()).isEqualTo(1)
assertThat(savedStateHandle?.keys()).isEqualTo(setOf("count"))
}
+
+ @OptIn(SavedStateHandleSaveableApi::class)
+ @Test
+ fun noConflictKeys_delegate_simpleRestore() {
+ activityTestRuleScenario.scenario.onActivity { activity ->
+ activity.setContent {
+ val viewModel = viewModel<SavingTestViewModel>(activity)
+ val firstClass = FirstClass(viewModel.savedStateHandle)
+ val secondClass = SecondClass(viewModel.savedStateHandle)
+ assertThat(firstClass.savedProperty).isEqualTo("One")
+ assertThat(secondClass.savedProperty).isEqualTo("Two")
+ }
+ }
+
+ activityTestRuleScenario.scenario.recreate()
+
+ activityTestRuleScenario.scenario.onActivity { activity ->
+ activity.setContent {
+ val viewModel = viewModel<SavingTestViewModel>(activity)
+ val firstClass = FirstClass(viewModel.savedStateHandle)
+ val secondClass = SecondClass(viewModel.savedStateHandle)
+ assertThat(firstClass.savedProperty).isEqualTo("One")
+ assertThat(secondClass.savedProperty).isEqualTo("Two")
+ }
+ }
+ }
+
+ @OptIn(SavedStateHandleSaveableApi::class)
+ @Test
+ fun conflictKeys_local_delegate_simpleRestore() {
+
+ fun firstFunction(handle: SavedStateHandle): String {
+ val localProperty by handle.saveable { mutableStateOf("One") }
+ return localProperty
+ }
+
+ fun secondFunction(handle: SavedStateHandle): String {
+ val localProperty by handle.saveable { mutableStateOf("Two") }
+ return localProperty
+ }
+
+ activityTestRuleScenario.scenario.onActivity { activity ->
+ activity.setContent {
+ val savedStateHandle = viewModel<SavingTestViewModel>(activity).savedStateHandle
+ firstFunction(savedStateHandle)
+ secondFunction(savedStateHandle)
+ }
+ }
+
+ activityTestRuleScenario.scenario.recreate()
+
+ activityTestRuleScenario.scenario.onActivity { activity ->
+ activity.setContent {
+ val savedStateHandle = viewModel<SavingTestViewModel>(activity).savedStateHandle
+ // TODO(b/331695354): Fix local property saveable delegate key conflict
+ assertThat(firstFunction(savedStateHandle)).isEqualTo("Two")
+ assertThat(secondFunction(savedStateHandle)).isEqualTo("Two")
+ }
+ }
+ }
}
class SavingTestViewModel(val savedStateHandle: SavedStateHandle) : ViewModel()
+
+class FirstClass(savedStateHandle: SavedStateHandle) {
+ @OptIn(SavedStateHandleSaveableApi::class)
+ val savedProperty by savedStateHandle.saveable { mutableStateOf("One") }
+}
+
+class SecondClass(savedStateHandle: SavedStateHandle) {
+ @OptIn(SavedStateHandleSaveableApi::class)
+ val savedProperty by savedStateHandle.saveable { mutableStateOf("Two") }
+}
diff --git a/lifecycle/lifecycle-viewmodel-compose/src/androidMain/kotlin/androidx/lifecycle/viewmodel/compose/SavedStateHandleSaver.android.kt b/lifecycle/lifecycle-viewmodel-compose/src/androidMain/kotlin/androidx/lifecycle/viewmodel/compose/SavedStateHandleSaver.android.kt
index ada4c35..019167e 100644
--- a/lifecycle/lifecycle-viewmodel-compose/src/androidMain/kotlin/androidx/lifecycle/viewmodel/compose/SavedStateHandleSaver.android.kt
+++ b/lifecycle/lifecycle-viewmodel-compose/src/androidMain/kotlin/androidx/lifecycle/viewmodel/compose/SavedStateHandleSaver.android.kt
@@ -117,9 +117,10 @@
saver: Saver<T, out Any> = autoSaver(),
init: () -> T,
): PropertyDelegateProvider<Any?, ReadOnlyProperty<Any?, T>> =
- PropertyDelegateProvider { _, property ->
+ PropertyDelegateProvider { thisRef, property ->
+ val classNamePrefix = if (thisRef != null) thisRef::class.qualifiedName + "." else ""
val value = saveable(
- key = property.name,
+ key = classNamePrefix + property.name,
saver = saver,
init = init
)
@@ -155,9 +156,10 @@
stateSaver: Saver<T, out Any> = autoSaver(),
init: () -> M,
): PropertyDelegateProvider<Any?, ReadWriteProperty<Any?, T>> =
- PropertyDelegateProvider<Any?, ReadWriteProperty<Any?, T>> { _, property ->
+ PropertyDelegateProvider<Any?, ReadWriteProperty<Any?, T>> { thisRef, property ->
+ val classNamePrefix = if (thisRef != null) thisRef::class.qualifiedName + "." else ""
val mutableState = saveable(
- key = property.name,
+ key = classNamePrefix + property.name,
stateSaver = stateSaver,
init = init
)
diff --git a/lint-checks/src/main/java/androidx/build/lint/ClassVerificationFailureDetector.kt b/lint-checks/src/main/java/androidx/build/lint/ClassVerificationFailureDetector.kt
index 6057cc1..9a36c5f 100644
--- a/lint-checks/src/main/java/androidx/build/lint/ClassVerificationFailureDetector.kt
+++ b/lint-checks/src/main/java/androidx/build/lint/ClassVerificationFailureDetector.kt
@@ -487,7 +487,7 @@
// Builtin R8 desugaring, such as rewriting compare calls (see b/36390874)
if (owner.startsWith("java.") &&
- DesugaredMethodLookup.isDesugared(owner, name, desc, context.sourceSetType)) {
+ DesugaredMethodLookup.isDesugaredMethod(owner, name, desc, context.sourceSetType)) {
return
}
@@ -573,7 +573,7 @@
api: Int
): LintFix? {
val callPsi = call.sourcePsi ?: return null
- if (isKotlin(callPsi)) {
+ if (isKotlin(callPsi.language)) {
// We only support Java right now.
return null
}
diff --git a/lint-checks/src/main/java/androidx/build/lint/MissingJvmDefaultWithCompatibilityDetector.kt b/lint-checks/src/main/java/androidx/build/lint/MissingJvmDefaultWithCompatibilityDetector.kt
index a9f7fbd..a773acc 100644
--- a/lint-checks/src/main/java/androidx/build/lint/MissingJvmDefaultWithCompatibilityDetector.kt
+++ b/lint-checks/src/main/java/androidx/build/lint/MissingJvmDefaultWithCompatibilityDetector.kt
@@ -59,7 +59,7 @@
return
}
- if (!isKotlin(node)) return
+ if (!isKotlin(node.language)) return
if (!node.isInterface) return
if (node.annotatedWithAnyOf(
// If the interface is not stable, it doesn't need the annotation
diff --git a/lint-checks/src/main/java/androidx/build/lint/NullabilityAnnotationsDetector.kt b/lint-checks/src/main/java/androidx/build/lint/NullabilityAnnotationsDetector.kt
index 6ed71ee..bf0227e 100644
--- a/lint-checks/src/main/java/androidx/build/lint/NullabilityAnnotationsDetector.kt
+++ b/lint-checks/src/main/java/androidx/build/lint/NullabilityAnnotationsDetector.kt
@@ -40,7 +40,8 @@
private inner class AnnotationChecker(val context: JavaContext) : UElementHandler() {
override fun visitAnnotation(node: UAnnotation) {
- if (isJava(node.sourcePsi)) {
+ val element = node.sourcePsi
+ if (element != null && isJava(element.language)) {
checkForAnnotation(node, "NotNull", "NonNull")
checkForAnnotation(node, "Nullable", "Nullable")
}
diff --git a/lint-checks/src/main/java/androidx/build/lint/RestrictToDetector.kt b/lint-checks/src/main/java/androidx/build/lint/RestrictToDetector.kt
index 36cbf9cd..9eaf45c 100644
--- a/lint-checks/src/main/java/androidx/build/lint/RestrictToDetector.kt
+++ b/lint-checks/src/main/java/androidx/build/lint/RestrictToDetector.kt
@@ -86,7 +86,9 @@
// here, but that points to impl classes in its hierarchy which leads to
// class loading trouble.
val sourcePsi = element.sourcePsi
- if (isKotlin(sourcePsi) && sourcePsi?.parent?.toString() == "CONSTRUCTOR_CALLEE") {
+ if (sourcePsi != null &&
+ isKotlin(sourcePsi.language) &&
+ sourcePsi.parent?.toString() == "CONSTRUCTOR_CALLEE") {
return
}
}
diff --git a/mediarouter/mediarouter/src/main/res/values-iw/strings.xml b/mediarouter/mediarouter/src/main/res/values-iw/strings.xml
index 03371fa..7bafb1a 100644
--- a/mediarouter/mediarouter/src/main/res/values-iw/strings.xml
+++ b/mediarouter/mediarouter/src/main/res/values-iw/strings.xml
@@ -18,11 +18,11 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="mr_system_route_name" msgid="7449553026175453403">"מערכת"</string>
<string name="mr_user_route_category_name" msgid="4088331695424166162">"מכשירים"</string>
- <string name="mr_button_content_description" msgid="2939063992730535343">"העברה (cast)"</string>
- <string name="mr_cast_button_disconnected" msgid="8071109333469380363">"העברה (cast). אין חיבור"</string>
- <string name="mr_cast_button_connecting" msgid="6629927151350192407">"העברה (cast). מתבצעת התחברות"</string>
- <string name="mr_cast_button_connected" msgid="6073720094880410356">"העברה (cast). מחובר"</string>
- <string name="mr_chooser_title" msgid="1419936397646839840">"העברה אל"</string>
+ <string name="mr_button_content_description" msgid="2939063992730535343">"הפעלת Cast"</string>
+ <string name="mr_cast_button_disconnected" msgid="8071109333469380363">"הפעלת Cast. אין מכשיר מחובר"</string>
+ <string name="mr_cast_button_connecting" msgid="6629927151350192407">"הפעלת Cast. מתחברים למכשיר"</string>
+ <string name="mr_cast_button_connected" msgid="6073720094880410356">"הפעלת Cast. יש מכשיר מחובר"</string>
+ <string name="mr_chooser_title" msgid="1419936397646839840">"הפעלת Cast אל"</string>
<string name="mr_chooser_searching" msgid="6114250663023140921">"מתבצע חיפוש מכשירים"</string>
<string name="mr_chooser_looking_for_devices" msgid="4257319068277776035">"מתבצע חיפוש מכשירים…"</string>
<string name="mr_controller_disconnect" msgid="7812275474138309497">"ניתוק"</string>
@@ -50,5 +50,5 @@
<string name="mr_chooser_wifi_warning_description_car" msgid="2998902945608081567">"מוודאים שהמכשיר השני והרכב מחוברים לאותה רשת Wi-Fi"</string>
<string name="mr_chooser_wifi_warning_description_unknown" msgid="3459891599800041449">"מוודאים ששני המכשירים מחוברים לאותה רשת Wi-Fi"</string>
<string name="mr_chooser_wifi_learn_more" msgid="3799500840179081429"><a href="https://support.google.com/chromecast/?p=trouble-finding-devices">"מידע נוסף"</a></string>
- <string name="ic_media_route_learn_more_accessibility" msgid="9119039724000326934">"איך להעביר (cast)"</string>
+ <string name="ic_media_route_learn_more_accessibility" msgid="9119039724000326934">"איך להפעיל Cast"</string>
</resources>
diff --git a/playground-common/gradle/wrapper/gradle-wrapper.properties b/playground-common/gradle/wrapper/gradle-wrapper.properties
index 61f9702..fc4b959 100644
--- a/playground-common/gradle/wrapper/gradle-wrapper.properties
+++ b/playground-common/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
-distributionSha256Sum=544c35d6bd849ae8a5ed0bcea39ba677dc40f49df7d1835561582da2009b961d
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-rc-1-bin.zip
+distributionSha256Sum=a2e1cfee7ffdeee86015b85b2dd2a435032c40eedc01d8172285556c7d8fea13
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
diff --git a/stableaidl/stableaidl-gradle-plugin/src/test/java/com/android/build/gradle/internal/fixtures/FakeGradleProperty.kt b/stableaidl/stableaidl-gradle-plugin/src/test/java/com/android/build/gradle/internal/fixtures/FakeGradleProperty.kt
index 2e7601c..4d9ce75 100644
--- a/stableaidl/stableaidl-gradle-plugin/src/test/java/com/android/build/gradle/internal/fixtures/FakeGradleProperty.kt
+++ b/stableaidl/stableaidl-gradle-plugin/src/test/java/com/android/build/gradle/internal/fixtures/FakeGradleProperty.kt
@@ -111,6 +111,10 @@
throw NotImplementedError()
}
+ override fun replace(transformation: Transformer<out Provider<out T>?, in Provider<T>>) {
+ throw NotImplementedError()
+ }
+
@Deprecated("Deprecated in Java")
override fun forUseAtConfigurationTime(): Provider<T> {
throw NotImplementedError()
diff --git a/testutils/testutils-datastore/build.gradle b/testutils/testutils-datastore/build.gradle
index 2721d70..a2057f8 100644
--- a/testutils/testutils-datastore/build.gradle
+++ b/testutils/testutils-datastore/build.gradle
@@ -23,14 +23,12 @@
*/
import androidx.build.LibraryType
-import static org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType.*
-
plugins {
id("AndroidXPlugin")
}
androidXMultiplatform {
- jvm {}
+ jvm()
mac()
linux()
ios()
diff --git a/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/CurvedColumn.kt b/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/CurvedColumn.kt
index 825a3ee..3ab85ce 100644
--- a/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/CurvedColumn.kt
+++ b/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/CurvedColumn.kt
@@ -44,6 +44,7 @@
* than the curved column, either at the [CurvedAlignment.Angular.Start] of the layout,
* at the [CurvedAlignment.Angular.End], or [CurvedAlignment.Angular.Center].
* If unspecified or null, they can choose for themselves.
+ * @param contentBuilder Scope used to provide the content for this column.
*/
public fun CurvedScope.curvedColumn(
modifier: CurvedModifier = CurvedModifier,
diff --git a/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/CurvedRow.kt b/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/CurvedRow.kt
index f4fa3ca..4f5daea 100644
--- a/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/CurvedRow.kt
+++ b/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/CurvedRow.kt
@@ -42,6 +42,7 @@
* and if those needs to be reversed in a Rtl layout.
* If not specified, it will be inherited from the enclosing [curvedRow] or [CurvedLayout]
* See [CurvedDirection.Angular].
+ * @param contentBuilder Scope used to provide the content for this row.
*/
public fun CurvedScope.curvedRow(
modifier: CurvedModifier = CurvedModifier,
diff --git a/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/SwipeableV2.kt b/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/SwipeableV2.kt
index e344c9f..2087c5c3 100644
--- a/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/SwipeableV2.kt
+++ b/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/SwipeableV2.kt
@@ -677,17 +677,19 @@
@RestrictTo(LIBRARY_GROUP)
public object SwipeableV2Defaults {
/**
- * The default animation used by [SwipeableV2State].
+ * The default animation that will be used to animate to a new state.
*/
val AnimationSpec = SpringSpec<Float>()
/**
- * The default velocity threshold (1.8 dp per millisecond) used by [rememberSwipeableV2State].
+ * The default velocity threshold (in dp per second) that the end velocity has to
+ * exceed in order to animate to the next state.
*/
val VelocityThreshold: Dp = 125.dp
/**
- * The default positional threshold (56 dp) used by [rememberSwipeableV2State]
+ * The default positional threshold used when calculating the target state while a swipe is in
+ * progress and when settling after the swipe ends.
*/
val PositionalThreshold: Density.(totalDistance: Float) -> Float =
fixedPositionalThreshold(56.dp)
diff --git a/wear/compose/compose-material-core/src/main/java/androidx/wear/compose/materialcore/SelectionControls.kt b/wear/compose/compose-material-core/src/main/java/androidx/wear/compose/materialcore/SelectionControls.kt
index 1fdbc11..114fd30 100644
--- a/wear/compose/compose-material-core/src/main/java/androidx/wear/compose/materialcore/SelectionControls.kt
+++ b/wear/compose/compose-material-core/src/main/java/androidx/wear/compose/materialcore/SelectionControls.kt
@@ -310,10 +310,9 @@
// Canvas internally uses Spacer.drawBehind.
// Using Spacer.drawWithCache to optimize the stroke allocations.
Spacer(
+ // NB We must set the semantic role to Role.RadioButton in the parent Button,
+ // not here in the selection control - see b/330869742
modifier = modifier
- .semantics {
- this.role = Role.RadioButton
- }
.maybeSelectable(
onClick, enabled, selected, interactionSource, ripple, width, height
)
diff --git a/wear/compose/compose-material-core/src/main/java/androidx/wear/compose/materialcore/ToggleButton.kt b/wear/compose/compose-material-core/src/main/java/androidx/wear/compose/materialcore/ToggleButton.kt
index 792bd00..3b0332a 100644
--- a/wear/compose/compose-material-core/src/main/java/androidx/wear/compose/materialcore/ToggleButton.kt
+++ b/wear/compose/compose-material-core/src/main/java/androidx/wear/compose/materialcore/ToggleButton.kt
@@ -211,6 +211,9 @@
indication = ripple,
interactionSource = interactionSource
)
+ // For a toggleable button, the role could be Checkbox or Switch,
+ // so we cannot set the semantics here. Instead,
+ // we set them in the toggle control
} else {
Modifier.selectable(
enabled = enabled,
@@ -218,7 +221,12 @@
onClick = { onCheckedChange(true) },
indication = ripple,
interactionSource = interactionSource
- )
+ ).semantics {
+ // For a selectable button, the role is always RadioButton.
+ // See also b/330869742 for issue with setting the RadioButton role
+ // within the selection control.
+ role = Role.RadioButton
+ }
}
)
.padding(contentPadding),
@@ -375,6 +383,12 @@
indication = ripple,
interactionSource = checkedInteractionSource
)
+ .semantics {
+ // For a selectable button, the role is always RadioButton.
+ // See also b/330869742 for issue with setting the RadioButton role
+ // within the selection control.
+ role = Role.RadioButton
+ }
}
Box(
diff --git a/wear/compose/compose-material/src/androidTest/kotlin/androidx/wear/compose/material/SelectableChipTest.kt b/wear/compose/compose-material/src/androidTest/kotlin/androidx/wear/compose/material/SelectableChipTest.kt
index 2fde27b..c3d34d8 100644
--- a/wear/compose/compose-material/src/androidTest/kotlin/androidx/wear/compose/material/SelectableChipTest.kt
+++ b/wear/compose/compose-material/src/androidTest/kotlin/androidx/wear/compose/material/SelectableChipTest.kt
@@ -136,6 +136,28 @@
}
@Test
+ fun selectable_chip_has_role_radiobutton() {
+ rule.setContentWithTheme {
+ SelectableChip(
+ selected = true,
+ onClick = {},
+ enabled = false,
+ label = { Text("Label") },
+ selectionControl = { TestImage() },
+ modifier = Modifier.testTag(TEST_TAG)
+ )
+ }
+
+ rule.onNodeWithTag(TEST_TAG)
+ .assert(
+ SemanticsMatcher.expectValue(
+ SemanticsProperties.Role,
+ Role.RadioButton
+ )
+ )
+ }
+
+ @Test
fun split_chip_has_clickaction_when_disabled() {
rule.setContentWithTheme {
SplitSelectableChip(
diff --git a/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/Card.kt b/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/Card.kt
index fe84508..c9c736c 100644
--- a/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/Card.kt
+++ b/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/Card.kt
@@ -90,6 +90,7 @@
* still happen internally.
* @param role The type of user interface element. Accessibility services might use this
* to describe the element or do customizations
+ * @param content Slot for composable body content displayed on the Card
*/
@Composable
public fun Card(
@@ -179,6 +180,7 @@
* set.
* @param timeColor The default color to use for time() slot unless explicitly set.
* @param titleColor The default color to use for title() slot unless explicitly set.
+ * @param content Slot for composable body content displayed on the Card
*/
@Composable
public fun AppCard(
@@ -289,6 +291,7 @@
* @param contentColor The default color to use for content() slot unless explicitly set.
* @param titleColor The default color to use for title() slot unless explicitly set.
* @param timeColor The default color to use for time() slot unless explicitly set.
+ * @param content Slot for composable body content displayed on the Card
*/
@Composable
public fun TitleCard(
diff --git a/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/Chip.kt b/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/Chip.kt
index d80d069..5fed098 100644
--- a/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/Chip.kt
+++ b/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/Chip.kt
@@ -172,6 +172,7 @@
* still happen internally.
* @param role The type of user interface element. Accessibility services might use this
* to describe the element or do customizations
+ * @param content Slot for composable body content displayed on the Chip
*/
@Composable
public fun Chip(
diff --git a/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/ListHeader.kt b/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/ListHeader.kt
index a7c7a0c..0566bf5 100644
--- a/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/ListHeader.kt
+++ b/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/ListHeader.kt
@@ -43,6 +43,7 @@
* @param modifier The modifier for the list header
* @param backgroundColor The background color to apply - typically Color.Transparent
* @param contentColor The color to apply to content
+ * @param content Slot for displayed header text
*/
@Composable
public fun ListHeader(
diff --git a/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/MaterialTheme.kt b/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/MaterialTheme.kt
index ed866b9..8f7bdbf 100644
--- a/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/MaterialTheme.kt
+++ b/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/MaterialTheme.kt
@@ -53,6 +53,7 @@
* @param colors A complete definition of the Wear Material Color theme for this hierarchy
* @param typography A set of text styles to be used as this hierarchy's typography system
* @param shapes A set of shapes to be used by the components in this hierarchy
+ * @param content Slot for composable content displayed with this theme
*/
@Composable
public fun MaterialTheme(
diff --git a/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/Scaffold.kt b/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/Scaffold.kt
index e9a468b..fb7965a 100644
--- a/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/Scaffold.kt
+++ b/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/Scaffold.kt
@@ -51,6 +51,7 @@
* page indicator is a pager with horizontally swipeable pages.
* @param timeText time and potential application status message to display at the top middle of the
* screen. Expected to be a TimeText component.
+ * @param content Slot for composable screen content
*/
@Composable
public fun Scaffold(
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/RadioButtonTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/RadioButtonTest.kt
index f5c3ef0..3997c14 100644
--- a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/RadioButtonTest.kt
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/RadioButtonTest.kt
@@ -82,6 +82,23 @@
}
@Test
+ fun radio_button_has_role_radiobutton() {
+ rule.setContentWithTheme {
+ RadioButtonWithDefaults(
+ modifier = Modifier.testTag(TEST_TAG)
+ )
+ }
+
+ rule.onNodeWithTag(TEST_TAG)
+ .assert(
+ SemanticsMatcher.expectValue(
+ SemanticsProperties.Role,
+ Role.RadioButton
+ )
+ )
+ }
+
+ @Test
fun radio_button_samples_build() {
rule.setContentWithTheme {
RadioButton()
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/SelectionControlsTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/SelectionControlsTest.kt
index 600e737..a8ac0da 100644
--- a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/SelectionControlsTest.kt
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/SelectionControlsTest.kt
@@ -22,10 +22,6 @@
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.testTag
-import androidx.compose.ui.semantics.Role
-import androidx.compose.ui.semantics.SemanticsProperties
-import androidx.compose.ui.test.SemanticsMatcher
-import androidx.compose.ui.test.assert
import androidx.compose.ui.test.assertIsEnabled
import androidx.compose.ui.test.assertWidthIsEqualTo
import androidx.compose.ui.test.captureToImage
@@ -67,25 +63,6 @@
}
@Test
- fun radio_control_has_role_radiobutton() {
- rule.setContentWithTheme {
- with(SelectionControlScope(isEnabled = true, isSelected = true)) {
- Radio(
- modifier = Modifier.testTag(TEST_TAG)
- )
- }
- }
-
- rule.onNodeWithTag(TEST_TAG)
- .assert(
- SemanticsMatcher.expectValue(
- SemanticsProperties.Role,
- Role.RadioButton
- )
- )
- }
-
- @Test
fun radio_control_is_correctly_enabled() {
rule.setContentWithTheme {
with(SelectionControlScope(isEnabled = true, isSelected = true)) {
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 2dc5958..3755e89 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
@@ -106,6 +106,7 @@
* emitting [Interaction]s for this button. You can use this to change the button's appearance
* or preview the button in different states. Note that if `null` is provided, interactions will
* still happen internally.
+ * @param content Slot for composable body content displayed on the Button
*/
@Composable
fun Button(
@@ -176,6 +177,7 @@
* emitting [Interaction]s for this button. You can use this to change the button's appearance
* or preview the button in different states. Note that if `null` is provided, interactions will
* still happen internally.
+ * @param content Slot for composable body content displayed on the Button
*/
@Composable
fun FilledTonalButton(
@@ -245,6 +247,7 @@
* emitting [Interaction]s for this button. You can use this to change the button's appearance
* or preview the button in different states. Note that if `null` is provided, interactions will
* still happen internally.
+ * @param content Slot for composable body content displayed on the OutlinedButton
*/
@Composable
fun OutlinedButton(
@@ -314,6 +317,7 @@
* emitting [Interaction]s for this button. You can use this to change the button's appearance
* or preview the button in different states. Note that if `null` is provided, interactions will
* still happen internally.
+ * @param content Slot for composable body content displayed on the ChildButton
*/
@Composable
fun ChildButton(
@@ -1058,6 +1062,8 @@
/**
* Creates a [BorderStroke], such as for an [OutlinedButton]
*
+ * @param enabled Controls the color of the border based on the enabled/disabled state of the
+ * button
* @param borderColor The color to use for the border for this outline when enabled
* @param disabledBorderColor The color to use for the border for this outline when
* disabled
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/MaterialTheme.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/MaterialTheme.kt
index 5ed024f..9ef0bde 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/MaterialTheme.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/MaterialTheme.kt
@@ -50,6 +50,7 @@
* @param colorScheme A complete definition of the Wear Material Color theme for this hierarchy
* @param typography A set of text styles to be used as this hierarchy's typography system
* @param shapes A set of shapes to be used by the components in this hierarchy
+ * @param content Slot for composable content displayed with this theme
*/
@Composable
fun MaterialTheme(
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/RadioButton.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/RadioButton.kt
index 70dbab71..b4a6f53 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/RadioButton.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/RadioButton.kt
@@ -144,7 +144,13 @@
indication = rippleOrFallbackImplementation(),
interactionSource = interactionSource
)
- .padding(contentPadding),
+ .padding(contentPadding)
+ .semantics {
+ // For a selectable button, the role is always RadioButton.
+ // See also b/330869742 for issue with setting the RadioButton role
+ // within the selection control.
+ role = Role.RadioButton
+ },
verticalAlignment = Alignment.CenterVertically
) {
if (icon != null) {
@@ -329,7 +335,13 @@
.width(SPLIT_WIDTH)
.wrapContentHeight(align = Alignment.CenterVertically)
.wrapContentWidth(align = Alignment.End)
- .then(endPadding),
+ .then(endPadding)
+ .semantics {
+ // For a selectable button, the role is always RadioButton.
+ // See also b/330869742 for issue with setting the RadioButton role
+ // within the selection control.
+ role = Role.RadioButton
+ },
) {
val scope = remember(enabled, selected) { SelectionControlScope(enabled, selected) }
selectionControl(scope)
diff --git a/wear/compose/compose-ui-tooling/src/main/java/androidx/wear/compose/ui/tooling/preview/WearPreviewFontScales.kt b/wear/compose/compose-ui-tooling/src/main/java/androidx/wear/compose/ui/tooling/preview/WearPreviewFontScales.kt
index 7981cf2..5b295fc 100644
--- a/wear/compose/compose-ui-tooling/src/main/java/androidx/wear/compose/ui/tooling/preview/WearPreviewFontScales.kt
+++ b/wear/compose/compose-ui-tooling/src/main/java/androidx/wear/compose/ui/tooling/preview/WearPreviewFontScales.kt
@@ -34,7 +34,7 @@
* note, the above list is not exhaustive. It previews the composables on a small round Wear device.
*
* @sample androidx.wear.compose.material.samples.TitleCardWithImagePreview
- * @see [Preview.fontScale]
+ * @see Preview.fontScale
*/
@Preview(
device = WearDevices.SMALL_ROUND,
diff --git a/wear/protolayout/protolayout-expression/api/current.txt b/wear/protolayout/protolayout-expression/api/current.txt
index bd9565b..756cbe0 100644
--- a/wear/protolayout/protolayout-expression/api/current.txt
+++ b/wear/protolayout/protolayout-expression/api/current.txt
@@ -14,7 +14,7 @@
}
public static final class AnimationParameterBuilders.AnimationParameters.Builder {
- ctor public AnimationParameterBuilders.AnimationParameters.Builder();
+ ctor @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=200) public AnimationParameterBuilders.AnimationParameters.Builder();
method public androidx.wear.protolayout.expression.AnimationParameterBuilders.AnimationParameters build();
method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=200) public androidx.wear.protolayout.expression.AnimationParameterBuilders.AnimationParameters.Builder setDelayMillis(@IntRange(from=0) long);
method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=200) public androidx.wear.protolayout.expression.AnimationParameterBuilders.AnimationParameters.Builder setDurationMillis(@IntRange(from=0) long);
@@ -27,10 +27,10 @@
}
public static final class AnimationParameterBuilders.AnimationSpec.Builder {
- ctor public AnimationParameterBuilders.AnimationSpec.Builder();
+ ctor @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=200) public AnimationParameterBuilders.AnimationSpec.Builder();
method public androidx.wear.protolayout.expression.AnimationParameterBuilders.AnimationSpec build();
method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=200) public androidx.wear.protolayout.expression.AnimationParameterBuilders.AnimationSpec.Builder setAnimationParameters(androidx.wear.protolayout.expression.AnimationParameterBuilders.AnimationParameters);
- method public androidx.wear.protolayout.expression.AnimationParameterBuilders.AnimationSpec.Builder setRepeatable(androidx.wear.protolayout.expression.AnimationParameterBuilders.Repeatable);
+ method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=200) public androidx.wear.protolayout.expression.AnimationParameterBuilders.AnimationSpec.Builder setRepeatable(androidx.wear.protolayout.expression.AnimationParameterBuilders.Repeatable);
}
@androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=200) public static interface AnimationParameterBuilders.Easing {
@@ -53,7 +53,7 @@
}
public static final class AnimationParameterBuilders.Repeatable.Builder {
- ctor public AnimationParameterBuilders.Repeatable.Builder();
+ ctor @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=200) public AnimationParameterBuilders.Repeatable.Builder();
method public androidx.wear.protolayout.expression.AnimationParameterBuilders.Repeatable build();
method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=200) public androidx.wear.protolayout.expression.AnimationParameterBuilders.Repeatable.Builder setForwardRepeatOverride(androidx.wear.protolayout.expression.AnimationParameterBuilders.AnimationParameters);
method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=200) public androidx.wear.protolayout.expression.AnimationParameterBuilders.Repeatable.Builder setIterations(@IntRange(from=1) int);
diff --git a/wear/protolayout/protolayout-expression/api/restricted_current.txt b/wear/protolayout/protolayout-expression/api/restricted_current.txt
index bd9565b..756cbe0 100644
--- a/wear/protolayout/protolayout-expression/api/restricted_current.txt
+++ b/wear/protolayout/protolayout-expression/api/restricted_current.txt
@@ -14,7 +14,7 @@
}
public static final class AnimationParameterBuilders.AnimationParameters.Builder {
- ctor public AnimationParameterBuilders.AnimationParameters.Builder();
+ ctor @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=200) public AnimationParameterBuilders.AnimationParameters.Builder();
method public androidx.wear.protolayout.expression.AnimationParameterBuilders.AnimationParameters build();
method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=200) public androidx.wear.protolayout.expression.AnimationParameterBuilders.AnimationParameters.Builder setDelayMillis(@IntRange(from=0) long);
method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=200) public androidx.wear.protolayout.expression.AnimationParameterBuilders.AnimationParameters.Builder setDurationMillis(@IntRange(from=0) long);
@@ -27,10 +27,10 @@
}
public static final class AnimationParameterBuilders.AnimationSpec.Builder {
- ctor public AnimationParameterBuilders.AnimationSpec.Builder();
+ ctor @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=200) public AnimationParameterBuilders.AnimationSpec.Builder();
method public androidx.wear.protolayout.expression.AnimationParameterBuilders.AnimationSpec build();
method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=200) public androidx.wear.protolayout.expression.AnimationParameterBuilders.AnimationSpec.Builder setAnimationParameters(androidx.wear.protolayout.expression.AnimationParameterBuilders.AnimationParameters);
- method public androidx.wear.protolayout.expression.AnimationParameterBuilders.AnimationSpec.Builder setRepeatable(androidx.wear.protolayout.expression.AnimationParameterBuilders.Repeatable);
+ method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=200) public androidx.wear.protolayout.expression.AnimationParameterBuilders.AnimationSpec.Builder setRepeatable(androidx.wear.protolayout.expression.AnimationParameterBuilders.Repeatable);
}
@androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=200) public static interface AnimationParameterBuilders.Easing {
@@ -53,7 +53,7 @@
}
public static final class AnimationParameterBuilders.Repeatable.Builder {
- ctor public AnimationParameterBuilders.Repeatable.Builder();
+ ctor @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=200) public AnimationParameterBuilders.Repeatable.Builder();
method public androidx.wear.protolayout.expression.AnimationParameterBuilders.Repeatable build();
method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=200) public androidx.wear.protolayout.expression.AnimationParameterBuilders.Repeatable.Builder setForwardRepeatOverride(androidx.wear.protolayout.expression.AnimationParameterBuilders.AnimationParameters);
method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=200) public androidx.wear.protolayout.expression.AnimationParameterBuilders.Repeatable.Builder setIterations(@IntRange(from=1) int);
diff --git a/wear/protolayout/protolayout-expression/src/main/java/androidx/wear/protolayout/expression/AnimationParameterBuilders.java b/wear/protolayout/protolayout-expression/src/main/java/androidx/wear/protolayout/expression/AnimationParameterBuilders.java
index 44eb9a7..a800b2a 100644
--- a/wear/protolayout/protolayout-expression/src/main/java/androidx/wear/protolayout/expression/AnimationParameterBuilders.java
+++ b/wear/protolayout/protolayout-expression/src/main/java/androidx/wear/protolayout/expression/AnimationParameterBuilders.java
@@ -140,6 +140,7 @@
AnimationParameterProto.AnimationSpec.newBuilder();
private final Fingerprint mFingerprint = new Fingerprint(-2136602843);
+ @RequiresSchemaVersion(major = 1, minor = 200)
public Builder() {}
/** Sets animation parameters including duration, easing and repeat delay. */
@@ -158,6 +159,7 @@
* Sets the repeatable mode to be used for specifying repetition parameters for the
* animation. If not set, animation won't be repeated.
*/
+ @RequiresSchemaVersion(major = 1, minor = 200)
@NonNull
public Builder setRepeatable(@NonNull Repeatable repeatable) {
mImpl.setRepeatable(repeatable.toProto());
@@ -261,6 +263,7 @@
AnimationParameterProto.AnimationParameters.newBuilder();
private final Fingerprint mFingerprint = new Fingerprint(-1301308590);
+ @RequiresSchemaVersion(major = 1, minor = 200)
public Builder() {}
/**
@@ -491,7 +494,6 @@
}
/** Returns the internal proto instance. */
- @RestrictTo(Scope.LIBRARY_GROUP)
@NonNull
AnimationParameterProto.CubicBezierEasing toProto() {
return mImpl;
@@ -525,6 +527,7 @@
AnimationParameterProto.CubicBezierEasing.newBuilder();
private final Fingerprint mFingerprint = new Fingerprint(856403705);
+ @RequiresSchemaVersion(major = 1, minor = 200)
public Builder() {}
/**
@@ -704,6 +707,7 @@
AnimationParameterProto.Repeatable.newBuilder();
private final Fingerprint mFingerprint = new Fingerprint(2110475048);
+ @RequiresSchemaVersion(major = 1, minor = 200)
public Builder() {}
/**
diff --git a/wear/protolayout/protolayout-expression/src/main/java/androidx/wear/protolayout/expression/ConditionScopes.java b/wear/protolayout/protolayout-expression/src/main/java/androidx/wear/protolayout/expression/ConditionScopes.java
index 36e1b0b..5a088b9 100644
--- a/wear/protolayout/protolayout-expression/src/main/java/androidx/wear/protolayout/expression/ConditionScopes.java
+++ b/wear/protolayout/protolayout-expression/src/main/java/androidx/wear/protolayout/expression/ConditionScopes.java
@@ -48,12 +48,14 @@
}
/** Sets the value to use as the value when true in a conditional expression. */
- public @NonNull IfTrueScope<T, RawT> use(T valueWhenTrue) {
+ @NonNull
+ public IfTrueScope<T, RawT> use(T valueWhenTrue) {
return new IfTrueScope<>(valueWhenTrue, conditionBuilder, rawTypeMapper);
}
/** Sets the value to use as the value when true in a conditional expression. */
- public @NonNull IfTrueScope<T, RawT> use(RawT valueWhenTrue) {
+ @NonNull
+ public IfTrueScope<T, RawT> use(RawT valueWhenTrue) {
return use(rawTypeMapper.apply(valueWhenTrue));
}
}
diff --git a/wear/protolayout/protolayout-expression/src/main/java/androidx/wear/protolayout/expression/DynamicBuilders.java b/wear/protolayout/protolayout-expression/src/main/java/androidx/wear/protolayout/expression/DynamicBuilders.java
index 17d89ce..500985d 100644
--- a/wear/protolayout/protolayout-expression/src/main/java/androidx/wear/protolayout/expression/DynamicBuilders.java
+++ b/wear/protolayout/protolayout-expression/src/main/java/androidx/wear/protolayout/expression/DynamicBuilders.java
@@ -708,7 +708,7 @@
return this;
}
- /** Sets the name space for the state key. */
+ /** Sets the namespace for the state key. */
@RequiresSchemaVersion(major = 1, minor = 200)
@NonNull
public Builder setSourceNamespace(@NonNull String sourceNamespace) {
@@ -1255,7 +1255,7 @@
/** Sets the value to start animating from. */
@RequiresSchemaVersion(major = 1, minor = 200)
@NonNull
- public AnimatableFixedInt32.Builder setFromValue(int fromValue) {
+ public Builder setFromValue(int fromValue) {
mImpl.setFromValue(fromValue);
mFingerprint.recordPropertyUpdate(1, fromValue);
return this;
@@ -1264,7 +1264,7 @@
/** Sets the value to animate to. */
@RequiresSchemaVersion(major = 1, minor = 200)
@NonNull
- public AnimatableFixedInt32.Builder setToValue(int toValue) {
+ public Builder setToValue(int toValue) {
mImpl.setToValue(toValue);
mFingerprint.recordPropertyUpdate(2, toValue);
return this;
@@ -1398,7 +1398,7 @@
/** Sets the value to watch, and animate when it changes. */
@RequiresSchemaVersion(major = 1, minor = 200)
@NonNull
- public AnimatableDynamicInt32.Builder setInput(@NonNull DynamicInt32 input) {
+ public Builder setInput(@NonNull DynamicInt32 input) {
mImpl.setInput(input.toDynamicInt32Proto());
mFingerprint.recordPropertyUpdate(
1, checkNotNull(input.getFingerprint()).aggregateValueAsInt());
@@ -2413,7 +2413,7 @@
/** Returns whether digit grouping is used or not. */
public boolean isGroupingUsed() {
- return mInt32FormatOp.getGroupingUsed();
+ return mInt32FormatOp.isGroupingUsed();
}
/** Builder to create {@link IntFormatter} objects. */
@@ -2579,7 +2579,7 @@
* locale. If not defined, defaults to false. For example, for locale en_US, using grouping
* with 1234 would yield "1,234".
*/
- public boolean getGroupingUsed() {
+ public boolean isGroupingUsed() {
return mImpl.getGroupingUsed();
}
@@ -2638,13 +2638,13 @@
+ ", minIntegerDigits="
+ getMinIntegerDigits()
+ ", groupingUsed="
- + getGroupingUsed()
+ + isGroupingUsed()
+ "}";
}
/** Builder for {@link Int32FormatOp}. */
public static final class Builder implements DynamicString.Builder {
- final DynamicProto.Int32FormatOp.Builder mImpl =
+ private final DynamicProto.Int32FormatOp.Builder mImpl =
DynamicProto.Int32FormatOp.newBuilder();
private final Fingerprint mFingerprint = new Fingerprint(196209833);
@@ -2792,7 +2792,7 @@
return this;
}
- /** Sets the name space for the state key. */
+ /** Sets the namespace for the state key. */
@RequiresSchemaVersion(major = 1, minor = 200)
@NonNull
public Builder setSourceNamespace(@NonNull String sourceNamespace) {
@@ -3143,7 +3143,7 @@
* locale. If not defined, defaults to false. For example, for locale en_US, using grouping
* with 1234.56 would yield "1,234.56".
*/
- public boolean getGroupingUsed() {
+ public boolean isGroupingUsed() {
return mImpl.getGroupingUsed();
}
@@ -3206,7 +3206,7 @@
+ ", minIntegerDigits="
+ getMinIntegerDigits()
+ ", groupingUsed="
- + getGroupingUsed()
+ + isGroupingUsed()
+ "}";
}
@@ -3736,7 +3736,7 @@
return this;
}
- /** Sets the name space for the state key. */
+ /** Sets the namespace for the state key. */
@RequiresSchemaVersion(major = 1, minor = 200)
@NonNull
public Builder setSourceNamespace(@NonNull String sourceNamespace) {
@@ -5012,7 +5012,7 @@
/** Returns whether digit grouping is used or not. */
public boolean isGroupingUsed() {
- return mFloatFormatOp.getGroupingUsed();
+ return mFloatFormatOp.isGroupingUsed();
}
/** Builder to create {@link FloatFormatter} objects. */
@@ -5228,8 +5228,8 @@
@NonNull
public DynamicProto.DynamicBool toDynamicBoolProto(boolean withFingerprint) {
if (withFingerprint) {
- return DynamicProto.DynamicBool.newBuilder().
- setStateSource(mImpl)
+ return DynamicProto.DynamicBool.newBuilder()
+ .setStateSource(mImpl)
.setFingerprint(checkNotNull(mFingerprint).toProto())
.build();
}
@@ -5265,7 +5265,7 @@
return this;
}
- /** Sets the name space for the state key. */
+ /** Sets the namespace for the state key. */
@RequiresSchemaVersion(major = 1, minor = 200)
@NonNull
public Builder setSourceNamespace(@NonNull String sourceNamespace) {
@@ -6144,7 +6144,7 @@
return this;
}
- /** Sets the name space for the state key. */
+ /** Sets the namespace for the state key. */
@RequiresSchemaVersion(major = 1, minor = 200)
@NonNull
public Builder setSourceNamespace(@NonNull String sourceNamespace) {
diff --git a/wear/protolayout/protolayout-expression/src/main/java/androidx/wear/protolayout/expression/DynamicDataBuilders.java b/wear/protolayout/protolayout-expression/src/main/java/androidx/wear/protolayout/expression/DynamicDataBuilders.java
index 69c88e3..c3b5c45 100644
--- a/wear/protolayout/protolayout-expression/src/main/java/androidx/wear/protolayout/expression/DynamicDataBuilders.java
+++ b/wear/protolayout/protolayout-expression/src/main/java/androidx/wear/protolayout/expression/DynamicDataBuilders.java
@@ -28,7 +28,6 @@
import androidx.wear.protolayout.expression.DynamicBuilders.DynamicInstant;
import androidx.wear.protolayout.expression.DynamicBuilders.DynamicInt32;
import androidx.wear.protolayout.expression.DynamicBuilders.DynamicString;
-import androidx.wear.protolayout.expression.DynamicBuilders.DynamicType;
import androidx.wear.protolayout.expression.FixedValueBuilders.FixedBool;
import androidx.wear.protolayout.expression.FixedValueBuilders.FixedColor;
import androidx.wear.protolayout.expression.FixedValueBuilders.FixedDuration;
@@ -51,7 +50,7 @@
/** Interface defining a dynamic data value. */
@RequiresSchemaVersion(major = 1, minor = 200)
- public interface DynamicDataValue<T extends DynamicType> {
+ public interface DynamicDataValue<T extends DynamicBuilders.DynamicType> {
/** Get the protocol buffer representation of this object. */
@RestrictTo(Scope.LIBRARY_GROUP)
@NonNull
@@ -310,7 +309,7 @@
/** Builder to create {@link DynamicDataValue} objects. */
@RestrictTo(Scope.LIBRARY_GROUP)
- interface Builder<T extends DynamicType> {
+ interface Builder<T extends DynamicBuilders.DynamicType> {
/** Builds an instance with values accumulated in this Builder. */
@NonNull
diff --git a/wear/protolayout/protolayout-expression/src/main/java/androidx/wear/protolayout/expression/FixedValueBuilders.java b/wear/protolayout/protolayout-expression/src/main/java/androidx/wear/protolayout/expression/FixedValueBuilders.java
index ced6685..e16e059 100644
--- a/wear/protolayout/protolayout-expression/src/main/java/androidx/wear/protolayout/expression/FixedValueBuilders.java
+++ b/wear/protolayout/protolayout-expression/src/main/java/androidx/wear/protolayout/expression/FixedValueBuilders.java
@@ -78,7 +78,6 @@
}
/** Returns the internal proto instance. */
- @RestrictTo(Scope.LIBRARY_GROUP)
@NonNull
FixedProto.FixedInt32 toProto() {
return mImpl;
@@ -144,6 +143,7 @@
private final FixedProto.FixedInt32.Builder mImpl = FixedProto.FixedInt32.newBuilder();
private final Fingerprint mFingerprint = new Fingerprint(974881783);
+ @RequiresSchemaVersion(major = 1, minor = 200)
public Builder() {}
/** Sets the value. */
@@ -203,7 +203,6 @@
}
/** Returns the internal proto instance. */
- @RestrictTo(Scope.LIBRARY_GROUP)
@NonNull
FixedProto.FixedString toProto() {
return mImpl;
@@ -271,6 +270,7 @@
FixedProto.FixedString.newBuilder();
private final Fingerprint mFingerprint = new Fingerprint(1963352072);
+ @RequiresSchemaVersion(major = 1, minor = 200)
public Builder() {}
/** Sets the value. */
@@ -333,7 +333,6 @@
}
/** Returns the internal proto instance. */
- @RestrictTo(Scope.LIBRARY_GROUP)
@NonNull
FixedProto.FixedFloat toProto() {
return mImpl;
@@ -399,6 +398,7 @@
private final FixedProto.FixedFloat.Builder mImpl = FixedProto.FixedFloat.newBuilder();
private final Fingerprint mFingerprint = new Fingerprint(-144724541);
+ @RequiresSchemaVersion(major = 1, minor = 200)
public Builder() {}
/**
@@ -461,7 +461,6 @@
}
/** Returns the internal proto instance. */
- @RestrictTo(Scope.LIBRARY_GROUP)
@NonNull
FixedProto.FixedBool toProto() {
return mImpl;
@@ -527,6 +526,7 @@
private final FixedProto.FixedBool.Builder mImpl = FixedProto.FixedBool.newBuilder();
private final Fingerprint mFingerprint = new Fingerprint(-665116398);
+ @RequiresSchemaVersion(major = 1, minor = 200)
public Builder() {}
/** Sets the value. */
@@ -587,7 +587,6 @@
}
/** Returns the internal proto instance. */
- @RestrictTo(Scope.LIBRARY_GROUP)
@NonNull
FixedProto.FixedColor toProto() {
return mImpl;
@@ -653,6 +652,7 @@
private final FixedProto.FixedColor.Builder mImpl = FixedProto.FixedColor.newBuilder();
private final Fingerprint mFingerprint = new Fingerprint(-1895809356);
+ @RequiresSchemaVersion(major = 1, minor = 200)
public Builder() {}
/** Sets the color value, in ARGB format. */
@@ -733,7 +733,6 @@
}
/** Returns the internal proto instance. */
- @RestrictTo(Scope.LIBRARY_GROUP)
@NonNull
FixedProto.FixedInstant toProto() {
return mImpl;
@@ -778,6 +777,7 @@
FixedProto.FixedInstant.newBuilder();
private final Fingerprint mFingerprint = new Fingerprint(-1986552556);
+ @RequiresSchemaVersion(major = 1, minor = 200)
public Builder() {}
/**
@@ -860,7 +860,6 @@
}
/** Returns the internal proto instance. */
- @RestrictTo(Scope.LIBRARY_GROUP)
@NonNull
FixedProto.FixedDuration toProto() {
return mImpl;
@@ -905,6 +904,7 @@
FixedProto.FixedDuration.newBuilder();
private final Fingerprint mFingerprint = new Fingerprint(9029504);
+ @RequiresSchemaVersion(major = 1, minor = 200)
public Builder() {}
/** Sets duration in seconds. */
diff --git a/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/common/Constants.java b/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/common/Constants.java
new file mode 100644
index 0000000..d6a206c
--- /dev/null
+++ b/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/common/Constants.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License = 0 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 = 0 software
+ * distributed under the License is distributed on an "AS IS" BASIS = 0
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND = 0 either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.protolayout.renderer.common;
+
+import androidx.annotation.IntDef;
+import androidx.annotation.RestrictTo;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/** Shared constants. */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public class Constants {
+
+ private Constants() {}
+
+ /** The reason why an update was requested. */
+ @IntDef({
+ UPDATE_REQUEST_REASON_UNKNOWN,
+ UPDATE_REQUEST_REASON_SYSUI_CAROUSEL,
+ UPDATE_REQUEST_REASON_FRESHNESS,
+ UPDATE_REQUEST_REASON_USER_INTERACTION,
+ UPDATE_REQUEST_REASON_UPDATE_REQUESTER,
+ UPDATE_REQUEST_REASON_CACHE_INVALIDATION,
+ UPDATE_REQUEST_REASON_RETRY
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface UpdateRequestReason {}
+
+ /** Unknown reason. */
+ public static final int UPDATE_REQUEST_REASON_UNKNOWN = 0;
+
+ /** Update triggered by SysUI Carousel. */
+ public static final int UPDATE_REQUEST_REASON_SYSUI_CAROUSEL = 1;
+
+ /** Update triggered by freshness. */
+ public static final int UPDATE_REQUEST_REASON_FRESHNESS = 2;
+
+ /** Update triggered by user interaction (e.g. clicking on the tile). */
+ public static final int UPDATE_REQUEST_REASON_USER_INTERACTION = 3;
+
+ /** Update triggered using update requester. */
+ public static final int UPDATE_REQUEST_REASON_UPDATE_REQUESTER = 4;
+
+ /** Update triggered due to clearing the cache. */
+ public static final int UPDATE_REQUEST_REASON_CACHE_INVALIDATION = 5;
+
+ /** Update triggered by retry policy. */
+ public static final int UPDATE_REQUEST_REASON_RETRY = 6;
+}
diff --git a/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/common/NoOpProviderStatsLogger.java b/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/common/NoOpProviderStatsLogger.java
new file mode 100644
index 0000000..f292c98
--- /dev/null
+++ b/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/common/NoOpProviderStatsLogger.java
@@ -0,0 +1,91 @@
+/*
+ * 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.protolayout.renderer.common;
+
+import android.graphics.drawable.Drawable;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
+import androidx.wear.protolayout.proto.StateProto.State;
+import androidx.wear.protolayout.renderer.common.Constants.UpdateRequestReason;
+
+/** A No-Op implementation of {@link ProviderStatsLogger}. */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public class NoOpProviderStatsLogger implements ProviderStatsLogger {
+ private static final String TAG = "NoOpProviderStatsLogger";
+
+ /** Creates an instance of {@link NoOpProviderStatsLogger}. */
+ public NoOpProviderStatsLogger(@NonNull String reason) {
+ Log.i(TAG, "Instance used because " + reason);
+ }
+
+ /** No-op method. */
+ @Override
+ public void logLayoutSchemaVersion(int major, int minor) {}
+
+ /** No-op method. */
+ @Override
+ public void logStateStructure(@NonNull State state, boolean isInitialState) {}
+
+ /** No-op method. */
+ @Override
+ public void logIgnoredFailure(int failure) {}
+
+ /** No-op method. */
+ @Override
+ public void logInflationFailed(@InflationFailureReason int failureReason) {}
+
+ /** No-op method. */
+ @Override
+ @NonNull
+ public InflaterStatsLogger createInflaterStatsLogger() {
+ return new NoOpInflaterStatsLogger();
+ }
+
+ /** No-op method. */
+ @Override
+ public void logInflationFinished(@NonNull InflaterStatsLogger inflaterStatsLogger) {}
+
+ /** No-op method. */
+ @Override
+ public void logTileRequestReason(@UpdateRequestReason int updateRequestReason) {}
+
+ /** A No-Op implementation of {@link InflaterStatsLogger}. */
+ public static class NoOpInflaterStatsLogger implements InflaterStatsLogger {
+
+ private NoOpInflaterStatsLogger() {}
+
+ @Override
+ public void logMutationChangedNodes(int changedNodesCount) {}
+
+ @Override
+ public void logTotalNodeCount(int totalNodesCount) {}
+
+ /** No-op method. */
+ @Override
+ public void logDrawableUsage(@NonNull Drawable drawable) {}
+
+ /** No-op method. */
+ @Override
+ public void logIgnoredFailure(@IgnoredFailure int failure) {}
+
+ /** No-op method. */
+ @Override
+ public void logInflationFailed(@InflationFailureReason int failureReason) {}
+ }
+}
diff --git a/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/common/ProviderStatsLogger.java b/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/common/ProviderStatsLogger.java
new file mode 100644
index 0000000..96a6fb0
--- /dev/null
+++ b/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/common/ProviderStatsLogger.java
@@ -0,0 +1,135 @@
+/*
+ * 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.protolayout.renderer.common;
+
+import android.graphics.drawable.Drawable;
+
+import androidx.annotation.IntDef;
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
+import androidx.annotation.UiThread;
+import androidx.wear.protolayout.proto.StateProto.State;
+import androidx.wear.protolayout.renderer.common.Constants.UpdateRequestReason;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/** Logger used for collecting metrics. */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public interface ProviderStatsLogger {
+
+ /** Failures that doesn't cause the inflation to fail. */
+ @IntDef({
+ IGNORED_FAILURE_UNKNOWN,
+ IGNORED_FAILURE_APPLY_MUTATION_EXCEPTION,
+ IGNORED_FAILURE_ANIMATION_QUOTA_EXCEEDED,
+ IGNORED_FAILURE_DIFFING_FAILURE
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface IgnoredFailure {}
+
+ /** Unknown failure. */
+ int IGNORED_FAILURE_UNKNOWN = 0;
+
+ /** Failure applying the diff mutation. */
+ int IGNORED_FAILURE_APPLY_MUTATION_EXCEPTION = 1;
+
+ /** Failure caused by exceeding animation quota. */
+ int IGNORED_FAILURE_ANIMATION_QUOTA_EXCEEDED = 2;
+
+ /** Failure diffing the layout. */
+ int IGNORED_FAILURE_DIFFING_FAILURE = 3;
+
+ /** Failures that causes the inflation to fail. */
+ @IntDef({
+ INFLATION_FAILURE_REASON_UNKNOWN,
+ INFLATION_FAILURE_REASON_LAYOUT_DEPTH_EXCEEDED,
+ INFLATION_FAILURE_REASON_EXPRESSION_NODE_COUNT_EXCEEDED
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface InflationFailureReason {}
+
+ /** Unknown failure. */
+ int INFLATION_FAILURE_REASON_UNKNOWN = 0;
+
+ /** Failure caused by exceeding maximum layout depth. */
+ int INFLATION_FAILURE_REASON_LAYOUT_DEPTH_EXCEEDED = 1;
+
+ /** Failure caused by exceeding maximum expression node count. */
+ int INFLATION_FAILURE_REASON_EXPRESSION_NODE_COUNT_EXCEEDED = 2;
+
+ /** Log the schema version of the received layout. */
+ void logLayoutSchemaVersion(int major, int minor);
+
+ /** Log Protolayout state structure. */
+ void logStateStructure(@NonNull State state, boolean isInitialState);
+
+ /** Log the occurrence of an ignored failure. */
+ @UiThread
+ void logIgnoredFailure(@IgnoredFailure int failure);
+
+ /** Log the reason for inflation failure. */
+ @UiThread
+ void logInflationFailed(@InflationFailureReason int failureReason);
+
+ /**
+ * Creates an {@link InflaterStatsLogger} and marks the start of inflation. The atoms will be
+ * logged to statsd only when {@link #logInflationFinished} is called.
+ */
+ @UiThread
+ @NonNull
+ InflaterStatsLogger createInflaterStatsLogger();
+
+ /** Makes the end of inflation and log the inflation results. */
+ @UiThread
+ void logInflationFinished(@NonNull InflaterStatsLogger inflaterStatsLogger);
+
+ /** Log tile request reason. */
+ void logTileRequestReason(@UpdateRequestReason int updateRequestReason);
+
+ /** Logger used for logging inflation stats. */
+ interface InflaterStatsLogger {
+ /** log the mutation changed nodes count for the ongoing inflation. */
+ @UiThread
+ void logMutationChangedNodes(int changedNodesCount);
+
+ /** Log the total nodes count for the ongoing inflation. */
+ @UiThread
+ void logTotalNodeCount(int totalNodesCount);
+
+ /**
+ * Log the usage of a drawable. This method should be called between {@link
+ * #createInflaterStatsLogger()} and {@link #logInflationFinished(InflaterStatsLogger)}.
+ */
+ @UiThread
+ void logDrawableUsage(@NonNull Drawable drawable);
+
+ /**
+ * Log the occurrence of an ignored failure. The usage of this method is not restricted to
+ * inflation start or end.
+ */
+ @UiThread
+ void logIgnoredFailure(@IgnoredFailure int failure);
+
+ /**
+ * Log the reason for inflation failure. This will make any future call {@link
+ * #logInflationFinished(InflaterStatsLogger)} a Noop.
+ */
+ @UiThread
+ void logInflationFailed(@InflationFailureReason int failureReason);
+ }
+}
diff --git a/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/common/RenderingArtifact.java b/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/common/RenderingArtifact.java
new file mode 100644
index 0000000..8424728
--- /dev/null
+++ b/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/common/RenderingArtifact.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.protolayout.renderer.common;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
+import androidx.annotation.RestrictTo.Scope;
+import androidx.wear.protolayout.renderer.common.ProviderStatsLogger.InflaterStatsLogger;
+
+/** Artifacts resulted from the layout rendering. */
+@RestrictTo(Scope.LIBRARY_GROUP)
+public interface RenderingArtifact {
+
+ /** Creates a {@link RenderingArtifact} instance. */
+ @NonNull
+ static RenderingArtifact create(@NonNull InflaterStatsLogger inflaterStatsLogger) {
+ return new SuccessfulRenderingArtifact(inflaterStatsLogger);
+ }
+
+ /** Creates a {@link RenderingArtifact} instance for a skipped inflation. */
+ @NonNull
+ static RenderingArtifact skipped() {
+ return new SkippedRenderingArtifact();
+ }
+
+ /** Creates a {@link RenderingArtifact} instance for a failed inflation. */
+ @NonNull
+ static RenderingArtifact failed() {
+ return new FailedRenderingArtifact();
+ }
+
+ /** Artifacts resulted from a successful layout rendering. */
+ class SuccessfulRenderingArtifact implements RenderingArtifact {
+ @NonNull private final InflaterStatsLogger mInflaterStatsLogger;
+
+ private SuccessfulRenderingArtifact(@NonNull InflaterStatsLogger inflaterStatsLogger) {
+ mInflaterStatsLogger = inflaterStatsLogger;
+ }
+
+ /**
+ * Returns the {@link ProviderStatsLogger.InflaterStatsLogger} used log inflation stats.
+ * This will return {@code null} if the inflation was skipped or failed.
+ */
+ @NonNull
+ public InflaterStatsLogger getInflaterStatsLogger() {
+ return mInflaterStatsLogger;
+ }
+ }
+
+ /** Artifacts resulted from a skipped layout rendering. */
+ class SkippedRenderingArtifact implements RenderingArtifact {}
+
+ /** Artifacts resulted from a failed layout rendering. */
+ class FailedRenderingArtifact implements RenderingArtifact {}
+}
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 08f0228..8f18ca8 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
@@ -20,6 +20,13 @@
import static android.widget.FrameLayout.LayoutParams.UNSPECIFIED_GRAVITY;
import static androidx.core.util.Preconditions.checkNotNull;
+import static androidx.wear.protolayout.renderer.common.ProviderStatsLogger.IGNORED_FAILURE_ANIMATION_QUOTA_EXCEEDED;
+import static androidx.wear.protolayout.renderer.common.ProviderStatsLogger.IGNORED_FAILURE_APPLY_MUTATION_EXCEPTION;
+import static androidx.wear.protolayout.renderer.common.ProviderStatsLogger.INFLATION_FAILURE_REASON_EXPRESSION_NODE_COUNT_EXCEEDED;
+import static androidx.wear.protolayout.renderer.common.ProviderStatsLogger.INFLATION_FAILURE_REASON_LAYOUT_DEPTH_EXCEEDED;
+
+import static com.google.common.util.concurrent.Futures.immediateCancelledFuture;
+import static com.google.common.util.concurrent.Futures.immediateFuture;
import android.content.Context;
import android.content.res.Resources;
@@ -41,6 +48,7 @@
import androidx.wear.protolayout.expression.PlatformDataKey;
import androidx.wear.protolayout.expression.pipeline.FixedQuotaManagerImpl;
import androidx.wear.protolayout.expression.pipeline.PlatformDataProvider;
+import androidx.wear.protolayout.expression.pipeline.QuotaManager;
import androidx.wear.protolayout.expression.pipeline.StateStore;
import androidx.wear.protolayout.proto.LayoutElementProto.ArcLayoutElement;
import androidx.wear.protolayout.proto.LayoutElementProto.ArcLayoutElement.InnerCase;
@@ -52,7 +60,11 @@
import androidx.wear.protolayout.renderer.ProtoLayoutTheme;
import androidx.wear.protolayout.renderer.ProtoLayoutVisibilityState;
import androidx.wear.protolayout.renderer.common.LoggingUtils;
+import androidx.wear.protolayout.renderer.common.NoOpProviderStatsLogger;
import androidx.wear.protolayout.renderer.common.ProtoLayoutDiffer;
+import androidx.wear.protolayout.renderer.common.ProviderStatsLogger;
+import androidx.wear.protolayout.renderer.common.ProviderStatsLogger.InflaterStatsLogger;
+import androidx.wear.protolayout.renderer.common.RenderingArtifact;
import androidx.wear.protolayout.renderer.dynamicdata.ProtoLayoutDynamicDataPipeline;
import androidx.wear.protolayout.renderer.inflater.ProtoLayoutInflater;
import androidx.wear.protolayout.renderer.inflater.ProtoLayoutInflater.InflateResult;
@@ -114,6 +126,7 @@
@NonNull private final ListeningExecutorService mUiExecutorService;
@NonNull private final ListeningExecutorService mBgExecutorService;
@NonNull private final String mClickableIdExtra;
+ @NonNull private final ProviderStatsLogger mProviderStatsLogger;
@Nullable private final LoggingUtils mLoggingUtils;
@Nullable private final ProtoLayoutExtensionViewProvider mExtensionViewProvider;
@@ -219,10 +232,11 @@
*/
@UiThread
@NonNull
- ListenableFuture<Void> postInflate(
+ ListenableFuture<RenderingArtifact> postInflate(
@NonNull ViewGroup attachParent,
@Nullable ViewGroup prevInflateParent,
- boolean isReattaching);
+ boolean isReattaching,
+ InflaterStatsLogger inflaterStatsLogger);
}
/** Result of a {@link #renderOrComputeMutations} call when no changes are required. */
@@ -234,11 +248,12 @@
@NonNull
@Override
- public ListenableFuture<Void> postInflate(
+ public ListenableFuture<RenderingArtifact> postInflate(
@NonNull ViewGroup attachParent,
@Nullable ViewGroup prevInflateParent,
- boolean isReattaching) {
- return Futures.immediateVoidFuture();
+ boolean isReattaching,
+ InflaterStatsLogger inflaterStatsLogger) {
+ return immediateFuture(RenderingArtifact.create(inflaterStatsLogger));
}
}
@@ -251,11 +266,12 @@
@NonNull
@Override
- public ListenableFuture<Void> postInflate(
+ public ListenableFuture<RenderingArtifact> postInflate(
@NonNull ViewGroup attachParent,
@Nullable ViewGroup prevInflateParent,
- boolean isReattaching) {
- return Futures.immediateVoidFuture();
+ boolean isReattaching,
+ InflaterStatsLogger inflaterStatsLogger) {
+ return immediateFuture(RenderingArtifact.failed());
}
}
@@ -278,10 +294,11 @@
@NonNull
@Override
@UiThread
- public ListenableFuture<Void> postInflate(
+ public ListenableFuture<RenderingArtifact> postInflate(
@NonNull ViewGroup attachParent,
@Nullable ViewGroup prevInflateParent,
- boolean isReattaching) {
+ boolean isReattaching,
+ InflaterStatsLogger inflaterStatsLogger) {
InflateResult inflateResult =
checkNotNull(
mNewInflateParentData.mInflateResult,
@@ -292,7 +309,7 @@
attachParent.addView(
inflateResult.inflateParent, new LayoutParams(MATCH_PARENT, MATCH_PARENT));
inflateResult.updateDynamicDataPipeline(isReattaching);
- return Futures.immediateVoidFuture();
+ return immediateFuture(RenderingArtifact.create(inflaterStatsLogger));
}
}
@@ -318,10 +335,11 @@
@NonNull
@Override
@UiThread
- public ListenableFuture<Void> postInflate(
+ public ListenableFuture<RenderingArtifact> postInflate(
@NonNull ViewGroup attachParent,
@Nullable ViewGroup prevInflateParent,
- boolean isReattaching) {
+ boolean isReattaching,
+ InflaterStatsLogger inflaterStatsLogger) {
return mInflater.applyMutation(checkNotNull(prevInflateParent), mMutation);
}
}
@@ -345,6 +363,7 @@
@NonNull private final String mClickableIdExtra;
@Nullable private final LoggingUtils mLoggingUtils;
+ @NonNull private final ProviderStatsLogger mProviderStatsLogger;
private final boolean mAnimationEnabled;
private final int mRunningAnimationsLimit;
@@ -366,6 +385,7 @@
@Nullable ProtoLayoutExtensionViewProvider extensionViewProvider,
@NonNull String clickableIdExtra,
@Nullable LoggingUtils loggingUtils,
+ @NonNull ProviderStatsLogger providerStatsLogger,
boolean animationEnabled,
int runningAnimationsLimit,
boolean updatesEnabled,
@@ -384,6 +404,7 @@
this.mExtensionViewProvider = extensionViewProvider;
this.mClickableIdExtra = clickableIdExtra;
this.mLoggingUtils = loggingUtils;
+ this.mProviderStatsLogger = providerStatsLogger;
this.mAnimationEnabled = animationEnabled;
this.mRunningAnimationsLimit = runningAnimationsLimit;
this.mUpdatesEnabled = updatesEnabled;
@@ -468,6 +489,13 @@
return mLoggingUtils;
}
+ /** Returns the provider stats logger used for telemetry. */
+ @RestrictTo(Scope.LIBRARY_GROUP)
+ @NonNull
+ public ProviderStatsLogger getProviderStatsLogger() {
+ return mProviderStatsLogger;
+ }
+
/** Returns whether animations are enabled. */
@RestrictTo(Scope.LIBRARY)
public boolean getAnimationEnabled() {
@@ -529,6 +557,7 @@
@Nullable private ProtoLayoutExtensionViewProvider mExtensionViewProvider;
@NonNull private final String mClickableIdExtra;
@Nullable private LoggingUtils mLoggingUtils;
+ @Nullable private ProviderStatsLogger mProviderStatsLogger;
private boolean mAnimationEnabled = true;
private int mRunningAnimationsLimit = DEFAULT_MAX_CONCURRENT_RUNNING_ANIMATIONS;
@@ -632,6 +661,15 @@
return this;
}
+ /** Sets the provider stats logger used for telemetry. */
+ @RestrictTo(Scope.LIBRARY_GROUP)
+ @NonNull
+ public Builder setProviderStatsLogger(
+ @NonNull ProviderStatsLogger providerStatsLogger) {
+ this.mProviderStatsLogger = providerStatsLogger;
+ return this;
+ }
+
/**
* Sets whether animation are enabled. If disabled, none of the animation will be
* played.
@@ -715,6 +753,12 @@
if (mRendererResources == null) {
this.mRendererResources = mUiContext.getResources();
}
+
+ if (mProviderStatsLogger == null) {
+ mProviderStatsLogger =
+ new NoOpProviderStatsLogger(
+ "ProviderStatsLogger not provided to " + TAG);
+ }
return new Config(
mUiContext,
mRendererResources,
@@ -728,6 +772,7 @@
mExtensionViewProvider,
mClickableIdExtra,
mLoggingUtils,
+ mProviderStatsLogger,
mAnimationEnabled,
mRunningAnimationsLimit,
mUpdatesEnabled,
@@ -754,24 +799,51 @@
this.mWasFullyVisibleBefore = false;
this.mAllowLayoutChangingBindsWithoutDefault =
config.getAllowLayoutChangingBindsWithoutDefault();
+ this.mProviderStatsLogger = config.getProviderStatsLogger();
StateStore stateStore = config.getStateStore();
- if (stateStore != null) {
- mDataPipeline =
- config.getAnimationEnabled()
- ? new ProtoLayoutDynamicDataPipeline(
- config.getPlatformDataProviders(),
- stateStore,
- new FixedQuotaManagerImpl(
- config.getRunningAnimationsLimit(), "animations"),
- new FixedQuotaManagerImpl(
- DYNAMIC_NODES_MAX_COUNT, "dynamic nodes"))
- : new ProtoLayoutDynamicDataPipeline(
- config.getPlatformDataProviders(), stateStore);
- mDataPipeline.setFullyVisible(config.getIsViewFullyVisible());
- } else {
+ if (stateStore == null) {
mDataPipeline = null;
+ return;
}
+
+ if (config.getAnimationEnabled()) {
+ QuotaManager nodeQuotaManager =
+ new FixedQuotaManagerImpl(DYNAMIC_NODES_MAX_COUNT, "dynamic nodes") {
+ @Override
+ public boolean tryAcquireQuota(int quota) {
+ boolean success = super.tryAcquireQuota(quota);
+ if (!success) {
+ mProviderStatsLogger.logInflationFailed(
+ INFLATION_FAILURE_REASON_EXPRESSION_NODE_COUNT_EXCEEDED);
+ }
+ return success;
+ }
+ };
+ mDataPipeline =
+ new ProtoLayoutDynamicDataPipeline(
+ config.getPlatformDataProviders(),
+ stateStore,
+ new FixedQuotaManagerImpl(
+ config.getRunningAnimationsLimit(), "animations") {
+ @Override
+ public boolean tryAcquireQuota(int quota) {
+ boolean success = super.tryAcquireQuota(quota);
+ if (!success) {
+ mProviderStatsLogger.logIgnoredFailure(
+ IGNORED_FAILURE_ANIMATION_QUOTA_EXCEEDED);
+ }
+ return success;
+ }
+ },
+ nodeQuotaManager);
+ } else {
+ mDataPipeline =
+ new ProtoLayoutDynamicDataPipeline(
+ config.getPlatformDataProviders(), stateStore);
+ }
+
+ mDataPipeline.setFullyVisible(config.getIsViewFullyVisible());
}
@WorkerThread
@@ -780,7 +852,8 @@
@NonNull Layout layout,
@NonNull ResourceProto.Resources resources,
@Nullable RenderedMetadata prevRenderedMetadata,
- @NonNull ViewProperties parentViewProp) {
+ @NonNull ViewProperties parentViewProp,
+ @NonNull InflaterStatsLogger inflaterStatsLogger) {
ResourceResolvers resolvers =
mResourceResolversProvider.getResourceResolvers(
mUiContext, resources, mUiExecutorService, mAnimationEnabled);
@@ -797,10 +870,10 @@
if (sameFingerprint) {
if (mPrevLayoutAlreadyFailingDepthCheck) {
- throwExceptionForLayoutDepthCheckFailure();
+ handleLayoutDepthCheckFailure(inflaterStatsLogger);
}
} else {
- checkLayoutDepth(layout.getRoot(), MAX_LAYOUT_ELEMENT_DEPTH);
+ checkLayoutDepth(layout.getRoot(), MAX_LAYOUT_ELEMENT_DEPTH, inflaterStatsLogger);
}
mPrevLayoutAlreadyFailingDepthCheck = false;
@@ -815,6 +888,7 @@
.setClickableIdExtra(mClickableIdExtra)
.setAllowLayoutChangingBindsWithoutDefault(
mAllowLayoutChangingBindsWithoutDefault)
+ .setInflaterStatsLogger(inflaterStatsLogger)
.setApplyFontVariantBodyAsDefault(true);
if (mDataPipeline != null) {
inflaterConfigBuilder.setDynamicDataPipeline(mDataPipeline);
@@ -886,6 +960,18 @@
return new InflateParentData(result);
}
+ @UiThread
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ @NonNull
+ public ListenableFuture<RenderingArtifact> renderLayoutAndAttach(
+ @NonNull Layout layout,
+ @NonNull ResourceProto.Resources resources,
+ @NonNull ViewGroup attachParent) {
+
+ return renderAndAttach(
+ layout, resources, attachParent, mProviderStatsLogger.createInflaterStatsLogger());
+ }
+
/**
* Render the layout for this layout and attach this layout instance to a {@code attachParent}
* container. Note that this method may clear all of {@code attachParent}'s children before
@@ -911,6 +997,47 @@
@NonNull Layout layout,
@NonNull ResourceProto.Resources resources,
@NonNull ViewGroup attachParent) {
+ SettableFuture<Void> result = SettableFuture.create();
+ ListenableFuture<RenderingArtifact> future =
+ renderLayoutAndAttach(layout, resources, attachParent);
+ if (future.isDone()) {
+ if (future.isCancelled()) {
+ return immediateCancelledFuture();
+ }
+ return immediateFuture(null);
+ } else {
+ future.addListener(
+ () -> {
+ if (future.isCancelled()) {
+ result.cancel(/* mayInterruptIfRunning= */ false);
+ } else {
+ try {
+ RenderingArtifact ignored = future.get();
+ result.set(null);
+ } catch (ExecutionException
+ | InterruptedException
+ | CancellationException e) {
+ Log.e(TAG, "Failed to render layout", e);
+ result.setException(e);
+ }
+ }
+ },
+ mUiExecutorService);
+ }
+ return result;
+ }
+
+ @UiThread
+ @SuppressWarnings({
+ "ReferenceEquality",
+ "ExecutorTaskName",
+ }) // layout == prevLayout is intentional (and enough in this case)
+ @NonNull
+ private ListenableFuture<RenderingArtifact> renderAndAttach(
+ @NonNull Layout layout,
+ @NonNull ResourceProto.Resources resources,
+ @NonNull ViewGroup attachParent,
+ @NonNull InflaterStatsLogger inflaterStatsLogger) {
if (mLoggingUtils != null && mLoggingUtils.canLogD(TAG)) {
mLoggingUtils.logD(TAG, "Layout received in #renderAndAttach:\n %s", layout.toString());
mLoggingUtils.logD(
@@ -930,7 +1057,7 @@
if (layout == mPrevLayout && mInflateParent != null) {
// Nothing to do.
- return Futures.immediateVoidFuture();
+ return Futures.immediateFuture(RenderingArtifact.skipped());
}
boolean isReattaching = false;
@@ -1000,10 +1127,11 @@
layout,
resources,
prevRenderedMetadata,
- parentViewProp));
+ parentViewProp,
+ inflaterStatsLogger));
mCanReattachWithoutRendering = false;
}
- SettableFuture<Void> result = SettableFuture.create();
+ SettableFuture<RenderingArtifact> result = SettableFuture.create();
if (!checkNotNull(mRenderFuture).isDone()) {
ListenableFuture<RenderResult> rendererFuture = mRenderFuture;
mRenderFuture.addListener(
@@ -1023,7 +1151,8 @@
checkNotNull(rendererFuture).get(),
/* isReattaching= */ false,
layout,
- resources));
+ resources,
+ inflaterStatsLogger));
} catch (ExecutionException
| InterruptedException
| CancellationException e) {
@@ -1048,7 +1177,8 @@
mRenderFuture.get(),
isReattaching,
layout,
- resources));
+ resources,
+ inflaterStatsLogger));
} catch (ExecutionException | InterruptedException | CancellationException e) {
Log.e(TAG, "Failed to render layout", e);
result.setException(e);
@@ -1064,6 +1194,12 @@
*/
public void invalidateCache() {
mPrevResourcesVersion = null;
+ // Cancel any ongoing rendering which might have a reference to older app resources.
+ if (mRenderFuture != null && !mRenderFuture.isDone()) {
+ mRenderFuture.cancel(/* mayInterruptIfRunning= */ false);
+ mRenderFuture = null;
+ Log.w(TAG, "Cancelled ongoing rendering due to cache invalidation.");
+ }
}
@Nullable
@@ -1080,13 +1216,14 @@
@UiThread
@SuppressWarnings("ExecutorTaskName")
@NonNull
- private ListenableFuture<Void> postInflate(
+ private ListenableFuture<RenderingArtifact> postInflate(
@NonNull ViewGroup attachParent,
@Nullable ViewGroup prevInflateParent,
@NonNull RenderResult renderResult,
boolean isReattaching,
@NonNull Layout layout,
- @NonNull ResourceProto.Resources resources) {
+ @NonNull ResourceProto.Resources resources,
+ InflaterStatsLogger inflaterStatsLogger) {
mCanReattachWithoutRendering = renderResult.canReattachWithoutRendering();
if (renderResult instanceof InflatedIntoNewParentRenderResult) {
@@ -1101,9 +1238,10 @@
.inflateParent;
}
- ListenableFuture<Void> postInflateFuture =
- renderResult.postInflate(attachParent, prevInflateParent, isReattaching);
- SettableFuture<Void> result = SettableFuture.create();
+ ListenableFuture<RenderingArtifact> postInflateFuture =
+ renderResult.postInflate(
+ attachParent, prevInflateParent, isReattaching, inflaterStatsLogger);
+ SettableFuture<RenderingArtifact> result = SettableFuture.create();
if (!postInflateFuture.isDone()) {
postInflateFuture.addListener(
() -> {
@@ -1114,20 +1252,24 @@
| CancellationException e) {
result.setFuture(
handlePostInflateFailure(
- e, layout, resources, prevInflateParent, attachParent));
+ e,
+ layout,
+ resources,
+ prevInflateParent,
+ attachParent,
+ inflaterStatsLogger));
}
},
mUiExecutorService);
} else {
try {
- postInflateFuture.get();
- return Futures.immediateVoidFuture();
+ return immediateFuture(postInflateFuture.get());
} catch (ExecutionException
| InterruptedException
| CancellationException
| ViewMutationException e) {
return handlePostInflateFailure(
- e, layout, resources, prevInflateParent, attachParent);
+ e, layout, resources, prevInflateParent, attachParent, inflaterStatsLogger);
}
}
return result;
@@ -1136,22 +1278,24 @@
@UiThread
@SuppressWarnings("ReferenceEquality") // layout == prevLayout is intentional
@NonNull
- private ListenableFuture<Void> handlePostInflateFailure(
+ private ListenableFuture<RenderingArtifact> handlePostInflateFailure(
@NonNull Throwable error,
@NonNull Layout layout,
@NonNull ResourceProto.Resources resources,
@Nullable ViewGroup prevInflateParent,
- @NonNull ViewGroup parent) {
+ @NonNull ViewGroup parent,
+ InflaterStatsLogger inflaterStatsLogger) {
// If a RuntimeError is thrown, it'll be wrapped in an UncheckedExecutionException
Throwable e = error.getCause();
if (e instanceof ViewMutationException) {
+ inflaterStatsLogger.logIgnoredFailure(IGNORED_FAILURE_APPLY_MUTATION_EXCEPTION);
Log.w(TAG, "applyMutation failed." + e.getMessage());
if (mPrevLayout == layout && parent == mAttachParent) {
Log.w(TAG, "Retrying full inflation.");
// Clear rendering metadata and prevLayout to force a full reinflation.
ProtoLayoutInflater.clearRenderedMetadata(checkNotNull(prevInflateParent));
mPrevLayout = null;
- return renderAndAttach(layout, resources, parent);
+ return renderAndAttach(layout, resources, parent, inflaterStatsLogger);
}
} else {
Log.e(TAG, "postInflate failed.", error);
@@ -1176,6 +1320,7 @@
private void detachInternal() {
if (mRenderFuture != null && !mRenderFuture.isDone()) {
mRenderFuture.cancel(/* mayInterruptIfRunning= */ false);
+ mRenderFuture = null;
}
setLayoutVisibility(ProtoLayoutVisibilityState.VISIBILITY_STATE_INVISIBLE);
@@ -1227,9 +1372,12 @@
}
/** Returns true if the layout element depth doesn't exceed the given {@code allowedDepth}. */
- private void checkLayoutDepth(LayoutElement layoutElement, int allowedDepth) {
+ private void checkLayoutDepth(
+ LayoutElement layoutElement,
+ int allowedDepth,
+ InflaterStatsLogger inflaterStatsLogger) {
if (allowedDepth <= 0) {
- throwExceptionForLayoutDepthCheckFailure();
+ handleLayoutDepthCheckFailure(inflaterStatsLogger);
}
List<LayoutElement> children = ImmutableList.of();
switch (layoutElement.getInnerCase()) {
@@ -1245,28 +1393,32 @@
case ARC:
List<ArcLayoutElement> arcElements = layoutElement.getArc().getContentsList();
if (!arcElements.isEmpty() && allowedDepth == 1) {
- throwExceptionForLayoutDepthCheckFailure();
+ handleLayoutDepthCheckFailure(inflaterStatsLogger);
}
for (ArcLayoutElement element : arcElements) {
if (element.getInnerCase() == InnerCase.ADAPTER) {
- checkLayoutDepth(element.getAdapter().getContent(), allowedDepth - 1);
+ checkLayoutDepth(
+ element.getAdapter().getContent(),
+ allowedDepth - 1,
+ inflaterStatsLogger);
}
}
break;
case SPANNABLE:
if (layoutElement.getSpannable().getSpansCount() > 0 && allowedDepth == 1) {
- throwExceptionForLayoutDepthCheckFailure();
+ handleLayoutDepthCheckFailure(inflaterStatsLogger);
}
break;
default:
// Other LayoutElements have depth of one.
}
for (LayoutElement child : children) {
- checkLayoutDepth(child, allowedDepth - 1);
+ checkLayoutDepth(child, allowedDepth - 1, inflaterStatsLogger);
}
}
- private void throwExceptionForLayoutDepthCheckFailure() {
+ private void handleLayoutDepthCheckFailure(InflaterStatsLogger inflaterStatsLogger) {
+ inflaterStatsLogger.logInflationFailed(INFLATION_FAILURE_REASON_LAYOUT_DEPTH_EXCEEDED);
mPrevLayoutAlreadyFailingDepthCheck = true;
throw new IllegalStateException(
"Layout depth exceeds maximum allowed depth: " + MAX_LAYOUT_ELEMENT_DEPTH);
diff --git a/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/inflater/ProtoLayoutInflater.java b/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/inflater/ProtoLayoutInflater.java
index 1ca9a23..4997dde 100644
--- a/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/inflater/ProtoLayoutInflater.java
+++ b/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/inflater/ProtoLayoutInflater.java
@@ -28,7 +28,7 @@
import static androidx.wear.protolayout.renderer.common.ProtoLayoutDiffer.getParentNodePosId;
import static com.google.common.util.concurrent.Futures.immediateFailedFuture;
-import static com.google.common.util.concurrent.Futures.immediateVoidFuture;
+import static com.google.common.util.concurrent.Futures.immediateFuture;
import static java.lang.Math.max;
import static java.lang.Math.min;
@@ -180,9 +180,12 @@
import androidx.wear.protolayout.renderer.ProtoLayoutTheme.FontSet;
import androidx.wear.protolayout.renderer.R;
import androidx.wear.protolayout.renderer.common.LoggingUtils;
+import androidx.wear.protolayout.renderer.common.NoOpProviderStatsLogger;
import androidx.wear.protolayout.renderer.common.ProtoLayoutDiffer;
import androidx.wear.protolayout.renderer.common.ProtoLayoutDiffer.LayoutDiff;
import androidx.wear.protolayout.renderer.common.ProtoLayoutDiffer.TreeNodeWithChange;
+import androidx.wear.protolayout.renderer.common.ProviderStatsLogger.InflaterStatsLogger;
+import androidx.wear.protolayout.renderer.common.RenderingArtifact;
import androidx.wear.protolayout.renderer.common.SeekableAnimatedVectorDrawable;
import androidx.wear.protolayout.renderer.dynamicdata.ProtoLayoutDynamicDataPipeline;
import androidx.wear.protolayout.renderer.inflater.RenderedMetadata.LayoutInfo;
@@ -301,6 +304,7 @@
final String mClickableIdExtra;
@Nullable private final LoggingUtils mLoggingUtils;
+ @NonNull private final InflaterStatsLogger mInflaterStatsLogger;
@Nullable final Executor mLoadActionExecutor;
final LoadActionListener mLoadActionListener;
@@ -528,6 +532,7 @@
@NonNull private final String mClickableIdExtra;
@Nullable private final LoggingUtils mLoggingUtils;
+ @NonNull private final InflaterStatsLogger mInflaterStatsLogger;
@Nullable private final ProtoLayoutExtensionViewProvider mExtensionViewProvider;
private final boolean mAnimationEnabled;
@@ -547,6 +552,7 @@
@Nullable ProtoLayoutExtensionViewProvider extensionViewProvider,
@NonNull String clickableIdExtra,
@Nullable LoggingUtils loggingUtils,
+ @NonNull InflaterStatsLogger inflaterStatsLogger,
boolean animationEnabled,
boolean allowLayoutChangingBindsWithoutDefault,
boolean applyFontVariantBodyAsDefault) {
@@ -562,6 +568,7 @@
this.mAllowLayoutChangingBindsWithoutDefault = allowLayoutChangingBindsWithoutDefault;
this.mClickableIdExtra = clickableIdExtra;
this.mLoggingUtils = loggingUtils;
+ this.mInflaterStatsLogger = inflaterStatsLogger;
this.mExtensionViewProvider = extensionViewProvider;
this.mApplyFontVariantBodyAsDefault = applyFontVariantBodyAsDefault;
}
@@ -638,6 +645,12 @@
return mLoggingUtils;
}
+ /** Stats logger used for telemetry. */
+ @NonNull
+ public InflaterStatsLogger getInflaterStatsLogger() {
+ return mInflaterStatsLogger;
+ }
+
/** View provider for the renderer extension. */
@Nullable
public ProtoLayoutExtensionViewProvider getExtensionViewProvider() {
@@ -678,6 +691,7 @@
@Nullable private String mClickableIdExtra;
@Nullable private LoggingUtils mLoggingUtils;
+ @Nullable private InflaterStatsLogger mInflaterStatsLogger;
@Nullable private ProtoLayoutExtensionViewProvider mExtensionViewProvider = null;
@@ -788,6 +802,14 @@
return this;
}
+ /** Sets the stats logger used for telemetry. */
+ @NonNull
+ public Builder setInflaterStatsLogger(
+ @NonNull InflaterStatsLogger inflaterStatsLogger) {
+ this.mInflaterStatsLogger = inflaterStatsLogger;
+ return this;
+ }
+
/**
* Sets whether a "layout changing" data bind can be applied without the
* "value_for_layout" field being filled in, or being set to zero / empty. Defaults to
@@ -834,7 +856,11 @@
if (mClickableIdExtra == null) {
mClickableIdExtra = DEFAULT_CLICKABLE_ID_EXTRA;
}
-
+ if (mInflaterStatsLogger == null) {
+ mInflaterStatsLogger =
+ new NoOpProviderStatsLogger("No implementation was provided")
+ .createInflaterStatsLogger();
+ }
return new Config(
mUiContext,
mLayout,
@@ -847,6 +873,7 @@
mExtensionViewProvider,
checkNotNull(mClickableIdExtra),
mLoggingUtils,
+ mInflaterStatsLogger,
mAnimationEnabled,
mAllowLayoutChangingBindsWithoutDefault,
mApplyFontVariantBodyAsDefault);
@@ -873,6 +900,7 @@
config.getAllowLayoutChangingBindsWithoutDefault();
this.mClickableIdExtra = config.getClickableIdExtra();
this.mLoggingUtils = config.getLoggingUtils();
+ this.mInflaterStatsLogger = config.getInflaterStatsLogger();
this.mExtensionViewProvider = config.getExtensionViewProvider();
this.mApplyFontVariantBodyAsDefault = config.getApplyFontVariantBodyAsDefault();
}
@@ -1731,7 +1759,9 @@
if (modifiers.hasTransformation()) {
applyTransformation(
- wrapper == null ? view : wrapper, modifiers.getTransformation(), posId,
+ wrapper == null ? view : wrapper,
+ modifiers.getTransformation(),
+ posId,
pipelineMaker);
}
@@ -2565,8 +2595,7 @@
}
@Override
- public void onViewDetachedFromWindow(@NonNull View v) {
- }
+ public void onViewDetachedFromWindow(@NonNull View v) {}
});
}
@@ -3152,7 +3181,7 @@
* to the image view; otherwise returns null to indicate the failure of setting drawable.
*/
@Nullable
- private static Drawable setImageDrawable(
+ private Drawable setImageDrawable(
ImageView imageView, Future<Drawable> drawableFuture, String protoResId) {
try {
return setImageDrawable(imageView, drawableFuture.get(), protoResId);
@@ -3169,8 +3198,10 @@
* null to indicate the failure of setting drawable.
*/
@Nullable
- private static Drawable setImageDrawable(
- ImageView imageView, Drawable drawable, String protoResId) {
+ private Drawable setImageDrawable(ImageView imageView, Drawable drawable, String protoResId) {
+ if (drawable != null) {
+ mInflaterStatsLogger.logDrawableUsage(drawable);
+ }
if (drawable instanceof BitmapDrawable
&& ((BitmapDrawable) drawable).getBitmap().getByteCount()
> DEFAULT_MAX_BITMAP_RAW_SIZE) {
@@ -3300,7 +3331,7 @@
Log.w(
TAG,
"ArcLine length's value_for_layout is not a positive value. Element"
- + " won't be visible.");
+ + " won't be visible.");
}
sizeWrapper.setSweepAngleDegrees(sizeForLayout);
sizedLp.setAngularAlignment(
@@ -4079,8 +4110,9 @@
Optional<ProtoLayoutDynamicDataPipeline.PipelineMaker> pipelineMaker) {
if (dpProp.hasDynamicValue() && pipelineMaker.isPresent()) {
try {
- pipelineMaker.get().addPipelineFor(dpProp, dpProp.getValue(), posId,
- dynamicValueConsumer);
+ pipelineMaker
+ .get()
+ .addPipelineFor(dpProp, dpProp.getValue(), posId, dynamicValueConsumer);
} catch (RuntimeException ex) {
Log.e(TAG, "Error building pipeline", ex);
staticValueConsumer.accept(dpProp.getValue());
@@ -4143,7 +4175,9 @@
pipelineMaker
.get()
.addPipelineFor(
- floatProp.getDynamicValue(), floatProp.getValue(), posId,
+ floatProp.getDynamicValue(),
+ floatProp.getValue(),
+ posId,
dynamicValueconsumer);
} catch (RuntimeException ex) {
Log.e(TAG, "Error building pipeline", ex);
@@ -4548,7 +4582,7 @@
/** Apply the mutation that was previously computed with {@link #computeMutation}. */
@UiThread
@NonNull
- public ListenableFuture<Void> applyMutation(
+ public ListenableFuture<RenderingArtifact> applyMutation(
@NonNull ViewGroup prevInflatedParent, @NonNull ViewGroupMutation groupMutation) {
RenderedMetadata prevRenderedMetadata = getRenderedMetadata(prevInflatedParent);
if (prevRenderedMetadata != null
@@ -4561,11 +4595,11 @@
}
if (groupMutation.isNoOp()) {
// Nothing to do.
- return immediateVoidFuture();
+ return immediateFuture(RenderingArtifact.create(mInflaterStatsLogger));
}
if (groupMutation.mPipelineMaker.isPresent()) {
- SettableFuture<Void> result = SettableFuture.create();
+ SettableFuture<RenderingArtifact> result = SettableFuture.create();
groupMutation
.mPipelineMaker
.get()
@@ -4575,7 +4609,7 @@
() -> {
try {
applyMutationInternal(prevInflatedParent, groupMutation);
- result.set(null);
+ result.set(RenderingArtifact.create(mInflaterStatsLogger));
} catch (ViewMutationException ex) {
result.setException(ex);
}
@@ -4584,7 +4618,7 @@
} else {
try {
applyMutationInternal(prevInflatedParent, groupMutation);
- return immediateVoidFuture();
+ return immediateFuture(RenderingArtifact.create(mInflaterStatsLogger));
} catch (ViewMutationException ex) {
return immediateFailedFuture(ex);
}
@@ -4593,6 +4627,7 @@
private void applyMutationInternal(
@NonNull ViewGroup prevInflatedParent, @NonNull ViewGroupMutation groupMutation) {
+ mInflaterStatsLogger.logMutationChangedNodes(groupMutation.mInflatedViews.size());
for (InflatedView inflatedView : groupMutation.mInflatedViews) {
String posId = inflatedView.getTag();
if (posId == null) {
@@ -4620,15 +4655,21 @@
}
// Remove the touch delegate to the view to be updated
if (immediateParent.getTouchDelegate() != null) {
- ((TouchDelegateComposite) immediateParent.getTouchDelegate())
- .removeDelegate(viewToUpdate);
+ TouchDelegateComposite delegateComposite =
+ (TouchDelegateComposite) immediateParent.getTouchDelegate();
+ delegateComposite.removeDelegate(viewToUpdate);
// Make sure to remove the touch delegate when the actual clickable view is wrapped,
// for example ImageView inside the RatioViewWrapper
if (viewToUpdate instanceof ViewGroup
&& ((ViewGroup) viewToUpdate).getChildCount() > 0) {
- ((TouchDelegateComposite) immediateParent.getTouchDelegate())
- .removeDelegate(((ViewGroup) viewToUpdate).getChildAt(0));
+ delegateComposite.removeDelegate(((ViewGroup) viewToUpdate).getChildAt(0));
+ }
+
+ // If no more touch delegate left in the composite, remove it completely from the
+ // parent
+ if (delegateComposite.isEmpty()) {
+ immediateParent.setTouchDelegate(null);
}
}
immediateParent.removeViewAt(childIndex);
diff --git a/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/inflater/TouchDelegateComposite.java b/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/inflater/TouchDelegateComposite.java
index e6bf97c..6184ac8 100644
--- a/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/inflater/TouchDelegateComposite.java
+++ b/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/inflater/TouchDelegateComposite.java
@@ -83,6 +83,10 @@
mDelegates.remove(delegateView);
}
+ boolean isEmpty() {
+ return mDelegates.isEmpty();
+ }
+
@Override
public boolean onTouchEvent(@NonNull MotionEvent event) {
boolean eventForwarded = false;
@@ -125,7 +129,7 @@
@Override
@NonNull
public AccessibilityNodeInfo.TouchDelegateInfo getTouchDelegateInfo() {
- if (VERSION.SDK_INT >= VERSION_CODES.Q) {
+ if (VERSION.SDK_INT >= VERSION_CODES.Q && !mDelegates.isEmpty()) {
Map<Region, View> targetMap = new ArrayMap<>(mDelegates.size());
for (Map.Entry<View, DelegateInfo> entry : mDelegates.entrySet()) {
AccessibilityNodeInfo.TouchDelegateInfo info =
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 6db2dfb..d095d58 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
@@ -53,6 +53,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.common.RenderingArtifact;
import androidx.wear.protolayout.renderer.helper.TestDsl.LayoutNode;
import androidx.wear.protolayout.renderer.impl.ProtoLayoutViewInstance.Config;
@@ -97,8 +98,8 @@
@Test
public void adaptiveUpdateRatesDisabled_attach_reinflatesCompletely() throws Exception {
setupInstance(/* adaptiveUpdateRatesEnabled= */ false);
- ListenableFuture<Void> result =
- mInstanceUnderTest.renderAndAttach(
+ ListenableFuture<RenderingArtifact> result =
+ mInstanceUnderTest.renderLayoutAndAttach(
layout(column(text(TEXT1), text(TEXT2))), RESOURCES, mRootContainer);
shadowOf(Looper.getMainLooper()).idle();
@@ -108,7 +109,7 @@
assertThat(layout1).hasSize(1);
result =
- mInstanceUnderTest.renderAndAttach(
+ mInstanceUnderTest.renderLayoutAndAttach(
layout(column(text(TEXT1), text(TEXT3))), RESOURCES, mRootContainer);
shadowOf(Looper.getMainLooper()).idle();
@@ -120,8 +121,8 @@
@Test
public void adaptiveUpdateRatesEnabled_attach_appliesDiffOnly() throws Exception {
setupInstance(/* adaptiveUpdateRatesEnabled= */ true);
- ListenableFuture<Void> result =
- mInstanceUnderTest.renderAndAttach(
+ ListenableFuture<RenderingArtifact> result =
+ mInstanceUnderTest.renderLayoutAndAttach(
layout(column(text(TEXT1), text(TEXT2))), RESOURCES, mRootContainer);
shadowOf(Looper.getMainLooper()).idle();
@@ -131,7 +132,7 @@
assertThat(layout1).hasSize(1);
result =
- mInstanceUnderTest.renderAndAttach(
+ mInstanceUnderTest.renderLayoutAndAttach(
layout(column(text(TEXT1), text(TEXT3))), RESOURCES, mRootContainer);
shadowOf(Looper.getMainLooper()).idle();
@@ -144,8 +145,8 @@
@Test
public void reattach_usesCachedLayoutForDiffUpdate() throws Exception {
setupInstance(/* adaptiveUpdateRatesEnabled= */ true);
- ListenableFuture<Void> result =
- mInstanceUnderTest.renderAndAttach(
+ ListenableFuture<RenderingArtifact> result =
+ mInstanceUnderTest.renderLayoutAndAttach(
layout(column(text(TEXT1), text(TEXT2))), RESOURCES, mRootContainer);
shadowOf(Looper.getMainLooper()).idle();
@@ -156,7 +157,7 @@
mInstanceUnderTest.detach(mRootContainer);
result =
- mInstanceUnderTest.renderAndAttach(
+ mInstanceUnderTest.renderLayoutAndAttach(
layout(column(text(TEXT1), text(TEXT3))), RESOURCES, mRootContainer);
shadowOf(Looper.getMainLooper()).idle();
@@ -172,8 +173,8 @@
setupInstance(/* adaptiveUpdateRatesEnabled= */ true);
// First one that does the full layout update.
- ListenableFuture<Void> result =
- mInstanceUnderTest.renderAndAttach(
+ ListenableFuture<RenderingArtifact> result =
+ mInstanceUnderTest.renderLayoutAndAttach(
layout(column(text(TEXT1), text(TEXT2))), RESOURCES, mRootContainer);
shadowOf(Looper.getMainLooper()).idle();
@@ -184,7 +185,7 @@
// Second one that applies mutation only.
result =
- mInstanceUnderTest.renderAndAttach(
+ mInstanceUnderTest.renderLayoutAndAttach(
layout(column(text(TEXT1), text(TEXT3))), RESOURCES, mRootContainer);
// Detach so it can't apply update.
mInstanceUnderTest.detach(mRootContainer);
@@ -200,9 +201,8 @@
// Render the first layout.
Layout layout1 = layout(column(dynamicFixedText(TEXT1), dynamicFixedText(TEXT2)));
- ListenableFuture<Void> result =
- mInstanceUnderTest.renderAndAttach(
- layout1, RESOURCES, mRootContainer);
+ ListenableFuture<RenderingArtifact> result =
+ mInstanceUnderTest.renderLayoutAndAttach(layout1, RESOURCES, mRootContainer);
shadowOf(Looper.getMainLooper()).idle();
assertNoException(result);
@@ -227,9 +227,7 @@
// not changed part of the layout was also changed in inflated View.
Layout layout2 = layout(column(dynamicFixedText(TEXT1), dynamicFixedText(TEXT3)));
- result =
- mInstanceUnderTest.renderAndAttach(
- layout2, RESOURCES, mRootContainer);
+ result = mInstanceUnderTest.renderLayoutAndAttach(layout2, RESOURCES, mRootContainer);
// Make sure future is computing result.
assertThat(result.isDone()).isFalse();
@@ -252,8 +250,8 @@
// Render the first layout.
Layout layout1 = layout(column(dynamicFixedText(TEXT1), dynamicFixedText(TEXT2)));
- ListenableFuture<Void> result =
- mInstanceUnderTest.renderAndAttach(layout1, RESOURCES, mRootContainer);
+ ListenableFuture<RenderingArtifact> result =
+ mInstanceUnderTest.renderLayoutAndAttach(layout1, RESOURCES, mRootContainer);
shadowOf(Looper.getMainLooper()).idle();
assertNoException(result);
@@ -263,7 +261,7 @@
assertThat(findViewsWithText(mRootContainer, TEXT2)).hasSize(1);
Layout layout2 = layout(column(dynamicFixedText(TEXT1), dynamicFixedText(TEXT3)));
- result = mInstanceUnderTest.renderAndAttach(layout2, RESOURCES, mRootContainer);
+ result = mInstanceUnderTest.renderLayoutAndAttach(layout2, RESOURCES, mRootContainer);
// Make sure future is computing result.
assertThat(result.isDone()).isFalse();
shadowOf(Looper.getMainLooper()).idle();
@@ -279,13 +277,13 @@
public void adaptiveUpdateRatesEnabled_ongoingRendering_skipsPreviousLayout() {
FrameLayout container = new FrameLayout(mApplicationContext);
setupInstance(/* adaptiveUpdateRatesEnabled= */ true);
- ListenableFuture<Void> result1 =
- mInstanceUnderTest.renderAndAttach(
+ ListenableFuture<RenderingArtifact> result1 =
+ mInstanceUnderTest.renderLayoutAndAttach(
layout(column(text(TEXT1), text(TEXT2))), RESOURCES, container);
assertThat(result1.isDone()).isFalse();
- ListenableFuture<Void> result2 =
- mInstanceUnderTest.renderAndAttach(
+ ListenableFuture<RenderingArtifact> result2 =
+ mInstanceUnderTest.renderLayoutAndAttach(
layout(column(text(TEXT1), text(TEXT3))), RESOURCES, container);
shadowOf(Looper.getMainLooper()).idle();
@@ -301,14 +299,14 @@
FrameLayout container1 = new FrameLayout(mApplicationContext);
FrameLayout container2 = new FrameLayout(mApplicationContext);
setupInstance(/* adaptiveUpdateRatesEnabled= */ true);
- ListenableFuture<Void> result =
- mInstanceUnderTest.renderAndAttach(
+ ListenableFuture<RenderingArtifact> result =
+ mInstanceUnderTest.renderLayoutAndAttach(
layout(column(text(TEXT1), text(TEXT2))), RESOURCES, container1);
assertThrows(
IllegalStateException.class,
() ->
- mInstanceUnderTest.renderAndAttach(
+ mInstanceUnderTest.renderLayoutAndAttach(
layout(column(text(TEXT1), text(TEXT2))), RESOURCES, container2));
shadowOf(Looper.getMainLooper()).idle();
@@ -321,12 +319,14 @@
FrameLayout container1 = new FrameLayout(mApplicationContext);
FrameLayout container2 = new FrameLayout(mApplicationContext);
setupInstance(/* adaptiveUpdateRatesEnabled= */ true);
- ListenableFuture<Void> result1 =
- mInstanceUnderTest.renderAndAttach(layout(text(TEXT1)), RESOURCES, container1);
+ ListenableFuture<RenderingArtifact> result1 =
+ mInstanceUnderTest.renderLayoutAndAttach(
+ layout(text(TEXT1)), RESOURCES, container1);
mInstanceUnderTest.detach(container1);
- ListenableFuture<Void> result2 =
- mInstanceUnderTest.renderAndAttach(layout(text(TEXT1)), RESOURCES, container2);
+ ListenableFuture<RenderingArtifact> result2 =
+ mInstanceUnderTest.renderLayoutAndAttach(
+ layout(text(TEXT1)), RESOURCES, container2);
shadowOf(Looper.getMainLooper()).idle();
assertThat(result1.isCancelled()).isTrue();
@@ -341,13 +341,13 @@
throws Exception {
Layout layout = layout(text(TEXT1));
setupInstance(/* adaptiveUpdateRatesEnabled= */ false);
- ListenableFuture<Void> result =
- mInstanceUnderTest.renderAndAttach(layout, RESOURCES, mRootContainer);
+ ListenableFuture<RenderingArtifact> result =
+ mInstanceUnderTest.renderLayoutAndAttach(layout, RESOURCES, mRootContainer);
shadowOf(Looper.getMainLooper()).idle();
assertNoException(result);
- result = mInstanceUnderTest.renderAndAttach(layout, RESOURCES, mRootContainer);
+ result = mInstanceUnderTest.renderLayoutAndAttach(layout, RESOURCES, mRootContainer);
shadowOf(Looper.getMainLooper()).idle();
assertNoException(result);
@@ -360,14 +360,14 @@
Layout layout1 = layout(text(TEXT1));
Layout layout2 = layout(text(TEXT1));
setupInstance(/* adaptiveUpdateRatesEnabled= */ true);
- ListenableFuture<Void> result =
- mInstanceUnderTest.renderAndAttach(layout1, RESOURCES, mRootContainer);
+ ListenableFuture<RenderingArtifact> result =
+ mInstanceUnderTest.renderLayoutAndAttach(layout1, RESOURCES, mRootContainer);
shadowOf(Looper.getMainLooper()).idle();
assertNoException(result);
// Make sure we have an UnchangedRenderResult
- result = mInstanceUnderTest.renderAndAttach(layout2, RESOURCES, mRootContainer);
+ result = mInstanceUnderTest.renderLayoutAndAttach(layout2, RESOURCES, mRootContainer);
shadowOf(Looper.getMainLooper()).idle();
assertNoException(result);
@@ -377,7 +377,7 @@
assertThat(findViewsWithText(mRootContainer, TEXT1)).isEmpty();
shadowOf(Looper.getMainLooper()).idle();
- result = mInstanceUnderTest.renderAndAttach(layout2, RESOURCES, mRootContainer);
+ result = mInstanceUnderTest.renderLayoutAndAttach(layout2, RESOURCES, mRootContainer);
assertThat(result.isDone()).isTrue();
assertNoException(result);
@@ -388,8 +388,8 @@
public void fullInflationResultCanBeReused() throws Exception {
setupInstance(/* adaptiveUpdateRatesEnabled= */ false);
Layout layout = layout(text(TEXT1));
- ListenableFuture<Void> result =
- mInstanceUnderTest.renderAndAttach(layout, RESOURCES, mRootContainer);
+ ListenableFuture<RenderingArtifact> result =
+ mInstanceUnderTest.renderLayoutAndAttach(layout, RESOURCES, mRootContainer);
shadowOf(Looper.getMainLooper()).idle();
assertNoException(result);
@@ -397,7 +397,7 @@
ListenableFuture<?> renderFuture = mInstanceUnderTest.mRenderFuture;
mInstanceUnderTest.detach(mRootContainer);
- result = mInstanceUnderTest.renderAndAttach(layout, RESOURCES, mRootContainer);
+ result = mInstanceUnderTest.renderLayoutAndAttach(layout, RESOURCES, mRootContainer);
shadowOf(Looper.getMainLooper()).idle();
assertNoException(result);
@@ -409,15 +409,15 @@
throws Exception {
Layout layout = layout(text(TEXT1));
setupInstance(/* adaptiveUpdateRatesEnabled= */ false);
- ListenableFuture<Void> result =
- mInstanceUnderTest.renderAndAttach(layout, RESOURCES, mRootContainer);
+ ListenableFuture<RenderingArtifact> result =
+ mInstanceUnderTest.renderLayoutAndAttach(layout, RESOURCES, mRootContainer);
shadowOf(Looper.getMainLooper()).idle();
assertNoException(result);
List<View> textViews1 = findViewsWithText(mRootContainer, TEXT1);
assertThat(textViews1).hasSize(1);
mInstanceUnderTest.close();
- result = mInstanceUnderTest.renderAndAttach(layout, RESOURCES, mRootContainer);
+ result = mInstanceUnderTest.renderLayoutAndAttach(layout, RESOURCES, mRootContainer);
assertThat(shadowOf(Looper.getMainLooper()).isIdle()).isFalse();
shadowOf(Looper.getMainLooper()).idle();
@@ -431,8 +431,8 @@
public void detach_clearsHostView() throws Exception {
Layout layout = layout(text(TEXT1));
setupInstance(/* adaptiveUpdateRatesEnabled= */ true);
- ListenableFuture<Void> result =
- mInstanceUnderTest.renderAndAttach(layout, RESOURCES, mRootContainer);
+ ListenableFuture<RenderingArtifact> result =
+ mInstanceUnderTest.renderLayoutAndAttach(layout, RESOURCES, mRootContainer);
shadowOf(Looper.getMainLooper()).idle();
assertNoException(result);
assertThat(findViewsWithText(mRootContainer, TEXT1)).hasSize(1);
@@ -449,14 +449,14 @@
Layout layout2 = layout(text(TEXT1));
Resources resources2 = Resources.newBuilder().setVersion("2").build();
setupInstance(/* adaptiveUpdateRatesEnabled= */ true);
- ListenableFuture<Void> result =
- mInstanceUnderTest.renderAndAttach(layout1, resources1, mRootContainer);
+ ListenableFuture<RenderingArtifact> result =
+ mInstanceUnderTest.renderLayoutAndAttach(layout1, resources1, mRootContainer);
shadowOf(Looper.getMainLooper()).idle();
assertNoException(result);
assertThat(findViewsWithText(mRootContainer, TEXT1)).hasSize(1);
View view1 = findViewsWithText(mRootContainer, TEXT1).get(0);
- result = mInstanceUnderTest.renderAndAttach(layout2, resources2, mRootContainer);
+ result = mInstanceUnderTest.renderLayoutAndAttach(layout2, resources2, mRootContainer);
shadowOf(Looper.getMainLooper()).idle();
assertNoException(result);
@@ -472,15 +472,15 @@
Layout layout2 = layout(text(TEXT1));
Resources resources2 = Resources.newBuilder().setVersion("1").build();
setupInstance(/* adaptiveUpdateRatesEnabled= */ true);
- ListenableFuture<Void> result =
- mInstanceUnderTest.renderAndAttach(layout1, resources1, mRootContainer);
+ ListenableFuture<RenderingArtifact> result =
+ mInstanceUnderTest.renderLayoutAndAttach(layout1, resources1, mRootContainer);
shadowOf(Looper.getMainLooper()).idle();
assertNoException(result);
assertThat(findViewsWithText(mRootContainer, TEXT1)).hasSize(1);
View view1 = findViewsWithText(mRootContainer, TEXT1).get(0);
mInstanceUnderTest.invalidateCache();
- result = mInstanceUnderTest.renderAndAttach(layout2, resources2, mRootContainer);
+ result = mInstanceUnderTest.renderLayoutAndAttach(layout2, resources2, mRootContainer);
shadowOf(Looper.getMainLooper()).idle();
assertNoException(result);
@@ -490,13 +490,28 @@
}
@Test
+ public void invalidateCache_ongoingInflation_oldInflationGetsCancelled() throws Exception {
+ Layout layout1 = layout(text(TEXT1));
+ Resources resources1 = Resources.newBuilder().setVersion("1").build();
+ setupInstance(/* adaptiveUpdateRatesEnabled= */ true);
+ ListenableFuture<RenderingArtifact> result =
+ mInstanceUnderTest.renderLayoutAndAttach(layout1, resources1, mRootContainer);
+
+ mInstanceUnderTest.invalidateCache();
+ shadowOf(Looper.getMainLooper()).idle();
+
+ assertThat(result.isCancelled()).isTrue();
+ assertThat(findViewsWithText(mRootContainer, TEXT1)).isEmpty();
+ }
+
+ @Test
public void adaptiveUpdateRatesEnabled_rootElementdiff_keepsElementCentered() throws Exception {
int dimension = 50;
setupInstance(/* adaptiveUpdateRatesEnabled= */ true);
// Full inflation.
- ListenableFuture<Void> result =
- mInstanceUnderTest.renderAndAttach(
+ ListenableFuture<RenderingArtifact> result =
+ mInstanceUnderTest.renderLayoutAndAttach(
layout(
column(
props -> {
@@ -518,7 +533,7 @@
// Diff update only for the root element.
result =
- mInstanceUnderTest.renderAndAttach(
+ mInstanceUnderTest.renderLayoutAndAttach(
layout(
column(
props -> {
@@ -546,8 +561,8 @@
public void close_clearsHostView() throws Exception {
Layout layout = layout(text(TEXT1));
setupInstance(/* adaptiveUpdateRatesEnabled= */ true);
- ListenableFuture<Void> result =
- mInstanceUnderTest.renderAndAttach(layout, RESOURCES, mRootContainer);
+ ListenableFuture<RenderingArtifact> result =
+ mInstanceUnderTest.renderLayoutAndAttach(layout, RESOURCES, mRootContainer);
shadowOf(Looper.getMainLooper()).idle();
assertNoException(result);
assertThat(findViewsWithText(mRootContainer, TEXT1)).hasSize(1);
@@ -562,7 +577,9 @@
setupInstance(/* adaptiveUpdateRatesEnabled= */ false);
assertThrows(
ExecutionException.class,
- () -> renderAndAttachLayout(layout(recursiveBox(MAX_LAYOUT_ELEMENT_DEPTH + 1))));
+ () ->
+ renderLayoutAndAttachLayout(
+ layout(recursiveBox(MAX_LAYOUT_ELEMENT_DEPTH + 1))));
}
@Test
@@ -573,8 +590,8 @@
for (int i = 0; i < children.length; i++) {
children[i] = recursiveBox(MAX_LAYOUT_ELEMENT_DEPTH - 1);
}
- ListenableFuture<Void> result =
- mInstanceUnderTest.renderAndAttach(
+ ListenableFuture<RenderingArtifact> result =
+ mInstanceUnderTest.renderLayoutAndAttach(
// 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);
@@ -591,7 +608,7 @@
assertThrows(
ExecutionException.class,
() ->
- renderAndAttachLayout(
+ renderLayoutAndAttachLayout(
// Total number of views is = MAX_LAYOUT_ELEMENT_DEPTH + 1 (span
// text)
layout(
@@ -599,8 +616,8 @@
MAX_LAYOUT_ELEMENT_DEPTH,
spannable(spanText("Hello"))))));
- ListenableFuture<Void> result =
- mInstanceUnderTest.renderAndAttach(
+ ListenableFuture<RenderingArtifact> result =
+ mInstanceUnderTest.renderLayoutAndAttach(
// Total number of views is = (MAX_LAYOUT_ELEMENT_DEPTH -1) + 1 (span text)
layout(
recursiveBox(
@@ -620,12 +637,12 @@
assertThrows(
ExecutionException.class,
() ->
- renderAndAttachLayout(
+ renderLayoutAndAttachLayout(
// Total number of views is = 1 (Arc) + (MAX_LAYOUT_ELEMENT_DEPTH)
layout(arc(arcAdapter(recursiveBox(MAX_LAYOUT_ELEMENT_DEPTH))))));
- ListenableFuture<Void> result =
- mInstanceUnderTest.renderAndAttach(
+ ListenableFuture<RenderingArtifact> result =
+ mInstanceUnderTest.renderLayoutAndAttach(
// 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)))),
@@ -637,9 +654,9 @@
assertThat(mRootContainer.getChildCount()).isEqualTo(1);
}
- private void renderAndAttachLayout(Layout layout) throws Exception {
- ListenableFuture<Void> result =
- mInstanceUnderTest.renderAndAttach(layout, RESOURCES, mRootContainer);
+ private void renderLayoutAndAttachLayout(Layout layout) throws Exception {
+ ListenableFuture<RenderingArtifact> result =
+ mInstanceUnderTest.renderLayoutAndAttach(layout, RESOURCES, mRootContainer);
shadowOf(Looper.getMainLooper()).idle();
assertNoException(result);
}
@@ -690,7 +707,8 @@
return views;
}
- private static void assertNoException(ListenableFuture<Void> result) throws Exception {
+ private static void assertNoException(ListenableFuture<RenderingArtifact> result)
+ throws Exception {
// Assert that result hasn't thrown exception.
result.get();
}
diff --git a/wear/protolayout/protolayout-renderer/src/test/java/androidx/wear/protolayout/renderer/inflater/ProtoLayoutInflaterTest.java b/wear/protolayout/protolayout-renderer/src/test/java/androidx/wear/protolayout/renderer/inflater/ProtoLayoutInflaterTest.java
index da07985..1a46140 100644
--- a/wear/protolayout/protolayout-renderer/src/test/java/androidx/wear/protolayout/renderer/inflater/ProtoLayoutInflaterTest.java
+++ b/wear/protolayout/protolayout-renderer/src/test/java/androidx/wear/protolayout/renderer/inflater/ProtoLayoutInflaterTest.java
@@ -83,7 +83,6 @@
import androidx.core.content.ContextCompat;
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.wear.protolayout.renderer.common.SeekableAnimatedVectorDrawable;
import androidx.wear.protolayout.expression.AppDataKey;
import androidx.wear.protolayout.expression.DynamicBuilders;
import androidx.wear.protolayout.expression.pipeline.FixedQuotaManagerImpl;
@@ -203,6 +202,8 @@
import androidx.wear.protolayout.proto.TypesProto.StringProp;
import androidx.wear.protolayout.protobuf.ByteString;
import androidx.wear.protolayout.renderer.ProtoLayoutTheme;
+import androidx.wear.protolayout.renderer.common.RenderingArtifact;
+import androidx.wear.protolayout.renderer.common.SeekableAnimatedVectorDrawable;
import androidx.wear.protolayout.renderer.dynamicdata.ProtoLayoutDynamicDataPipeline;
import androidx.wear.protolayout.renderer.helper.TestFingerprinter;
import androidx.wear.protolayout.renderer.inflater.ProtoLayoutInflater.InflateResult;
@@ -222,6 +223,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.Robolectric;
+import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowChoreographer;
import org.robolectric.shadows.ShadowLooper;
import org.robolectric.shadows.ShadowPackageManager;
@@ -543,37 +545,41 @@
int width = 10;
int height = 12;
byte[] payload = "Hello World".getBytes(UTF_8);
+ LayoutElement extension =
+ LayoutElement.newBuilder()
+ .setExtension(
+ ExtensionLayoutElement.newBuilder()
+ .setExtensionId("foo")
+ .setPayload(ByteString.copyFrom(payload))
+ .setWidth(
+ ExtensionDimension.newBuilder()
+ .setLinearDimension(dp(width))
+ .build())
+ .setHeight(
+ ExtensionDimension.newBuilder()
+ .setLinearDimension(dp(height))
+ .build()))
+ .build();
LayoutElement root =
LayoutElement.newBuilder()
- .setBox(
- Box.newBuilder()
- // Outer box's width and height left at default value of "wrap"
- .addContents(
- LayoutElement.newBuilder()
- .setExtension(
- ExtensionLayoutElement.newBuilder()
- .setExtensionId("foo")
- .setPayload(ByteString.copyFrom(payload))
- .setWidth(
- ExtensionDimension.newBuilder()
- .setLinearDimension(dp(width))
- .build())
- .setHeight(
- ExtensionDimension.newBuilder()
- .setLinearDimension(dp(height))
- .build()))))
+ .setBox(
+ Box.newBuilder()
+ // Outer box's width and height left at default value of
+ // "wrap"
+ .addContents(extension))
.build();
FrameLayout rootLayout =
renderer(
- newRendererConfigBuilder(fingerprintedLayout(root))
- .setExtensionViewProvider(
- (extensionPayload, id) -> {
- TextView returnedView = new TextView(getApplicationContext());
- returnedView.setText("testing");
+ newRendererConfigBuilder(fingerprintedLayout(root))
+ .setExtensionViewProvider(
+ (extensionPayload, id) -> {
+ TextView returnedView =
+ new TextView(getApplicationContext());
+ returnedView.setText("testing");
- return returnedView;
- }))
+ return returnedView;
+ }))
.inflate();
// Check that the outer box is displayed and it has a child.
@@ -976,6 +982,23 @@
// A column with a row (Spacer + Spacer) and Spacer, everything has weighted expand
// dimension.
+
+ Row rowWithSpacers =
+ Row.newBuilder()
+ .setWidth(expand())
+ .setHeight(
+ ContainerDimension.newBuilder()
+ .setExpandedDimension(expandWithWeight(heightWeight1))
+ .build())
+ .addContents(
+ LayoutElement.newBuilder()
+ .setSpacer(
+ buildExpandedSpacer(widthWeight1, DEFAULT_WEIGHT)))
+ .addContents(
+ LayoutElement.newBuilder()
+ .setSpacer(
+ buildExpandedSpacer(widthWeight2, DEFAULT_WEIGHT)))
+ .build();
LayoutElement root =
LayoutElement.newBuilder()
.setColumn(
@@ -983,26 +1006,13 @@
.setWidth(expand())
.setHeight(expand())
.addContents(
- LayoutElement.newBuilder()
- .setRow(
- Row.newBuilder()
- .setWidth(expand())
- .setHeight(
- ContainerDimension.newBuilder()
- .setExpandedDimension(expandWithWeight(heightWeight1))
- .build())
- .addContents(
- LayoutElement.newBuilder()
- .setSpacer(
- buildExpandedSpacer(widthWeight1, DEFAULT_WEIGHT)))
- .addContents(
- LayoutElement.newBuilder()
- .setSpacer(
- buildExpandedSpacer(
- widthWeight2, DEFAULT_WEIGHT)))))
+ LayoutElement.newBuilder().setRow(rowWithSpacers))
.addContents(
LayoutElement.newBuilder()
- .setSpacer(buildExpandedSpacer(DEFAULT_WEIGHT, heightWeight2)))
+ .setSpacer(
+ buildExpandedSpacer(
+ DEFAULT_WEIGHT,
+ heightWeight2)))
.build())
.build();
@@ -1034,9 +1044,9 @@
.setExpandedDimension(
ExpandedDimensionProp
.getDefaultInstance()))
- .setWidth(SpacerDimension
- .newBuilder()
- .setLinearDimension(dp(width))))
+ .setWidth(
+ SpacerDimension.newBuilder()
+ .setLinearDimension(dp(width))))
.build();
FrameLayout rootLayout = renderer(fingerprintedLayout(root)).inflate();
@@ -1543,17 +1553,17 @@
ContainerDimension.newBuilder().setLinearDimension(dp(childSize)).build();
LayoutElement childBox =
- LayoutElement.newBuilder().setBox(
- Box.newBuilder()
- .setWidth(childBoxSize)
- .setHeight(childBoxSize)
- .setModifiers(
- Modifiers.newBuilder()
- .setClickable(
- Clickable.newBuilder()
- .setId("foo")
- .setOnClick(
- action))))
+ LayoutElement.newBuilder()
+ .setBox(
+ Box.newBuilder()
+ .setWidth(childBoxSize)
+ .setHeight(childBoxSize)
+ .setModifiers(
+ Modifiers.newBuilder()
+ .setClickable(
+ Clickable.newBuilder()
+ .setId("foo")
+ .setOnClick(action))))
.build();
LayoutElement root =
@@ -1563,13 +1573,14 @@
.setWidth(parentBoxSize)
.setHeight(parentBoxSize)
.addContents(childBox))
- .build();
+ .build();
State.Builder receivedState = State.newBuilder();
FrameLayout rootLayout =
renderer(
- newRendererConfigBuilder(fingerprintedLayout(root), resourceResolvers())
- .setLoadActionListener(receivedState::mergeFrom))
+ newRendererConfigBuilder(
+ fingerprintedLayout(root), resourceResolvers())
+ .setLoadActionListener(receivedState::mergeFrom))
.inflate();
shadowOf(Looper.getMainLooper()).idle();
@@ -1627,8 +1638,7 @@
.setWidth(
SpacerDimension.newBuilder()
.setLinearDimension(dp(spacerSize))
- .build()
- ));
+ .build()));
// |--clickable area child box 1 (5 - 35)--|
// |---clickable area child box 2 (30-60)--|
@@ -1647,8 +1657,7 @@
dp(clickTargetSize))
.setMinimumClickableHeight(
dp(clickTargetSize))
- .setOnClick(
- action)
+ .setOnClick(action)
.setId("foo1"))))
.build();
@@ -1666,8 +1675,7 @@
dp(clickTargetSize))
.setMinimumClickableHeight(
dp(clickTargetSize))
- .setOnClick(
- action)
+ .setOnClick(action)
.setId("foo2"))))
.build();
@@ -1692,8 +1700,9 @@
State.Builder receivedState = State.newBuilder();
FrameLayout rootLayout =
renderer(
- newRendererConfigBuilder(fingerprintedLayout(root), resourceResolvers())
- .setLoadActionListener(receivedState::mergeFrom))
+ newRendererConfigBuilder(
+ fingerprintedLayout(root), resourceResolvers())
+ .setLoadActionListener(receivedState::mergeFrom))
.inflate();
ShadowLooper.runUiThreadTasks();
@@ -1812,8 +1821,8 @@
// Compute the mutation
ViewGroupMutation mutation =
- renderer.computeMutation(getRenderedMetadata(rootLayout),
- fingerprintedLayout(root2));
+ renderer.computeMutation(
+ getRenderedMetadata(rootLayout), fingerprintedLayout(root2));
assertThat(mutation).isNotNull();
assertThat(mutation.isNoOp()).isFalse();
@@ -1848,8 +1857,9 @@
.setSpacer(
Spacer.newBuilder()
.setWidth(
- SpacerDimension.newBuilder().setLinearDimension(
- dp(spacerSize)).build()));
+ SpacerDimension.newBuilder()
+ .setLinearDimension(dp(spacerSize))
+ .build()));
int parentHeight = 45;
int parentWidth = 125;
@@ -1916,13 +1926,13 @@
// Compute the mutation
ViewGroupMutation mutation =
- renderer.computeMutation(getRenderedMetadata(rootLayout),
- fingerprintedLayout(root2));
+ renderer.computeMutation(
+ getRenderedMetadata(rootLayout), fingerprintedLayout(root2));
assertThat(mutation).isNotNull();
assertThat(mutation.isNoOp()).isFalse();
// Apply the mutation
- ListenableFuture<Void> applyMutationFuture =
+ ListenableFuture<RenderingArtifact> applyMutationFuture =
renderer.mRenderer.applyMutation(rootLayout, mutation);
shadowOf(getMainLooper()).idle();
try {
@@ -1958,6 +1968,108 @@
}
@Test
+ @Config(minSdk = VERSION_CODES.Q)
+ public void inflateThenMutate_withClickableSizeChange_clickableModifier_extendClickTargetSize()
+ {
+ Action action = Action.newBuilder().setLoadAction(LoadAction.getDefaultInstance()).build();
+ int parentSize = 50;
+ ContainerDimension parentBoxSize =
+ ContainerDimension.newBuilder().setLinearDimension(dp(parentSize)).build();
+ ContainerDimension childBoxSize =
+ ContainerDimension.newBuilder().setLinearDimension(dp(parentSize / 2f)).build();
+
+ Modifiers testModifiers1 =
+ Modifiers.newBuilder()
+ .setClickable(Clickable.newBuilder().setOnClick(action).setId("foo1"))
+ .build();
+
+ // Child box has a size smaller than the minimum clickable size, touch delegation is
+ // required.
+ LayoutElement root =
+ LayoutElement.newBuilder()
+ .setBox(
+ Box.newBuilder()
+ .setWidth(parentBoxSize)
+ .setHeight(parentBoxSize)
+ .addContents(
+ LayoutElement.newBuilder()
+ .setBox(
+ Box.newBuilder()
+ .setWidth(childBoxSize)
+ .setHeight(childBoxSize)
+ .setModifiers(
+ testModifiers1))))
+ .build();
+
+ State.Builder receivedState = State.newBuilder();
+ Renderer renderer =
+ renderer(
+ newRendererConfigBuilder(fingerprintedLayout(root), resourceResolvers())
+ .setLoadActionListener(receivedState::mergeFrom));
+ FrameLayout rootLayout = renderer.inflate();
+ ViewGroup parent = (ViewGroup) rootLayout.getChildAt(0);
+ // Confirm the touch delegation has happened.
+ assertThat(parent.getTouchDelegate()).isNotNull();
+ // Dispatch a click event to the parent View within the expanded clickable area;
+ // it should trigger the LoadAction...
+ receivedState.clearLastClickableId();
+ dispatchTouchEvent(parent, 5, 5);
+ expect.that(receivedState.getLastClickableId()).isEqualTo("foo1");
+
+ // Produce a new layout with child box specifies its minimum clickable size, NO touch
+ // delegation is required.
+ Modifiers testModifiers2 =
+ Modifiers.newBuilder()
+ .setClickable(
+ Clickable.newBuilder()
+ .setOnClick(action)
+ .setId("foo2")
+ .setMinimumClickableWidth(dp(parentSize / 2f))
+ .setMinimumClickableHeight(dp(parentSize / 2f)))
+ .build();
+ LayoutElement root2 =
+ LayoutElement.newBuilder()
+ .setBox(
+ Box.newBuilder()
+ .setWidth(parentBoxSize)
+ .setHeight(parentBoxSize)
+ .addContents(
+ LayoutElement.newBuilder()
+ .setBox(
+ Box.newBuilder()
+ .setWidth(childBoxSize)
+ .setHeight(childBoxSize)
+ .setModifiers(
+ testModifiers2))))
+ .build();
+
+ // Compute the mutation
+ ViewGroupMutation mutation =
+ renderer.computeMutation(getRenderedMetadata(rootLayout),
+ fingerprintedLayout(root2));
+ assertThat(mutation).isNotNull();
+ assertThat(mutation.isNoOp()).isFalse();
+
+ // Apply the mutation
+ boolean mutationResult = renderer.applyMutation(rootLayout, mutation);
+ assertThat(mutationResult).isTrue();
+
+ // Verify that the parent removed the touch delegation.
+ // Keep an empty touch delegate composite will lead to failure when calling
+ // {@link TouchDelegateComposite#getTouchDelegateInfo}
+ assertThat(parent.getTouchDelegate()).isNull();
+
+ // Dispatch a click event to the parent View within the expanded clickable area;
+ // it should no longer trigger the LoadAction.
+ receivedState.clearLastClickableId();
+ dispatchTouchEvent(parent, 5, 5);
+ expect.that(receivedState.getLastClickableId()).isEmpty();
+ View box = parent.getChildAt(0);
+ dispatchTouchEvent(box, 1, 1);
+ expect.that(receivedState.getLastClickableId()).isEqualTo("foo2");
+ }
+
+ @Test
public void inflate_clickable_withoutRippleEffect_rippleDrawableNotAdded() throws IOException {
final String textContentsWithRipple = "clickable with ripple";
final String textContentsWithoutRipple = "clickable without ripple";
@@ -1993,15 +2105,15 @@
FrameLayout rootLayout =
renderer(
- fingerprintedLayout(
- LayoutElement.newBuilder()
- .setColumn(
- Column.newBuilder()
- .addContents(textElementWithRipple)
- .addContents(
- textElementWithoutRipple)
- .build())
- .build()))
+ fingerprintedLayout(
+ LayoutElement.newBuilder()
+ .setColumn(
+ Column.newBuilder()
+ .addContents(textElementWithRipple)
+ .addContents(
+ textElementWithoutRipple)
+ .build())
+ .build()))
.inflate();
// Column
@@ -2025,6 +2137,49 @@
}
@Test
+ public void inflate_hiddenModifier_inhibitsClicks() {
+ final String textContents = "I am a clickable";
+
+ Action action = Action.newBuilder().setLoadAction(LoadAction.getDefaultInstance()).build();
+
+ LayoutElement root =
+ LayoutElement.newBuilder()
+ .setBox(
+ Box.newBuilder()
+ .addContents(
+ LayoutElement.newBuilder()
+ .setText(
+ createTextWithVisibility(
+ textContents,
+ "back",
+ action,
+ true)))
+ .addContents(
+ LayoutElement.newBuilder()
+ .setText(
+ createTextWithVisibility(
+ textContents,
+ "front",
+ action,
+ false))))
+ .build();
+
+ State.Builder receivedState = State.newBuilder();
+ FrameLayout rootLayout =
+ renderer(
+ newRendererConfigBuilder(
+ fingerprintedLayout(root), resourceResolvers())
+ .setLoadActionListener(receivedState::mergeFrom))
+ .inflate();
+
+ // Try to tap the stacked clickables.
+ dispatchTouchEvent(rootLayout, 5f, 5f);
+ shadowOf(Looper.getMainLooper()).idle();
+
+ expect.that(receivedState.getLastClickableId()).isEqualTo("back");
+ }
+
+ @Test
public void inflate_arc_withLineDrawnWithArcTo() {
LayoutElement root =
LayoutElement.newBuilder()
@@ -2151,8 +2306,8 @@
@Test
public void inflate_arc_withText_autoSize_notSet() {
int lastSize = 12;
- FontStyle.Builder style = FontStyle.newBuilder()
- .addAllSize(buildSizesList(new int[]{10, 20, lastSize}));
+ FontStyle.Builder style =
+ FontStyle.newBuilder().addAllSize(buildSizesList(new int[] {10, 20, lastSize}));
LayoutElement root =
LayoutElement.newBuilder()
.setArc(
@@ -2889,12 +3044,13 @@
.setFontStyle(FontStyle.newBuilder().addSize(sp(16)))
.setMaxLines(Int32Prop.newBuilder().setValue(6))
.setOverflow(
- TextOverflowProp.newBuilder().setValue(
- TextOverflow.TEXT_OVERFLOW_ELLIPSIZE));
+ TextOverflowProp.newBuilder()
+ .setValue(TextOverflow.TEXT_OVERFLOW_ELLIPSIZE));
Layout layout1 =
fingerprintedLayout(
LayoutElement.newBuilder()
- .setBox(buildFixedSizeBoxWIthText(text1)).build());
+ .setBox(buildFixedSizeBoxWIthText(text1))
+ .build());
Text.Builder text2 =
Text.newBuilder()
@@ -2904,18 +3060,19 @@
.setFontStyle(FontStyle.newBuilder().addSize(sp(4)))
.setMaxLines(Int32Prop.newBuilder().setValue(6))
.setOverflow(
- TextOverflowProp.newBuilder().setValue(
- TextOverflow.TEXT_OVERFLOW_ELLIPSIZE));
+ TextOverflowProp.newBuilder()
+ .setValue(TextOverflow.TEXT_OVERFLOW_ELLIPSIZE));
Layout layout2 =
fingerprintedLayout(
LayoutElement.newBuilder()
- .setBox(buildFixedSizeBoxWIthText(text2)).build());
+ .setBox(buildFixedSizeBoxWIthText(text2))
+ .build());
// Initial layout.
Renderer renderer = renderer(layout1);
ViewGroup inflatedViewParent = renderer.inflate();
- TextView textView1 = (TextView) ((ViewGroup) inflatedViewParent
- .getChildAt(0)).getChildAt(0);
+ TextView textView1 =
+ (TextView) ((ViewGroup) inflatedViewParent.getChildAt(0)).getChildAt(0);
// Apply the mutation.
ViewGroupMutation mutation =
@@ -2926,8 +3083,8 @@
assertThat(mutationResult).isTrue();
// This contains layout after the mutation.
- TextView textView2 = (TextView) ((ViewGroup) inflatedViewParent
- .getChildAt(0)).getChildAt(0);
+ TextView textView2 =
+ (TextView) ((ViewGroup) inflatedViewParent.getChildAt(0)).getChildAt(0);
expect.that(textView1.getEllipsize()).isEqualTo(TruncateAt.END);
expect.that(textView1.getMaxLines()).isEqualTo(2);
@@ -3028,7 +3185,7 @@
@Test
public void inflate_textView_autosize_set() {
String text = "Test text";
- int[] presetSizes = new int[]{12, 20, 10};
+ int[] presetSizes = new int[] {12, 20, 10};
List<DimensionProto.SpProp> sizes = buildSizesList(presetSizes);
LayoutElement textElement =
@@ -3036,16 +3193,16 @@
.setText(
Text.newBuilder()
.setText(string(text))
- .setFontStyle(
- FontStyle.newBuilder()
- .addAllSize(sizes)))
+ .setFontStyle(FontStyle.newBuilder().addAllSize(sizes)))
.build();
LayoutElement root =
- LayoutElement.newBuilder().setBox(
- Box.newBuilder()
- .setWidth(expand())
- .setHeight(expand())
- .addContents(textElement)).build();
+ LayoutElement.newBuilder()
+ .setBox(
+ Box.newBuilder()
+ .setWidth(expand())
+ .setHeight(expand())
+ .addContents(textElement))
+ .build();
FrameLayout rootLayout = renderer(fingerprintedLayout(root)).inflate();
ViewGroup firstChild = (ViewGroup) rootLayout.getChildAt(0);
@@ -3076,16 +3233,16 @@
Text.newBuilder()
.setText(string(text))
.setMaxLines(Int32Prop.newBuilder().setValue(4))
- .setFontStyle(
- FontStyle.newBuilder()
- .addAllSize(sizes)))
+ .setFontStyle(FontStyle.newBuilder().addAllSize(sizes)))
.build();
LayoutElement root =
- LayoutElement.newBuilder().setBox(
- Box.newBuilder()
- .setWidth(expand())
- .setHeight(expand())
- .addContents(textElement)).build();
+ LayoutElement.newBuilder()
+ .setBox(
+ Box.newBuilder()
+ .setWidth(expand())
+ .setHeight(expand())
+ .addContents(textElement))
+ .build();
FrameLayout rootLayout = renderer(fingerprintedLayout(root)).inflate();
ViewGroup firstChild = (ViewGroup) rootLayout.getChildAt(0);
@@ -3099,23 +3256,23 @@
public void inflate_textView_autosize_notSet() {
String text = "Test text";
int size = 24;
- List<DimensionProto.SpProp> sizes = buildSizesList(new int[]{size});
+ List<DimensionProto.SpProp> sizes = buildSizesList(new int[] {size});
LayoutElement textElement =
LayoutElement.newBuilder()
.setText(
Text.newBuilder()
.setText(string(text))
- .setFontStyle(
- FontStyle.newBuilder()
- .addAllSize(sizes)))
+ .setFontStyle(FontStyle.newBuilder().addAllSize(sizes)))
.build();
LayoutElement root =
- LayoutElement.newBuilder().setBox(
- Box.newBuilder()
- .setWidth(expand())
- .setHeight(expand())
- .addContents(textElement)).build();
+ LayoutElement.newBuilder()
+ .setBox(
+ Box.newBuilder()
+ .setWidth(expand())
+ .setHeight(expand())
+ .addContents(textElement))
+ .build();
FrameLayout rootLayout = renderer(fingerprintedLayout(root)).inflate();
ViewGroup firstChild = (ViewGroup) rootLayout.getChildAt(0);
@@ -3129,23 +3286,23 @@
public void inflate_textView_autosize_setDynamic_noop() {
String text = "Test text";
int lastSize = 24;
- List<DimensionProto.SpProp> sizes = buildSizesList(new int[]{10, 30, lastSize});
+ List<DimensionProto.SpProp> sizes = buildSizesList(new int[] {10, 30, lastSize});
LayoutElement textElement =
LayoutElement.newBuilder()
.setText(
Text.newBuilder()
.setText(dynamicString(text))
- .setFontStyle(
- FontStyle.newBuilder()
- .addAllSize(sizes)))
+ .setFontStyle(FontStyle.newBuilder().addAllSize(sizes)))
.build();
LayoutElement root =
- LayoutElement.newBuilder().setBox(
- Box.newBuilder()
- .setWidth(expand())
- .setHeight(expand())
- .addContents(textElement)).build();
+ LayoutElement.newBuilder()
+ .setBox(
+ Box.newBuilder()
+ .setWidth(expand())
+ .setHeight(expand())
+ .addContents(textElement))
+ .build();
FrameLayout rootLayout = renderer(fingerprintedLayout(root)).inflate();
ArrayList<View> textChildren = new ArrayList<>();
@@ -3159,23 +3316,23 @@
@Test
public void inflate_textView_autosize_wrongSizes_noop() {
String text = "Test text";
- List<DimensionProto.SpProp> sizes = buildSizesList(new int[]{0, -2, 0});
+ List<DimensionProto.SpProp> sizes = buildSizesList(new int[] {0, -2, 0});
LayoutElement textElement =
LayoutElement.newBuilder()
.setText(
Text.newBuilder()
.setText(string(text))
- .setFontStyle(
- FontStyle.newBuilder()
- .addAllSize(sizes)))
+ .setFontStyle(FontStyle.newBuilder().addAllSize(sizes)))
.build();
LayoutElement root =
- LayoutElement.newBuilder().setBox(
- Box.newBuilder()
- .setWidth(expand())
- .setHeight(expand())
- .addContents(textElement)).build();
+ LayoutElement.newBuilder()
+ .setBox(
+ Box.newBuilder()
+ .setWidth(expand())
+ .setHeight(expand())
+ .addContents(textElement))
+ .build();
FrameLayout rootLayout = renderer(fingerprintedLayout(root)).inflate();
ArrayList<View> textChildren = new ArrayList<>();
@@ -3219,8 +3376,8 @@
public void inflate_spantext_ignoresMultipleSizes() {
String text = "Test text";
int firstSize = 12;
- FontStyle.Builder style = FontStyle.newBuilder()
- .addAllSize(buildSizesList(new int[]{firstSize, 10, 20}));
+ FontStyle.Builder style =
+ FontStyle.newBuilder().addAllSize(buildSizesList(new int[] {firstSize, 10, 20}));
LayoutElement root =
LayoutElement.newBuilder()
.setSpannable(
@@ -4780,7 +4937,7 @@
boolean applyMutation(ViewGroup parent, ViewGroupMutation mutation) {
try {
- ListenableFuture<Void> applyMutationFuture =
+ ListenableFuture<RenderingArtifact> applyMutationFuture =
mRenderer.applyMutation(parent, mutation);
shadowOf(Looper.getMainLooper()).idle();
applyMutationFuture.get();
@@ -5000,33 +5157,25 @@
LayoutElement image = buildImage(protoResId, 30, 30);
-
- BoolProp.Builder stateBoolPropBuilder = BoolProp
- .newBuilder()
- .setValue(
- true)
- .setDynamicValue(
- DynamicBool
- .newBuilder()
- .setStateSource(
- StateBoolSource
- .newBuilder()
- .setSourceKey(
- boolKey)));
- LayoutElement.Builder boxBuilder = LayoutElement.newBuilder()
- .setBox(
- Box.newBuilder()
- .addContents(image)
- .setModifiers(
- Modifiers
- .newBuilder()
- .setHidden(stateBoolPropBuilder)));
+ BoolProp.Builder stateBoolPropBuilder =
+ BoolProp.newBuilder()
+ .setValue(true)
+ .setDynamicValue(
+ DynamicBool.newBuilder()
+ .setStateSource(
+ StateBoolSource.newBuilder()
+ .setSourceKey(boolKey)));
+ LayoutElement.Builder boxBuilder =
+ LayoutElement.newBuilder()
+ .setBox(
+ Box.newBuilder()
+ .addContents(image)
+ .setModifiers(
+ Modifiers.newBuilder()
+ .setHidden(stateBoolPropBuilder)));
LayoutElement root =
LayoutElement.newBuilder()
- .setRow(
- Row.newBuilder()
- .addContents(boxBuilder)
- .addContents(image))
+ .setRow(Row.newBuilder().addContents(boxBuilder).addContents(image))
.build();
FrameLayout layout = renderer(fingerprintedLayout(root)).inflate();
@@ -5059,7 +5208,8 @@
assertThat(secondImage.getLeft()).isEqualTo(secondImageLeft);
}
- @Test public void inflate_box_withVisibleModifier() {
+ @Test
+ public void inflate_box_withVisibleModifier() {
final String protoResId = "android";
final String boolKey = "bool-key";
@@ -5226,20 +5376,18 @@
ContainerDimension.newBuilder().setLinearDimension(dp(100.f).build()).build();
ContainerDimension innerBoxSize =
ContainerDimension.newBuilder().setLinearDimension(dp(60.f).build()).build();
- Box.Builder boxBuilder = Box.newBuilder()
- .setWidth(expand())
- .setHeight(wrap())
- .setModifiers(
- Modifiers.newBuilder()
- .setTransformation(
- transformation)
- .build())
- .addContents(
- LayoutElement.newBuilder()
- .setBox(
- Box.newBuilder()
- .setWidth(innerBoxSize)
- .setHeight(innerBoxSize)));
+ Box.Builder boxBuilder =
+ Box.newBuilder()
+ .setWidth(expand())
+ .setHeight(wrap())
+ .setModifiers(
+ Modifiers.newBuilder().setTransformation(transformation).build())
+ .addContents(
+ LayoutElement.newBuilder()
+ .setBox(
+ Box.newBuilder()
+ .setWidth(innerBoxSize)
+ .setHeight(innerBoxSize)));
LayoutElement root =
LayoutElement.newBuilder()
.setBox(
@@ -5657,7 +5805,7 @@
renderer.computeMutation(
getRenderedMetadata(inflatedViewParent),
fingerprintedLayout(textFadeIn("World")));
- ListenableFuture<Void> applyMutationFuture =
+ ListenableFuture<RenderingArtifact> applyMutationFuture =
renderer.mRenderer.applyMutation(inflatedViewParent, mutation);
// Idle for running code for starting animations.
@@ -5689,7 +5837,7 @@
getRenderedMetadata(inflatedViewParent),
fingerprintedLayout(
getTextElementWithExitAnimation("World", /* iterations= */ 0)));
- ListenableFuture<Void> applyMutationFuture =
+ ListenableFuture<RenderingArtifact> applyMutationFuture =
renderer.mRenderer.applyMutation(inflatedViewParent, mutation);
assertThat(mDataPipeline.getRunningAnimationsCount()).isEqualTo(0);
@@ -5715,7 +5863,7 @@
getRenderedMetadata(inflatedViewParent),
fingerprintedLayout(
getTextElementWithExitAnimation("World", /* iterations= */ 1)));
- ListenableFuture<Void> applyMutationFuture =
+ ListenableFuture<RenderingArtifact> applyMutationFuture =
renderer.mRenderer.applyMutation(inflatedViewParent, mutation);
assertThat(mDataPipeline.getRunningAnimationsCount()).isEqualTo(0);
@@ -5738,7 +5886,7 @@
getRenderedMetadata(inflatedViewParent),
fingerprintedLayout(
getTextElementWithExitAnimation("World", /* iterations= */ 1)));
- ListenableFuture<Void> applyMutationFuture =
+ ListenableFuture<RenderingArtifact> applyMutationFuture =
renderer.mRenderer.applyMutation(inflatedViewParent, mutation);
assertThat(mDataPipeline.getRunningAnimationsCount()).isEqualTo(0);
@@ -5764,7 +5912,7 @@
getRenderedMetadata(inflatedViewParent),
fingerprintedLayout(
getTextElementWithExitAnimation("World", /* iterations= */ 10)));
- ListenableFuture<Void> applyMutationFuture =
+ ListenableFuture<RenderingArtifact> applyMutationFuture =
renderer.mRenderer.applyMutation(inflatedViewParent, mutation);
shadowOf(getMainLooper()).idleFor(Duration.ofMillis(100));
@@ -5792,7 +5940,7 @@
getRenderedMetadata(inflatedViewParent),
fingerprintedLayout(
getTextElementWithExitAnimation("World", /* iterations= */ 10)));
- ListenableFuture<Void> applyMutationFuture =
+ ListenableFuture<RenderingArtifact> applyMutationFuture =
renderer.mRenderer.applyMutation(inflatedViewParent, mutation);
shadowOf(getMainLooper()).idle();
@@ -5821,7 +5969,7 @@
fingerprintedLayout(
getMultipleTextElementWithExitAnimation(
Arrays.asList("Hello"), /* iterations= */ 10)));
- ListenableFuture<Void> applyMutationFuture =
+ ListenableFuture<RenderingArtifact> applyMutationFuture =
renderer.mRenderer.applyMutation(inflatedViewParent, mutation);
shadowOf(getMainLooper()).idleFor(Duration.ofMillis(100));
@@ -5850,7 +5998,7 @@
getRenderedMetadata(inflatedViewParent),
fingerprintedLayout(
getTextElementWithExitAnimation("World", /* iterations= */ 10)));
- ListenableFuture<Void> applyMutationFuture =
+ ListenableFuture<RenderingArtifact> applyMutationFuture =
renderer.mRenderer.applyMutation(inflatedViewParent, mutation);
shadowOf(getMainLooper()).idleFor(Duration.ofMillis(100));
@@ -5863,7 +6011,7 @@
getTextElementWithExitAnimation(
"Second mutation", /* iterations= */ 10)));
- ListenableFuture<Void> applySecondMutationFuture =
+ ListenableFuture<RenderingArtifact> applySecondMutationFuture =
renderer.mRenderer.applyMutation(inflatedViewParent, secondMutation);
// the previous mutation should be finished
@@ -6222,9 +6370,12 @@
private static Spacer.Builder buildExpandedSpacer(int widthWeight, int heightWeight) {
return Spacer.newBuilder()
- .setWidth(SpacerDimension.newBuilder().setExpandedDimension(expandWithWeight(widthWeight)))
+ .setWidth(
+ SpacerDimension.newBuilder()
+ .setExpandedDimension(expandWithWeight(widthWeight)))
.setHeight(
- SpacerDimension.newBuilder().setExpandedDimension(expandWithWeight(heightWeight)));
+ SpacerDimension.newBuilder()
+ .setExpandedDimension(expandWithWeight(heightWeight)));
}
private static ExpandedDimensionProp expandWithWeight(int weight) {
@@ -6255,4 +6406,15 @@
.addContents(LayoutElement.newBuilder().setSpacer(spacer)))
.build());
}
+
+ private static Text createTextWithVisibility(
+ String text, String id, Action action, boolean visibility) {
+ return Text.newBuilder()
+ .setText(string(text))
+ .setModifiers(
+ Modifiers.newBuilder()
+ .setVisible(BoolProp.newBuilder().setValue(visibility))
+ .setClickable(Clickable.newBuilder().setId(id).setOnClick(action)))
+ .build();
+ }
}
diff --git a/webkit/webkit/src/main/java/androidx/webkit/CookieManagerCompat.java b/webkit/webkit/src/main/java/androidx/webkit/CookieManagerCompat.java
index 3aaad88..cb31832 100644
--- a/webkit/webkit/src/main/java/androidx/webkit/CookieManagerCompat.java
+++ b/webkit/webkit/src/main/java/androidx/webkit/CookieManagerCompat.java
@@ -39,6 +39,7 @@
* <a href="https://httpwg.org/specs/rfc6265.html#sane-set-cookie-syntax">the RFC6265 spec.</a>
* eg. "name=value; domain=.example.com; path=/"
*
+ * @param cookieManager The CookieManager instance to get info from.
* @param url the URL for which the API retrieves all available cookies.
* @return the cookies as a list of strings.
*/
diff --git a/webkit/webkit/src/main/java/androidx/webkit/WebSettingsCompat.java b/webkit/webkit/src/main/java/androidx/webkit/WebSettingsCompat.java
index 46d466f..5c15f3a 100644
--- a/webkit/webkit/src/main/java/androidx/webkit/WebSettingsCompat.java
+++ b/webkit/webkit/src/main/java/androidx/webkit/WebSettingsCompat.java
@@ -119,6 +119,7 @@
* {@link WebViewFeature#isFeatureSupported(String)}
* returns true for {@link WebViewFeature#SAFE_BROWSING_ENABLE}.
*
+ * @param settings The WebSettings object to update.
* @param enabled Whether Safe Browsing is enabled.
*/
@RequiresFeature(name = WebViewFeature.SAFE_BROWSING_ENABLE,
@@ -179,6 +180,7 @@
* {@link WebViewFeature#isFeatureSupported(String)}
* returns true for {@link WebViewFeature#DISABLED_ACTION_MODE_MENU_ITEMS}.
*
+ * @param settings The WebSettings object to update.
* @param menuItems an integer field flag for the menu items to be disabled.
*/
@RequiresFeature(name = WebViewFeature.DISABLED_ACTION_MODE_MENU_ITEMS,
@@ -425,6 +427,7 @@
* is created.
*
* <p>
+ * @param settings The WebSettings object to update.
* @param allow allow algorithmic darkening or not.
*
*/
@@ -593,6 +596,7 @@
* {@link WebViewFeature#isFeatureSupported(String)}
* returns true for {@link WebViewFeature#ENTERPRISE_AUTHENTICATION_APP_LINK_POLICY}.
*
+ * @param settings The WebSettings object to update.
* @param enabled Whether EnterpriseAuthenticationAppLinkPolicy should be enabled.
*/
@RequiresFeature(name = WebViewFeature.ENTERPRISE_AUTHENTICATION_APP_LINK_POLICY,
diff --git a/webkit/webkit/src/main/java/androidx/webkit/WebViewCompat.java b/webkit/webkit/src/main/java/androidx/webkit/WebViewCompat.java
index d3f6250..de2c728 100644
--- a/webkit/webkit/src/main/java/androidx/webkit/WebViewCompat.java
+++ b/webkit/webkit/src/main/java/androidx/webkit/WebViewCompat.java
@@ -160,6 +160,7 @@
* {@link WebViewFeature#isFeatureSupported(String)}
* returns true for {@link WebViewFeature#VISUAL_STATE_CALLBACK}.
*
+ * @param webview The WebView to post to.
* @param requestId An id that will be returned in the callback to allow callers to match
* requests with callbacks.
* @param callback The callback to be invoked.
@@ -497,6 +498,7 @@
* }
* </pre
*
+ * @param webview The WebView to post to.
* @param message the WebMessage
* @param targetOrigin the target origin.
*/
@@ -760,6 +762,7 @@
* This method should only be called if {@link WebViewFeature#isFeatureSupported(String)}
* returns true for {@link WebViewFeature#WEB_MESSAGE_LISTENER}.
*
+ * @param webview The WebView object to remove from.
* @param jsObjectName The JavaScript object's name that was previously passed to {@link
* #addWebMessageListener(WebView, String, Set, WebMessageListener)}.
*