Merge "Allow invalidations during the draw path" into androidx-main
diff --git a/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt b/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt
index eee3524..78bd208 100644
--- a/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt
@@ -48,7 +48,7 @@
val CONTENTPAGER = Version("1.1.0-alpha01")
val COMPOSE = Version(System.getenv("COMPOSE_CUSTOM_VERSION") ?: "1.0.0-beta08")
val COORDINATORLAYOUT = Version("1.2.0-alpha01")
- val CORE = Version("1.6.0-beta02")
+ val CORE = Version("1.7.0-alpha01")
val CORE_ANIMATION = Version("1.0.0-alpha03")
val CORE_ANIMATION_TESTING = Version("1.0.0-alpha03")
val CORE_APPDIGEST = Version("1.0.0-alpha01")
diff --git a/car/app/app/api/current.txt b/car/app/app/api/current.txt
index f7ec0ae9..3a71b2b 100644
--- a/car/app/app/api/current.txt
+++ b/car/app/app/api/current.txt
@@ -499,6 +499,7 @@
}
@androidx.car.app.annotations.CarProtocol public final class MessageTemplate implements androidx.car.app.model.Template {
+ method @androidx.car.app.annotations.RequiresCarApi(2) public androidx.car.app.model.ActionStrip? getActionStrip();
method public java.util.List<androidx.car.app.model.Action!> getActions();
method public androidx.car.app.model.CarText? getDebugMessage();
method public androidx.car.app.model.Action? getHeaderAction();
@@ -513,6 +514,7 @@
ctor public MessageTemplate.Builder(androidx.car.app.model.CarText);
method public androidx.car.app.model.MessageTemplate.Builder addAction(androidx.car.app.model.Action);
method public androidx.car.app.model.MessageTemplate build();
+ method @androidx.car.app.annotations.RequiresCarApi(2) public androidx.car.app.model.MessageTemplate.Builder setActionStrip(androidx.car.app.model.ActionStrip);
method public androidx.car.app.model.MessageTemplate.Builder setDebugMessage(Throwable);
method public androidx.car.app.model.MessageTemplate.Builder setDebugMessage(String);
method public androidx.car.app.model.MessageTemplate.Builder setHeaderAction(androidx.car.app.model.Action);
diff --git a/car/app/app/api/public_plus_experimental_current.txt b/car/app/app/api/public_plus_experimental_current.txt
index 9ddca75..dbcb362 100644
--- a/car/app/app/api/public_plus_experimental_current.txt
+++ b/car/app/app/api/public_plus_experimental_current.txt
@@ -502,6 +502,7 @@
}
@androidx.car.app.annotations.CarProtocol public final class MessageTemplate implements androidx.car.app.model.Template {
+ method @androidx.car.app.annotations.RequiresCarApi(2) public androidx.car.app.model.ActionStrip? getActionStrip();
method public java.util.List<androidx.car.app.model.Action!> getActions();
method public androidx.car.app.model.CarText? getDebugMessage();
method public androidx.car.app.model.Action? getHeaderAction();
@@ -516,6 +517,7 @@
ctor public MessageTemplate.Builder(androidx.car.app.model.CarText);
method public androidx.car.app.model.MessageTemplate.Builder addAction(androidx.car.app.model.Action);
method public androidx.car.app.model.MessageTemplate build();
+ method @androidx.car.app.annotations.RequiresCarApi(2) public androidx.car.app.model.MessageTemplate.Builder setActionStrip(androidx.car.app.model.ActionStrip);
method public androidx.car.app.model.MessageTemplate.Builder setDebugMessage(Throwable);
method public androidx.car.app.model.MessageTemplate.Builder setDebugMessage(String);
method public androidx.car.app.model.MessageTemplate.Builder setHeaderAction(androidx.car.app.model.Action);
diff --git a/car/app/app/api/restricted_current.txt b/car/app/app/api/restricted_current.txt
index f7ec0ae9..3a71b2b 100644
--- a/car/app/app/api/restricted_current.txt
+++ b/car/app/app/api/restricted_current.txt
@@ -499,6 +499,7 @@
}
@androidx.car.app.annotations.CarProtocol public final class MessageTemplate implements androidx.car.app.model.Template {
+ method @androidx.car.app.annotations.RequiresCarApi(2) public androidx.car.app.model.ActionStrip? getActionStrip();
method public java.util.List<androidx.car.app.model.Action!> getActions();
method public androidx.car.app.model.CarText? getDebugMessage();
method public androidx.car.app.model.Action? getHeaderAction();
@@ -513,6 +514,7 @@
ctor public MessageTemplate.Builder(androidx.car.app.model.CarText);
method public androidx.car.app.model.MessageTemplate.Builder addAction(androidx.car.app.model.Action);
method public androidx.car.app.model.MessageTemplate build();
+ method @androidx.car.app.annotations.RequiresCarApi(2) public androidx.car.app.model.MessageTemplate.Builder setActionStrip(androidx.car.app.model.ActionStrip);
method public androidx.car.app.model.MessageTemplate.Builder setDebugMessage(Throwable);
method public androidx.car.app.model.MessageTemplate.Builder setDebugMessage(String);
method public androidx.car.app.model.MessageTemplate.Builder setHeaderAction(androidx.car.app.model.Action);
diff --git a/car/app/app/src/main/java/androidx/car/app/model/MessageTemplate.java b/car/app/app/src/main/java/androidx/car/app/model/MessageTemplate.java
index a45261c..0434b6e 100644
--- a/car/app/app/src/main/java/androidx/car/app/model/MessageTemplate.java
+++ b/car/app/app/src/main/java/androidx/car/app/model/MessageTemplate.java
@@ -18,6 +18,7 @@
import static androidx.car.app.model.constraints.ActionsConstraints.ACTIONS_CONSTRAINTS_BODY;
import static androidx.car.app.model.constraints.ActionsConstraints.ACTIONS_CONSTRAINTS_HEADER;
+import static androidx.car.app.model.constraints.ActionsConstraints.ACTIONS_CONSTRAINTS_SIMPLE;
import static java.util.Objects.hash;
import static java.util.Objects.requireNonNull;
@@ -67,6 +68,9 @@
private final Action mHeaderAction;
@Keep
private final List<Action> mActionList;
+ @Keep
+ @Nullable
+ private final ActionStrip mActionStrip;
/**
* Returns whether the template is loading.
@@ -100,6 +104,17 @@
}
/**
+ * Returns the {@link ActionStrip} for this template or {@code null} if not set.
+ *
+ * @see Builder#setActionStrip(ActionStrip)
+ */
+ @RequiresCarApi(2)
+ @Nullable
+ public ActionStrip getActionStrip() {
+ return mActionStrip;
+ }
+
+ /**
* Returns the message to display in the template.
*
* @see Builder#Builder(CharSequence)
@@ -148,7 +163,8 @@
@Override
public int hashCode() {
- return hash(mIsLoading, mTitle, mMessage, mDebugMessage, mHeaderAction, mActionList, mIcon);
+ return hash(mIsLoading, mTitle, mMessage, mDebugMessage, mHeaderAction, mActionList, mIcon,
+ mActionStrip);
}
@Override
@@ -167,7 +183,8 @@
&& Objects.equals(mDebugMessage, otherTemplate.mDebugMessage)
&& Objects.equals(mHeaderAction, otherTemplate.mHeaderAction)
&& Objects.equals(mActionList, otherTemplate.mActionList)
- && Objects.equals(mIcon, otherTemplate.mIcon);
+ && Objects.equals(mIcon, otherTemplate.mIcon)
+ && Objects.equals(mActionStrip, otherTemplate.mActionStrip);
}
MessageTemplate(Builder builder) {
@@ -177,6 +194,7 @@
mDebugMessage = builder.mDebugMessage;
mIcon = builder.mIcon;
mHeaderAction = builder.mHeaderAction;
+ mActionStrip = builder.mActionStrip;
mActionList = CollectionUtils.unmodifiableCopy(builder.mActionList);
}
@@ -188,6 +206,7 @@
mDebugMessage = null;
mIcon = null;
mHeaderAction = null;
+ mActionStrip = null;
mActionList = Collections.emptyList();
}
@@ -203,6 +222,8 @@
CarIcon mIcon;
@Nullable
Action mHeaderAction;
+ @Nullable
+ ActionStrip mActionStrip;
List<Action> mActionList = new ArrayList<>();
@Nullable
Throwable mDebugCause;
@@ -323,6 +344,29 @@
}
/**
+ * Sets the {@link ActionStrip} for this template or {@code null} to not display an {@link
+ * ActionStrip}.
+ *
+ * <p>Unless set with this method, the template will not have an action strip.
+ *
+ * <h4>Requirements</h4>
+ *
+ * This template allows up to 2 {@link Action}s in its {@link ActionStrip}. Of the 2 allowed
+ * {@link Action}s, one of them can contain a title as set via
+ * {@link Action.Builder#setTitle}. Otherwise, only {@link Action}s with icons are allowed.
+ *
+ * @throws IllegalArgumentException if {@code actionStrip} does not meet the requirements
+ * @throws NullPointerException if {@code actionStrip} is {@code null}
+ */
+ @RequiresCarApi(2)
+ @NonNull
+ public Builder setActionStrip(@NonNull ActionStrip actionStrip) {
+ ACTIONS_CONSTRAINTS_SIMPLE.validateOrThrow(requireNonNull(actionStrip).getActions());
+ mActionStrip = actionStrip;
+ return this;
+ }
+
+ /**
* Adds an {@link Action} to display along with the message.
*
* <h4>Requirements</h4>
diff --git a/car/app/app/src/test/java/androidx/car/app/model/MessageTemplateTest.java b/car/app/app/src/test/java/androidx/car/app/model/MessageTemplateTest.java
index 35721a3..01ff47c 100644
--- a/car/app/app/src/test/java/androidx/car/app/model/MessageTemplateTest.java
+++ b/car/app/app/src/test/java/androidx/car/app/model/MessageTemplateTest.java
@@ -47,6 +47,7 @@
private final String mMessage = "foo";
private final Action mAction = Action.BACK;
private final CarIcon mIcon = CarIcon.ALERT;
+ private final ActionStrip mActionStrip = new ActionStrip.Builder().addAction(mAction).build();
@Test
public void emptyMessage_throws() {
@@ -101,6 +102,7 @@
assertThat(template.getIcon()).isNull();
assertThat(template.getHeaderAction()).isNull();
assertThat(template.getActions()).isEmpty();
+ assertThat(template.getActionStrip()).isNull();
assertThat(template.getDebugMessage()).isNull();
}
@@ -131,6 +133,7 @@
.setDebugMessage(exception)
.setIcon(icon)
.addAction(action)
+ .setActionStrip(mActionStrip)
.build();
assertThat(template.getMessage().toString()).isEqualTo(mMessage);
@@ -140,6 +143,7 @@
assertThat(template.getIcon()).isEqualTo(icon);
assertThat(template.getHeaderAction()).isEqualTo(Action.BACK);
assertThat(template.getActions()).containsExactly(action);
+ assertThat(template.getActionStrip()).isEqualTo(mActionStrip);
}
@Test
@@ -170,6 +174,7 @@
.setDebugMessage(mCause)
.setHeaderAction(Action.BACK)
.addAction(mAction)
+ .setActionStrip(mActionStrip)
.setIcon(mIcon)
.build();
MessageTemplate template2 =
@@ -179,6 +184,7 @@
.setDebugMessage(mCause)
.setHeaderAction(Action.BACK)
.addAction(mAction)
+ .setActionStrip(mActionStrip)
.setIcon(mIcon)
.build();
@@ -193,6 +199,7 @@
.setDebugMessage(mDebugMessage)
.setDebugMessage(mCause)
.addAction(mAction)
+ .setActionStrip(mActionStrip)
.setIcon(mIcon)
.build();
MessageTemplate template2 =
@@ -201,6 +208,7 @@
.setDebugMessage("yo")
.setDebugMessage(mCause)
.addAction(mAction)
+ .setActionStrip(mActionStrip)
.setIcon(mIcon)
.build();
@@ -215,6 +223,7 @@
.setDebugMessage(mDebugMessage)
.setDebugMessage(mCause)
.addAction(mAction)
+ .setActionStrip(mActionStrip)
.setIcon(mIcon)
.build();
MessageTemplate template2 =
@@ -223,6 +232,7 @@
.setDebugMessage(mDebugMessage)
.setDebugMessage(new IllegalStateException("something else bad"))
.addAction(mAction)
+ .setActionStrip(mActionStrip)
.setIcon(mIcon)
.build();
@@ -237,6 +247,7 @@
.setDebugMessage(mDebugMessage)
.setDebugMessage(mCause)
.addAction(mAction)
+ .setActionStrip(mActionStrip)
.setIcon(mIcon)
.build();
MessageTemplate template2 =
@@ -245,6 +256,7 @@
.setDebugMessage(mDebugMessage)
.setDebugMessage(mCause)
.addAction(mAction)
+ .setActionStrip(mActionStrip)
.setIcon(mIcon)
.build();
@@ -260,6 +272,7 @@
.setDebugMessage(mCause)
.setHeaderAction(Action.BACK)
.addAction(mAction)
+ .setActionStrip(mActionStrip)
.setIcon(mIcon)
.build();
MessageTemplate template2 =
@@ -269,6 +282,7 @@
.setDebugMessage(mCause)
.setHeaderAction(Action.APP_ICON)
.addAction(mAction)
+ .setActionStrip(mActionStrip)
.setIcon(mIcon)
.build();
@@ -283,6 +297,7 @@
.setDebugMessage(mDebugMessage)
.setDebugMessage(mCause)
.addAction(mAction)
+ .setActionStrip(mActionStrip)
.setIcon(mIcon)
.build();
MessageTemplate template2 =
@@ -292,6 +307,34 @@
.setDebugMessage(mCause)
.addAction(mAction)
.addAction(mAction)
+ .setActionStrip(mActionStrip)
+ .setIcon(mIcon)
+ .build();
+
+ assertThat(template1).isNotEqualTo(template2);
+ }
+
+ @Test
+ public void notEquals_differentActionStrip() {
+ MessageTemplate template1 =
+ new MessageTemplate.Builder(mMessage)
+ .setTitle(mTitle)
+ .setDebugMessage(mDebugMessage)
+ .setDebugMessage(mCause)
+ .addAction(mAction)
+ .setActionStrip(mActionStrip)
+ .setIcon(mIcon)
+ .build();
+ MessageTemplate template2 =
+ new MessageTemplate.Builder(mMessage)
+ .setTitle(mTitle)
+ .setDebugMessage(mDebugMessage)
+ .setDebugMessage(mCause)
+ .addAction(mAction)
+ .setActionStrip(new ActionStrip.Builder()
+ .addAction(Action.BACK)
+ .addAction(Action.APP_ICON)
+ .build())
.setIcon(mIcon)
.build();
@@ -306,6 +349,7 @@
.setDebugMessage(mDebugMessage)
.setDebugMessage(mCause)
.addAction(mAction)
+ .setActionStrip(mActionStrip)
.setIcon(mIcon)
.build();
MessageTemplate template2 =
@@ -314,6 +358,7 @@
.setDebugMessage(mDebugMessage)
.setDebugMessage(mCause)
.addAction(mAction)
+ .setActionStrip(mActionStrip)
.setIcon(CarIcon.ERROR)
.build();
@@ -328,6 +373,7 @@
.setDebugMessage(mDebugMessage)
.setDebugMessage(mCause)
.addAction(mAction)
+ .setActionStrip(mActionStrip)
.setIcon(mIcon)
.build();
MessageTemplate template2 =
@@ -336,6 +382,7 @@
.setDebugMessage(mDebugMessage)
.setDebugMessage(mCause)
.addAction(mAction)
+ .setActionStrip(mActionStrip)
.setIcon(mIcon)
.build();
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ControlFlowTransformTests.kt b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ControlFlowTransformTests.kt
index 943df22..159fae8 100644
--- a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ControlFlowTransformTests.kt
+++ b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ControlFlowTransformTests.kt
@@ -3845,4 +3845,33 @@
fun Text(text: String) { }
"""
)
+
+ @Test
+ fun testReadOnlyComposableWithEarlyReturn() = controlFlow(
+ source = """
+ import androidx.compose.runtime.ReadOnlyComposable
+
+ @ReadOnlyComposable
+ @Composable
+ fun getSomeValue(a: Int): Int {
+ if (a < 100) return 0
+ return 1
+ }
+ """,
+ """
+ @ReadOnlyComposable
+ @Composable
+ fun getSomeValue(a: Int, %composer: Composer?, %changed: Int): Int {
+ sourceInformationMarkerStart(%composer, <>, "C(getSomeValue):Test.kt")
+ if (a < 100) {
+ val tmp1_return = 0
+ sourceInformationMarkerEnd(%composer)
+ return tmp1_return
+ }
+ val tmp0 = 1
+ sourceInformationMarkerEnd(%composer)
+ return tmp0
+ }
+ """
+ )
}
diff --git a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposableFunctionBodyTransformer.kt b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposableFunctionBodyTransformer.kt
index 4164418..5ef38c8 100644
--- a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposableFunctionBodyTransformer.kt
+++ b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposableFunctionBodyTransformer.kt
@@ -846,6 +846,15 @@
returnVar?.let { irReturn(declaration.symbol, irGet(it)) }
)
)
+ if (
+ elideGroups &&
+ collectSourceInformation &&
+ !declaration.descriptor.hasExplicitGroupsAnnotation()
+ ) {
+ scope.realizeEndCalls {
+ irSourceInformationMarkerEnd(body)
+ }
+ }
return declaration
}
@@ -3572,7 +3581,7 @@
}
}
- protected open fun realizeEndCalls(makeEnd: () -> IrExpression) {
+ open fun realizeEndCalls(makeEnd: () -> IrExpression) {
extraEndLocations.forEach {
it(makeEnd())
}
diff --git a/compose/desktop/desktop/samples/src/jvmMain/kotlin/androidx/compose/desktop/examples/example1/Main.jvm.kt b/compose/desktop/desktop/samples/src/jvmMain/kotlin/androidx/compose/desktop/examples/example1/Main.jvm.kt
index 0d6099f..55405aca 100644
--- a/compose/desktop/desktop/samples/src/jvmMain/kotlin/androidx/compose/desktop/examples/example1/Main.jvm.kt
+++ b/compose/desktop/desktop/samples/src/jvmMain/kotlin/androidx/compose/desktop/examples/example1/Main.jvm.kt
@@ -498,16 +498,16 @@
private fun RightColumn(modifier: Modifier) = Box {
val state = rememberLazyListState()
val itemCount = 100000
- val itemHeight = 20.dp
LazyColumn(modifier.graphicsLayer(alpha = 0.5f), state = state) {
items((1..itemCount).toList()) { x ->
+ val itemHeight = 20.dp + 20.dp * Math.random().toFloat()
Text(x.toString(), Modifier.graphicsLayer(alpha = 0.5f).height(itemHeight))
}
}
VerticalScrollbar(
- rememberScrollbarAdapter(state, itemCount, itemHeight),
+ rememberScrollbarAdapter(state),
Modifier.align(Alignment.CenterEnd)
)
}
diff --git a/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/Scrollbar.desktop.kt b/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/Scrollbar.desktop.kt
index 6dd2571..926af66 100644
--- a/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/Scrollbar.desktop.kt
+++ b/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/Scrollbar.desktop.kt
@@ -22,6 +22,7 @@
import androidx.compose.foundation.gestures.detectTapAndPress
import androidx.compose.foundation.gestures.drag
import androidx.compose.foundation.gestures.forEachGesture
+import androidx.compose.foundation.gestures.scrollBy
import androidx.compose.foundation.interaction.DragInteraction
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Box
@@ -59,6 +60,7 @@
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
+import kotlin.math.abs
import kotlin.math.roundToInt
import kotlin.math.sign
@@ -117,6 +119,10 @@
*
* @param adapter [ScrollbarAdapter] that will be used to communicate with scrollable component
* @param modifier the modifier to apply to this layout
+ * @param reverseLayout reverse the direction of scrolling and layout, when `true`
+ * and [LazyListState.firstVisibleItemIndex] == 0 then scrollbar
+ * will be at the bottom of the container.
+ * It is usually used in pair with `LazyColumn(reverseLayout = true)`
* @param style [ScrollbarStyle] to define visual style of scrollbar
* @param interactionSource [MutableInteractionSource] that will be used to dispatch
* [DragInteraction.Start] when this Scrollbar is being dragged.
@@ -125,11 +131,13 @@
fun VerticalScrollbar(
adapter: ScrollbarAdapter,
modifier: Modifier = Modifier,
+ reverseLayout: Boolean = false,
style: ScrollbarStyle = LocalScrollbarStyle.current,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }
) = Scrollbar(
adapter,
modifier,
+ reverseLayout,
style,
interactionSource,
isVertical = true
@@ -157,6 +165,10 @@
*
* @param adapter [ScrollbarAdapter] that will be used to communicate with scrollable component
* @param modifier the modifier to apply to this layout
+ * @param reverseLayout reverse the direction of scrolling and layout, when `true`
+ * and [LazyListState.firstVisibleItemIndex] == 0 then scrollbar
+ * will be at the end of the container.
+ * It is usually used in pair with `LazyRow(reverseLayout = true)`
* @param style [ScrollbarStyle] to define visual style of scrollbar
* @param interactionSource [MutableInteractionSource] that will be used to dispatch
* [DragInteraction.Start] when this Scrollbar is being dragged.
@@ -165,11 +177,13 @@
fun HorizontalScrollbar(
adapter: ScrollbarAdapter,
modifier: Modifier = Modifier,
+ reverseLayout: Boolean = false,
style: ScrollbarStyle = LocalScrollbarStyle.current,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }
) = Scrollbar(
adapter,
modifier,
+ reverseLayout,
style,
interactionSource,
isVertical = false
@@ -181,6 +195,7 @@
private fun Scrollbar(
adapter: ScrollbarAdapter,
modifier: Modifier = Modifier,
+ reverseLayout: Boolean,
style: ScrollbarStyle,
interactionSource: MutableInteractionSource,
isVertical: Boolean
@@ -205,8 +220,8 @@
}
val minimalHeight = style.minimalHeight.toPx()
- val sliderAdapter = remember(adapter, containerSize, minimalHeight) {
- SliderAdapter(adapter, containerSize, minimalHeight)
+ val sliderAdapter = remember(adapter, containerSize, minimalHeight, reverseLayout) {
+ SliderAdapter(adapter, containerSize, minimalHeight, reverseLayout)
}
val scrollThickness = style.thickness.roundToPx()
@@ -335,18 +350,34 @@
* Create and [remember] [ScrollbarAdapter] for lazy scrollable container and current instance of
* [scrollState] and item configuration
*/
+@Suppress("UNUSED_PARAMETER")
@ExperimentalFoundationApi
@Composable
+@Deprecated(
+ "itemCount and averageItemSize are calculated automatically. Use " +
+ "another overload rememberScrollbarAdapter without passing them",
+ ReplaceWith("rememberScrollbarAdapter(scrollState)")
+)
fun rememberScrollbarAdapter(
scrollState: LazyListState,
itemCount: Int,
averageItemSize: Dp
): ScrollbarAdapter {
- val averageItemSizePx = with(LocalDensity.current) {
- averageItemSize.toPx()
+ return remember(scrollState) {
+ ScrollbarAdapter(scrollState)
}
- return remember(scrollState, itemCount, averageItemSizePx) {
- ScrollbarAdapter(scrollState, itemCount, averageItemSizePx)
+}
+
+/**
+ * Create and [remember] [ScrollbarAdapter] for lazy scrollable container and current instance of
+ * [scrollState]
+ */
+@Composable
+fun rememberScrollbarAdapter(
+ scrollState: LazyListState,
+): ScrollbarAdapter {
+ return remember(scrollState) {
+ ScrollbarAdapter(scrollState)
}
}
@@ -386,22 +417,16 @@
scrollState.maxValue.toFloat()
}
-// TODO(demin): if item height is different then slider will have wrong
-// position when we dragging it (we can drag it to the beginning, but content will not be at the
-// beginning). We can implement adaptive scrollbar height after b/170472532
-
/**
- * Experimental ScrollbarAdapter for lazy lists. Doesn't work stable with non-fixed item height.
+ * ScrollbarAdapter for lazy lists.
*
* [scrollState] is instance of [LazyListState] which is used by scrollable component
*
- * Scrollbar size and position will be calculated by passed [itemCount] and [averageItemSize]
+ * Scrollbar size and position will be dynamically changed on the current visible content.
*
* Example:
* Box(Modifier.fillMaxSize()) {
* val state = rememberLazyListState()
- * val itemCount = 100
- * val itemHeight = 20.dp
*
* LazyColumn(state = state) {
* ...
@@ -409,34 +434,39 @@
*
* VerticalScrollbar(
* Modifier.align(Alignment.CenterEnd),
- * rememberScrollbarAdapter(state, itemCount, itemHeight)
+ * rememberScrollbarAdapter(state)
* )
* }
*/
-@ExperimentalFoundationApi
fun ScrollbarAdapter(
- scrollState: LazyListState,
- itemCount: Int,
- averageItemSize: Float
+ scrollState: LazyListState
): ScrollbarAdapter = LazyScrollbarAdapter(
- scrollState, itemCount, averageItemSize
+ scrollState
)
private class LazyScrollbarAdapter(
- private val scrollState: LazyListState,
- private val itemCount: Int,
- private val averageItemSize: Float
+ private val scrollState: LazyListState
) : ScrollbarAdapter {
- init {
- require(itemCount >= 0f) { "itemCount should be non-negative ($itemCount)" }
- require(averageItemSize > 0f) { "averageItemSize should be positive ($averageItemSize)" }
- }
-
override val scrollOffset: Float
get() = scrollState.firstVisibleItemIndex * averageItemSize +
scrollState.firstVisibleItemScrollOffset
override suspend fun scrollTo(containerSize: Int, scrollOffset: Float) {
+ val distance = scrollOffset - [email protected]
+
+ // if we scroll less than containerSize we need to use scrollBy function to avoid
+ // undesirable scroll jumps (when an item size is different)
+ //
+ // if we scroll more than containerSize we should immediately jump to this position
+ // without recreating all items between the current and the new position
+ if (abs(distance) <= containerSize) {
+ scrollState.scrollBy(distance)
+ } else {
+ snapTo(containerSize, scrollOffset)
+ }
+ }
+
+ private suspend fun snapTo(containerSize: Int, scrollOffset: Float) {
// In case of very big values, we can catch an overflow, so convert values to double and
// coerce them
// val averageItemSize = 26.000002f
@@ -463,6 +493,18 @@
override fun maxScrollOffset(containerSize: Int) =
averageItemSize * itemCount - containerSize
+
+ private val itemCount get() = scrollState.layoutInfo.totalItemsCount
+
+ private val averageItemSize by derivedStateOf {
+ scrollState
+ .layoutInfo
+ .visibleItemsInfo
+ .asSequence()
+ .map { it.size }
+ .average()
+ .toFloat()
+ }
}
/**
@@ -497,7 +539,8 @@
private class SliderAdapter(
val adapter: ScrollbarAdapter,
val containerSize: Int,
- val minHeight: Float
+ val minHeight: Float,
+ val reverseLayout: Boolean
) {
private val contentSize get() = adapter.maxScrollOffset(containerSize) + containerSize
private val visiblePart get() = containerSize.toFloat() / contentSize
@@ -514,7 +557,7 @@
return if (extraContentSpace == 0f) 1f else extraScrollbarSpace / extraContentSpace
}
- var position: Float
+ private var rawPosition: Float
get() = scrollScale * adapter.scrollOffset
set(value) {
runBlocking {
@@ -522,6 +565,16 @@
}
}
+ var position: Float
+ get() = if (reverseLayout) containerSize - size - rawPosition else rawPosition
+ set(value) {
+ rawPosition = if (reverseLayout) {
+ containerSize - size - value
+ } else {
+ value
+ }
+ }
+
val bounds get() = position..position + size
}
diff --git a/compose/foundation/foundation/src/desktopTest/kotlin/androidx/compose/foundation/ScrollbarTest.kt b/compose/foundation/foundation/src/desktopTest/kotlin/androidx/compose/foundation/ScrollbarTest.kt
index 4b1be7e..02ac894 100644
--- a/compose/foundation/foundation/src/desktopTest/kotlin/androidx/compose/foundation/ScrollbarTest.kt
+++ b/compose/foundation/foundation/src/desktopTest/kotlin/androidx/compose/foundation/ScrollbarTest.kt
@@ -314,6 +314,35 @@
@Suppress("SameParameterValue")
@OptIn(ExperimentalFoundationApi::class)
@Test(timeout = 3000)
+ fun `scroll in reversed lazy list`() {
+ runBlocking(Dispatchers.Main) {
+ lateinit var state: LazyListState
+
+ rule.setContent {
+ state = rememberLazyListState()
+ LazyTestBox(
+ state,
+ size = 100.dp,
+ childSize = 20.dp,
+ childCount = 20,
+ scrollbarWidth = 10.dp,
+ reverseLayout = true
+ )
+ }
+ rule.awaitIdle()
+
+ rule.onNodeWithTag("scrollbar").performGesture {
+ instantSwipe(start = Offset(0f, 99f), end = Offset(0f, 88f))
+ }
+ rule.awaitIdle()
+ assertEquals(2, state.firstVisibleItemIndex)
+ assertEquals(4, state.firstVisibleItemScrollOffset)
+ }
+ }
+
+ @Suppress("SameParameterValue")
+ @OptIn(ExperimentalFoundationApi::class)
+ @Test(timeout = 3000)
fun `scroll by more than one page in lazy list`() {
runBlocking(Dispatchers.Main) {
lateinit var state: LazyListState
@@ -456,11 +485,13 @@
childSize: Dp,
childCount: Int,
scrollbarWidth: Dp,
+ reverseLayout: Boolean = false
) = withTestEnvironment {
Box(Modifier.size(size)) {
LazyColumn(
Modifier.fillMaxSize().testTag("column"),
- state
+ state,
+ reverseLayout = reverseLayout
) {
items((0 until childCount).toList()) {
Box(Modifier.size(childSize).testTag("box$it"))
@@ -468,7 +499,8 @@
}
VerticalScrollbar(
- adapter = rememberScrollbarAdapter(state, childCount, childSize),
+ adapter = rememberScrollbarAdapter(state),
+ reverseLayout = reverseLayout,
modifier = Modifier
.width(scrollbarWidth)
.fillMaxHeight()
diff --git a/compose/integration-tests/demos/src/main/AndroidManifest.xml b/compose/integration-tests/demos/src/main/AndroidManifest.xml
index 6c31162..12e95e6 100644
--- a/compose/integration-tests/demos/src/main/AndroidManifest.xml
+++ b/compose/integration-tests/demos/src/main/AndroidManifest.xml
@@ -38,7 +38,5 @@
android:name=".DemoSettingsActivity"
android:label="Demo Theme Settings"
android:theme="@style/Theme.AppCompat.Light.DarkActionBar" />
-
- <meta-data android:value="true" android:name="COMPOSE_TEXT_FIELD_UNDO_ENABLED"/>
</application>
</manifest>
diff --git a/compose/material/material-ripple/src/androidMain/kotlin/androidx/compose/material/ripple/RippleContainer.android.kt b/compose/material/material-ripple/src/androidMain/kotlin/androidx/compose/material/ripple/RippleContainer.android.kt
index 840f932..6571a8c 100644
--- a/compose/material/material-ripple/src/androidMain/kotlin/androidx/compose/material/ripple/RippleContainer.android.kt
+++ b/compose/material/material-ripple/src/androidMain/kotlin/androidx/compose/material/ripple/RippleContainer.android.kt
@@ -18,6 +18,7 @@
import android.content.Context
import android.view.ViewGroup
+import androidx.compose.ui.R
/**
* A root-level container [ViewGroup] that manages creating and assigning [RippleHostView]s used
@@ -64,6 +65,9 @@
// Since we now have an unused ripple host, the next index should be 1 - the unused host
// will be used first.
nextHostIndex = 1
+
+ // Hide this view and its children in tools:
+ setTag(R.id.hide_in_inspector_tag, true)
}
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
diff --git a/compose/ui/ui-graphics/src/desktopMain/kotlin/androidx/compose/ui/graphics/DesktopCanvas.desktop.kt b/compose/ui/ui-graphics/src/desktopMain/kotlin/androidx/compose/ui/graphics/DesktopCanvas.desktop.kt
index c2539ea..6b80b7c 100644
--- a/compose/ui/ui-graphics/src/desktopMain/kotlin/androidx/compose/ui/graphics/DesktopCanvas.desktop.kt
+++ b/compose/ui/ui-graphics/src/desktopMain/kotlin/androidx/compose/ui/graphics/DesktopCanvas.desktop.kt
@@ -19,11 +19,17 @@
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.geometry.RoundRect
+import androidx.compose.ui.geometry.Size
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.util.fastForEach
+import org.jetbrains.skija.CubicResampler
+import org.jetbrains.skija.FilterMipmap
+import org.jetbrains.skija.FilterMode
import org.jetbrains.skija.Image
import org.jetbrains.skija.Matrix44
+import org.jetbrains.skija.MipmapMode
+import org.jetbrains.skija.SamplingMode
import org.jetbrains.skija.impl.Native
import org.jetbrains.skija.ClipMode as SkijaClipMode
import org.jetbrains.skija.RRect as SkijaRRect
@@ -164,14 +170,8 @@
}
override fun drawImage(image: ImageBitmap, topLeftOffset: Offset, paint: Paint) {
- val bitmap = image.asDesktopBitmap()
- Image.makeFromBitmap(bitmap).use { skijaImage ->
- skija.drawImage(
- skijaImage,
- topLeftOffset.x, topLeftOffset.y,
- paint.skija
- )
- }
+ val size = Size(image.width.toFloat(), image.height.toFloat())
+ drawImageRect(image, Offset.Zero, size, topLeftOffset, size, paint)
}
override fun drawImageRect(
@@ -182,23 +182,44 @@
dstSize: IntSize,
paint: Paint
) {
+ drawImageRect(
+ image,
+ Offset(srcOffset.x.toFloat(), srcOffset.y.toFloat()),
+ Size(srcSize.width.toFloat(), srcSize.height.toFloat()),
+ Offset(dstOffset.x.toFloat(), dstOffset.y.toFloat()),
+ Size(dstSize.width.toFloat(), dstSize.height.toFloat()),
+ paint
+ )
+ }
+
+ // TODO(demin): probably this method should be in the common Canvas
+ private fun drawImageRect(
+ image: ImageBitmap,
+ srcOffset: Offset,
+ srcSize: Size,
+ dstOffset: Offset,
+ dstSize: Size,
+ paint: Paint
+ ) {
val bitmap = image.asDesktopBitmap()
Image.makeFromBitmap(bitmap).use { skijaImage ->
skija.drawImageRect(
skijaImage,
SkijaRect.makeXYWH(
- srcOffset.x.toFloat(),
- srcOffset.y.toFloat(),
- srcSize.width.toFloat(),
- srcSize.height.toFloat()
+ srcOffset.x,
+ srcOffset.y,
+ srcSize.width,
+ srcSize.height
),
SkijaRect.makeXYWH(
- dstOffset.x.toFloat(),
- dstOffset.y.toFloat(),
- dstSize.width.toFloat(),
- dstSize.height.toFloat()
+ dstOffset.x,
+ dstOffset.y,
+ dstSize.width,
+ dstSize.height
),
- paint.skija
+ paint.filterQuality.toSkija(),
+ paint.skija,
+ true
)
}
}
@@ -356,4 +377,14 @@
this[2, 3],
this[3, 3]
)
+
+ // These constants are chosen to correspond the old implementation of SkFilterQuality:
+ // https://github.com/google/skia/blob/1f193df9b393d50da39570dab77a0bb5d28ec8ef/src/image/SkImage.cpp#L809
+ // https://github.com/google/skia/blob/1f193df9b393d50da39570dab77a0bb5d28ec8ef/include/core/SkSamplingOptions.h#L86
+ private fun FilterQuality.toSkija(): SamplingMode = when (this) {
+ FilterQuality.Low -> FilterMipmap(FilterMode.LINEAR, MipmapMode.NONE)
+ FilterQuality.Medium -> FilterMipmap(FilterMode.LINEAR, MipmapMode.NEAREST)
+ FilterQuality.High -> CubicResampler(1 / 3.0f, 1 / 3.0f)
+ else -> FilterMipmap(FilterMode.NEAREST, MipmapMode.NONE)
+ }
}
\ No newline at end of file
diff --git a/compose/ui/ui-graphics/src/desktopMain/kotlin/androidx/compose/ui/graphics/DesktopPaint.desktop.kt b/compose/ui/ui-graphics/src/desktopMain/kotlin/androidx/compose/ui/graphics/DesktopPaint.desktop.kt
index 9148102d..3242cf1 100644
--- a/compose/ui/ui-graphics/src/desktopMain/kotlin/androidx/compose/ui/graphics/DesktopPaint.desktop.kt
+++ b/compose/ui/ui-graphics/src/desktopMain/kotlin/androidx/compose/ui/graphics/DesktopPaint.desktop.kt
@@ -16,7 +16,6 @@
package androidx.compose.ui.graphics
-import org.jetbrains.skija.FilterQuality as SkijaFilterQuality
import org.jetbrains.skija.PaintMode as SkijaPaintMode
import org.jetbrains.skija.PaintStrokeCap as SkijaPaintStrokeCap
import org.jetbrains.skija.PaintStrokeJoin as SkijaPaintStrokeJoin
@@ -28,10 +27,6 @@
class DesktopPaint : Paint {
internal val skija = org.jetbrains.skija.Paint()
- constructor() {
- filterQuality = FilterQuality.Medium
- }
-
override fun asFrameworkPaint(): NativePaint = skija
override var alpha: Float
@@ -88,11 +83,7 @@
field = value
}
- override var filterQuality: FilterQuality = FilterQuality.None
- set(value) {
- skija.filterQuality = value.toSkija()
- field = value
- }
+ override var filterQuality: FilterQuality = FilterQuality.Medium
override var shader: Shader? = null
set(value) {
@@ -131,14 +122,6 @@
StrokeJoin.Bevel -> SkijaPaintStrokeJoin.BEVEL
else -> SkijaPaintStrokeJoin.MITER
}
-
- private fun FilterQuality.toSkija() = when (this) {
- FilterQuality.None -> SkijaFilterQuality.NONE
- FilterQuality.Low -> SkijaFilterQuality.LOW
- FilterQuality.Medium -> SkijaFilterQuality.MEDIUM
- FilterQuality.High -> SkijaFilterQuality.HIGH
- else -> SkijaFilterQuality.NONE
- }
}
actual fun BlendMode.isSupported(): Boolean = true
\ No newline at end of file
diff --git a/compose/ui/ui-graphics/src/desktopTest/kotlin/androidx/compose/ui/graphics/DesktopPaintTest.kt b/compose/ui/ui-graphics/src/desktopTest/kotlin/androidx/compose/ui/graphics/DesktopPaintTest.kt
index aa73daa2..462a5ba 100644
--- a/compose/ui/ui-graphics/src/desktopTest/kotlin/androidx/compose/ui/graphics/DesktopPaintTest.kt
+++ b/compose/ui/ui-graphics/src/desktopTest/kotlin/androidx/compose/ui/graphics/DesktopPaintTest.kt
@@ -17,7 +17,6 @@
package androidx.compose.ui.graphics
import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.platform.DesktopPlatform
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize
import org.junit.Assert.assertEquals
@@ -90,7 +89,7 @@
@Test
fun filterQuality() {
- assumeTrue(DesktopPlatform.Current == DesktopPlatform.MacOS)
+ assumeTrue(isWindows || isLinux)
canvas.drawImageRect(
image = imageFromResource("androidx/compose/desktop/test.png"),
@@ -127,6 +126,46 @@
}
@Test
+ fun `filterQuality with scaled Canvas`() {
+ assumeTrue(isWindows || isLinux)
+
+ canvas.scale(2f, 2f)
+
+ canvas.drawImageRect(
+ image = imageFromResource("androidx/compose/desktop/test.png"),
+ srcOffset = IntOffset(0, 2),
+ srcSize = IntSize(2, 4),
+ dstOffset = IntOffset(0, 2),
+ dstSize = IntSize(2, 6),
+ paint = redPaint.apply {
+ filterQuality = FilterQuality.None
+ }
+ )
+ canvas.drawImageRect(
+ image = imageFromResource("androidx/compose/desktop/test.png"),
+ srcOffset = IntOffset(0, 2),
+ srcSize = IntSize(2, 4),
+ dstOffset = IntOffset(2, 2),
+ dstSize = IntSize(2, 6),
+ paint = redPaint.apply {
+ filterQuality = FilterQuality.Low
+ }
+ )
+ canvas.drawImageRect(
+ image = imageFromResource("androidx/compose/desktop/test.png"),
+ srcOffset = IntOffset(0, 2),
+ srcSize = IntSize(2, 4),
+ dstOffset = IntOffset(4, 2),
+ dstSize = IntSize(2, 6),
+ paint = redPaint.apply {
+ filterQuality = FilterQuality.High
+ }
+ )
+
+ screenshotRule.snap(surface)
+ }
+
+ @Test
fun linearGradientShader() {
canvas.drawRect(left = 0f, top = 0f, right = 16f, bottom = 16f, paint = redPaint)
diff --git a/compose/ui/ui-graphics/src/desktopTest/kotlin/androidx/compose/ui/graphics/TestUtils.kt b/compose/ui/ui-graphics/src/desktopTest/kotlin/androidx/compose/ui/graphics/TestUtils.kt
new file mode 100644
index 0000000..c98c362
--- /dev/null
+++ b/compose/ui/ui-graphics/src/desktopTest/kotlin/androidx/compose/ui/graphics/TestUtils.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.ui.graphics
+
+private val os = System.getProperty("os.name").lowercase()
+internal val isLinux = os.startsWith("linux")
+internal val isWindows = os.startsWith("win")
+internal val isMacOs = os.startsWith("mac")
\ No newline at end of file
diff --git a/compose/ui/ui-inspection/src/androidTest/AndroidManifest.xml b/compose/ui/ui-inspection/src/androidTest/AndroidManifest.xml
index 58b8a25..bc217cf 100644
--- a/compose/ui/ui-inspection/src/androidTest/AndroidManifest.xml
+++ b/compose/ui/ui-inspection/src/androidTest/AndroidManifest.xml
@@ -19,6 +19,7 @@
<activity android:name="androidx.compose.ui.inspection.testdata.AndroidViewTestActivity" />
<activity android:name="androidx.compose.ui.inspection.testdata.DialogTestActivity" />
<activity android:name="androidx.compose.ui.inspection.testdata.ParametersTestActivity" />
+ <activity android:name="androidx.compose.ui.inspection.testdata.RippleTestActivity" />
<activity android:name="androidx.compose.ui.inspection.testdata.TestActivity"
android:theme="@style/TestTheme"/>
</application>
diff --git a/compose/ui/ui-inspection/src/androidTest/java/androidx/compose/ui/inspection/RippleTest.kt b/compose/ui/ui-inspection/src/androidTest/java/androidx/compose/ui/inspection/RippleTest.kt
new file mode 100644
index 0000000..34efc1c
--- /dev/null
+++ b/compose/ui/ui-inspection/src/androidTest/java/androidx/compose/ui/inspection/RippleTest.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.ui.inspection
+
+import android.view.ViewGroup
+import androidx.compose.ui.inspection.framework.getChildren
+import androidx.compose.ui.inspection.rules.ComposeInspectionRule
+import androidx.compose.ui.inspection.rules.sendCommand
+import androidx.compose.ui.inspection.testdata.RippleTestActivity
+import androidx.compose.ui.inspection.util.GetComposablesCommand
+import androidx.compose.ui.inspection.util.ThreadUtils
+import androidx.test.filters.LargeTest
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.runBlocking
+import org.junit.Rule
+import org.junit.Test
+
+@LargeTest
+class RippleTest {
+ @get:Rule
+ val rule = ComposeInspectionRule(RippleTestActivity::class)
+
+ @Test
+ fun rippleViewsAreMarked(): Unit = runBlocking {
+ val app = rule.inspectorTester.sendCommand(GetComposablesCommand(rule.rootId))
+ .getComposablesResponse
+ val composeViewChildren = ThreadUtils.runOnMainThread {
+ val composeView = rule.rootsForTest.single() as ViewGroup
+ composeView.getChildren().map { it.uniqueDrawingId }
+ }.get()
+ val toSkip = app.rootsList.single().viewsToSkipList
+ assertThat(composeViewChildren).containsExactlyElementsIn(toSkip)
+ assertThat(toSkip).hasSize(1)
+ }
+}
diff --git a/compose/ui/ui-inspection/src/androidTest/java/androidx/compose/ui/inspection/testdata/RippleTestActivity.kt b/compose/ui/ui-inspection/src/androidTest/java/androidx/compose/ui/inspection/testdata/RippleTestActivity.kt
new file mode 100644
index 0000000..04f415b
--- /dev/null
+++ b/compose/ui/ui-inspection/src/androidTest/java/androidx/compose/ui/inspection/testdata/RippleTestActivity.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.ui.inspection.testdata
+
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import androidx.compose.foundation.LocalIndication
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.indication
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.requiredHeight
+import androidx.compose.material.Text
+import androidx.compose.material.ripple.rememberRipple
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+
+class RippleTestActivity : ComponentActivity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContent {
+ Column {
+ val interactionSource = remember { MutableInteractionSource() }
+ Text(
+ text = "Click me with indication",
+ modifier = Modifier
+ .clickable(
+ interactionSource = interactionSource,
+ indication = rememberRipple()
+ ) { /* do something */ }
+ .padding(10.dp)
+ )
+ Spacer(Modifier.requiredHeight(10.dp))
+ Text(
+ text = "I show indication with clicking on other Text composable",
+ modifier = Modifier
+ .indication(interactionSource, LocalIndication.current)
+ .padding(10.dp)
+ )
+ }
+ }
+ }
+}
diff --git a/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/ComposeLayoutInspector.kt b/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/ComposeLayoutInspector.kt
index 1d02de5..6999d46 100644
--- a/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/ComposeLayoutInspector.kt
+++ b/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/ComposeLayoutInspector.kt
@@ -74,6 +74,7 @@
val rootView: View,
val viewParent: View,
val nodes: List<InspectorNode>,
+ val viewsToSkip: List<Long>
) {
/** The cached nodes as a map from node id to InspectorNode */
val lookup = nodes.flatMap { it.flatten() }.associateBy { it.id }
@@ -149,8 +150,11 @@
addAllStrings(stringTable.toStringEntries())
addRoots(
ComposableRoot.newBuilder().apply {
- viewId = data?.viewParent?.uniqueDrawingId ?: 0L
- addAllNodes(composeNodes)
+ if (data != null) {
+ viewId = data.viewParent.uniqueDrawingId
+ addAllNodes(composeNodes)
+ addAllViewsToSkip(data.viewsToSkip)
+ }
}
)
}.build()
@@ -281,7 +285,7 @@
val data = ThreadUtils.runOnMainThread {
layoutInspectorTree.resetAccumulativeState()
val data = getAndroidComposeViews(rootViewId, skipSystemComposables, generation).map {
- CacheData(it.rootView, it.viewParent, it.createNodes())
+ CacheData(it.rootView, it.viewParent, it.createNodes(), it.viewsToSkip)
}
layoutInspectorTree.resetAccumulativeState()
data
diff --git a/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/compose/AndroidComposeViewWrapper.kt b/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/compose/AndroidComposeViewWrapper.kt
index 5b11b69..c6b88ce 100644
--- a/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/compose/AndroidComposeViewWrapper.kt
+++ b/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/compose/AndroidComposeViewWrapper.kt
@@ -18,7 +18,10 @@
import android.content.res.Resources
import android.view.View
+import android.view.ViewGroup
+import androidx.compose.ui.R
import androidx.compose.ui.inspection.framework.ancestors
+import androidx.compose.ui.inspection.framework.getChildren
import androidx.compose.ui.inspection.framework.isRoot
import androidx.compose.ui.inspection.inspector.InspectorNode
import androidx.compose.ui.inspection.inspector.LayoutInspectorTree
@@ -71,7 +74,7 @@
class AndroidComposeViewWrapper(
private val layoutInspectorTree: LayoutInspectorTree,
val rootView: View,
- private val composeView: View,
+ private val composeView: ViewGroup,
skipSystemComposables: Boolean
) {
companion object {
@@ -85,7 +88,7 @@
AndroidComposeViewWrapper(
layoutInspectorTree,
rootView,
- composeView,
+ composeView as ViewGroup,
skipSystemComposables
)
} else {
@@ -103,6 +106,11 @@
if (!skipSystemComposables) composeView
else composeView.ancestors().first { !it.isSystemView() || it.isRoot() }
+ val viewsToSkip: List<Long> =
+ composeView.getChildren()
+ .filter { it.getTag(R.id.hide_in_inspector_tag) != null }
+ .map { it.uniqueDrawingId }
+
private val inspectorNodes = layoutInspectorTree.apply {
this.hideSystemNodes = skipSystemComposables
}.convert(composeView)
diff --git a/compose/ui/ui-inspection/src/main/proto/compose_layout_inspection.proto b/compose/ui/ui-inspection/src/main/proto/compose_layout_inspection.proto
index 2cc5b3f..cbad50f6 100644
--- a/compose/ui/ui-inspection/src/main/proto/compose_layout_inspection.proto
+++ b/compose/ui/ui-inspection/src/main/proto/compose_layout_inspection.proto
@@ -65,6 +65,8 @@
int64 view_id = 1;
// All composables owned by this view (usually just one but could be more)
repeated ComposableNode nodes = 2;
+ // All views owned by this view that should be hidden
+ repeated int64 views_to_skip = 3;
}
message ComposableNode {
diff --git a/compose/ui/ui-text/src/desktopTest/kotlin/androidx/compose/ui/text/TestUtils.kt b/compose/ui/ui-text/src/desktopTest/kotlin/androidx/compose/ui/text/TestUtils.kt
index c5ad284..e283426 100644
--- a/compose/ui/ui-text/src/desktopTest/kotlin/androidx/compose/ui/text/TestUtils.kt
+++ b/compose/ui/ui-text/src/desktopTest/kotlin/androidx/compose/ui/text/TestUtils.kt
@@ -16,9 +16,7 @@
package androidx.compose.ui.text
-import java.util.Locale
-
-private val os = System.getProperty("os.name").lowercase(Locale.US)
+private val os = System.getProperty("os.name").lowercase()
internal val isLinux = os.startsWith("linux")
internal val isWindows = os.startsWith("win")
internal val isMacOs = os.startsWith("mac")
\ No newline at end of file
diff --git a/compose/ui/ui/api/1.0.0-beta08.txt b/compose/ui/ui/api/1.0.0-beta08.txt
index 90caf3a..ef6a13d 100644
--- a/compose/ui/ui/api/1.0.0-beta08.txt
+++ b/compose/ui/ui/api/1.0.0-beta08.txt
@@ -2575,13 +2575,17 @@
}
@androidx.compose.runtime.Immutable public final class PopupProperties {
- ctor public PopupProperties(optional boolean focusable, optional boolean dismissOnBackPress, optional boolean dismissOnClickOutside, optional androidx.compose.ui.window.SecureFlagPolicy securePolicy);
+ ctor public PopupProperties(optional boolean focusable, optional boolean dismissOnBackPress, optional boolean dismissOnClickOutside, optional androidx.compose.ui.window.SecureFlagPolicy securePolicy, optional boolean excludeFromSystemGesture, optional boolean clippingEnabled);
+ method public boolean getClippingEnabled();
method public boolean getDismissOnBackPress();
method public boolean getDismissOnClickOutside();
+ method public boolean getExcludeFromSystemGesture();
method public boolean getFocusable();
method public androidx.compose.ui.window.SecureFlagPolicy getSecurePolicy();
+ property public final boolean clippingEnabled;
property public final boolean dismissOnBackPress;
property public final boolean dismissOnClickOutside;
+ property public final boolean excludeFromSystemGesture;
property public final boolean focusable;
property public final androidx.compose.ui.window.SecureFlagPolicy securePolicy;
}
diff --git a/compose/ui/ui/api/current.ignore b/compose/ui/ui/api/current.ignore
index 266ec45..f30e81b 100644
--- a/compose/ui/ui/api/current.ignore
+++ b/compose/ui/ui/api/current.ignore
@@ -109,3 +109,5 @@
Removed method androidx.compose.ui.input.pointer.PointerInputChange.getType()
RemovedMethod: androidx.compose.ui.semantics.SemanticsPropertiesKt#setImeAction(androidx.compose.ui.semantics.SemanticsPropertyReceiver, androidx.compose.ui.text.input.ImeAction):
Removed method androidx.compose.ui.semantics.SemanticsPropertiesKt.setImeAction(androidx.compose.ui.semantics.SemanticsPropertyReceiver,androidx.compose.ui.text.input.ImeAction)
+RemovedMethod: androidx.compose.ui.window.PopupProperties#PopupProperties(boolean, boolean, boolean, androidx.compose.ui.window.SecureFlagPolicy):
+ Removed constructor androidx.compose.ui.window.PopupProperties(boolean,boolean,boolean,androidx.compose.ui.window.SecureFlagPolicy)
diff --git a/compose/ui/ui/api/current.txt b/compose/ui/ui/api/current.txt
index 90caf3a..ef6a13d 100644
--- a/compose/ui/ui/api/current.txt
+++ b/compose/ui/ui/api/current.txt
@@ -2575,13 +2575,17 @@
}
@androidx.compose.runtime.Immutable public final class PopupProperties {
- ctor public PopupProperties(optional boolean focusable, optional boolean dismissOnBackPress, optional boolean dismissOnClickOutside, optional androidx.compose.ui.window.SecureFlagPolicy securePolicy);
+ ctor public PopupProperties(optional boolean focusable, optional boolean dismissOnBackPress, optional boolean dismissOnClickOutside, optional androidx.compose.ui.window.SecureFlagPolicy securePolicy, optional boolean excludeFromSystemGesture, optional boolean clippingEnabled);
+ method public boolean getClippingEnabled();
method public boolean getDismissOnBackPress();
method public boolean getDismissOnClickOutside();
+ method public boolean getExcludeFromSystemGesture();
method public boolean getFocusable();
method public androidx.compose.ui.window.SecureFlagPolicy getSecurePolicy();
+ property public final boolean clippingEnabled;
property public final boolean dismissOnBackPress;
property public final boolean dismissOnClickOutside;
+ property public final boolean excludeFromSystemGesture;
property public final boolean focusable;
property public final androidx.compose.ui.window.SecureFlagPolicy securePolicy;
}
diff --git a/compose/ui/ui/api/public_plus_experimental_1.0.0-beta08.txt b/compose/ui/ui/api/public_plus_experimental_1.0.0-beta08.txt
index 580e82d..e989440 100644
--- a/compose/ui/ui/api/public_plus_experimental_1.0.0-beta08.txt
+++ b/compose/ui/ui/api/public_plus_experimental_1.0.0-beta08.txt
@@ -2729,18 +2729,17 @@
}
@androidx.compose.runtime.Immutable public final class PopupProperties {
- ctor @androidx.compose.ui.ExperimentalComposeUiApi public PopupProperties(optional boolean focusable, optional boolean dismissOnBackPress, optional boolean dismissOnClickOutside, optional androidx.compose.ui.window.SecureFlagPolicy securePolicy, optional boolean excludeFromSystemGesture, optional boolean clippingEnabled);
- ctor public PopupProperties(optional boolean focusable, optional boolean dismissOnBackPress, optional boolean dismissOnClickOutside, optional androidx.compose.ui.window.SecureFlagPolicy securePolicy);
- method @androidx.compose.ui.ExperimentalComposeUiApi public boolean getClippingEnabled();
+ ctor public PopupProperties(optional boolean focusable, optional boolean dismissOnBackPress, optional boolean dismissOnClickOutside, optional androidx.compose.ui.window.SecureFlagPolicy securePolicy, optional boolean excludeFromSystemGesture, optional boolean clippingEnabled);
+ method public boolean getClippingEnabled();
method public boolean getDismissOnBackPress();
method public boolean getDismissOnClickOutside();
- method @androidx.compose.ui.ExperimentalComposeUiApi public boolean getExcludeFromSystemGesture();
+ method public boolean getExcludeFromSystemGesture();
method public boolean getFocusable();
method public androidx.compose.ui.window.SecureFlagPolicy getSecurePolicy();
- property @androidx.compose.ui.ExperimentalComposeUiApi public final boolean clippingEnabled;
+ property public final boolean clippingEnabled;
property public final boolean dismissOnBackPress;
property public final boolean dismissOnClickOutside;
- property @androidx.compose.ui.ExperimentalComposeUiApi public final boolean excludeFromSystemGesture;
+ property public final boolean excludeFromSystemGesture;
property public final boolean focusable;
property public final androidx.compose.ui.window.SecureFlagPolicy securePolicy;
}
diff --git a/compose/ui/ui/api/public_plus_experimental_current.txt b/compose/ui/ui/api/public_plus_experimental_current.txt
index 580e82d..e989440 100644
--- a/compose/ui/ui/api/public_plus_experimental_current.txt
+++ b/compose/ui/ui/api/public_plus_experimental_current.txt
@@ -2729,18 +2729,17 @@
}
@androidx.compose.runtime.Immutable public final class PopupProperties {
- ctor @androidx.compose.ui.ExperimentalComposeUiApi public PopupProperties(optional boolean focusable, optional boolean dismissOnBackPress, optional boolean dismissOnClickOutside, optional androidx.compose.ui.window.SecureFlagPolicy securePolicy, optional boolean excludeFromSystemGesture, optional boolean clippingEnabled);
- ctor public PopupProperties(optional boolean focusable, optional boolean dismissOnBackPress, optional boolean dismissOnClickOutside, optional androidx.compose.ui.window.SecureFlagPolicy securePolicy);
- method @androidx.compose.ui.ExperimentalComposeUiApi public boolean getClippingEnabled();
+ ctor public PopupProperties(optional boolean focusable, optional boolean dismissOnBackPress, optional boolean dismissOnClickOutside, optional androidx.compose.ui.window.SecureFlagPolicy securePolicy, optional boolean excludeFromSystemGesture, optional boolean clippingEnabled);
+ method public boolean getClippingEnabled();
method public boolean getDismissOnBackPress();
method public boolean getDismissOnClickOutside();
- method @androidx.compose.ui.ExperimentalComposeUiApi public boolean getExcludeFromSystemGesture();
+ method public boolean getExcludeFromSystemGesture();
method public boolean getFocusable();
method public androidx.compose.ui.window.SecureFlagPolicy getSecurePolicy();
- property @androidx.compose.ui.ExperimentalComposeUiApi public final boolean clippingEnabled;
+ property public final boolean clippingEnabled;
property public final boolean dismissOnBackPress;
property public final boolean dismissOnClickOutside;
- property @androidx.compose.ui.ExperimentalComposeUiApi public final boolean excludeFromSystemGesture;
+ property public final boolean excludeFromSystemGesture;
property public final boolean focusable;
property public final androidx.compose.ui.window.SecureFlagPolicy securePolicy;
}
diff --git a/compose/ui/ui/api/restricted_1.0.0-beta08.txt b/compose/ui/ui/api/restricted_1.0.0-beta08.txt
index db057a1..7938cd4 100644
--- a/compose/ui/ui/api/restricted_1.0.0-beta08.txt
+++ b/compose/ui/ui/api/restricted_1.0.0-beta08.txt
@@ -2605,13 +2605,17 @@
}
@androidx.compose.runtime.Immutable public final class PopupProperties {
- ctor public PopupProperties(optional boolean focusable, optional boolean dismissOnBackPress, optional boolean dismissOnClickOutside, optional androidx.compose.ui.window.SecureFlagPolicy securePolicy);
+ ctor public PopupProperties(optional boolean focusable, optional boolean dismissOnBackPress, optional boolean dismissOnClickOutside, optional androidx.compose.ui.window.SecureFlagPolicy securePolicy, optional boolean excludeFromSystemGesture, optional boolean clippingEnabled);
+ method public boolean getClippingEnabled();
method public boolean getDismissOnBackPress();
method public boolean getDismissOnClickOutside();
+ method public boolean getExcludeFromSystemGesture();
method public boolean getFocusable();
method public androidx.compose.ui.window.SecureFlagPolicy getSecurePolicy();
+ property public final boolean clippingEnabled;
property public final boolean dismissOnBackPress;
property public final boolean dismissOnClickOutside;
+ property public final boolean excludeFromSystemGesture;
property public final boolean focusable;
property public final androidx.compose.ui.window.SecureFlagPolicy securePolicy;
}
diff --git a/compose/ui/ui/api/restricted_current.ignore b/compose/ui/ui/api/restricted_current.ignore
index 266ec45..f30e81b 100644
--- a/compose/ui/ui/api/restricted_current.ignore
+++ b/compose/ui/ui/api/restricted_current.ignore
@@ -109,3 +109,5 @@
Removed method androidx.compose.ui.input.pointer.PointerInputChange.getType()
RemovedMethod: androidx.compose.ui.semantics.SemanticsPropertiesKt#setImeAction(androidx.compose.ui.semantics.SemanticsPropertyReceiver, androidx.compose.ui.text.input.ImeAction):
Removed method androidx.compose.ui.semantics.SemanticsPropertiesKt.setImeAction(androidx.compose.ui.semantics.SemanticsPropertyReceiver,androidx.compose.ui.text.input.ImeAction)
+RemovedMethod: androidx.compose.ui.window.PopupProperties#PopupProperties(boolean, boolean, boolean, androidx.compose.ui.window.SecureFlagPolicy):
+ Removed constructor androidx.compose.ui.window.PopupProperties(boolean,boolean,boolean,androidx.compose.ui.window.SecureFlagPolicy)
diff --git a/compose/ui/ui/api/restricted_current.txt b/compose/ui/ui/api/restricted_current.txt
index db057a1..7938cd4 100644
--- a/compose/ui/ui/api/restricted_current.txt
+++ b/compose/ui/ui/api/restricted_current.txt
@@ -2605,13 +2605,17 @@
}
@androidx.compose.runtime.Immutable public final class PopupProperties {
- ctor public PopupProperties(optional boolean focusable, optional boolean dismissOnBackPress, optional boolean dismissOnClickOutside, optional androidx.compose.ui.window.SecureFlagPolicy securePolicy);
+ ctor public PopupProperties(optional boolean focusable, optional boolean dismissOnBackPress, optional boolean dismissOnClickOutside, optional androidx.compose.ui.window.SecureFlagPolicy securePolicy, optional boolean excludeFromSystemGesture, optional boolean clippingEnabled);
+ method public boolean getClippingEnabled();
method public boolean getDismissOnBackPress();
method public boolean getDismissOnClickOutside();
+ method public boolean getExcludeFromSystemGesture();
method public boolean getFocusable();
method public androidx.compose.ui.window.SecureFlagPolicy getSecurePolicy();
+ property public final boolean clippingEnabled;
property public final boolean dismissOnBackPress;
property public final boolean dismissOnClickOutside;
+ property public final boolean excludeFromSystemGesture;
property public final boolean focusable;
property public final androidx.compose.ui.window.SecureFlagPolicy securePolicy;
}
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/draw/InvalidatingNotPlacedChildTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/draw/InvalidatingNotPlacedChildTest.kt
index aff3562..befd806 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/draw/InvalidatingNotPlacedChildTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/draw/InvalidatingNotPlacedChildTest.kt
@@ -17,20 +17,21 @@
package androidx.compose.ui.draw
import android.os.Build
+import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.requiredSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
-import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.background
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.layout.assertCenterPixelColor
+import androidx.compose.ui.layout.layout
+import androidx.compose.ui.platform.AndroidOwnerExtraAssertionsRule
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.test.captureToImage
import androidx.compose.ui.test.junit4.createComposeRule
@@ -50,6 +51,9 @@
@get:Rule
val composeTestRule = createComposeRule()
+ @get:Rule
+ val excessiveAssertions = AndroidOwnerExtraAssertionsRule()
+
@Test
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
fun childIsDisplayedWhenItWasNotPlacedOriginallyButPlacedLater() {
@@ -116,44 +120,6 @@
@Test
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
- fun grandChildIsDisplayedCorrectlyWhenTheColorWasChangedWhileNotPlaced() {
- val shouldPlace = mutableStateOf(false)
- var color by mutableStateOf(Color.Gray)
- composeTestRule.setContent {
- ConditionallyPlacedChild(
- shouldPlace,
- Modifier.background(Color.Blue)
- .testTag("node")
- ) {
- MeasureInLayoutBlock {
- Spacer(
- Modifier.fillMaxSize()
- .graphicsLayer()
- .background(color)
- )
- }
- }
- }
-
- composeTestRule.runOnIdle {
- shouldPlace.value = false
- }
-
- composeTestRule.runOnIdle {
- color = Color.Red
- }
-
- composeTestRule.runOnIdle {
- shouldPlace.value = true
- }
-
- composeTestRule.onNodeWithTag("node")
- .captureToImage()
- .assertCenterPixelColor(Color.Red)
- }
-
- @Test
- @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
fun childIsNotDisplayedWhenIsNotPlacedAnymore() {
val shouldPlace = mutableStateOf(true)
composeTestRule.setContent {
@@ -179,30 +145,289 @@
.captureToImage()
.assertCenterPixelColor(Color.Blue)
}
+
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+ fun childRedrawRequestedWhileNotPlaced() {
+ assertChangeWhileNotPlacedIsApplied { shouldPlace, color ->
+ ConditionallyPlacedChild(shouldPlace) {
+ Spacer(
+ Modifier.fillMaxSize()
+ .drawBehind {
+ drawRect(color.value)
+ }
+ )
+ }
+ }
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+ fun childRedrawRequestedWhileNotPlaced_hadLayer() {
+ assertChangeWhileNotPlacedIsApplied { shouldPlace, color ->
+ ConditionallyPlacedChild(shouldPlace) {
+ Spacer(
+ Modifier.fillMaxSize()
+ .graphicsLayer()
+ .drawBehind {
+ drawRect(color.value)
+ }
+ )
+ }
+ }
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+ fun childRedrawRequestedWhileNotPlaced_hadLayerAsLastModifierInTheChain() {
+ assertChangeWhileNotPlacedIsApplied { shouldPlace, color ->
+ ConditionallyPlacedChild(shouldPlace) {
+ Box(Modifier.graphicsLayer()) {
+ Spacer(
+ Modifier.fillMaxSize()
+ .drawBehind {
+ drawRect(color.value)
+ }
+ )
+ }
+ }
+ }
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+ fun childRedrawRequestedWhileNotPlaced_placedWithLayer() {
+ assertChangeWhileNotPlacedIsApplied { shouldPlace, color ->
+ ConditionallyPlacedChild(shouldPlace, placeWithLayer = true) {
+ Spacer(
+ Modifier.fillMaxSize()
+ .drawBehind {
+ drawRect(color.value)
+ }
+ )
+ }
+ }
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+ fun childRedrawAndRemeasureRequestedWhileNotPlaced() {
+ assertChangeWhileNotPlacedIsApplied { shouldPlace, color ->
+ ConditionallyPlacedChild(shouldPlace) {
+ Spacer(
+ Modifier.fillMaxSize()
+ .layout(useDuringMeasure = color)
+ .drawBehind {
+ drawRect(color.value)
+ }
+ )
+ }
+ }
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+ fun childRedrawAndRelayoutRequestedWhileNotPlaced() {
+ assertChangeWhileNotPlacedIsApplied { shouldPlace, color ->
+ ConditionallyPlacedChild(shouldPlace) {
+ Spacer(
+ Modifier.fillMaxSize()
+ .layout(useDuringLayout = color)
+ .drawBehind {
+ drawRect(color.value)
+ }
+ )
+ }
+ }
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+ fun grandChildRedrawRequestedWhileNotPlaced() {
+ assertChangeWhileNotPlacedIsApplied { shouldPlace, color ->
+ ConditionallyPlacedChild(shouldPlace) {
+ MeasureInLayoutBlock {
+ Spacer(
+ Modifier.fillMaxSize()
+ .drawBehind {
+ drawRect(color.value)
+ }
+ )
+ }
+ }
+ }
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+ fun grandChildRedrawRequestedWhileNotPlaced_hadLayer() {
+ assertChangeWhileNotPlacedIsApplied { shouldPlace, color ->
+ ConditionallyPlacedChild(shouldPlace) {
+ MeasureInLayoutBlock {
+ Spacer(
+ Modifier.fillMaxSize()
+ .graphicsLayer()
+ .drawBehind {
+ drawRect(color.value)
+ }
+ )
+ }
+ }
+ }
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+ fun grandChildRedrawRequestedWhileNotPlaced_hadLayerAsLastModifierInTheChain() {
+ assertChangeWhileNotPlacedIsApplied { shouldPlace, color ->
+ ConditionallyPlacedChild(shouldPlace) {
+ MeasureInLayoutBlock {
+ Box(Modifier.graphicsLayer()) {
+ Spacer(
+ Modifier.fillMaxSize()
+ .drawBehind {
+ drawRect(color.value)
+ }
+ )
+ }
+ }
+ }
+ }
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+ fun grandChildRedrawRequestedWhileNotPlaced_placedWithLayer() {
+ assertChangeWhileNotPlacedIsApplied { shouldPlace, color ->
+ ConditionallyPlacedChild(shouldPlace) {
+ MeasureInLayoutBlock(placeWithLayer = true) {
+ Spacer(
+ Modifier.fillMaxSize()
+ .drawBehind {
+ drawRect(color.value)
+ }
+ )
+ }
+ }
+ }
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+ fun grandChildRedrawAndRemeasureRequestedWhileNotPlaced() {
+ assertChangeWhileNotPlacedIsApplied { shouldPlace, color ->
+ ConditionallyPlacedChild(shouldPlace) {
+ MeasureInLayoutBlock {
+ Spacer(
+ Modifier.fillMaxSize()
+ .layout(useDuringMeasure = color)
+ .drawBehind {
+ drawRect(color.value)
+ }
+ )
+ }
+ }
+ }
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+ fun grandChildRedrawAndRelayoutRequestedWhileNotPlaced() {
+ assertChangeWhileNotPlacedIsApplied { shouldPlace, color ->
+ ConditionallyPlacedChild(shouldPlace) {
+ MeasureInLayoutBlock {
+ Spacer(
+ Modifier.fillMaxSize()
+ .layout(useDuringLayout = color)
+ .drawBehind {
+ drawRect(color.value)
+ }
+ )
+ }
+ }
+ }
+ }
+
+ fun assertChangeWhileNotPlacedIsApplied(
+ content: @Composable (State<Boolean>, State<Color>) -> Unit
+ ) {
+ val shouldPlace = mutableStateOf(true)
+ var color = mutableStateOf(Color.Gray)
+ composeTestRule.setContent {
+ Box(
+ Modifier.background(Color.Blue)
+ .testTag("node")
+ ) {
+ content(shouldPlace, color)
+ }
+ }
+
+ composeTestRule.runOnIdle {
+ shouldPlace.value = false
+ }
+
+ composeTestRule.runOnIdle {
+ color.value = Color.Red
+ }
+
+ composeTestRule.runOnIdle {
+ shouldPlace.value = true
+ }
+
+ composeTestRule.onNodeWithTag("node")
+ .captureToImage()
+ .assertCenterPixelColor(Color.Red)
+ }
}
@Composable
private fun ConditionallyPlacedChild(
shouldPlace: State<Boolean>,
- modifier: Modifier,
+ modifier: Modifier = Modifier,
+ placeWithLayer: Boolean = false,
content: @Composable () -> Unit
) {
Layout(content = content, modifier = modifier) { measurables, constraints ->
val placeable = measurables.first().measure(constraints)
layout(placeable.width, placeable.height) {
if (shouldPlace.value) {
- placeable.place(0, 0)
+ if (placeWithLayer) {
+ placeable.placeWithLayer(0, 0)
+ } else {
+ placeable.place(0, 0)
+ }
}
}
}
}
@Composable
-private fun MeasureInLayoutBlock(content: @Composable () -> Unit) {
- Layout(content = content) { measurables, constraints ->
+private fun MeasureInLayoutBlock(
+ modifier: Modifier = Modifier,
+ placeWithLayer: Boolean = false,
+ content: @Composable () -> Unit
+) {
+ Layout(content = content, modifier = modifier) { measurables, constraints ->
val size = 5.dp.roundToPx()
layout(size, size) {
- measurables.first().measure(constraints).place(0, 0)
+ val placeable = measurables.first().measure(constraints)
+ if (placeWithLayer) {
+ placeable.placeWithLayer(0, 0)
+ } else {
+ placeable.place(0, 0)
+ }
}
}
}
+
+private fun Modifier.layout(
+ useDuringMeasure: State<*>? = null,
+ useDuringLayout: State<*>? = null,
+): Modifier = layout { measurable, constraints ->
+ useDuringMeasure?.value
+ val placeable = measurable.measure(constraints)
+ layout(placeable.width, placeable.height) {
+ useDuringLayout?.value
+ placeable.place(0, 0)
+ }
+}
\ No newline at end of file
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/ViewLayerContainer.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/ViewLayerContainer.android.kt
index 4a3f22c..a4282ee 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/ViewLayerContainer.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/ViewLayerContainer.android.kt
@@ -19,6 +19,7 @@
import android.content.Context
import android.view.View
import android.view.ViewGroup
+import androidx.compose.ui.R
import androidx.compose.ui.graphics.Canvas
import androidx.compose.ui.graphics.nativeCanvas
@@ -45,6 +46,9 @@
internal open class DrawChildContainer(context: Context) : ViewGroup(context) {
init {
clipChildren = false
+
+ // Hide this view and its children in tools:
+ setTag(R.id.hide_in_inspector_tag, true)
}
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/window/AndroidPopup.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/window/AndroidPopup.android.kt
index 807327f..45c4c1a 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/window/AndroidPopup.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/window/AndroidPopup.android.kt
@@ -95,32 +95,14 @@
* The default value is true.
*/
@Immutable
-class PopupProperties @ExperimentalComposeUiApi constructor(
+class PopupProperties(
val focusable: Boolean = false,
val dismissOnBackPress: Boolean = true,
val dismissOnClickOutside: Boolean = true,
val securePolicy: SecureFlagPolicy = SecureFlagPolicy.Inherit,
- @get:ExperimentalComposeUiApi
val excludeFromSystemGesture: Boolean = true,
- @get:ExperimentalComposeUiApi
val clippingEnabled: Boolean = true
) {
- @OptIn(ExperimentalComposeUiApi::class)
- constructor(
- focusable: Boolean = false,
- dismissOnBackPress: Boolean = true,
- dismissOnClickOutside: Boolean = true,
- securePolicy: SecureFlagPolicy = SecureFlagPolicy.Inherit,
- ) : this (
- focusable,
- dismissOnBackPress,
- dismissOnClickOutside,
- securePolicy,
- excludeFromSystemGesture = true,
- clippingEnabled = true
- )
-
- @OptIn(ExperimentalComposeUiApi::class)
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is PopupProperties) return false
@@ -135,7 +117,6 @@
return true
}
- @OptIn(ExperimentalComposeUiApi::class)
override fun hashCode(): Int {
var result = dismissOnBackPress.hashCode()
result = 31 * result + focusable.hashCode()
@@ -502,7 +483,6 @@
/**
* Updates the position of the popup based on current position properties.
*/
- @OptIn(ExperimentalComposeUiApi::class)
fun updatePosition() {
val parentBounds = parentBounds ?: return
val popupContentSize = popupContentSize ?: return
diff --git a/compose/ui/ui/src/androidMain/res/values/ids.xml b/compose/ui/ui/src/androidMain/res/values/ids.xml
index caa764c..3c04edc 100644
--- a/compose/ui/ui/src/androidMain/res/values/ids.xml
+++ b/compose/ui/ui/src/androidMain/res/values/ids.xml
@@ -52,4 +52,5 @@
<item name="inspection_slot_table_set" type="id" />
<item name="androidx_compose_ui_view_composition_context" type="id" />
<item name="compose_view_saveable_id_tag" type="id" />
+ <item name="hide_in_inspector_tag" type="id" />
</resources>
\ No newline at end of file
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNode.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNode.kt
index 07483f5..b4362fe 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNode.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNode.kt
@@ -535,7 +535,8 @@
* every placed node assigns this variable to [parent]s [nextChildPlaceOrder] and increments
* this counter. Not placed items will still have [NotPlacedPlaceOrder] set.
*/
- private var placeOrder: Int = NotPlacedPlaceOrder
+ internal var placeOrder: Int = NotPlacedPlaceOrder
+ private set
/**
* The counter on a parent node which is used by its children to understand the order in which
@@ -840,17 +841,10 @@
}
if (!isPlaced) {
- isPlaced = true
// when the visibility of a child has been changed we need to invalidate
// parents inner layer - the layer in which this child will be drawn
parent?.invalidateLayer()
- // plus all the inner layers that were invalidated while the node was not placed
- forEachDelegate {
- if (it.lastLayerDrawingWasSkipped) {
- it.invalidateLayer()
- }
- }
- markSubtreeAsPlaced()
+ markNodeAndSubtreeAsPlaced()
}
if (parent != null) {
@@ -917,14 +911,44 @@
if (alignmentLines.dirty && alignmentLines.required) alignmentLines.recalculate()
}
- private fun markSubtreeAsPlaced() {
- _children.forEach {
- // if the layout state is not Ready then isPlaced will be set during the layout
- if (it.layoutState == Ready && it.placeOrder != NotPlacedPlaceOrder) {
- it.isPlaced = true
- it.markSubtreeAsPlaced()
+ private fun markNodeAndSubtreeAsPlaced() {
+ isPlaced = true
+ // invalidate all the nodes layers that were invalidated while the node was not placed
+ forEachDelegateIncludingInner {
+ if (it.lastLayerDrawingWasSkipped) {
+ it.invalidateLayer()
}
}
+ _children.forEach {
+ // this child was placed during the previous parent's layoutChildren(). this means that
+ // before the parent became not placed this child was placed. we need to restore that
+ if (it.placeOrder != NotPlacedPlaceOrder) {
+ it.markNodeAndSubtreeAsPlaced()
+ rescheduleRemeasureOrRelayout(it)
+ }
+ }
+ }
+
+ private fun rescheduleRemeasureOrRelayout(it: LayoutNode) {
+ when (val state = it.layoutState) {
+ NeedsRemeasure, NeedsRelayout -> {
+ // we need to reset the state before requesting as otherwise the request
+ // would be ignored.
+ it.layoutState = Ready
+ // this node was scheduled for remeasure or relayout while it was not
+ // placed. such requests are ignored for non-placed nodes so we have to
+ // re-schedule remeasure or relayout.
+ if (state == NeedsRemeasure) {
+ it.requestRemeasure()
+ } else {
+ it.requestRelayout()
+ }
+ }
+ Ready -> {
+ // no extra work required and node is ready to be displayed
+ }
+ else -> throw IllegalStateException("Unexpected state ${it.layoutState}")
+ }
}
private fun markSubtreeAsNotPlaced() {
@@ -1252,7 +1276,7 @@
/**
* Constant used by [placeOrder].
*/
- private const val NotPlacedPlaceOrder = Int.MAX_VALUE
+ internal const val NotPlacedPlaceOrder = Int.MAX_VALUE
/**
* Pre-allocated constructor to be used with ComposeNode
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNodeWrapper.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNodeWrapper.kt
index 2fa6f6c..6073d13 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNodeWrapper.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNodeWrapper.kt
@@ -299,6 +299,7 @@
}
}
layer = null
+ lastLayerDrawingWasSkipped = false
}
}
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutTreeConsistencyChecker.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutTreeConsistencyChecker.kt
index 337e2b0..be1fb03 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutTreeConsistencyChecker.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutTreeConsistencyChecker.kt
@@ -49,7 +49,10 @@
}
private fun LayoutNode.consistentLayoutState(): Boolean {
- if (isPlaced) {
+ val parent = this.parent
+ if (isPlaced ||
+ placeOrder != LayoutNode.NotPlacedPlaceOrder && parent?.isPlaced == true
+ ) {
if (layoutState == LayoutNode.LayoutState.NeedsRemeasure &&
postponedMeasureRequests.contains(this)
) {
@@ -58,7 +61,7 @@
return true
}
// remeasure or relayout is scheduled
- val parentLayoutState = this.parent?.layoutState
+ val parentLayoutState = parent?.layoutState
if (layoutState == LayoutNode.LayoutState.NeedsRemeasure) {
return relayoutNodes.contains(this) ||
parentLayoutState == LayoutNode.LayoutState.NeedsRemeasure ||
diff --git a/core/core-google-shortcuts/build.gradle b/core/core-google-shortcuts/build.gradle
index ea77000..7f7bf68 100644
--- a/core/core-google-shortcuts/build.gradle
+++ b/core/core-google-shortcuts/build.gradle
@@ -32,7 +32,7 @@
}
dependencies {
- api(project(":core:core"))
+ api("androidx.core:core:1.6.0-beta01")
implementation("com.google.firebase:firebase-appindexing:19.2.0")
implementation("com.google.crypto.tink:tink-android:1.5.0")
diff --git a/core/core/api/current.txt b/core/core/api/current.txt
index 2cf275c..83d6a2d 100644
--- a/core/core/api/current.txt
+++ b/core/core/api/current.txt
@@ -1542,6 +1542,7 @@
method @Deprecated @ChecksSdkIntAtLeast(api=android.os.Build.VERSION_CODES.Q) public static boolean isAtLeastQ();
method @Deprecated @ChecksSdkIntAtLeast(api=android.os.Build.VERSION_CODES.R) public static boolean isAtLeastR();
method @ChecksSdkIntAtLeast(codename="S") public static boolean isAtLeastS();
+ method @ChecksSdkIntAtLeast(codename="T") public static boolean isAtLeastT();
}
public final class CancellationSignal {
diff --git a/core/core/api/public_plus_experimental_current.txt b/core/core/api/public_plus_experimental_current.txt
index 3934ab1..ebb5dfb 100644
--- a/core/core/api/public_plus_experimental_current.txt
+++ b/core/core/api/public_plus_experimental_current.txt
@@ -1540,6 +1540,7 @@
method @Deprecated @ChecksSdkIntAtLeast(api=android.os.Build.VERSION_CODES.Q) public static boolean isAtLeastQ();
method @Deprecated @ChecksSdkIntAtLeast(api=android.os.Build.VERSION_CODES.R) public static boolean isAtLeastR();
method @ChecksSdkIntAtLeast(codename="S") public static boolean isAtLeastS();
+ method @ChecksSdkIntAtLeast(codename="T") public static boolean isAtLeastT();
}
public final class CancellationSignal {
diff --git a/core/core/api/restricted_current.txt b/core/core/api/restricted_current.txt
index 46113a6..34557d9 100644
--- a/core/core/api/restricted_current.txt
+++ b/core/core/api/restricted_current.txt
@@ -1861,6 +1861,7 @@
method @Deprecated @ChecksSdkIntAtLeast(api=android.os.Build.VERSION_CODES.Q) public static boolean isAtLeastQ();
method @Deprecated @ChecksSdkIntAtLeast(api=android.os.Build.VERSION_CODES.R) public static boolean isAtLeastR();
method @ChecksSdkIntAtLeast(codename="S") public static boolean isAtLeastS();
+ method @ChecksSdkIntAtLeast(codename="T") public static boolean isAtLeastT();
}
public final class CancellationSignal {
diff --git a/core/core/src/main/java/androidx/core/os/BuildCompat.java b/core/core/src/main/java/androidx/core/os/BuildCompat.java
index ba91322..389622f 100644
--- a/core/core/src/main/java/androidx/core/os/BuildCompat.java
+++ b/core/core/src/main/java/androidx/core/os/BuildCompat.java
@@ -139,6 +139,21 @@
*/
@ChecksSdkIntAtLeast(codename = "S")
public static boolean isAtLeastS() {
- return VERSION.CODENAME.equals("S");
+ return VERSION.CODENAME.equals("S") || VERSION.CODENAME.equals("T");
+ }
+
+ /**
+ * Checks if the device is running on a pre-release version of Android T or a release version of
+ * Android T or newer.
+ * <p>
+ * <strong>Note:</strong> When Android T is finalized for release, this method will be
+ * deprecated and all calls should be replaced with {@code Build.VERSION.SDK_INT >=
+ * Build.VERSION_CODES.T}.
+ *
+ * @return {@code true} if T APIs are available for use, {@code false} otherwise
+ */
+ @ChecksSdkIntAtLeast(codename = "T")
+ public static boolean isAtLeastT() {
+ return VERSION.CODENAME.equals("T");
}
}
diff --git a/core/core/src/main/java/androidx/core/view/OnReceiveContentListener.java b/core/core/src/main/java/androidx/core/view/OnReceiveContentListener.java
index 9031f8c..4682dce 100644
--- a/core/core/src/main/java/androidx/core/view/OnReceiveContentListener.java
+++ b/core/core/src/main/java/androidx/core/view/OnReceiveContentListener.java
@@ -80,16 +80,40 @@
* implementation and see {@link ContentInfoCompat#partition} for a convenient way to split the
* passed-in content.
*
- * <p>If implementing handling for text: if the view has a selection, the selection should
- * be overwritten by the passed-in content; if there's no selection, the passed-in content
- * should be inserted at the current cursor position.
+ * <h3>Handling different content</h3>
+ * <ul>
+ * <li>Text. The passed-in text should overwrite the current selection or be inserted at
+ * the current cursor position if there is no selection.
+ * <li>Non-text content (e.g. images). The content may be inserted inline if the widget
+ * supports this, or it may be added as an attachment (could potentially be shown in a
+ * completely separate view).
+ * </ul>
*
- * <p>If implementing handling for non-text content (e.g. images): the content may be
- * inserted inline, or it may be added as an attachment (could potentially be shown in a
- * completely separate view).
+ * <h3>URI permissions</h3>
+ * <p>{@link android.content.Intent#FLAG_GRANT_READ_URI_PERMISSION Read permissions} are
+ * granted automatically by the platform for any
+ * {@link android.content.ContentResolver#SCHEME_CONTENT content URIs} in the payload passed
+ * to this listener. Permissions are transient and will be released automatically by the
+ * platform.
+ * <p>Processing of content should normally be done in a service or activity.
+ * For long-running processing, using {@code androidx.work.WorkManager} is recommended.
+ * When implementing this, permissions should be extended to the target service or activity
+ * by passing the content using {@link android.content.Intent#setClipData Intent.setClipData}
+ * and {@link android.content.Intent#addFlags(int) setting} the flag
+ * {@link android.content.Intent#FLAG_GRANT_READ_URI_PERMISSION FLAG_GRANT_READ_URI_PERMISSION}.
+ * <p>Alternatively, if using a background thread within the current context to process the
+ * content, a reference to the {@code payload} object should be maintained to ensure that
+ * permissions are not revoked prematurely.
*
* @param view The view where the content insertion was requested.
- * @param payload The content to insert and related metadata.
+ * @param payload The content to insert and related metadata. The payload may contain multiple
+ * items and their MIME types may be different (e.g. an image item and a text
+ * item). The payload may also contain items whose MIME type is not in the list
+ * of MIME types specified when
+ * {@link ViewCompat#setOnReceiveContentListener setting} the listener. For
+ * those items, the listener may reject the content (defer to the default
+ * platform behavior) or execute some other fallback logic (e.g. show an
+ * appropriate message to the user).
*
* @return The portion of the passed-in content whose processing should be delegated to
* the platform. Return null if all content was handled in some way. Actual insertion of
diff --git a/core/core/src/main/java/androidx/core/view/ViewCompat.java b/core/core/src/main/java/androidx/core/view/ViewCompat.java
index 88180d1..52df3c3 100644
--- a/core/core/src/main/java/androidx/core/view/ViewCompat.java
+++ b/core/core/src/main/java/androidx/core/view/ViewCompat.java
@@ -2701,19 +2701,25 @@
* Sets the listener to be used to handle insertion of content into the given view.
*
* <p>Depending on the type of view, this listener may be invoked for different scenarios. For
- * example, for an AppCompatEditText, this listener will be invoked for the following scenarios:
+ * example, for an {@code AppCompatEditText}, this listener will be invoked for the following
+ * scenarios:
* <ol>
* <li>Paste from the clipboard (e.g. "Paste" or "Paste as plain text" action in the
* insertion/selection menu)
* <li>Content insertion from the keyboard (from {@link InputConnection#commitContent})
+ * <li>Drag and drop (drop events from {@link View#onDragEvent})
* </ol>
*
- * <p>When setting a listener, clients should also declare the MIME types accepted by it.
- * When invoked with other types of content, the listener may reject the content (defer to
- * the default platform behavior) or execute some other fallback logic. The MIME types
- * declared here allow different features to optionally alter their behavior. For example,
- * the soft keyboard may choose to hide its UI for inserting GIFs for a particular input
- * field if the MIME types set here for that field don't include "image/gif" or "image/*".
+ * <p>When setting a listener, clients must also declare the accepted MIME types.
+ * The listener will still be invoked even if the MIME type of the content is not one of the
+ * declared MIME types (e.g. if the user pastes content whose type is not one of the declared
+ * MIME types).
+ * In that case, the listener may reject the content (defer to the default platform behavior)
+ * or execute some other fallback logic (e.g. show an appropriate message to the user).
+ * The declared MIME types serve as a hint to allow different features to optionally alter
+ * their behavior. For example, a soft keyboard may optionally choose to hide its UI for
+ * inserting GIFs for a particular input field if the MIME types set here for that field
+ * don't include "image/gif" or "image/*".
*
* <p>Note: MIME type matching in the Android framework is case-sensitive, unlike formal RFC
* MIME types. As a result, you should always write your MIME types with lowercase letters,
diff --git a/mediarouter/mediarouter/lint-baseline.xml b/mediarouter/mediarouter/lint-baseline.xml
index e41e0ea..7aa92ae 100644
--- a/mediarouter/mediarouter/lint-baseline.xml
+++ b/mediarouter/mediarouter/lint-baseline.xml
@@ -958,15 +958,4 @@
column="65"/>
</issue>
- <issue
- id="ObsoleteSdkInt"
- message="Unnecessary; SDK_INT is always >= 14"
- errorLine1=" } else if (Build.VERSION.SDK_INT >= 14) {"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/mediarouter/media/MediaRouter.java"
- line="3403"
- column="24"/>
- </issue>
-
</issues>
diff --git a/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRouter.java b/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRouter.java
index 9e98d05..2e7ff569 100644
--- a/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRouter.java
+++ b/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRouter.java
@@ -3400,7 +3400,7 @@
mCompatSession = session;
if (Build.VERSION.SDK_INT >= 21) {
setMediaSessionRecord(session != null ? new MediaSessionRecord(session) : null);
- } else if (Build.VERSION.SDK_INT >= 14) {
+ } else {
if (mRccMediaSession != null) {
removeRemoteControlClient(mRccMediaSession.getRemoteControlClient());
mRccMediaSession.removeOnActiveChangeListener(mSessionActiveListener);
diff --git a/wear/tiles/tiles-renderer/build.gradle b/wear/tiles/tiles-renderer/build.gradle
index ae9634b..8b6d6ee 100644
--- a/wear/tiles/tiles-renderer/build.gradle
+++ b/wear/tiles/tiles-renderer/build.gradle
@@ -33,6 +33,8 @@
implementation "androidx.concurrent:concurrent-futures:1.1.0"
implementation "androidx.core:core:1.3.2"
+ implementation "androidx.wear:wear:1.2.0-alpha09"
+
implementation(project(":wear:tiles:tiles"))
implementation(project(path: ":wear:tiles:tiles-proto", configuration: "shadow"))
implementation(libs.kotlinCoroutinesCore)
diff --git a/wear/tiles/tiles-renderer/src/androidTest/java/androidx/wear/tiles/renderer/test/TileRendererGoldenTest.java b/wear/tiles/tiles-renderer/src/androidTest/java/androidx/wear/tiles/renderer/test/TileRendererGoldenTest.java
index 4bc4b41..433bec7 100644
--- a/wear/tiles/tiles-renderer/src/androidTest/java/androidx/wear/tiles/renderer/test/TileRendererGoldenTest.java
+++ b/wear/tiles/tiles-renderer/src/androidTest/java/androidx/wear/tiles/renderer/test/TileRendererGoldenTest.java
@@ -120,8 +120,8 @@
public AndroidXScreenshotTestRule screenshotRule =
new AndroidXScreenshotTestRule("wear/wear-tiles-renderer");
- // This isn't totally ideal right now.
- // The screenshot tests run on a phone, so emulate some watch dimensions here.
+ // This isn't totally ideal right now. The screenshot tests run on a phone, so emulate some
+ // watch dimensions here.
private static final int SCREEN_WIDTH = 390;
private static final int SCREEN_HEIGHT = 390;
@@ -149,9 +149,9 @@
byte[] inlineImagePayload =
new byte[INLINE_IMAGE_WIDTH * INLINE_IMAGE_HEIGHT * INLINE_IMAGE_PIXEL_STRIDE];
- // Generate a square image, with a white square in the center.
- // This replaces an inline payload as a byte array. We could hardcode it, but the
- // autoformatter will ruin the formatting.
+ // Generate a square image, with a white square in the center. This replaces an inline
+ // payload as a byte array. We could hardcode it, but the autoformatter will ruin the
+ // formatting.
for (int y = 0; y < 8; y++) {
for (int x = 0; x < 8; x++) {
int index = ((y * INLINE_IMAGE_WIDTH) + x) * INLINE_IMAGE_PIXEL_STRIDE;
diff --git a/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/manager/UpdateScheduler.java b/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/manager/UpdateScheduler.java
index 861ecfc..efcc923 100644
--- a/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/manager/UpdateScheduler.java
+++ b/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/manager/UpdateScheduler.java
@@ -41,9 +41,8 @@
private long mScheduledUpdateTimeMillis = NO_SCHEDULED_UPDATE;
// Last time at which we updated the tile, measured by the device uptime. This needs to be
- // device
- // uptime to prevent issues when time changes (e.g. time jumps caused by syncs with NTP or
- // similar).
+ // device uptime to prevent issues when time changes (e.g. time jumps caused by syncs with NTP
+ // or similar).
private long mLastUpdateRealtimeMillis = 0;
UpdateScheduler(AlarmManager alarmManager, Clock clock) {
@@ -99,8 +98,7 @@
fireUpdate();
} else {
// "Schedule" an update. This is just so enableUpdates will definitely trigger the
- // update
- // when called.
+ // update when called.
mScheduledUpdateTimeMillis = nowMillis;
}
}
@@ -125,8 +123,7 @@
if (mScheduledUpdateTimeMillis != Long.MAX_VALUE) {
// If the schedule update is in the past, then fire now, otherwise schedule for the
- // given
- // time.
+ // given time.
long now = mClock.getElapsedTimeMillis();
if (now >= mScheduledUpdateTimeMillis) {
diff --git a/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/renderer/internal/DefaultInlineImageResourceResolver.java b/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/renderer/internal/DefaultInlineImageResourceResolver.java
index b31a850..3f28ec0 100644
--- a/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/renderer/internal/DefaultInlineImageResourceResolver.java
+++ b/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/renderer/internal/DefaultInlineImageResourceResolver.java
@@ -62,8 +62,7 @@
}
// The app Context is correct here, as it's just used for display density, so it doesn't
- // depend
- // on anything from the provider app.
+ // depend on anything from the provider app.
return new BitmapDrawable(mAppContext.getResources(), bitmap);
}
diff --git a/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/renderer/internal/RatioViewWrapper.java b/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/renderer/internal/RatioViewWrapper.java
index 6cb97bd..512318d 100644
--- a/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/renderer/internal/RatioViewWrapper.java
+++ b/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/renderer/internal/RatioViewWrapper.java
@@ -16,7 +16,6 @@
package androidx.wear.tiles.renderer.internal;
-
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
@@ -113,8 +112,7 @@
}
// If both are MeasureSpec.EXACTLY, we can't do anything else. Set our dimensions to be the
- // same
- // and exit.
+ // same and exit.
if (widthMeasureMode == MeasureSpec.EXACTLY && heightMeasureMode == MeasureSpec.EXACTLY) {
setMeasuredDimension(childView.getMeasuredWidth(), childView.getMeasuredHeight());
return;
@@ -133,16 +131,13 @@
&& (heightMeasureMode == MeasureSpec.AT_MOST
|| heightMeasureMode == MeasureSpec.UNSPECIFIED)) {
// Generally, this happens if this view has both width/height=WRAP_CONTENT. This can
- // also
- // happen though if this view has both dimensions as MATCH_CONTENT, but the parent view
- // is
- // WRAP_CONTENT. In that case, the parent will run a first view pass to get the size of
- // the
- // children, then calculate its size and re-size this widget with EXACTLY MeasureSpecs.
+ // also happen though if this view has both dimensions as MATCH_CONTENT, but the parent
+ // view is WRAP_CONTENT. In that case, the parent will run a first view pass to get the
+ // size of the children, then calculate its size and re-size this widget with EXACTLY
+ // MeasureSpecs.
//
// In this case, let's just assume that the child has reached the maximum size that it
- // wants,
- // so rescale the dimension that will make it _smaller_.
+ // wants, so rescale the dimension that will make it _smaller_.
float targetWidth = childView.getMeasuredHeight() * mAspectRatio;
float targetHeight = childView.getMeasuredWidth() / mAspectRatio;
@@ -170,7 +165,7 @@
// This should have been picked up by the aspect ratio check above...
throw new IllegalStateException(
"Neither target width nor target height was smaller than measured"
- + " width/height");
+ + " width/height");
}
} else if (widthMeasureMode == MeasureSpec.EXACTLY) {
// Can't change the width, but can change height.
@@ -182,10 +177,8 @@
childView.measure(childWidth, childHeight);
- // We're pulling some hacks here.
- // We get an AT_MOST constraint, but if we oversize ourselves, the parent container
- // should
- // do appropriate clipping.
+ // We're pulling some hacks here. We get an AT_MOST constraint, but if we oversize
+ // ourselves, the parent container should do appropriate clipping.
setMeasuredDimension(childView.getMeasuredWidth(), childView.getMeasuredHeight());
} else if (heightMeasureMode == MeasureSpec.EXACTLY) {
// Can't change height, change width.
@@ -200,12 +193,9 @@
setMeasuredDimension(childView.getMeasuredWidth(), childView.getMeasuredHeight());
} else {
// This should never happen; the first if checks that both MeasureSpecs are either
- // AT_MOST
- // or UNSPECIFIED. If that branch isn't taken, one of the MeasureSpecs must be EXACTLY.
- // It's
- // technically possible to smash the flag bits though (mode == 3 is invalid), so if we
- // get
- // here, that must have happened.
+ // AT_MOST or UNSPECIFIED. If that branch isn't taken, one of the MeasureSpecs must be
+ // EXACTLY. It's technically possible to smash the flag bits though (mode == 3 is
+ // invalid), so if we get here, that must have happened.
throw new IllegalArgumentException("Unknown measure mode bits in given MeasureSpecs");
}
}
@@ -215,16 +205,13 @@
View childView = getChildAt(0);
// Place the child view within the bounds. If the child is greater than the bounds (i.e. one
- // of
- // the constraints was MATCH_PARENT, and the other was free), then just align the top-left
- // for
- // now.
+ // of the constraints was MATCH_PARENT, and the other was free), then just align the
+ // top-left for now.
childView.layout(0, 0, childView.getMeasuredWidth(), childView.getMeasuredHeight());
}
// setPadding(Relative) should just pass straight through to the child; this View should just be
- // a
- // wrapper, so should not itself introduce any extra spacing.
+ // a wrapper, so should not itself introduce any extra spacing.
//
// We don't override the getters, since nothing in the layout tree should actually use them.
@Override
diff --git a/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/renderer/internal/ResourceResolvers.java b/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/renderer/internal/ResourceResolvers.java
index 5325714..5c7bd19 100644
--- a/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/renderer/internal/ResourceResolvers.java
+++ b/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/renderer/internal/ResourceResolvers.java
@@ -179,11 +179,9 @@
"Can't find resolver for image resource " + protoResourceId));
}
- /** Returns whether an image can be tinted or not. */
public boolean canImageBeTinted(@NonNull String protoResourceId) {
// Only Android image resources can be tinted for now. This is because we don't really know
- // what
- // is in an inline image.
+ // what is in an inline image.
ResourceProto.ImageResource imageResource =
mProtoResources.getIdToImageMap().get(protoResourceId);
diff --git a/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/renderer/internal/TileRendererInternal.java b/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/renderer/internal/TileRendererInternal.java
index 7154cd0..55cd63b 100644
--- a/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/renderer/internal/TileRendererInternal.java
+++ b/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/renderer/internal/TileRendererInternal.java
@@ -117,7 +117,8 @@
import androidx.wear.tiles.proto.StateProto.State;
import androidx.wear.tiles.renderer.R;
import androidx.wear.tiles.renderer.internal.ResourceResolvers.ResourceAccessException;
-import androidx.wear.tiles.renderer.internal.WearArcLayout.ArcLayoutWidget;
+import androidx.wear.widget.ArcLayout;
+import androidx.wear.widget.CurvedTextView;
import com.google.common.util.concurrent.ListenableFuture;
@@ -142,9 +143,9 @@
private static final int TEXT_ALIGN_DEFAULT = Gravity.CENTER_HORIZONTAL;
private static final ScaleType IMAGE_DEFAULT_SCALE_TYPE = ScaleType.FIT_CENTER;
- @WearArcLayout.LayoutParams.VerticalAlignment
+ @ArcLayout.LayoutParams.VerticalAlignment
private static final int ARC_VERTICAL_ALIGN_DEFAULT =
- WearArcLayout.LayoutParams.VERTICAL_ALIGN_CENTER;
+ ArcLayout.LayoutParams.VERTICAL_ALIGN_CENTER;
private static final int SPAN_VERTICAL_ALIGN_DEFAULT = ImageSpan.ALIGN_BOTTOM;
@@ -162,8 +163,7 @@
.setWrappedDimension(WrappedDimensionProp.getDefaultInstance())
.build();
- @WearArcLayout.AnchorType
- private static final int ARC_ANCHOR_DEFAULT = WearArcLayout.ANCHOR_CENTER;
+ @ArcLayout.AnchorType private static final int ARC_ANCHOR_DEFAULT = ArcLayout.ANCHOR_CENTER;
// White
private static final int LINE_COLOR_DEFAULT = 0xFFFFFFFF;
@@ -302,13 +302,13 @@
//
// A Row (LinearLayout) supports this with width=0 and weight>0. After doing a layout pass,
// it will assign all remaining space to elements with width=0 and weight>0, biased by the
- // weight. This causes problems if there are two (or more) "expand" elements in a row,
- // which is itself set to WRAP_CONTENTS, and one of those elements has a measured width
- // (e.g. Text). In that case, the LinearLayout will measure the text, then ensure that
- // all elements with a weight set have their widths set according to the weight. For us,
- // that means that _all_ elements with expand=true will size themselves to the same width
- // as the Text, pushing out the bounds of the parent row. This happens on columns too,
- // but of course regarding height.
+ // weight. This causes problems if there are two (or more) "expand" elements in a row, which
+ // is itself set to WRAP_CONTENTS, and one of those elements has a measured width (e.g.
+ // Text). In that case, the LinearLayout will measure the text, then ensure that all
+ // elements with a weight set have their widths set according to the weight. For us, that
+ // means that _all_ elements with expand=true will size themselves to the same width as the
+ // Text, pushing out the bounds of the parent row. This happens on columns too, but of
+ // course regarding height.
//
// To get around this, if an element with expand=true is added to a row that is WRAP_CONTENT
// (e.g. a row with no explicit width, that is not expanded), we ignore the expand=true, and
@@ -403,15 +403,15 @@
return VERTICAL_ALIGN_DEFAULT_GRAVITY;
}
- @WearArcLayout.LayoutParams.VerticalAlignment
+ @ArcLayout.LayoutParams.VerticalAlignment
private static int verticalAlignmentToArcVAlign(VerticalAlignmentProp alignment) {
switch (alignment.getValue()) {
case VERTICAL_ALIGN_TOP:
- return WearArcLayout.LayoutParams.VERTICAL_ALIGN_OUTER;
+ return ArcLayout.LayoutParams.VERTICAL_ALIGN_OUTER;
case VERTICAL_ALIGN_CENTER:
- return WearArcLayout.LayoutParams.VERTICAL_ALIGN_CENTER;
+ return ArcLayout.LayoutParams.VERTICAL_ALIGN_CENTER;
case VERTICAL_ALIGN_BOTTOM:
- return WearArcLayout.LayoutParams.VERTICAL_ALIGN_INNER;
+ return ArcLayout.LayoutParams.VERTICAL_ALIGN_INNER;
case UNRECOGNIZED:
case VERTICAL_ALIGN_UNDEFINED:
return ARC_VERTICAL_ALIGN_DEFAULT;
@@ -459,10 +459,10 @@
private static boolean isBold(FontStyle fontStyle) {
// Although this method could be a simple equality check against FONT_WEIGHT_BOLD, we list
// all current cases here so that this will become a compile time error as soon as a new
- // FontWeight value is added to the schema. If this fails to build, then this means that
- // an int typeface style is no longer enough to represent all FontWeight values and a
- // customizable, per-weight text style must be introduced to TileRendererInternal to
- // handle this. See b/176980535
+ // FontWeight value is added to the schema. If this fails to build, then this means that an
+ // int typeface style is no longer enough to represent all FontWeight values and a
+ // customizable, per-weight text style must be introduced to TileRendererInternal to handle
+ // this. See b/176980535
switch (fontStyle.getWeight().getValue()) {
case FONT_WEIGHT_BOLD:
return true;
@@ -538,8 +538,8 @@
private void applyFontStyle(FontStyle style, TextView textView) {
// Need to supply typefaceStyle when creating the typeface (will select specialist
- // bold/italic typefaces), *and* when setting the typeface (will set synthetic
- // bold/italic flags in Paint if they're not supported by the given typeface).
+ // bold/italic typefaces), *and* when setting the typeface (will set synthetic bold/italic
+ // flags in Paint if they're not supported by the given typeface).
textView.setTypeface(createTypeface(style), fontStyleToTypefaceStyle(style));
int currentPaintFlags = textView.getPaintFlags();
@@ -564,22 +564,15 @@
textView.setTextColor(extractTextColorArgb(style));
}
- private void applyFontStyle(FontStyle style, WearCurvedTextView textView) {
+ private void applyFontStyle(FontStyle style, CurvedTextView textView) {
// Need to supply typefaceStyle when creating the typeface (will select specialist
- // bold/italic typefaces), *and* when setting the typeface (will set synthetic
- // bold/italic flags in Paint if they're not supported by the given typeface).
+ // bold/italic typefaces), *and* when setting the typeface (will set synthetic bold/italic
+ // flags in Paint if they're not supported by the given typeface).
textView.setTypeface(createTypeface(style), fontStyleToTypefaceStyle(style));
- int currentPaintFlags = textView.getPaintFlags();
-
- // Remove the bits we're setting
- currentPaintFlags &= ~Paint.UNDERLINE_TEXT_FLAG;
-
- if (style.hasUnderline() && style.getUnderline().getValue()) {
- currentPaintFlags |= Paint.UNDERLINE_TEXT_FLAG;
- }
-
- textView.setPaintFlags(currentPaintFlags);
+ // TODO(b/188801917): Implement underline. CurvedTextView (well, drawTextOnArc) doesn't
+ // support underline. We can implement this later by drawing a line under the text ourselves
+ // though.
if (style.hasSize()) {
textView.setTextSize(toPx(style.getSize()));
@@ -716,14 +709,14 @@
return view;
}
- // This is a little nasty; ArcLayoutWidget is just an interface, so we have no guarantee that
- // the instance also extends View (as it should). Instead, just take a View in and rename
- // this, and check that it's an ArcLayoutWidget internally.
+ // This is a little nasty; ArcLayout.Widget is just an interface, so we have no guarantee that
+ // the instance also extends View (as it should). Instead, just take a View in and rename this,
+ // and check that it's an ArcLayout.Widget internally.
private View applyModifiersToArcLayoutView(View view, ArcModifiers modifiers) {
- if (!(view instanceof ArcLayoutWidget)) {
+ if (!(view instanceof ArcLayout.Widget)) {
Log.e(
TAG,
- "applyModifiersToArcLayoutView should only be called with an ArcLayoutWidget");
+ "applyModifiersToArcLayoutView should only be called with an ArcLayout.Widget");
return view;
}
@@ -770,15 +763,15 @@
return TEXT_OVERFLOW_DEFAULT;
}
- @WearArcLayout.AnchorType
+ @ArcLayout.AnchorType
private static int anchorTypeToAnchorPos(ArcAnchorTypeProp type) {
switch (type.getValue()) {
case ARC_ANCHOR_START:
- return WearArcLayout.ANCHOR_START;
+ return ArcLayout.ANCHOR_START;
case ARC_ANCHOR_CENTER:
- return WearArcLayout.ANCHOR_CENTER;
+ return ArcLayout.ANCHOR_CENTER;
case ARC_ANCHOR_END:
- return WearArcLayout.ANCHOR_END;
+ return ArcLayout.ANCHOR_END;
case ARC_ANCHOR_UNDEFINED:
case UNRECOGNIZED:
return ARC_ANCHOR_DEFAULT;
@@ -939,17 +932,17 @@
// HACK: FrameLayout has a bug in it. If we add one WRAP_CONTENT child, and one MATCH_PARENT
// child, the expected behaviour is that the FrameLayout sizes itself to fit the
- // WRAP_CONTENT child (e.g. a TextView), then the MATCH_PARENT child is forced to the
- // same size as the outer FrameLayout (and hence, the size of the TextView, after
- // accounting for padding etc). Because of a bug though, this doesn't happen; instead,
- // the MATCH_PARENT child will just keep its intrinsic size. This is because FrameLayout
- // only forces MATCH_PARENT children to a given size if there are _more than one_ of them
- // (see the bottom of FrameLayout#onMeasure).
+ // WRAP_CONTENT child (e.g. a TextView), then the MATCH_PARENT child is forced to the same
+ // size as the outer FrameLayout (and hence, the size of the TextView, after accounting for
+ // padding etc). Because of a bug though, this doesn't happen; instead, the MATCH_PARENT
+ // child will just keep its intrinsic size. This is because FrameLayout only forces
+ // MATCH_PARENT children to a given size if there are _more than one_ of them (see the
+ // bottom of FrameLayout#onMeasure).
//
// To work around this (without copying the whole of FrameLayout just to change a "1" to
- // "0"), we add a Space element in if there is one MATCH_PARENT child. This has a tiny
- // cost to the measure pass, and negligible cost to layout/draw (since it doesn't take
- // part in those passes).
+ // "0"), we add a Space element in if there is one MATCH_PARENT child. This has a tiny cost
+ // to the measure pass, and negligible cost to layout/draw (since it doesn't take part in
+ // those passes).
int numMatchParentChildren = 0;
for (int i = 0; i < frame.getChildCount(); i++) {
LayoutParams lp = frame.getChildAt(i).getLayoutParams();
@@ -1015,7 +1008,7 @@
LayoutParams layoutParams = generateDefaultLayoutParams();
space.setSweepAngleDegrees(lengthDegrees);
- space.setThicknessPx(thicknessPx);
+ space.setThickness(thicknessPx);
View wrappedView = applyModifiersToArcLayoutView(space, spacer.getModifiers());
parent.addView(wrappedView, layoutParams);
@@ -1067,8 +1060,8 @@
}
private View inflateArcText(ViewGroup parent, ArcText text) {
- WearCurvedTextView textView =
- new WearCurvedTextView(
+ CurvedTextView textView =
+ new CurvedTextView(
mAppContext, /* attrs= */ null, R.attr.tilesFallbackTextAppearance);
LayoutParams layoutParams = generateDefaultLayoutParams();
@@ -1217,17 +1210,20 @@
parent.addView(wrappedView, ratioWrapperLayoutParams);
ListenableFuture<Drawable> drawableFuture = mResourceResolvers.getDrawable(protoResId);
- if (drawableFuture.isDone()) {
+ boolean isImageSet = false;
+ if (drawableFuture.isDone() && !drawableFuture.isCancelled()) {
// If the future is done, immediately draw.
- setImageDrawable(imageView, drawableFuture, protoResId);
- } else {
+ isImageSet = setImageDrawable(imageView, drawableFuture, protoResId);
+ }
+
+ if (!isImageSet) {
// Is there a placeholder to use in the meantime?
try {
if (mResourceResolvers.hasPlaceholderDrawable(protoResId)) {
imageView.setImageDrawable(
mResourceResolvers.getPlaceholderDrawableOrThrow(protoResId));
}
- } catch (ResourceAccessException ex) {
+ } catch (ResourceAccessException | IllegalArgumentException ex) {
Log.e(TAG, "Exception loading placeholder for resource " + protoResId, ex);
}
@@ -1262,13 +1258,15 @@
return wrappedView;
}
- private static void setImageDrawable(
+ private static boolean setImageDrawable(
ImageView imageView, Future<Drawable> drawableFuture, String protoResId) {
try {
imageView.setImageDrawable(drawableFuture.get());
+ return true;
} catch (ExecutionException | InterruptedException e) {
Log.w(TAG, "Could not get drawable for image " + protoResId);
}
+ return false;
}
@Nullable
@@ -1293,7 +1291,7 @@
lineColor = line.getColor().getArgb();
}
- lineView.setThicknessPx(thicknessPx);
+ lineView.setThickness(thicknessPx);
lineView.setSweepAngleDegrees(lengthDegrees);
lineView.setColor(lineColor);
@@ -1305,7 +1303,7 @@
@Nullable
private View inflateArc(ViewGroup parent, Arc arc) {
- WearArcLayout arcLayout = new WearArcLayout(mAppContext);
+ ArcLayout arcLayout = new ArcLayout(mAppContext);
LayoutParams layoutParams = generateDefaultLayoutParams();
layoutParams.width = LayoutParams.MATCH_PARENT;
@@ -1318,15 +1316,15 @@
for (ArcLayoutElement child : arc.getContentsList()) {
@Nullable View childView = inflateArcLayoutElement(arcLayout, child);
if (childView != null) {
- WearArcLayout.LayoutParams childLayoutParams =
- (WearArcLayout.LayoutParams) childView.getLayoutParams();
+ ArcLayout.LayoutParams childLayoutParams =
+ (ArcLayout.LayoutParams) childView.getLayoutParams();
boolean rotate = false;
if (child.hasAdapter()) {
rotate = child.getAdapter().getRotateContents().getValue();
}
// Apply rotation and gravity.
- childLayoutParams.setRotate(rotate);
+ childLayoutParams.setRotated(rotate);
childLayoutParams.setVerticalAlignment(
verticalAlignmentToArcVAlign(arc.getVerticalAlign()));
}
@@ -1891,9 +1889,9 @@
private static Typeface loadTypeface(TypedArray array, int styleableResId) {
// Resources are a little nasty; we can't just check if resType =
// TypedValue.TYPE_REFERENCE, because it never is (if you use @font/foo inside of
- // styles.xml, the value will be a string of the form res/font/foo.ttf). Instead, see
- // if there's a resource ID at all, and use that, otherwise assume it's a well known
- // font family.
+ // styles.xml, the value will be a string of the form res/font/foo.ttf). Instead, see if
+ // there's a resource ID at all, and use that, otherwise assume it's a well known font
+ // family.
int resType = array.getType(styleableResId);
if (array.getResourceId(styleableResId, -1) != -1
diff --git a/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/renderer/internal/WearArcLayout.java b/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/renderer/internal/WearArcLayout.java
deleted file mode 100644
index 589a78f..0000000
--- a/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/renderer/internal/WearArcLayout.java
+++ /dev/null
@@ -1,747 +0,0 @@
-/*
- * Copyright 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.wear.tiles.renderer.internal;
-
-import static java.lang.Math.asin;
-import static java.lang.Math.max;
-import static java.lang.Math.round;
-
-import android.annotation.SuppressLint;
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.graphics.Canvas;
-import android.graphics.Matrix;
-import android.util.AttributeSet;
-import android.util.DisplayMetrics;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewGroup;
-
-import androidx.annotation.AttrRes;
-import androidx.annotation.IntDef;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.RestrictTo;
-import androidx.annotation.StyleRes;
-import androidx.annotation.UiThread;
-import androidx.wear.tiles.renderer.R;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-/**
- * Container which will lay its elements out on an arc. Elements will be relative to a given anchor
- * angle (where 0 degrees = 12 o clock), where the layout relative to the anchor angle is controlled
- * using {@code anchorAngleDegrees} and {@code anchorType}. The thickness of the arc is calculated
- * based on the child element with the greatest height (in the case of Android widgets), or greatest
- * thickness (for curved widgets). By default, the container lays its children one by one in
- * clockwise direction. The attribute 'clockwise' can be set to false to make the layout direction
- * as anti-clockwise. These two types of widgets will be drawn as follows.
- *
- * <p>Standard Android Widgets:
- *
- * <p>These widgets will be drawn as usual, but placed at the correct position on the arc, with the
- * correct amount of rotation applied. As an example, for an Android Text widget, the text baseline
- * would be drawn at a tangent to the arc. The arc length of a widget is obtained by measuring the
- * width of the widget, and transforming that to the length of an arc on a circle.
- *
- * <p>A standard Android widget will be measured as usual, but the maximum height constraint will be
- * capped at the minimum radius of the arc (i.e. width / 2).
- *
- * <p>"Curved" widgets:
- *
- * <p>Widgets which implement {@link ArcLayoutWidget} are expected to draw themselves within an arc
- * automatically. These widgets will be measured with the full dimensions of the arc container. They
- * are also expected to provide their thickness (used when calculating the thickness of the arc) and
- * the current sweep angle (used for laying out when drawing). Note that the WearArcLayout will
- * apply a rotation transform to the canvas before drawing this child; the inner child need not
- * perform any rotations itself.
- *
- * <p>An example of a widget which implements this interface is {@link WearCurvedTextView}, which
- * will lay itself out along the arc.
- */
-// TODO(b/174649543): Replace this with the actual androidx.wear.widget.WearArcLayout when
-// the next stable release of it is ready.
-// TODO(b/177464637): Resolve RestrictToUsage suppression.
-@SuppressWarnings("RestrictToUsage")
-@UiThread
-public class WearArcLayout extends ViewGroup {
-
- /**
- * Interface for a widget which knows it is being rendered inside an arc, and will draw itself
- * accordingly. Any widget implementing this interface will receive the full-sized canvas,
- * pre-rotated, in its draw call.
- */
- public interface ArcLayoutWidget {
-
- /** Returns the sweep angle that this widget is drawn with. */
- float getSweepAngleDegrees();
-
- /** Returns the thickness of this widget inside the arc. */
- int getThicknessPx();
-
- /** Check whether the widget contains invalid attributes as a child of WearArcLayout */
- void checkInvalidAttributeAsChild();
-
- /**
- * Return true when the given point is in the clickable area of the child widget. In
- * particular, the coordinates should be considered as if the child was drawn centered at
- * the default angle (12 o clock).
- */
- boolean insideClickArea(float x, float y);
- }
-
- /**
- * Layout parameters for a widget added to an arc. This allows each element to specify whether
- * or not it should be rotated(around the center of the child) when drawn inside the arc. For
- * example, when the child is put at the center-bottom of the arc, whether the parent layout is
- * responsible to rotate it 180 degree to draw it upside down.
- *
- * <p>Note that the {@code rotate} parameter is ignored when drawing "Fullscreen" elements.
- */
- public static class LayoutParams extends ViewGroup.MarginLayoutParams {
-
- /**
- * Vertical alignment of elements within the arc.
- *
- * @hide
- */
- @Retention(RetentionPolicy.SOURCE)
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
- @IntDef({VERTICAL_ALIGN_OUTER, VERTICAL_ALIGN_CENTER, VERTICAL_ALIGN_INNER})
- public @interface VerticalAlignment {}
-
- /** Align to the outer edge of the parent WearArcLayout. */
- public static final int VERTICAL_ALIGN_OUTER = 0;
-
- /** Align to the center of the parent WearArcLayout. */
- public static final int VERTICAL_ALIGN_CENTER = 1;
-
- /** Align to the inner edge of the parent WearArcLayout. */
- public static final int VERTICAL_ALIGN_INNER = 2;
-
- private boolean mRotate = true;
- @VerticalAlignment private int mVerticalAlignment = VERTICAL_ALIGN_CENTER;
-
- // Internally used during layout/draw
- // Stores the angle of the child, used to handle touch events.
- float mMiddleAngle;
-
- /**
- * Creates a new set of layout parameters. The values are extracted from the supplied
- * attributes set and context.
- *
- * @param context the application environment
- * @param attrs the set of attributes from which to extract the layout parameters' values
- */
- public LayoutParams(@NonNull Context context, @NonNull AttributeSet attrs) {
- super(context, attrs);
-
- TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.WearArcLayout_Layout);
-
- mRotate = a.getBoolean(R.styleable.WearArcLayout_Layout_layout_rotate, true);
- mVerticalAlignment =
- a.getInt(R.styleable.WearArcLayout_Layout_layout_valign, VERTICAL_ALIGN_CENTER);
-
- a.recycle();
- }
-
- /**
- * Creates a new set of layout parameters with specified width and height
- *
- * @param width the width, either WRAP_CONTENT, MATCH_PARENT or a fixed size in pixels
- * @param height the height, either WRAP_CONTENT, MATCH_PARENT or a fixed size in pixels
- */
- public LayoutParams(int width, int height) {
- super(width, height);
- }
-
- /** Copy constructor */
- public LayoutParams(@NonNull ViewGroup.LayoutParams source) {
- super(source);
- }
-
- /**
- * Gets whether the widget shall be rotated by the WearArcLayout container corresponding to
- * its layout position angle
- */
- public boolean getRotate() {
- return mRotate;
- }
-
- /**
- * Sets whether the widget shall be rotated by the WearArcLayout container corresponding to
- * its layout position angle
- */
- public void setRotate(boolean rotate) {
- mRotate = rotate;
- }
-
- /** Gets how the widget is positioned vertically in the WearArcLayout. */
- @VerticalAlignment
- public int getVerticalAlignment() {
- return mVerticalAlignment;
- }
-
- /**
- * Sets how the widget is positioned vertically in the WearArcLayout.
- *
- * @param verticalAlignment align the widget to outer, inner edges or center.
- */
- public void setVerticalAlignment(@VerticalAlignment int verticalAlignment) {
- mVerticalAlignment = verticalAlignment;
- }
- }
-
- /**
- * Annotation for anchor types.
- *
- * @hide
- */
- @Retention(RetentionPolicy.SOURCE)
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
- @IntDef({ANCHOR_START, ANCHOR_CENTER, ANCHOR_END})
- public @interface AnchorType {}
-
- /**
- * Anchor at the start of the set of elements drawn within this container. This causes the first
- * child to be drawn from {@code anchorAngle} degrees, to the right.
- *
- * <p>As an example, if this container contains two arcs, one having 10 degrees of sweep and the
- * other having 20 degrees of sweep, the first will be drawn between 0-10 degrees, and the
- * second between 10-30 degrees.
- */
- public static final int ANCHOR_START = 0;
-
- /**
- * Anchor at the center of the set of elements drawn within this container.
- *
- * <p>As an example, if this container contains two arcs, one having 10 degrees of sweep and the
- * other having 20 degrees of sweep, the first will be drawn between -15 and -5 degrees, and the
- * second between -5 and 15 degrees.
- */
- public static final int ANCHOR_CENTER = 1;
-
- /**
- * Anchor at the end of the set of elements drawn within this container. This causes the last
- * element to end at {@code anchorAngle} degrees, with the other elements swept to the left.
- *
- * <p>As an example, if this container contains two arcs, one having 10 degrees of sweep and the
- * other having 20 degrees of sweep, the first will be drawn between -30 and -20 degrees, and
- * the second between -20 and 0 degrees.
- */
- public static final int ANCHOR_END = 2;
-
- private static final float DEFAULT_START_ANGLE_DEGREES = 0f;
- private static final boolean DEFAULT_LAYOUT_DIRECTION_IS_CLOCKWISE = true; // clockwise
- @AnchorType private static final int DEFAULT_ANCHOR_TYPE = ANCHOR_START;
-
- private int mThicknessPx = 0;
-
- @AnchorType private int mAnchorType;
- private float mAnchorAngleDegrees;
- private boolean mClockwise;
-
- @SuppressWarnings("SyntheticAccessor")
- private final ChildArcAngles mChildArcAngles = new ChildArcAngles();
-
- public WearArcLayout(@NonNull Context context) {
- this(context, null);
- }
-
- public WearArcLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public WearArcLayout(
- @NonNull Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr) {
- this(context, attrs, defStyleAttr, 0);
- }
-
- public WearArcLayout(
- @NonNull Context context,
- @Nullable AttributeSet attrs,
- @AttrRes int defStyleAttr,
- @StyleRes int defStyleRes) {
- super(context, attrs, defStyleAttr, defStyleRes);
-
- TypedArray a =
- context.obtainStyledAttributes(
- attrs, R.styleable.WearArcLayout, defStyleAttr, defStyleRes);
-
- mAnchorType = a.getInt(R.styleable.WearArcLayout_anchorPosition, DEFAULT_ANCHOR_TYPE);
- mAnchorAngleDegrees =
- a.getFloat(
- R.styleable.WearArcLayout_anchorAngleDegrees, DEFAULT_START_ANGLE_DEGREES);
- mClockwise =
- a.getBoolean(
- R.styleable.WearArcLayout_clockwise, DEFAULT_LAYOUT_DIRECTION_IS_CLOCKWISE);
-
- a.recycle();
- }
-
- @Override
- public void requestLayout() {
- super.requestLayout();
-
- for (int i = 0; i < getChildCount(); i++) {
- getChildAt(i).forceLayout();
- }
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- // Need to derive the thickness of the curve from the children. We're a curve, so the
- // children can only be sized up to (width or height)/2 units. This currently only
- // supports fitting to a circle.
- //
- // No matter what, fit to the given size, be it a maximum or a fixed size. It doesn't make
- // sense for this container to wrap its children.
- int actualWidthPx = MeasureSpec.getSize(widthMeasureSpec);
- int actualHeightPx = MeasureSpec.getSize(heightMeasureSpec);
-
- if (MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.UNSPECIFIED
- && MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.UNSPECIFIED) {
- // We can't actually resolve this.
- // Let's fit to the screen dimensions, for need of anything better...
- DisplayMetrics displayMetrics = getContext().getResources().getDisplayMetrics();
- actualWidthPx = displayMetrics.widthPixels;
- actualHeightPx = displayMetrics.heightPixels;
- }
-
- // Fit to a square.
- if (actualWidthPx < actualHeightPx) {
- actualHeightPx = actualWidthPx;
- } else if (actualHeightPx < actualWidthPx) {
- actualWidthPx = actualHeightPx;
- }
-
- int maxChildDimension = actualHeightPx / 2;
-
- // Measure all children in the new measurespec, and cache the largest.
- int childMeasureSpec = MeasureSpec.makeMeasureSpec(maxChildDimension, MeasureSpec.AT_MOST);
-
- // We need to do two measure passes. First, we need to measure all "normal" children, and
- // get the thickness of all "CurvedContainer" children. Once we have that, we know the
- // maximum thickness, and we can lay out the "CurvedContainer" children, taking into
- // account their vertical alignment.
- int maxChildHeightPx = 0;
- int childState = 0;
- for (int i = 0; i < getChildCount(); i++) {
- View child = getChildAt(i);
-
- if (child.getVisibility() == GONE) {
- continue;
- }
-
- // ArcLayoutWidget is a special case. Because of how it draws, fit it to the size
- // of the whole widget.
- int childMeasuredHeight;
- if (child instanceof ArcLayoutWidget) {
- childMeasuredHeight = ((ArcLayoutWidget) child).getThicknessPx();
- } else {
- measureChild(
- child,
- getChildMeasureSpec(childMeasureSpec, 0, child.getLayoutParams().width),
- getChildMeasureSpec(childMeasureSpec, 0, child.getLayoutParams().height));
- childMeasuredHeight = child.getMeasuredHeight();
- childState = combineMeasuredStates(childState, child.getMeasuredState());
- }
- LayoutParams childLayoutParams = (LayoutParams) child.getLayoutParams();
- maxChildHeightPx =
- max(
- maxChildHeightPx,
- childMeasuredHeight
- + childLayoutParams.topMargin
- + childLayoutParams.bottomMargin);
- }
-
- mThicknessPx = maxChildHeightPx;
-
- // And now do the pass for the ArcLayoutWidgets
- for (int i = 0; i < getChildCount(); i++) {
- View child = getChildAt(i);
-
- if (child.getVisibility() == GONE) {
- continue;
- }
-
- if (child instanceof ArcLayoutWidget) {
- LayoutParams childLayoutParams = (LayoutParams) child.getLayoutParams();
-
- float insetPx = getChildTopInset(child);
-
- int innerChildMeasureSpec =
- MeasureSpec.makeMeasureSpec(
- maxChildDimension * 2 - round(insetPx * 2), MeasureSpec.EXACTLY);
-
- measureChild(
- child,
- getChildMeasureSpec(innerChildMeasureSpec, 0, childLayoutParams.width),
- getChildMeasureSpec(innerChildMeasureSpec, 0, childLayoutParams.height));
-
- childState = combineMeasuredStates(childState, child.getMeasuredState());
- }
- }
-
- setMeasuredDimension(
- resolveSizeAndState(actualWidthPx, widthMeasureSpec, childState),
- resolveSizeAndState(actualHeightPx, heightMeasureSpec, childState));
- }
-
- @Override
- protected void onLayout(boolean changed, int l, int t, int r, int b) {
- for (int i = 0; i < getChildCount(); i++) {
- View child = getChildAt(i);
-
- if (child.getVisibility() == GONE) {
- continue;
- }
-
- // Curved container widgets have been measured so that the "arc" inside their widget
- // will touch the outside of the box they have been measured in, taking into account
- // the vertical alignment. Just grow them from the center.
- if (child instanceof ArcLayoutWidget) {
- int leftPx = round((getMeasuredWidth() / 2f) - (child.getMeasuredWidth() / 2f));
- int topPx = round((getMeasuredHeight() / 2f) - (child.getMeasuredHeight() / 2f));
-
- child.layout(
- leftPx,
- topPx,
- leftPx + child.getMeasuredWidth(),
- topPx + child.getMeasuredHeight());
- } else {
- // Normal widgets need to be placed on their canvas, taking into account their
- // vertical position.
- int leftPx = round((getMeasuredWidth() / 2f) - (child.getMeasuredWidth() / 2f));
- int topPx = round(getChildTopInset(child));
-
- child.layout(
- leftPx,
- topPx,
- leftPx + child.getMeasuredWidth(),
- topPx + child.getMeasuredHeight());
- }
- }
-
- // Once dimensions are set, also layout the children in the arc, computing the
- // center angle where they should be drawn.
- float currentCumulativeAngle = calculateInitialRotation();
- for (int i = 0; i < getChildCount(); i++) {
- View child = getChildAt(i);
-
- if (child.getVisibility() == GONE) {
- continue;
- }
-
- calculateArcAngle(child, mChildArcAngles);
- float preRotation =
- mChildArcAngles.leftMarginAsAngle + mChildArcAngles.actualChildAngle / 2f;
- float multiplier = mClockwise ? 1f : -1f;
-
- float middleAngle = multiplier * (currentCumulativeAngle + preRotation);
- LayoutParams childLayoutParams = (LayoutParams) child.getLayoutParams();
- childLayoutParams.mMiddleAngle = middleAngle;
-
- currentCumulativeAngle += mChildArcAngles.getTotalAngle();
- }
- }
-
- // When a view (that can handle it) receives a TOUCH_DOWN event, it will get all subsequent
- // events until the touch is released, even if the pointer goes outside of it's bounds.
- @Nullable private View mTouchedView = null;
-
- @Override
- public boolean onInterceptTouchEvent(@NonNull MotionEvent event) {
- if (mTouchedView == null && event.getActionMasked() == MotionEvent.ACTION_DOWN) {
- for (int i = 0; i < getChildCount(); i++) {
- View child = getChildAt(i);
- // Ensure that the view is visible
- if (child.getVisibility() != VISIBLE) {
- continue;
- }
-
- // Map the event to the child's coordinate system
- LayoutParams childLayoutParams = (LayoutParams) child.getLayoutParams();
- float angle = childLayoutParams.mMiddleAngle;
-
- float[] point = new float[] {event.getX(), event.getY()};
- mapPoint(child, angle, point);
-
- // Check if the click is actually in the child area
- float x = point[0];
- float y = point[1];
-
- if (insideChildClickArea(child, x, y)) {
- mTouchedView = child;
- break;
- }
- }
- }
- // We can't do normal dispatching because it will capture touch in the original position
- // of children.
- return true;
- }
-
- private static boolean insideChildClickArea(View child, float x, float y) {
- if (child instanceof ArcLayoutWidget) {
- return ((ArcLayoutWidget) child).insideClickArea(x, y);
- }
- return x >= 0 && x < child.getMeasuredWidth() && y >= 0 && y < child.getMeasuredHeight();
- }
-
- // Map a point to local child coordinates.
- private void mapPoint(View child, float angle, float[] point) {
- float cx = getMeasuredWidth() / 2;
- float cy = getMeasuredHeight() / 2;
-
- Matrix m = new Matrix();
- m.postRotate(-angle, cx, cy);
- m.postTranslate(-child.getX(), -child.getY());
- if (!(child instanceof ArcLayoutWidget)) {
- LayoutParams childLayoutParams = (LayoutParams) child.getLayoutParams();
- if (!childLayoutParams.getRotate()) {
- m.postRotate(angle, child.getWidth() / 2, child.getHeight() / 2);
- }
- }
- m.mapPoints(point);
- }
-
- @Override
- @SuppressLint("ClickableViewAccessibility")
- public boolean onTouchEvent(@NonNull MotionEvent event) {
- @Nullable View touchedView = mTouchedView;
- if (touchedView != null) {
- // Map the event's coordinates to the child's coordinate space
- float[] point = new float[] {event.getX(), event.getY()};
- LayoutParams touchedViewLayoutParams = (LayoutParams) touchedView.getLayoutParams();
- mapPoint(touchedView, touchedViewLayoutParams.mMiddleAngle, point);
-
- float dx = point[0] - event.getX();
- float dy = point[1] - event.getY();
- event.offsetLocation(dx, dy);
-
- touchedView.dispatchTouchEvent(event);
-
- if (event.getActionMasked() == MotionEvent.ACTION_UP
- || event.getActionMasked() == MotionEvent.ACTION_CANCEL) {
- // We have finished handling these series of events.
- mTouchedView = null;
- }
- return true;
- }
- return false;
- }
-
- @Override
- protected boolean drawChild(@NonNull Canvas canvas, @NonNull View child, long drawingTime) {
- // Rotate the canvas to make the children render in the right place.
- canvas.save();
-
- LayoutParams childLayoutParams = (LayoutParams) child.getLayoutParams();
- float middleAngle = childLayoutParams.mMiddleAngle;
-
- // Rotate the child widget.
- canvas.rotate(middleAngle, getMeasuredWidth() / 2f, getMeasuredHeight() / 2f);
-
- if (child instanceof ArcLayoutWidget) {
- ((ArcLayoutWidget) child).checkInvalidAttributeAsChild();
- } else {
- // Do we need to do some counter rotation?
- LayoutParams layoutParams = (LayoutParams) child.getLayoutParams();
-
- float angleToRotate = 0f;
-
- if (layoutParams.getRotate()) {
- // For counterclockwise layout, especially when mixing standard Android widget with
- // ArcLayoutWidget as children, we might need to rotate the standard widget to make
- // them have the same upwards direction.
- if (!mClockwise) {
- angleToRotate = 180f;
- }
- } else {
- // Un-rotate about the top of the canvas, around the center of the actual child.
- // This compounds with the initial rotation into a translation.
- angleToRotate = -middleAngle;
- }
-
- // Do the actual rotation. Note that the strange rotation center is because the child
- // view is x-centered but at the top of this container.
- float childInset = getChildTopInset(child);
- canvas.rotate(
- angleToRotate,
- getMeasuredWidth() / 2f,
- child.getMeasuredHeight() / 2f + childInset);
- }
-
- boolean wasInvalidateIssued = super.drawChild(canvas, child, drawingTime);
-
- canvas.restore();
-
- return wasInvalidateIssued;
- }
-
- private float calculateInitialRotation() {
- float multiplier = mClockwise ? 1f : -1f;
- if (mAnchorType == ANCHOR_START) {
- return multiplier * mAnchorAngleDegrees;
- }
-
- float totalArcAngle = 0;
-
- for (int i = 0; i < getChildCount(); i++) {
- calculateArcAngle(getChildAt(i), mChildArcAngles);
- totalArcAngle += mChildArcAngles.getTotalAngle();
- }
-
- if (mAnchorType == ANCHOR_CENTER) {
- return multiplier * mAnchorAngleDegrees - (totalArcAngle / 2f);
- } else if (mAnchorType == ANCHOR_END) {
- return multiplier * mAnchorAngleDegrees - totalArcAngle;
- }
-
- return 0;
- }
-
- private static float widthToAngleDegrees(float widthPx, float radiusPx) {
- return (float) Math.toDegrees(2 * asin(widthPx / radiusPx / 2f));
- }
-
- private void calculateArcAngle(@NonNull View view, @NonNull ChildArcAngles childAngles) {
- if (view.getVisibility() == GONE) {
- childAngles.leftMarginAsAngle = 0;
- childAngles.rightMarginAsAngle = 0;
- childAngles.actualChildAngle = 0;
- return;
- }
-
- float radiusPx = (getMeasuredWidth() / 2f) - mThicknessPx;
-
- LayoutParams childLayoutParams = (LayoutParams) view.getLayoutParams();
-
- childAngles.leftMarginAsAngle = widthToAngleDegrees(childLayoutParams.leftMargin, radiusPx);
- childAngles.rightMarginAsAngle =
- widthToAngleDegrees(childLayoutParams.rightMargin, radiusPx);
-
- if (view instanceof ArcLayoutWidget) {
- childAngles.actualChildAngle = ((ArcLayoutWidget) view).getSweepAngleDegrees();
- } else {
- childAngles.actualChildAngle = widthToAngleDegrees(view.getMeasuredWidth(), radiusPx);
- }
- }
-
- private float getChildTopInset(@NonNull View child) {
- LayoutParams childLayoutParams = (LayoutParams) child.getLayoutParams();
-
- int childHeight =
- child instanceof ArcLayoutWidget
- ? ((ArcLayoutWidget) child).getThicknessPx()
- : child.getMeasuredHeight();
-
- int thicknessDiffPx =
- mThicknessPx
- - childLayoutParams.topMargin
- - childLayoutParams.bottomMargin
- - childHeight;
-
- int margin = mClockwise ? childLayoutParams.topMargin : childLayoutParams.bottomMargin;
-
- switch (childLayoutParams.getVerticalAlignment()) {
- case LayoutParams.VERTICAL_ALIGN_OUTER:
- return margin;
- case LayoutParams.VERTICAL_ALIGN_CENTER:
- return margin + thicknessDiffPx / 2f;
- case LayoutParams.VERTICAL_ALIGN_INNER:
- return margin + thicknessDiffPx;
- default:
- // Nortmally unreachable...
- return 0;
- }
- }
-
- @Override
- protected boolean checkLayoutParams(@Nullable ViewGroup.LayoutParams p) {
- return p instanceof LayoutParams;
- }
-
- @Override
- @NonNull
- protected ViewGroup.LayoutParams generateLayoutParams(@NonNull ViewGroup.LayoutParams p) {
- return new LayoutParams(p);
- }
-
- @Override
- @NonNull
- public ViewGroup.LayoutParams generateLayoutParams(@NonNull AttributeSet attrs) {
- return new LayoutParams(getContext(), attrs);
- }
-
- @Override
- @NonNull
- protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
- return new LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
- }
-
- /** Returns the anchor type used for this container. */
- @AnchorType
- public int getAnchorType() {
- return mAnchorType;
- }
-
- /** Sets the anchor type used for this container. */
- public void setAnchorType(@AnchorType int anchorType) {
- if (anchorType < ANCHOR_START || anchorType > ANCHOR_END) {
- throw new IllegalArgumentException("Unknown anchor type");
- }
-
- mAnchorType = anchorType;
- invalidate();
- }
-
- /** Returns the anchor angle used for this container, in degrees. */
- public float getAnchorAngleDegrees() {
- return mAnchorAngleDegrees;
- }
-
- /** Sets the anchor angle used for this container, in degrees. */
- public void setAnchorAngleDegrees(float anchorAngleDegrees) {
- mAnchorAngleDegrees = anchorAngleDegrees;
- invalidate();
- }
-
- /** returns the layout direction */
- public boolean getClockwise() {
- return mClockwise;
- }
-
- /** Sets the layout direction */
- public void setClockwise(boolean clockwise) {
- mClockwise = clockwise;
- invalidate();
- }
-
- private static class ChildArcAngles {
- public float leftMarginAsAngle;
- public float rightMarginAsAngle;
- public float actualChildAngle;
-
- public float getTotalAngle() {
- return leftMarginAsAngle + rightMarginAsAngle + actualChildAngle;
- }
- }
-}
diff --git a/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/renderer/internal/WearCurvedLineView.java b/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/renderer/internal/WearCurvedLineView.java
index 61459b2..208edd2 100644
--- a/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/renderer/internal/WearCurvedLineView.java
+++ b/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/renderer/internal/WearCurvedLineView.java
@@ -33,6 +33,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.wear.tiles.renderer.R;
+import androidx.wear.widget.ArcLayout;
/**
* A line, drawn inside an arc.
@@ -41,7 +42,7 @@
* the color to draw with. This widget will then draw an arc, with the specified thickness, around
* its parent arc. The sweep angle is specified in degrees, clockwise.
*/
-public class WearCurvedLineView extends View implements WearArcLayout.ArcLayoutWidget {
+public class WearCurvedLineView extends View implements ArcLayout.Widget {
private static final int DEFAULT_THICKNESS_PX = 0;
private static final float DEFAULT_SWEEP_ANGLE_DEGREES = 0;
@ColorInt private static final int DEFAULT_COLOR = 0xFFFFFFFF;
@@ -133,18 +134,13 @@
updatePathAndPaint();
}
- @Override
- public int getThicknessPx() {
- return mThicknessPx;
- }
-
/** Sets the thickness of this arc in pixels. */
- public void setThicknessPx(int thicknessPx) {
- if (thicknessPx < 0) {
- thicknessPx = 0;
+ public void setThickness(int thickness) {
+ if (thickness < 0) {
+ thickness = 0;
}
- this.mThicknessPx = thicknessPx;
+ this.mThicknessPx = thickness;
updatePathAndPaint();
invalidate();
}
@@ -154,6 +150,11 @@
return mSweepAngleDegrees;
}
+ @Override
+ public int getThickness() {
+ return mThicknessPx;
+ }
+
/** Sets the sweep angle of this arc in degrees. */
public void setSweepAngleDegrees(float sweepAngleDegrees) {
this.mSweepAngleDegrees = sweepAngleDegrees;
@@ -185,7 +186,7 @@
}
@Override
- public boolean insideClickArea(float x, float y) {
+ public boolean isPointInsideClickArea(float x, float y) {
// Stolen from WearCurvedTextView...
float radius2 = min(getWidth(), getHeight()) / 2f - getPaddingTop();
float radius1 = radius2 - mThicknessPx;
diff --git a/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/renderer/internal/WearCurvedSpacer.java b/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/renderer/internal/WearCurvedSpacer.java
index dd29df2..b6e6a55 100644
--- a/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/renderer/internal/WearCurvedSpacer.java
+++ b/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/renderer/internal/WearCurvedSpacer.java
@@ -24,13 +24,13 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.wear.tiles.renderer.R;
+import androidx.wear.widget.ArcLayout;
/**
* A lightweight curved widget that represents space between elements inside an Arc. This does no
- * rendering; it simply causes the parent {@link WearArcLayout} to advance by {@code
- * sweepAngleDegrees}.
+ * rendering; it simply causes the parent {@link ArcLayout} to advance by {@code sweepAngleDegrees}.
*/
-public class WearCurvedSpacer extends View implements WearArcLayout.ArcLayoutWidget {
+public class WearCurvedSpacer extends View implements ArcLayout.Widget {
private static final float DEFAULT_SWEEP_ANGLE_DEGREES = 0f;
private static final int DEFAULT_THICKNESS_PX = 0;
@@ -75,26 +75,26 @@
return mSweepAngleDegrees;
}
+ @Override
+ public int getThickness() {
+ return mThicknessPx;
+ }
+
/** Sets the sweep angle of this spacer, in degrees. */
public void setSweepAngleDegrees(float sweepAngleDegrees) {
this.mSweepAngleDegrees = sweepAngleDegrees;
}
- @Override
- public int getThicknessPx() {
- return mThicknessPx;
- }
-
/** Sets the thickness of this spacer, in DP. */
- public void setThicknessPx(int thicknessPx) {
- this.mThicknessPx = thicknessPx;
+ public void setThickness(int thickness) {
+ this.mThicknessPx = thickness;
}
@Override
public void checkInvalidAttributeAsChild() {}
@Override
- public boolean insideClickArea(float x, float y) {
+ public boolean isPointInsideClickArea(float x, float y) {
return false;
}
}
diff --git a/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/renderer/internal/WearCurvedTextView.java b/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/renderer/internal/WearCurvedTextView.java
deleted file mode 100644
index 242d853..0000000
--- a/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/renderer/internal/WearCurvedTextView.java
+++ /dev/null
@@ -1,946 +0,0 @@
-/*
- * Copyright 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.wear.tiles.renderer.internal;
-
-import static java.lang.Math.cos;
-import static java.lang.Math.max;
-import static java.lang.Math.min;
-import static java.lang.Math.round;
-import static java.lang.Math.sin;
-
-import android.annotation.SuppressLint;
-import android.content.Context;
-import android.content.res.ColorStateList;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.Path;
-import android.graphics.Rect;
-import android.graphics.Typeface;
-import android.os.Build;
-import android.text.StaticLayout;
-import android.text.TextPaint;
-import android.text.TextUtils;
-import android.util.AttributeSet;
-import android.view.MotionEvent;
-import android.view.View;
-
-import androidx.annotation.ColorInt;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
-import androidx.wear.tiles.renderer.R;
-
-/**
- * A WearCurvedTextView is a component allowing developers to easily write curved text following the
- * curvature of the largest circle that can be inscribed in the view. WearArcLayout could be used to
- * concatenate multiple curved texts, also layout together with other widgets such as icons.
- */
-// TODO(b/174649543): Replace this with the actual androidx.wear.widget.WearCurvedTextView when
-// the next stable release of it is ready.
-public final class WearCurvedTextView extends View implements WearArcLayout.ArcLayoutWidget {
- private static final float UNSET_ANCHOR_DEGREE = -1f;
- private static final int UNSET_ANCHOR_TYPE = -1;
- private static final float MIN_SWEEP_DEGREE = 0f;
- private static final float MAX_SWEEP_DEGREE = 359.9f;
- private static final float DEFAULT_TEXT_SIZE = 24f;
- @ColorInt private static final int DEFAULT_TEXT_COLOR = Color.WHITE;
- private static final int DEFAULT_TEXT_STYLE = Typeface.NORMAL;
- private static final boolean DEFAULT_CLOCKWISE = true;
- private static final int FONT_WEIGHT_MAX = 1000;
- private static final float ITALIC_SKEW_X = -0.25f;
- // make 0 degree at 12 o'clock, since canvas assumes 0 degree is 3 o'clock
- private static final float ANCHOR_DEGREE_OFFSET = -90f;
-
- private final Path mPath = new Path();
- private final Path mBgPath = new Path();
- private final TextPaint mPaint = new TextPaint();
- private final Rect mBounds = new Rect();
- private final Rect mBgBounds = new Rect();
- private boolean mDirty = true;
- private String mTextToDraw = "";
- private float mPathRadius = 0f;
- private float mTextSweepDegrees = 0f;
- private float mBackgroundSweepDegrees = MAX_SWEEP_DEGREE;
- private int mLastUsedTextAlignment = -1;
- private float mLocalRotateAngle = 0f;
-
- private int mAnchorType = UNSET_ANCHOR_TYPE;
- private float mAnchorAngleDegrees = UNSET_ANCHOR_DEGREE;
- private float mMinSweepDegrees = MIN_SWEEP_DEGREE;
- private float mMaxSweepDegrees = MAX_SWEEP_DEGREE;
- private String mText = "";
- private float mTextSize = DEFAULT_TEXT_SIZE;
- @Nullable private Typeface mTypeface = null;
- private boolean mClockwise = DEFAULT_CLOCKWISE;
- @ColorInt private int mTextColor = DEFAULT_TEXT_COLOR;
- @Nullable private TextUtils.TruncateAt mEllipsize = null;
- private float mLetterSpacing = 0f;
- @Nullable private String mFontFeatureSettings = null;
- @Nullable private String mFontVariationSettings = null;
-
- // If true, it means we got the touch_down event and are receiving the touch events that follow.
- private boolean mHandlingTouch = false;
-
- public WearCurvedTextView(@NonNull Context context) {
- this(context, null);
- }
-
- public WearCurvedTextView(@NonNull Context context, @Nullable AttributeSet attrs) {
- this(context, attrs, android.R.attr.textViewStyle);
- }
-
- public WearCurvedTextView(
- @NonNull Context context, @Nullable AttributeSet attrs, int defStyle) {
- this(context, attrs, defStyle, 0);
- }
-
- public WearCurvedTextView(
- @NonNull Context context, @Nullable AttributeSet attrs, int defStyle, int defStyleRes) {
- super(context, attrs, defStyle, defStyleRes);
-
- mPaint.setAntiAlias(true);
-
- TextAppearanceAttributes attributes = new TextAppearanceAttributes();
- attributes.mTextColor = ColorStateList.valueOf(DEFAULT_TEXT_COLOR);
-
- final Resources.Theme theme = context.getTheme();
- TypedArray a =
- theme.obtainStyledAttributes(
- attrs, R.styleable.TextViewAppearance, defStyle, defStyleRes);
-
- TypedArray appearance = null;
- int ap = a.getResourceId(R.styleable.TextViewAppearance_android_textAppearance, -1);
- a.recycle();
-
- if (ap != -1) {
- appearance = theme.obtainStyledAttributes(ap, R.styleable.TextAppearance);
- }
- if (appearance != null) {
- readTextAppearance(appearance, attributes, true);
- appearance.recycle();
- }
-
- a =
- context.obtainStyledAttributes(
- attrs, R.styleable.WearCurvedTextView, defStyle, defStyleRes);
- // overrride the value in the appearance with explicitly specified attribute values
- readTextAppearance(a, attributes, false);
-
- // read the other supported TextView attributes
- if (a.hasValue(R.styleable.WearCurvedTextView_android_text)) {
- mText = nullToEmpty(a.getString(R.styleable.WearCurvedTextView_android_text));
- }
-
- int textEllipsize = a.getInt(R.styleable.WearCurvedTextView_android_ellipsize, 0);
- switch (textEllipsize) {
- case 1:
- mEllipsize = TextUtils.TruncateAt.START;
- break;
- case 2:
- mEllipsize = TextUtils.TruncateAt.MIDDLE;
- break;
- case 3:
- mEllipsize = TextUtils.TruncateAt.END;
- break;
- default:
- mEllipsize = null;
- }
-
- // read the custom WearCurvedTextView attributes
- mMaxSweepDegrees =
- a.getFloat(R.styleable.WearCurvedTextView_maxSweepDegrees, MAX_SWEEP_DEGREE);
- mMaxSweepDegrees = min(mMaxSweepDegrees, MAX_SWEEP_DEGREE);
- mMinSweepDegrees =
- a.getFloat(R.styleable.WearCurvedTextView_minSweepDegrees, MIN_SWEEP_DEGREE);
- if (mMinSweepDegrees > mMaxSweepDegrees) {
- throw new IllegalArgumentException(
- "MinSweepDegrees cannot be bigger than MaxSweepDegrees");
- }
- mAnchorType = a.getInt(R.styleable.WearCurvedTextView_anchorPosition, UNSET_ANCHOR_TYPE);
- mAnchorAngleDegrees =
- a.getFloat(R.styleable.WearCurvedTextView_anchorAngleDegrees, UNSET_ANCHOR_DEGREE);
- mAnchorAngleDegrees = mAnchorAngleDegrees % 360f;
- mClockwise = a.getBoolean(R.styleable.WearCurvedTextView_clockwise, DEFAULT_CLOCKWISE);
-
- a.recycle();
-
- applyTextAppearance(attributes);
-
- mPaint.setTextSize(mTextSize);
- }
-
- @Override
- public float getSweepAngleDegrees() {
- return mBackgroundSweepDegrees;
- }
-
- @Override
- public int getThicknessPx() {
- return round(mPaint.getFontMetrics().descent - mPaint.getFontMetrics().ascent);
- }
-
- /**
- * @throws IllegalArgumentException if the anchorType and/or anchorAngleDegrees attributes were
- * set for a widget in WearArcLayout
- */
- @Override
- public void checkInvalidAttributeAsChild() {
- if (mAnchorType != UNSET_ANCHOR_TYPE) {
- throw new IllegalArgumentException(
- "WearCurvedTextView shall not set anchorType value when added into"
- + "WearArcLayout");
- }
-
- if (mAnchorAngleDegrees != UNSET_ANCHOR_DEGREE) {
- throw new IllegalArgumentException(
- "WearCurvedTextView shall not set anchorAngleDegrees value when added into "
- + "WearArcLayout");
- }
- }
-
- @Override
- public boolean insideClickArea(float x, float y) {
- float radius2 =
- min(getWidth(), getHeight()) / 2f
- - (mClockwise ? getPaddingTop() : getPaddingBottom());
- float radius1 = radius2 - mPaint.getFontMetrics().descent + mPaint.getFontMetrics().ascent;
-
- float dx = x - getWidth() / 2;
- float dy = y - getHeight() / 2;
-
- float r2 = dx * dx + dy * dy;
- if (r2 < radius1 * radius1 || r2 > radius2 * radius2) {
- return false;
- }
-
- // Since we are symmetrical on the Y-axis, we can constrain the angle to the x>=0 quadrants.
- float angle = (float) Math.toDegrees(Math.atan2(Math.abs(dx), -dy));
- return angle < mBackgroundSweepDegrees / 2;
- }
-
- @Override
- protected void onSizeChanged(int w, int h, int oldw, int oldh) {
- super.onSizeChanged(w, h, oldw, oldh);
- doUpdate();
- }
-
- @Override
- protected void onLayout(boolean changed, int l, int t, int r, int b) {
- super.onLayout(changed, l, t, r, b);
-
- mPaint.getTextBounds(mText, 0, mText.length(), mBounds);
-
- // Note that ascent is negative.
- mPathRadius =
- min(getWidth(), getHeight()) / 2f
- + (mClockwise
- ? mPaint.getFontMetrics().ascent - getPaddingTop()
- : -mPaint.getFontMetrics().descent - getPaddingBottom());
- mTextSweepDegrees =
- min(getWidthSelf() / mPathRadius / (float) Math.PI * 180f, MAX_SWEEP_DEGREE);
- mBackgroundSweepDegrees = max(min(mMaxSweepDegrees, mTextSweepDegrees), mMinSweepDegrees);
- }
-
- private float getWidthSelf() {
- return (float) mBounds.width() + getPaddingLeft() + getPaddingRight();
- }
-
- private String ellipsize(int ellipsizedWidth) {
- StaticLayout.Builder layoutBuilder =
- StaticLayout.Builder.obtain(mText, 0, mText.length(), mPaint, ellipsizedWidth);
- layoutBuilder.setEllipsize(mEllipsize);
- layoutBuilder.setMaxLines(1);
- StaticLayout layout = layoutBuilder.build();
-
- // Cut text that it's too big even if no ellipsize mode is provided.
- if (mEllipsize == null) {
- return mText.substring(0, layout.getLineEnd(0));
- }
-
- int ellipsisCount = layout.getEllipsisCount(0);
- if (ellipsisCount == 0) {
- return mText;
- }
-
- int ellipsisStart = layout.getEllipsisStart(0);
- char[] textToDrawArray = mText.toCharArray();
- textToDrawArray[ellipsisStart] = '\u2026'; // ellipsis "..."
- for (int i = ellipsisStart + 1; i < ellipsisStart + ellipsisCount; i++) {
- if (i >= 0 && i < mText.length()) {
- textToDrawArray[i] = '\uFEFF'; // 0-width space
- }
- }
- return new String(textToDrawArray);
- }
-
- private void updatePathsIfNeeded(boolean withBackground) {
- // The dirty flag is not set when properties we inherit from View are modified
- if (!mDirty && ((int) getTextAlignment() == mLastUsedTextAlignment)) {
- return;
- }
-
- mDirty = false;
- mLastUsedTextAlignment = (int) getTextAlignment();
-
- if (mTextSweepDegrees <= mMaxSweepDegrees) {
- mTextToDraw = mText;
- } else {
- mTextToDraw =
- ellipsize(
- (int) (Math.toRadians(mMaxSweepDegrees) * mPathRadius)
- - getPaddingLeft()
- - getPaddingRight());
- mTextSweepDegrees = mMaxSweepDegrees;
- }
-
- float clockwiseFactor = mClockwise ? 1f : -1f;
-
- float alignmentFactor = 0.5f;
- switch (getTextAlignment()) {
- case TEXT_ALIGNMENT_TEXT_START:
- case TEXT_ALIGNMENT_VIEW_START:
- alignmentFactor = 0f;
- break;
- case TEXT_ALIGNMENT_TEXT_END:
- case TEXT_ALIGNMENT_VIEW_END:
- alignmentFactor = 1f;
- break;
- default:
- alignmentFactor = 0.5f; // TEXT_ALIGNMENT_CENTER
- }
-
- float anchorTypeFactor;
- switch (mAnchorType) {
- case WearArcLayout.ANCHOR_START:
- anchorTypeFactor = 0.5f;
- break;
- case WearArcLayout.ANCHOR_END:
- anchorTypeFactor = -0.5f;
- break;
- case WearArcLayout.ANCHOR_CENTER: // Center is the default.
- default:
- anchorTypeFactor = 0f;
- }
-
- mLocalRotateAngle =
- (mAnchorAngleDegrees == UNSET_ANCHOR_DEGREE ? 0f : mAnchorAngleDegrees)
- + clockwiseFactor * anchorTypeFactor * mBackgroundSweepDegrees;
-
- // Always draw the curved text on top center, then rotate the canvas to the right position
- float backgroundStartAngle =
- -clockwiseFactor * 0.5f * mBackgroundSweepDegrees + ANCHOR_DEGREE_OFFSET;
-
- float textStartAngle =
- backgroundStartAngle
- + clockwiseFactor
- * (float)
- (alignmentFactor
- * (mBackgroundSweepDegrees
- - mTextSweepDegrees)
- + Math.toDegrees(getPaddingLeft() / mPathRadius));
-
- float centerX = getWidth() / 2f;
- float centerY = getHeight() / 2f;
- mPath.reset();
- mPath.addArc(
- centerX - mPathRadius,
- centerY - mPathRadius,
- centerX + mPathRadius,
- centerY + mPathRadius,
- textStartAngle,
- clockwiseFactor * mTextSweepDegrees);
-
- if (withBackground) {
- mBgPath.reset();
- // NOTE: Ensure that if the code to compute these radius* change, containsPoint() is
- // also updated.
- float radius1 = mPathRadius - clockwiseFactor * mPaint.getFontMetrics().descent;
- float radius2 = mPathRadius - clockwiseFactor * mPaint.getFontMetrics().ascent;
- mBgPath.arcTo(
- centerX - radius2,
- centerY - radius2,
- centerX + radius2,
- centerY + radius2,
- backgroundStartAngle,
- clockwiseFactor * mBackgroundSweepDegrees,
- false);
- mBgPath.arcTo(
- centerX - radius1,
- centerY - radius1,
- centerX + radius1,
- centerY + radius1,
- backgroundStartAngle + clockwiseFactor * mBackgroundSweepDegrees,
- -clockwiseFactor * mBackgroundSweepDegrees,
- false);
- mBgPath.close();
-
- float angle = backgroundStartAngle;
- float x0 = (float) (centerX + radius2 * cos(Math.toRadians(angle)));
- float x1 = (float) (centerX + radius1 * cos(Math.toRadians(angle)));
- float y0 = (float) (centerX + radius2 * sin(Math.toRadians(angle)));
- float y1 = (float) (centerX + radius1 * sin(Math.toRadians(angle)));
- angle = backgroundStartAngle + clockwiseFactor * mBackgroundSweepDegrees;
- float x2 = (float) (centerX + radius2 * cos(Math.toRadians(angle)));
- float x3 = (float) (centerX + radius1 * cos(Math.toRadians(angle)));
- // Background axis-aligned bounding box calculation. Note that, we always center the
- // text on the top-center of the view.
- // top: always will be centerY - outerRadius
- // bottom: the max y of end points of the outer and inner arc contains the text
- // left: if over -90 degrees, centerX - outerRadius, otherwise the min x of start,
- // end points of the outer and inner arc contains the text
- // right: if over 90 degrees, centerX + outerRadius, otherwise the max x of start,
- // end points of the outer and inner arc contains the text
- float outerRadius = max(radius1, radius2);
- mBgBounds.top = (int) (centerY - outerRadius);
- mBgBounds.bottom = (int) max(y0, y1);
- mBgBounds.left =
- mBackgroundSweepDegrees >= 180.0f
- ? (int) (centerX - outerRadius)
- : (int) min(x0, min(x1, min(x2, x3)));
- mBgBounds.right =
- mBackgroundSweepDegrees >= 180.0f
- ? (int) (centerX + outerRadius)
- : (int) max(x0, max(x1, max(x2, x3)));
- }
- }
-
- @Override
- // We only filter events and defer to super.onTouchEvent()
- @SuppressLint("ClickableViewAccessibility")
- public boolean onTouchEvent(@NonNull MotionEvent event) {
- if (!mHandlingTouch && event.getAction() != MotionEvent.ACTION_DOWN) {
- return false;
- }
-
- float x0 = event.getX() - getWidth() / 2;
- float y0 = event.getY() - getHeight() / 2;
-
- double rotAngle = -Math.toRadians(mLocalRotateAngle);
-
- float tempX = (float) ((x0 * cos(rotAngle) - y0 * sin(rotAngle)) + getWidth() / 2);
- y0 = (float) ((x0 * sin(rotAngle) + y0 * cos(rotAngle)) + getHeight() / 2);
- x0 = tempX;
-
- // Should we start handling the touch events?
- if (!mHandlingTouch && insideClickArea(x0, y0)) {
- mHandlingTouch = true;
- }
-
- // We just started or are in the middle of handling events, forward to View to handle.
- if (mHandlingTouch) {
- if (event.getAction() == MotionEvent.ACTION_UP
- || event.getAction() == MotionEvent.ACTION_CANCEL) {
- // We should end handling events now
- mHandlingTouch = false;
- }
- event.offsetLocation(x0 - event.getX(), y0 - event.getY());
- return super.onTouchEvent(event);
- }
-
- return false;
- }
-
- @Override
- public void draw(@NonNull Canvas canvas) {
- canvas.save();
-
- boolean withBackground = getBackground() != null;
- updatePathsIfNeeded(withBackground);
- canvas.rotate(mLocalRotateAngle, getWidth() / 2f, getHeight() / 2f);
-
- if (withBackground) {
- canvas.clipPath(mBgPath);
- getBackground().setBounds(mBgBounds);
- }
- super.draw(canvas);
-
- canvas.restore();
- }
-
- @Override
- protected void onDraw(@NonNull Canvas canvas) {
- mPaint.setColor(mTextColor);
- mPaint.setStyle(Paint.Style.FILL);
- canvas.drawTextOnPath(mTextToDraw, mPath, 0f, 0f, mPaint);
- }
-
- /**
- * Sets the Typeface taking into account the given attributes.
- *
- * @param familyName family name string, e.g. "serif"
- * @param typefaceIndex an index of the typeface enum, e.g. SANS, SERIF.
- * @param style a typeface style
- * @param weight a weight value for the Typeface or -1 if not specified.
- */
- private void setTypefaceFromAttrs(
- @Nullable String familyName, int typefaceIndex, int style, int weight) {
- // typeface is ignored when font family is set
- if (mTypeface == null && familyName != null) {
- // Lookup normal Typeface from system font map.
- Typeface normalTypeface = Typeface.create(familyName, Typeface.NORMAL);
- resolveStyleAndSetTypeface(normalTypeface, style, weight);
- } else if (mTypeface != null) {
- resolveStyleAndSetTypeface(mTypeface, style, weight);
- } else { // both typeface and familyName is null.
- switch (typefaceIndex) {
- case 1:
- resolveStyleAndSetTypeface(Typeface.SANS_SERIF, style, weight);
- break;
- case 2:
- resolveStyleAndSetTypeface(Typeface.SERIF, style, weight);
- break;
- case 3:
- resolveStyleAndSetTypeface(Typeface.MONOSPACE, style, weight);
- break;
- default:
- resolveStyleAndSetTypeface(null, style, weight);
- }
- }
- }
-
- private void resolveStyleAndSetTypeface(@Nullable Typeface tf, int style, int weight) {
- if (weight >= 0 && Build.VERSION.SDK_INT >= 28) {
- int clampedWeight = min(FONT_WEIGHT_MAX, weight);
- boolean italic = (style & Typeface.ITALIC) != 0;
- mTypeface = Api28Impl.createTypeface(tf, clampedWeight, italic);
- mPaint.setTypeface(mTypeface);
- } else {
- setTypeface(tf, style);
- }
- }
-
- /** Set of attribute that can be defined in a Text Appearance. */
- private static class TextAppearanceAttributes {
- @Nullable ColorStateList mTextColor = null;
- float mTextSize = DEFAULT_TEXT_SIZE;
- @Nullable String mFontFamily = null;
- boolean mFontFamilyExplicit = false;
- int mTypefaceIndex = -1;
- int mTextStyle = DEFAULT_TEXT_STYLE;
- int mFontWeight = -1;
- float mLetterSpacing = 0f;
- @Nullable String mFontFeatureSettings = null;
- @Nullable String mFontVariationSettings = null;
-
- TextAppearanceAttributes() {}
- }
-
- /** Sets the textColor, size, style, font etc from the specified TextAppearanceAttributes */
- private void applyTextAppearance(WearCurvedTextView this, TextAppearanceAttributes attributes) {
- if (attributes.mTextColor != null) {
- mTextColor = attributes.mTextColor.getDefaultColor();
- }
-
- if (attributes.mTextSize != -1f) {
- mTextSize = attributes.mTextSize;
- }
-
- setTypefaceFromAttrs(
- attributes.mFontFamily,
- attributes.mTypefaceIndex,
- attributes.mTextStyle,
- attributes.mFontWeight);
-
- mPaint.setLetterSpacing(attributes.mLetterSpacing);
- mLetterSpacing = attributes.mLetterSpacing;
-
- // setFontFeatureSettings does accept null, but is not annotated as such. Empty string does
- // the
- // same thing though.
- mPaint.setFontFeatureSettings(nullToEmpty(attributes.mFontFeatureSettings));
- mFontFeatureSettings = attributes.mFontFeatureSettings;
- if (Build.VERSION.SDK_INT >= 26 && attributes.mFontVariationSettings != null) {
- Api26Impl.paintSetFontVariationSettings(mPaint, attributes.mFontVariationSettings);
- }
- mFontVariationSettings = attributes.mFontVariationSettings;
- }
-
- /**
- * Read the Text Appearance attributes from a given TypedArray and set its values to the given
- * set. If the TypedArray contains a value that already set in the given attributes, that will
- * be overridden.
- */
- private static void readTextAppearance(
- TypedArray appearance, TextAppearanceAttributes attributes, boolean isTextAppearance) {
- int attrIndex =
- isTextAppearance
- ? R.styleable.TextAppearance_android_textColor
- : R.styleable.WearCurvedTextView_android_textColor;
- if (appearance.hasValue(attrIndex)) {
- attributes.mTextColor = appearance.getColorStateList(attrIndex);
- }
-
- attributes.mTextSize =
- appearance.getDimensionPixelSize(
- isTextAppearance
- ? R.styleable.TextAppearance_android_textSize
- : R.styleable.WearCurvedTextView_android_textSize,
- (int) attributes.mTextSize);
-
- attributes.mTextStyle =
- appearance.getInt(
- isTextAppearance
- ? R.styleable.TextAppearance_android_textStyle
- : R.styleable.WearCurvedTextView_android_textStyle,
- attributes.mTextStyle);
-
- // make sure that the typeface attribute is read before fontFamily attribute
- attributes.mTypefaceIndex =
- appearance.getInt(
- isTextAppearance
- ? R.styleable.TextAppearance_android_typeface
- : R.styleable.WearCurvedTextView_android_typeface,
- attributes.mTypefaceIndex);
- if (attributes.mTypefaceIndex != -1 && !attributes.mFontFamilyExplicit) {
- attributes.mFontFamily = null;
- }
-
- attrIndex =
- isTextAppearance
- ? R.styleable.TextAppearance_android_fontFamily
- : R.styleable.WearCurvedTextView_android_fontFamily;
- if (appearance.hasValue(attrIndex)) {
- attributes.mFontFamily = appearance.getString(attrIndex);
- attributes.mFontFamilyExplicit = !isTextAppearance;
- }
-
- attributes.mFontWeight =
- appearance.getInt(
- isTextAppearance
- ? R.styleable.TextAppearance_android_textFontWeight
- : R.styleable.WearCurvedTextView_android_textFontWeight,
- attributes.mFontWeight);
-
- attributes.mLetterSpacing =
- appearance.getFloat(
- isTextAppearance
- ? R.styleable.TextAppearance_android_letterSpacing
- : R.styleable.WearCurvedTextView_android_letterSpacing,
- attributes.mLetterSpacing);
-
- attrIndex =
- isTextAppearance
- ? R.styleable.TextAppearance_android_fontFeatureSettings
- : R.styleable.WearCurvedTextView_android_fontFeatureSettings;
- if (appearance.hasValue(attrIndex)) {
- attributes.mFontFeatureSettings = appearance.getString(attrIndex);
- }
-
- attrIndex =
- isTextAppearance
- ? R.styleable.TextAppearance_android_fontVariationSettings
- : R.styleable.WearCurvedTextView_android_fontVariationSettings;
- if (appearance.hasValue(attrIndex)) {
- attributes.mFontVariationSettings = appearance.getString(attrIndex);
- }
- }
-
- private void doUpdate() {
- mDirty = true;
- requestLayout();
- postInvalidate();
- }
-
- private void doRedraw() {
- mDirty = true;
- postInvalidate();
- }
-
- /** returns the anchor type for positioning the curved text */
- public int getAnchorType() {
- return mAnchorType;
- }
-
- /**
- * Sets the anchor type for positioning the curved text.
- *
- * @param value the anchor type, one of {ANCHOR_START, ANCHOR_CENTER, ANCHOR_END}
- */
- public void setAnchorType(@WearArcLayout.AnchorType int value) {
- mAnchorType = value;
- doUpdate();
- }
-
- /** Returns the anchor angle used for positioning the text, in degrees. */
- public float getAnchorAngleDegrees() {
- return mAnchorAngleDegrees;
- }
-
- /** Sets the anchor angle used for positioning the text, in degrees. */
- public void setAnchorAngleDegrees(float value) {
- mAnchorAngleDegrees = value;
- doRedraw();
- }
-
- /** returns the maximum sweep angle in degrees for rendering the text */
- public float getMaxSweepDegrees() {
- return mMaxSweepDegrees;
- }
-
- /** sets the maximum sweep angle in degrees for rendering the text */
- public void setMaxSweepDegrees(float value) {
- if (value < mMinSweepDegrees) {
- throw new IllegalArgumentException(
- "MaxSweepDegrees cannot be smaller than MinSweepDegrees");
- }
- mMaxSweepDegrees = min(value, MAX_SWEEP_DEGREE);
- doUpdate();
- }
- /** returns the sweep angle in degrees for rendering the text */
- public float getMinSweepDegrees() {
- return mMinSweepDegrees;
- }
-
- /** sets the sweep angle in degrees for rendering the text */
- public void setMinSweepDegrees(float value) {
- if (value > mMaxSweepDegrees) {
- throw new IllegalArgumentException(
- "MinSweepDegrees cannot be bigger than MaxSweepDegrees");
- }
- mMinSweepDegrees = value;
- doUpdate();
- }
-
- /** returns the text to be rendered */
- @Nullable
- public String getText() {
- return mText;
- }
-
- /** sets the text to be rendered */
- public void setText(@Nullable String value) {
- mText = nullToEmpty(value);
- doUpdate();
- }
-
- /** returns the text size for rendering the text */
- public float getTextSize() {
- return mTextSize;
- }
-
- /** sets the text size for rendering the text */
- public void setTextSize(float value) {
- mTextSize = value;
- mPaint.setTextSize(mTextSize);
- doUpdate();
- }
-
- /** Gets the current Typeface that is used to style the text. */
- @Nullable
- public Typeface getTypeface() {
- return mTypeface;
- }
-
- /**
- * Sets the typeface and style in which the text should be displayed. Note that not all Typeface
- * families actually have bold and italic variants
- */
- public void setTypeface(@Nullable Typeface value) {
- mTypeface = value;
- doUpdate();
- }
-
- /**
- * Sets the typeface and style in which the text should be displayed, and turns on the fake bold
- * and italic bits in the Paint if the Typeface that you provided does not have all the bits in
- * the style that you specified.
- */
- public void setTypeface(@Nullable Typeface tf, int style) {
- if (style > 0) {
- if (tf == null) {
- tf = Typeface.defaultFromStyle(style);
- } else {
- tf = Typeface.create(tf, style);
- }
- if (!tf.equals(mPaint.getTypeface())) {
- mPaint.setTypeface(tf);
- mTypeface = tf;
- }
- // now compute what (if any) algorithmic styling is needed
- int typefaceStyle = tf != null ? tf.getStyle() : 0;
- int need = style & ~typefaceStyle;
- mPaint.setFakeBoldText((need & Typeface.BOLD) != 0);
- mPaint.setTextSkewX(((need & Typeface.ITALIC) != 0) ? ITALIC_SKEW_X : 0f);
- } else {
- mPaint.setFakeBoldText(false);
- mPaint.setTextSkewX(0f);
- if ((tf != null && !tf.equals(mPaint.getTypeface()))
- || (tf == null && mPaint.getTypeface() != null)) {
- mPaint.setTypeface(tf);
- mTypeface = tf;
- }
- }
-
- doUpdate();
- }
-
- /** returns the curved text layout direction */
- public boolean getClockwise() {
- return mClockwise;
- }
-
- /** sets the curved text layout direction */
- public void setClockwise(boolean value) {
- mClockwise = value;
- doUpdate();
- }
-
- /** returns the color for rendering the text */
- @ColorInt
- public int getTextColor() {
- return mTextColor;
- }
-
- /** sets the color for rendering the text */
- public void setTextColor(@ColorInt int value) {
- mTextColor = value;
- doRedraw();
- }
-
- /**
- * Returns where, if anywhere, words that are longer than the view is wide should be ellipsized.
- */
- @Nullable
- public TextUtils.TruncateAt getEllipsize() {
- return mEllipsize;
- }
-
- /**
- * Causes words in the text that are longer than the view's width to be ellipsized. Use null to
- * turn off ellipsizing.
- */
- public void setEllipsize(@Nullable TextUtils.TruncateAt value) {
- mEllipsize = value;
- doRedraw();
- }
-
- /**
- * Gets the text letter-space value, which determines the spacing between characters. The value
- * returned is in ems. Normally, this value is 0.0.
- *
- * @return The text letter-space value in ems.
- */
- public float getLetterSpacing() {
- return mLetterSpacing;
- }
-
- /**
- * Sets text letter-spacing in ems. Typical values for slight expansion will be around 0.05.
- * Negative values tighten text.
- *
- * @param value A text letter-space value in ems.
- */
- public void setLetterSpacing(float value) {
- mLetterSpacing = value;
- doUpdate();
- }
-
- /**
- * Returns the font feature settings. The format is the same as the CSS font-feature-settings
- * attribute: https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop
- *
- * @return the currently set font feature settings. Default is null.
- */
- @Nullable
- public String getFontFeatureSettings() {
- return mFontFeatureSettings;
- }
-
- /**
- * Sets font feature settings. The format is the same as the CSS font-feature-settings
- * attribute: https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop
- *
- * @param value font feature settings represented as CSS compatible string. This value may be
- * null.
- */
- public void setFontFeatureSettings(@Nullable String value) {
- mFontFeatureSettings = value;
- doUpdate();
- }
-
- /** Returns TrueType or OpenType font variation settings. */
- @Nullable
- public String getFontVariationSettings() {
- return mFontVariationSettings;
- }
-
- /**
- * Sets TrueType or OpenType font variation settings.
- *
- * @param value font variation settings. You can pass null or empty string as no variation
- * settings. This value may be null
- */
- public void setFontVariationSettings(@Nullable String value) {
- mFontVariationSettings = value;
- doUpdate();
- }
-
- /**
- * Gets the flags on the Paint being used to display the text.
- *
- * @return The flags on the Paint being used to display the text.
- * @see Paint#getFlags
- */
- public int getPaintFlags() {
- return mPaint.getFlags();
- }
-
- /**
- * Sets flags on the Paint being used to display the text and reflows the text if they are
- * different from the old flags.
- *
- * @see Paint#setFlags
- */
- public void setPaintFlags(int flags) {
- if (mPaint.getFlags() != flags) {
- mPaint.setFlags(flags);
- doUpdate();
- }
- }
-
- @NonNull
- private String nullToEmpty(@Nullable String str) {
- if (str == null) {
- return "";
- } else {
- return str;
- }
- }
-
- /** Nested class to avoid verification errors for methods induces in API level 26 */
- @RequiresApi(26)
- private static class Api26Impl {
- private Api26Impl() {}
-
- static void paintSetFontVariationSettings(Paint paint, String fontVariationSettings) {
- paint.setFontVariationSettings(fontVariationSettings);
- }
- }
-
- /** Nested class to avoid verification errors for methods induces in API level 28 */
- @RequiresApi(28)
- private static class Api28Impl {
- private Api28Impl() {}
-
- static Typeface createTypeface(@Nullable Typeface family, int weight, boolean italic) {
- return Typeface.create(family, weight, italic);
- }
- }
-}
diff --git a/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/timeline/internal/TilesTimelineCacheInternal.java b/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/timeline/internal/TilesTimelineCacheInternal.java
index a454af9..91dc111 100644
--- a/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/timeline/internal/TilesTimelineCacheInternal.java
+++ b/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/timeline/internal/TilesTimelineCacheInternal.java
@@ -195,8 +195,7 @@
}
// Discard if it's less than "fromTime". This prevents accidentally returning valid
- // times in
- // the past.
+ // times in the past.
if (nextEntryValidity.getStartMillis() < fromTimeMillis) {
continue;
}
diff --git a/wear/tiles/tiles-renderer/src/main/res/values/attrs.xml b/wear/tiles/tiles-renderer/src/main/res/values/attrs.xml
index 81f80a1..9ec117e 100644
--- a/wear/tiles/tiles-renderer/src/main/res/values/attrs.xml
+++ b/wear/tiles/tiles-renderer/src/main/res/values/attrs.xml
@@ -12,28 +12,6 @@
<attr name="sweepAngleDegrees" format="float" />
<attr name="thickness" format="dimension" />
- <declare-styleable name="WearCurvedTextView">
- <!-- supported TextView attributes -->
- <attr name="android:text" />
- <attr name="android:textSize" />
- <attr name="android:textColor" />
- <attr name="android:typeface" />
- <attr name="android:fontFamily" />
- <attr name="android:textStyle" />
- <attr name="android:textFontWeight" />
- <attr name="android:letterSpacing" />
- <attr name="android:fontFeatureSettings" />
- <attr name="android:fontVariationSettings" />
- <attr name="android:textAppearance" />
- <attr name="android:ellipsize" />
- <!-- custom WearCurvedTextView Attributes -->
- <attr name="anchorAngleDegrees" />
- <attr name="anchorPosition" />
- <attr name="minSweepDegrees" format="float" />
- <attr name="maxSweepDegrees" format="float" />
- <attr name="clockwise" />
- </declare-styleable>
-
<declare-styleable name="TextAppearance">
<!-- Text color. -->
<attr name="android:textColor" />
@@ -60,21 +38,6 @@
<attr name="android:textAppearance" />
</declare-styleable>
- <declare-styleable name="WearArcLayout_Layout">
- <attr name="layout_rotate" format="boolean" />
- <attr name="layout_valign" format="enum">
- <enum name="outer" value="0" />
- <enum name="center" value="1" />
- <enum name="inner" value="2" />
- </attr>
- </declare-styleable>
-
- <declare-styleable name="WearArcLayout">
- <attr name="anchorAngleDegrees"/>
- <attr name="anchorPosition"/>
- <attr name="clockwise" />
- </declare-styleable>
-
<declare-styleable name="WearCurvedLineView">
<attr name="sweepAngleDegrees" />
<attr name="thickness" />
diff --git a/wear/tiles/tiles-renderer/src/test/java/androidx/wear/tiles/manager/UpdateSchedulerTest.java b/wear/tiles/tiles-renderer/src/test/java/androidx/wear/tiles/manager/UpdateSchedulerTest.java
index 95c752c..c96a0bc 100644
--- a/wear/tiles/tiles-renderer/src/test/java/androidx/wear/tiles/manager/UpdateSchedulerTest.java
+++ b/wear/tiles/tiles-renderer/src/test/java/androidx/wear/tiles/manager/UpdateSchedulerTest.java
@@ -54,9 +54,9 @@
public void setUp() {
mFired = new ArrayList<>();
- // Set the current time to the min interval. This is because the "last update time" inits
- // to zero, so we can get some weird results if the starting time is lower than the min
- // update interval (i.e. alarms being postponed where they shouldn't be)
+ // Set the current time to the min interval. This is because the "last update time" inits to
+ // zero, so we can get some weird results if the starting time is lower than the min update
+ // interval (i.e. alarms being postponed where they shouldn't be)
mCurrentTime = MIN_INTER_UPDATE_INTERVAL_MILLIS;
AlarmManager alarmManager = getApplicationContext().getSystemService(AlarmManager.class);
diff --git a/wear/tiles/tiles-renderer/src/test/java/androidx/wear/tiles/timeline/TilesTimelineCacheTest.java b/wear/tiles/tiles-renderer/src/test/java/androidx/wear/tiles/timeline/TilesTimelineCacheTest.java
index 1f1d66e..7d73149 100644
--- a/wear/tiles/tiles-renderer/src/test/java/androidx/wear/tiles/timeline/TilesTimelineCacheTest.java
+++ b/wear/tiles/tiles-renderer/src/test/java/androidx/wear/tiles/timeline/TilesTimelineCacheTest.java
@@ -113,8 +113,7 @@
@Test
public void timelineCache_overlappingEntryWithDefault() {
// Test that with a default, and an entry "on top", the entry is shown for its validity
- // period,
- // and the default for all other times. As an example
+ // period, and the default for all other times. As an example
// +---------------------+
// | E1 |
// ...---------+---------------------+----------------...
@@ -484,8 +483,7 @@
TilesTimelineCache timelineCache = new TilesTimelineCache(timeline);
// This is really undefined behaviour at the moment, but, well, let's keep this as the
- // assumed behaviour for now.
- // Should just pick entry1 in this case.
+ // assumed behaviour for now. Should just pick entry1 in this case.
expectTimelineEntryEqual(timelineCache.findTimelineEntryForTime(0L), null);
expectTimelineEntryEqual(timelineCache.findClosestTimelineEntry(0L), entry1);
expect.that(timelineCache.findCurrentTimelineEntryExpiry(entry1, 0L))
diff --git a/wear/tiles/tiles-renderer/src/test/java/androidx/wear/tiles/timeline/TilesTimelineManagerTest.java b/wear/tiles/tiles-renderer/src/test/java/androidx/wear/tiles/timeline/TilesTimelineManagerTest.java
index 7ee2185..f6c490f 100644
--- a/wear/tiles/tiles-renderer/src/test/java/androidx/wear/tiles/timeline/TilesTimelineManagerTest.java
+++ b/wear/tiles/tiles-renderer/src/test/java/androidx/wear/tiles/timeline/TilesTimelineManagerTest.java
@@ -71,8 +71,8 @@
(AlarmManager) getApplicationContext().getSystemService(Context.ALARM_SERVICE);
// Sync the clock with the current time. This is really just to ensure that the test times
- // we're going to use are in the future, which prevents AlarmManager (which always uses
- // the system time :( ) from immediately firing all our alarms.
+ // we're going to use are in the future, which prevents AlarmManager (which always uses the
+ // system time :( ) from immediately firing all our alarms.
mCurrentTime = System.currentTimeMillis();
mTimelineManager = null;
@@ -280,8 +280,7 @@
public void timelineManager_minDelayUsesCorrectEntry() {
// This has three entries, one initial one, one that happens after MIN_DELAY/2, and one that
// happens after MIN_DELAY. This should totally skip the middle entry, and only show the
- // first
- // and last entries.
+ // first and last entries.
List<Layout> returnedLayouts = new ArrayList<>();
final long cutover1Millis =
diff --git a/wear/tiles/tiles/src/main/java/androidx/wear/tiles/ModifiersBuilders.java b/wear/tiles/tiles/src/main/java/androidx/wear/tiles/ModifiersBuilders.java
index a70797a..ba77d87 100644
--- a/wear/tiles/tiles/src/main/java/androidx/wear/tiles/ModifiersBuilders.java
+++ b/wear/tiles/tiles/src/main/java/androidx/wear/tiles/ModifiersBuilders.java
@@ -370,8 +370,8 @@
* side of the container if the device is using an RTL locale). If false, start/end will
* always map to left/right, accordingly.
*/
- @NonNull
@SuppressLint("MissingGetterMatchingBuilder")
+ @NonNull
public Builder setRtlAware(boolean rtlAware) {
mImpl.setRtlAware(TypesProto.BoolProp.newBuilder().setValue(rtlAware));
return this;
diff --git a/wear/tiles/tiles/src/main/java/androidx/wear/tiles/SysUiTileUpdateRequester.java b/wear/tiles/tiles/src/main/java/androidx/wear/tiles/SysUiTileUpdateRequester.java
index 3965e0e7..c1d54c8 100644
--- a/wear/tiles/tiles/src/main/java/androidx/wear/tiles/SysUiTileUpdateRequester.java
+++ b/wear/tiles/tiles/src/main/java/androidx/wear/tiles/SysUiTileUpdateRequester.java
@@ -140,9 +140,8 @@
}
// This is a little suboptimal, as if an update is requested in this lock,
- // we'll
- // unbind, then immediately rebind. That said, this class should be used
- // pretty rarely
+ // we'll unbind, then immediately rebind. That said, this class should be
+ // used pretty rarely
// (and it'll be rare to have two in-flight update requests at once
// regardless), so
// it's probably fine.
diff --git a/wear/tiles/tiles/src/main/java/androidx/wear/tiles/TileProviderService.java b/wear/tiles/tiles/src/main/java/androidx/wear/tiles/TileProviderService.java
index 5b682ae..6029f15 100644
--- a/wear/tiles/tiles/src/main/java/androidx/wear/tiles/TileProviderService.java
+++ b/wear/tiles/tiles/src/main/java/androidx/wear/tiles/TileProviderService.java
@@ -277,7 +277,7 @@
Log.e(
TAG,
"RemoteException while returning resources"
- + " payload",
+ + " payload",
ex);
}
},
diff --git a/wear/wear-watchface-data/proguard-rules.pro b/wear/wear-watchface-data/proguard-rules.pro
index 1de112f7f..ecdad1c 100644
--- a/wear/wear-watchface-data/proguard-rules.pro
+++ b/wear/wear-watchface-data/proguard-rules.pro
@@ -14,4 +14,7 @@
# Prevent Parcelizer objects from being removed or renamed.
-keep public class androidx.wear.watchface.**Parcelizer { *; }
--keep public class androidx.wear.watchface.style.data.ComplicationOverlayWireFormat { *; }
\ No newline at end of file
+-keep public class androidx.wear.watchface.style.data.ComplicationOverlayWireFormat { *; }
+-keep public class android.support.wearable.complications.ComplicationData { *; }
+-keep public class android.support.wearable.complications.ComplicationProviderInfo { *; }
+-keep public class android.support.wearable.complications.ComplicationText { *; }
diff --git a/wear/wear-watchface/samples/src/main/java/androidx/wear/watchface/samples/ExampleCanvasDigitalWatchFaceService.kt b/wear/wear-watchface/samples/src/main/java/androidx/wear/watchface/samples/ExampleCanvasDigitalWatchFaceService.kt
index 2be3a38..0395226 100644
--- a/wear/wear-watchface/samples/src/main/java/androidx/wear/watchface/samples/ExampleCanvasDigitalWatchFaceService.kt
+++ b/wear/wear-watchface/samples/src/main/java/androidx/wear/watchface/samples/ExampleCanvasDigitalWatchFaceService.kt
@@ -469,36 +469,36 @@
// Lazy because the context isn't initialized til later.
private val watchFaceStyle by lazy { WatchFaceColorStyle.create(this, RED_STYLE) }
- private val colorStyleSetting = UserStyleSetting.ListUserStyleSetting(
- UserStyleSetting.Id(COLOR_STYLE_SETTING),
- getString(R.string.colors_style_setting),
- getString(R.string.colors_style_setting_description),
- icon = null,
- options = listOf(
- UserStyleSetting.ListUserStyleSetting.ListOption(
- Option.Id(RED_STYLE),
- getString(R.string.colors_style_red),
- Icon.createWithResource(this, R.drawable.red_style)
+ private val colorStyleSetting by lazy {
+ UserStyleSetting.ListUserStyleSetting(
+ UserStyleSetting.Id(COLOR_STYLE_SETTING),
+ getString(R.string.colors_style_setting),
+ getString(R.string.colors_style_setting_description),
+ icon = null,
+ options = listOf(
+ UserStyleSetting.ListUserStyleSetting.ListOption(
+ Option.Id(RED_STYLE),
+ getString(R.string.colors_style_red),
+ Icon.createWithResource(this, R.drawable.red_style)
+ ),
+ UserStyleSetting.ListUserStyleSetting.ListOption(
+ Option.Id(GREEN_STYLE),
+ getString(R.string.colors_style_green),
+ Icon.createWithResource(this, R.drawable.green_style)
+ ),
+ UserStyleSetting.ListUserStyleSetting.ListOption(
+ Option.Id(BLUE_STYLE),
+ getString(R.string.colors_style_blue),
+ Icon.createWithResource(this, R.drawable.blue_style)
+ )
),
- UserStyleSetting.ListUserStyleSetting.ListOption(
- Option.Id(GREEN_STYLE),
- getString(R.string.colors_style_green),
- Icon.createWithResource(this, R.drawable.green_style)
- ),
- UserStyleSetting.ListUserStyleSetting.ListOption(
- Option.Id(BLUE_STYLE),
- getString(R.string.colors_style_blue),
- Icon.createWithResource(this, R.drawable.blue_style)
+ listOf(
+ WatchFaceLayer.BASE,
+ WatchFaceLayer.COMPLICATIONS,
+ WatchFaceLayer.COMPLICATIONS_OVERLAY
)
- ),
- listOf(
- WatchFaceLayer.BASE,
- WatchFaceLayer.COMPLICATIONS,
- WatchFaceLayer.COMPLICATIONS_OVERLAY
)
- )
-
- private val userStyleSchema = UserStyleSchema(listOf(colorStyleSetting))
+ }
private val canvasComplicationFactory =
CanvasComplicationFactory { watchState, listener ->
@@ -608,7 +608,7 @@
DefaultComplicationProviderPolicy()
).build()
- override fun createUserStyleSchema() = userStyleSchema
+ override fun createUserStyleSchema() = UserStyleSchema(listOf(colorStyleSetting))
override fun createComplicationsManager(
currentUserStyleRepository: CurrentUserStyleRepository