Add avatar button component

This needs to be separate component (altought in Compose is not),
because the spec is very different. It is similar to graph card, meaning
that the elements inside of this button are spaced out proportionally to
the buttons width. Avatar content has certain size in % and in order for
button edge shape to "hug" it, side padding should also be %.

Percentages here are implemented as weights, as width depends on the
layout margins and placement of this button.

Fixes: 388851915
Test: Added
Relnote: "Added avatar buttong ProtoLayout Material3 component."
Change-Id: Idb5aea121b876a50b8e67970d279ff60eb863c3a
diff --git a/wear/protolayout/protolayout-material3/api/current.txt b/wear/protolayout/protolayout-material3/api/current.txt
index 5b94c7b..fb54399 100644
--- a/wear/protolayout/protolayout-material3/api/current.txt
+++ b/wear/protolayout/protolayout-material3/api/current.txt
@@ -11,6 +11,15 @@
     method public androidx.wear.protolayout.material3.AppCardStyle smallAppCardStyle();
   }
 
+  public final class AvatarButtonStyle {
+    field public static final androidx.wear.protolayout.material3.AvatarButtonStyle.Companion Companion;
+  }
+
+  public static final class AvatarButtonStyle.Companion {
+    method public androidx.wear.protolayout.material3.AvatarButtonStyle defaultAvatarButtonStyle();
+    method public androidx.wear.protolayout.material3.AvatarButtonStyle largeAvatarButtonStyle();
+  }
+
   public final class ButtonColors {
     ctor public ButtonColors();
     ctor public ButtonColors(optional androidx.wear.protolayout.types.LayoutColor containerColor, optional androidx.wear.protolayout.types.LayoutColor iconColor, optional androidx.wear.protolayout.types.LayoutColor labelColor, optional androidx.wear.protolayout.types.LayoutColor secondaryLabelColor);
@@ -48,6 +57,7 @@
   }
 
   public final class ButtonKt {
+    method public static androidx.wear.protolayout.LayoutElementBuilders.LayoutElement avatarButton(androidx.wear.protolayout.material3.MaterialScope, androidx.wear.protolayout.ModifiersBuilders.Clickable onClick, kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement> labelContent, kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement> avatarContent, optional androidx.wear.protolayout.modifiers.LayoutModifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? secondaryLabelContent, optional androidx.wear.protolayout.DimensionBuilders.ContainerDimension height, optional androidx.wear.protolayout.ModifiersBuilders.Corner shape, optional androidx.wear.protolayout.material3.ButtonColors colors, optional androidx.wear.protolayout.material3.AvatarButtonStyle style, optional int horizontalAlignment, optional androidx.wear.protolayout.ModifiersBuilders.Padding contentPadding);
     method public static androidx.wear.protolayout.LayoutElementBuilders.LayoutElement button(androidx.wear.protolayout.material3.MaterialScope, androidx.wear.protolayout.ModifiersBuilders.Clickable onClick, kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement> labelContent, optional androidx.wear.protolayout.modifiers.LayoutModifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? secondaryLabelContent, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? iconContent, optional androidx.wear.protolayout.DimensionBuilders.ContainerDimension width, optional androidx.wear.protolayout.DimensionBuilders.ContainerDimension height, optional androidx.wear.protolayout.ModifiersBuilders.Corner shape, optional androidx.wear.protolayout.material3.ButtonColors colors, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? backgroundContent, optional androidx.wear.protolayout.material3.ButtonStyle style, optional int horizontalAlignment, optional androidx.wear.protolayout.ModifiersBuilders.Padding contentPadding);
     method public static androidx.wear.protolayout.LayoutElementBuilders.LayoutElement compactButton(androidx.wear.protolayout.material3.MaterialScope, androidx.wear.protolayout.ModifiersBuilders.Clickable onClick, optional androidx.wear.protolayout.modifiers.LayoutModifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? labelContent, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? iconContent, optional androidx.wear.protolayout.DimensionBuilders.ContainerDimension width, optional androidx.wear.protolayout.ModifiersBuilders.Corner shape, optional androidx.wear.protolayout.material3.ButtonColors colors, optional int horizontalAlignment, optional androidx.wear.protolayout.ModifiersBuilders.Padding contentPadding);
     method public static androidx.wear.protolayout.LayoutElementBuilders.LayoutElement iconButton(androidx.wear.protolayout.material3.MaterialScope, androidx.wear.protolayout.ModifiersBuilders.Clickable onClick, kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement> iconContent, optional androidx.wear.protolayout.modifiers.LayoutModifier modifier, optional androidx.wear.protolayout.DimensionBuilders.ContainerDimension width, optional androidx.wear.protolayout.DimensionBuilders.ContainerDimension height, optional androidx.wear.protolayout.ModifiersBuilders.Corner shape, optional androidx.wear.protolayout.material3.ButtonColors colors, optional androidx.wear.protolayout.material3.IconButtonStyle style, optional androidx.wear.protolayout.ModifiersBuilders.Padding contentPadding);
diff --git a/wear/protolayout/protolayout-material3/api/restricted_current.txt b/wear/protolayout/protolayout-material3/api/restricted_current.txt
index 5b94c7b..fb54399 100644
--- a/wear/protolayout/protolayout-material3/api/restricted_current.txt
+++ b/wear/protolayout/protolayout-material3/api/restricted_current.txt
@@ -11,6 +11,15 @@
     method public androidx.wear.protolayout.material3.AppCardStyle smallAppCardStyle();
   }
 
+  public final class AvatarButtonStyle {
+    field public static final androidx.wear.protolayout.material3.AvatarButtonStyle.Companion Companion;
+  }
+
+  public static final class AvatarButtonStyle.Companion {
+    method public androidx.wear.protolayout.material3.AvatarButtonStyle defaultAvatarButtonStyle();
+    method public androidx.wear.protolayout.material3.AvatarButtonStyle largeAvatarButtonStyle();
+  }
+
   public final class ButtonColors {
     ctor public ButtonColors();
     ctor public ButtonColors(optional androidx.wear.protolayout.types.LayoutColor containerColor, optional androidx.wear.protolayout.types.LayoutColor iconColor, optional androidx.wear.protolayout.types.LayoutColor labelColor, optional androidx.wear.protolayout.types.LayoutColor secondaryLabelColor);
@@ -48,6 +57,7 @@
   }
 
   public final class ButtonKt {
+    method public static androidx.wear.protolayout.LayoutElementBuilders.LayoutElement avatarButton(androidx.wear.protolayout.material3.MaterialScope, androidx.wear.protolayout.ModifiersBuilders.Clickable onClick, kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement> labelContent, kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement> avatarContent, optional androidx.wear.protolayout.modifiers.LayoutModifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? secondaryLabelContent, optional androidx.wear.protolayout.DimensionBuilders.ContainerDimension height, optional androidx.wear.protolayout.ModifiersBuilders.Corner shape, optional androidx.wear.protolayout.material3.ButtonColors colors, optional androidx.wear.protolayout.material3.AvatarButtonStyle style, optional int horizontalAlignment, optional androidx.wear.protolayout.ModifiersBuilders.Padding contentPadding);
     method public static androidx.wear.protolayout.LayoutElementBuilders.LayoutElement button(androidx.wear.protolayout.material3.MaterialScope, androidx.wear.protolayout.ModifiersBuilders.Clickable onClick, kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement> labelContent, optional androidx.wear.protolayout.modifiers.LayoutModifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? secondaryLabelContent, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? iconContent, optional androidx.wear.protolayout.DimensionBuilders.ContainerDimension width, optional androidx.wear.protolayout.DimensionBuilders.ContainerDimension height, optional androidx.wear.protolayout.ModifiersBuilders.Corner shape, optional androidx.wear.protolayout.material3.ButtonColors colors, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? backgroundContent, optional androidx.wear.protolayout.material3.ButtonStyle style, optional int horizontalAlignment, optional androidx.wear.protolayout.ModifiersBuilders.Padding contentPadding);
     method public static androidx.wear.protolayout.LayoutElementBuilders.LayoutElement compactButton(androidx.wear.protolayout.material3.MaterialScope, androidx.wear.protolayout.ModifiersBuilders.Clickable onClick, optional androidx.wear.protolayout.modifiers.LayoutModifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? labelContent, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? iconContent, optional androidx.wear.protolayout.DimensionBuilders.ContainerDimension width, optional androidx.wear.protolayout.ModifiersBuilders.Corner shape, optional androidx.wear.protolayout.material3.ButtonColors colors, optional int horizontalAlignment, optional androidx.wear.protolayout.ModifiersBuilders.Padding contentPadding);
     method public static androidx.wear.protolayout.LayoutElementBuilders.LayoutElement iconButton(androidx.wear.protolayout.material3.MaterialScope, androidx.wear.protolayout.ModifiersBuilders.Clickable onClick, kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement> iconContent, optional androidx.wear.protolayout.modifiers.LayoutModifier modifier, optional androidx.wear.protolayout.DimensionBuilders.ContainerDimension width, optional androidx.wear.protolayout.DimensionBuilders.ContainerDimension height, optional androidx.wear.protolayout.ModifiersBuilders.Corner shape, optional androidx.wear.protolayout.material3.ButtonColors colors, optional androidx.wear.protolayout.material3.IconButtonStyle style, optional androidx.wear.protolayout.ModifiersBuilders.Padding contentPadding);
diff --git a/wear/protolayout/protolayout-material3/samples/src/main/java/androidx/wear/protolayout/material3/samples/Material3ComponentsSample.kt b/wear/protolayout/protolayout-material3/samples/src/main/java/androidx/wear/protolayout/material3/samples/Material3ComponentsSample.kt
index 619cff2..64bd836 100644
--- a/wear/protolayout/protolayout-material3/samples/src/main/java/androidx/wear/protolayout/material3/samples/Material3ComponentsSample.kt
+++ b/wear/protolayout/protolayout-material3/samples/src/main/java/androidx/wear/protolayout/material3/samples/Material3ComponentsSample.kt
@@ -38,10 +38,13 @@
 import androidx.wear.protolayout.material3.DataCardStyle.Companion.extraLargeDataCardStyle
 import androidx.wear.protolayout.material3.DataCardStyle.Companion.largeCompactDataCardStyle
 import androidx.wear.protolayout.material3.GraphicDataCardStyle.Companion.largeGraphicDataCardStyle
+import androidx.wear.protolayout.material3.MaterialScope
 import androidx.wear.protolayout.material3.PrimaryLayoutMargins.Companion.MAX_PRIMARY_LAYOUT_MARGIN
 import androidx.wear.protolayout.material3.TitleCardStyle.Companion.largeTitleCardStyle
 import androidx.wear.protolayout.material3.Typography
 import androidx.wear.protolayout.material3.appCard
+import androidx.wear.protolayout.material3.avatarButton
+import androidx.wear.protolayout.material3.avatarImage
 import androidx.wear.protolayout.material3.backgroundImage
 import androidx.wear.protolayout.material3.button
 import androidx.wear.protolayout.material3.buttonGroup
@@ -467,6 +470,16 @@
     }
 
 @Sampled
+fun MaterialScope.avatarButtonSample() =
+    avatarButton(
+        onClick = clickable(),
+        modifier = LayoutModifier.contentDescription("Pill button"),
+        avatarContent = { avatarImage("id") },
+        labelContent = { text("Primary label".layoutString) },
+        secondaryLabelContent = { text("Secondary label".layoutString) },
+    )
+
+@Sampled
 fun compactButtonsSample(
     context: Context,
     deviceConfiguration: DeviceParameters,
diff --git a/wear/protolayout/protolayout-material3/src/androidTest/java/androidx/wear/protolayout/material3/TestCasesGenerator.kt b/wear/protolayout/protolayout-material3/src/androidTest/java/androidx/wear/protolayout/material3/TestCasesGenerator.kt
index 7e34220..b631b44 100644
--- a/wear/protolayout/protolayout-material3/src/androidTest/java/androidx/wear/protolayout/material3/TestCasesGenerator.kt
+++ b/wear/protolayout/protolayout-material3/src/androidTest/java/androidx/wear/protolayout/material3/TestCasesGenerator.kt
@@ -24,8 +24,10 @@
 import androidx.wear.protolayout.DimensionBuilders.expand
 import androidx.wear.protolayout.LayoutElementBuilders
 import androidx.wear.protolayout.LayoutElementBuilders.Column
+import androidx.wear.protolayout.LayoutElementBuilders.HORIZONTAL_ALIGN_END
 import androidx.wear.protolayout.expression.VersionBuilders.VersionInfo
 import androidx.wear.protolayout.material3.AppCardStyle.Companion.largeAppCardStyle
+import androidx.wear.protolayout.material3.AvatarButtonStyle.Companion.largeAvatarButtonStyle
 import androidx.wear.protolayout.material3.ButtonDefaults.filledButtonColors
 import androidx.wear.protolayout.material3.ButtonDefaults.filledTonalButtonColors
 import androidx.wear.protolayout.material3.ButtonDefaults.filledVariantButtonColors
@@ -388,6 +390,47 @@
                     },
                 )
             }
+        testCases["primarylayout_nobottomslotnotitle_avatarbuttons_golden$NORMAL_SCALE_SUFFIX"] =
+            materialScope(
+                ApplicationProvider.getApplicationContext(),
+                deviceParameters,
+                allowDynamicTheme = false
+            ) {
+                primaryLayout(
+                    mainSlot = {
+                        Column.Builder()
+                            .setWidth(expand())
+                            .setHeight(expand())
+                            .addContent(
+                                avatarButton(
+                                    onClick = clickable,
+                                    labelContent = { text("Primary label".layoutString) },
+                                    secondaryLabelContent = {
+                                        text("Secondary label".layoutString)
+                                    },
+                                    avatarContent = { avatarImage(IMAGE_ID) },
+                                )
+                            )
+                            .addContent(DEFAULT_SPACER_BETWEEN_BUTTON_GROUPS)
+                            .addContent(
+                                avatarButton(
+                                    onClick = clickable,
+                                    labelContent = {
+                                        text("Primary label overflowing".layoutString)
+                                    },
+                                    secondaryLabelContent = {
+                                        text("Secondary label overflowing".layoutString)
+                                    },
+                                    avatarContent = { avatarImage(IMAGE_ID) },
+                                    height = expand(),
+                                    style = largeAvatarButtonStyle(),
+                                    horizontalAlignment = HORIZONTAL_ALIGN_END
+                                )
+                            )
+                            .build()
+                    },
+                )
+            }
         testCases["primarylayout_oneslotbuttons_golden$NORMAL_SCALE_SUFFIX"] =
             materialScope(
                 ApplicationProvider.getApplicationContext(),
diff --git a/wear/protolayout/protolayout-material3/src/main/java/androidx/wear/protolayout/material3/Button.kt b/wear/protolayout/protolayout-material3/src/main/java/androidx/wear/protolayout/material3/Button.kt
index 939df6f..8940585 100644
--- a/wear/protolayout/protolayout-material3/src/main/java/androidx/wear/protolayout/material3/Button.kt
+++ b/wear/protolayout/protolayout-material3/src/main/java/androidx/wear/protolayout/material3/Button.kt
@@ -16,10 +16,12 @@
 
 package androidx.wear.protolayout.material3
 
+import androidx.wear.protolayout.DimensionBuilders
 import androidx.wear.protolayout.DimensionBuilders.ContainerDimension
 import androidx.wear.protolayout.DimensionBuilders.dp
 import androidx.wear.protolayout.DimensionBuilders.expand
 import androidx.wear.protolayout.DimensionBuilders.weight
+import androidx.wear.protolayout.LayoutElementBuilders
 import androidx.wear.protolayout.LayoutElementBuilders.Box
 import androidx.wear.protolayout.LayoutElementBuilders.HORIZONTAL_ALIGN_CENTER
 import androidx.wear.protolayout.LayoutElementBuilders.HORIZONTAL_ALIGN_END
@@ -29,9 +31,11 @@
 import androidx.wear.protolayout.ModifiersBuilders.Clickable
 import androidx.wear.protolayout.ModifiersBuilders.Corner
 import androidx.wear.protolayout.ModifiersBuilders.Padding
+import androidx.wear.protolayout.material3.AvatarButtonStyle.Companion.defaultAvatarButtonStyle
 import androidx.wear.protolayout.material3.ButtonDefaults.DEFAULT_CONTENT_PADDING
 import androidx.wear.protolayout.material3.ButtonDefaults.IMAGE_BUTTON_DEFAULT_SIZE_DP
 import androidx.wear.protolayout.material3.ButtonDefaults.METADATA_TAG_BUTTON
+import androidx.wear.protolayout.material3.ButtonDefaults.buildContentForAvatarButton
 import androidx.wear.protolayout.material3.ButtonDefaults.buildContentForCompactButton
 import androidx.wear.protolayout.material3.ButtonDefaults.buildContentForPillShapeButton
 import androidx.wear.protolayout.material3.ButtonDefaults.filledButtonColors
@@ -291,6 +295,119 @@
     )
 
 /**
+ * Opinionated ProtoLayout Material3 pill shape avatar button that offers up to three slots to take
+ * content representing vertically stacked label and secondary label, and an image (avatar) next to
+ * it.
+ *
+ * Difference from the [button] is that this one takes an image instead of an icon and spaces the
+ * content proportionally, so that edge of the button nicely hugs the avatar image.
+ *
+ * @param onClick Associated [Clickable] for click events. When the button is clicked it will fire
+ *   the associated action.
+ * @param labelContent The text slot for content displayed in this button. It is recommended to use
+ *   default styling that is automatically provided by only calling [text].
+ * @param modifier Modifiers to set to this element. It's highly recommended to set a content
+ *   description using [contentDescription].
+ * @param secondaryLabelContent The text slot for content displayed in this button. It is
+ *   recommended to use default styling that is automatically provided by only calling [text].
+ * @param avatarContent The avatar slot for content displayed in this button. It is recommended to
+ *   use default styling that is automatically provided by only calling [avatarImage] with the
+ *   resource ID. Width and height of this element should be set to [expand].
+ * @param shape Defines the button's shape, in other words the corner radius for this button. If
+ *   changing these to radius smaller than [Shapes.medium], it is important to adjusts the margins
+ *   of [primaryLayout] used to accommodate for more space, for example by using
+ *   [maxPrimaryLayoutMargins]. Or, if the [shape] is set to [Shapes.full], using
+ *   [minPrimaryLayoutMargins] can be considered.
+ * @param height The height of this button. It's highly recommended to set this to [expand] or
+ *   [weight]
+ * @param colors The colors used for this button. If not set, [ButtonDefaults.filledButtonColors]
+ *   will be used as high emphasis button. Other recommended colors are
+ *   [ButtonDefaults.filledTonalButtonColors] and [ButtonDefaults.filledVariantButtonColors]. If
+ *   using custom colors, it is important to choose a color pair from same role to ensure
+ *   accessibility with sufficient color contrast.
+ * @param style The style which provides the attribute values required for constructing this pill
+ *   shape button and its inner content. It also provides default style for the inner content, that
+ *   can be overridden by each content slot.
+ * @param horizontalAlignment The horizontal placement of the [avatarContent]. This should be
+ *   [HORIZONTAL_ALIGN_START] to place the [avatarContent] on the start side of the button, or
+ *   [HORIZONTAL_ALIGN_END] to place in on the end side. [HORIZONTAL_ALIGN_CENTER] will be ignored
+ *   and replaced with [HORIZONTAL_ALIGN_START].
+ * @param contentPadding The inner padding used to prevent inner content from being too close to the
+ *   button's edge. It's highly recommended to keep the default. Only vertical values would be used,
+ *   as horizontally elements are spaced out proportionally to the buttons width.
+ * @sample androidx.wear.protolayout.material3.samples.avatarButtonSample
+ */
+// TODO: b/346958146 - Link Button visuals in DAC
+public fun MaterialScope.avatarButton(
+    onClick: Clickable,
+    labelContent: (MaterialScope.() -> LayoutElement),
+    avatarContent: (MaterialScope.() -> LayoutElement),
+    modifier: LayoutModifier = LayoutModifier,
+    secondaryLabelContent: (MaterialScope.() -> LayoutElement)? = null,
+    height: ContainerDimension = wrapWithMinTapTargetDimension(),
+    shape: Corner = shapes.full,
+    colors: ButtonColors = filledButtonColors(),
+    style: AvatarButtonStyle = defaultAvatarButtonStyle(),
+    @HorizontalAlignment horizontalAlignment: Int = HORIZONTAL_ALIGN_START,
+    contentPadding: Padding = style.innerVerticalPadding
+): LayoutElement =
+    buttonContainer(
+        onClick = onClick,
+        modifier = modifier.background(color = colors.containerColor, corner = shape),
+        width = expand(),
+        height = height,
+        contentPadding = contentPadding,
+        content = {
+            buildContentForAvatarButton(
+                label =
+                    withStyle(
+                            defaultTextElementStyle =
+                                TextElementStyle(
+                                    typography = style.labelTypography,
+                                    color = colors.labelColor,
+                                    multilineAlignment =
+                                        HORIZONTAL_ALIGN_START.horizontalAlignToTextAlign()
+                                )
+                        )
+                        .labelContent(),
+                secondaryLabel =
+                    secondaryLabelContent?.let {
+                        withStyle(
+                                defaultTextElementStyle =
+                                    TextElementStyle(
+                                        typography = style.secondaryLabelTypography,
+                                        color = colors.secondaryLabelColor,
+                                        multilineAlignment =
+                                            HORIZONTAL_ALIGN_START.horizontalAlignToTextAlign()
+                                    )
+                            )
+                            .secondaryLabelContent()
+                    },
+                avatar =
+                    withStyle(
+                            defaultAvatarImageStyle =
+                                AvatarImageStyle(
+                                    width = expand(),
+                                    // We want height to be same as the calculated width
+                                    height =
+                                        DimensionBuilders.ProportionalDimensionProp.Builder()
+                                            .setAspectRatioWidth(1)
+                                            .setAspectRatioHeight(1)
+                                            .build(),
+                                    contentScaleMode = LayoutElementBuilders.CONTENT_SCALE_MODE_FIT
+                                )
+                        )
+                        .avatarContent(),
+                horizontalAlignment =
+                    if (horizontalAlignment == HORIZONTAL_ALIGN_CENTER) HORIZONTAL_ALIGN_START
+                    else horizontalAlignment,
+                style = style,
+                height = height
+            )
+        }
+    )
+
+/**
  * ProtoLayout Material3 clickable image button that doesn't offer additional slots, only image (for
  * example [backgroundImage] as a background.
  *
diff --git a/wear/protolayout/protolayout-material3/src/main/java/androidx/wear/protolayout/material3/ButtonDefaults.kt b/wear/protolayout/protolayout-material3/src/main/java/androidx/wear/protolayout/material3/ButtonDefaults.kt
index 491d263..dbb8d68 100644
--- a/wear/protolayout/protolayout-material3/src/main/java/androidx/wear/protolayout/material3/ButtonDefaults.kt
+++ b/wear/protolayout/protolayout-material3/src/main/java/androidx/wear/protolayout/material3/ButtonDefaults.kt
@@ -19,10 +19,14 @@
 import android.graphics.Color
 import androidx.annotation.Dimension
 import androidx.annotation.Dimension.Companion.DP
+import androidx.annotation.FloatRange
 import androidx.wear.protolayout.DimensionBuilders
+import androidx.wear.protolayout.DimensionBuilders.ContainerDimension
 import androidx.wear.protolayout.DimensionBuilders.expand
+import androidx.wear.protolayout.DimensionBuilders.weight
 import androidx.wear.protolayout.LayoutElementBuilders.Box
 import androidx.wear.protolayout.LayoutElementBuilders.Column
+import androidx.wear.protolayout.LayoutElementBuilders.HORIZONTAL_ALIGN_END
 import androidx.wear.protolayout.LayoutElementBuilders.HORIZONTAL_ALIGN_START
 import androidx.wear.protolayout.LayoutElementBuilders.HorizontalAlignment
 import androidx.wear.protolayout.LayoutElementBuilders.LayoutElement
@@ -81,6 +85,95 @@
     }
 
     /**
+     * Returns [LayoutElement] describing the inner content for the avatar shape button.
+     *
+     * This is a [Row] containing the following:
+     * * avatar
+     * * spacing if icon is present
+     * * labels that are in [Column]
+     *
+     * Additionally, horizontal padding and spacing for avatar and labels is weight based.
+     *
+     * [horizontalAlignment] defines side that avatar is.
+     */
+    internal fun buildContentForAvatarButton(
+        avatar: LayoutElement,
+        label: LayoutElement,
+        secondaryLabel: LayoutElement?,
+        @HorizontalAlignment horizontalAlignment: Int,
+        style: AvatarButtonStyle,
+        height: ContainerDimension,
+    ): LayoutElement {
+        val verticalElementBuilder: Column.Builder =
+            Column.Builder().setWidth(expand()).setHorizontalAlignment(HORIZONTAL_ALIGN_START)
+        val horizontalElementBuilder: Row.Builder =
+            Row.Builder().setWidth(expand()).setHeight(height)
+
+        ContainerWithSpacersBuilder<LayoutElement>(
+                { it: LayoutElement? -> verticalElementBuilder.addContent(it!!) },
+                label
+            )
+            .addElement(secondaryLabel, horizontalSpacer(style.labelsSpaceDp))
+
+        // Side padding - start
+        horizontalElementBuilder.addContent(
+            verticalSpacer(
+                weight(
+                    if (horizontalAlignment == HORIZONTAL_ALIGN_START) style.avatarPaddingWeight
+                    else style.labelsPaddingWeight
+                )
+            )
+        )
+
+        // Wrap avatar in expandable box with weights
+        val wrapAvatar =
+            Box.Builder()
+                .setWidth(weight(style.avatarSizeWeight))
+                .setHeight(height)
+                .addContent(avatar)
+                .build()
+
+        if (horizontalAlignment == HORIZONTAL_ALIGN_START) {
+            horizontalElementBuilder.addContent(wrapAvatar)
+            horizontalElementBuilder.addContent(verticalSpacer(style.avatarToLabelsSpaceDp))
+        }
+
+        // Labels
+        horizontalElementBuilder.addContent(
+            Box.Builder()
+                .setHorizontalAlignment(HORIZONTAL_ALIGN_START)
+                // Remaining % from 100% is for labels
+                .setWidth(
+                    weight(
+                        100 -
+                            style.avatarPaddingWeight -
+                            style.labelsPaddingWeight -
+                            style.avatarSizeWeight
+                    )
+                )
+                .addContent(verticalElementBuilder.build())
+                .build()
+        )
+
+        if (horizontalAlignment == HORIZONTAL_ALIGN_END) {
+            horizontalElementBuilder.addContent(verticalSpacer(style.avatarToLabelsSpaceDp))
+            horizontalElementBuilder.addContent(wrapAvatar)
+        }
+
+        // Side padding - end
+        horizontalElementBuilder.addContent(
+            verticalSpacer(
+                weight(
+                    if (horizontalAlignment == HORIZONTAL_ALIGN_START) style.labelsPaddingWeight
+                    else style.avatarPaddingWeight
+                )
+            )
+        )
+
+        return horizontalElementBuilder.build()
+    }
+
+    /**
      * Returns [LayoutElement] describing the inner content for the compact button.
      *
      * This is a [Row] wrapped inside of the Box for alignment, containing the following:
@@ -283,3 +376,50 @@
             )
     }
 }
+
+/** Provides style values for the avatar button component. */
+public class AvatarButtonStyle
+internal constructor(
+    @TypographyToken internal val labelTypography: Int,
+    @TypographyToken internal val secondaryLabelTypography: Int,
+    @FloatRange(from = 0.0, to = 100.0) internal val avatarSizeWeight: Float,
+    @FloatRange(from = 0.0, to = 100.0) internal val avatarPaddingWeight: Float,
+    @FloatRange(from = 0.0, to = 100.0) internal val labelsPaddingWeight: Float,
+    internal val innerVerticalPadding: Padding,
+    @Dimension(DP) internal val avatarToLabelsSpaceDp: Int,
+    @Dimension(DP) internal val labelsSpaceDp: Int,
+) {
+    public companion object {
+        /**
+         * Default style variation for the [avatarButton] where all opinionated inner content is
+         * displayed in a medium size.
+         */
+        public fun defaultAvatarButtonStyle(): AvatarButtonStyle =
+            AvatarButtonStyle(
+                labelTypography = Typography.LABEL_MEDIUM,
+                secondaryLabelTypography = Typography.BODY_SMALL,
+                avatarSizeWeight = 19.6f,
+                avatarPaddingWeight = 4.16f,
+                labelsPaddingWeight = 7.1f,
+                innerVerticalPadding = padding(vertical = 8f, horizontal = Float.NaN),
+                avatarToLabelsSpaceDp = 6,
+                labelsSpaceDp = 0
+            )
+
+        /**
+         * Default style variation for the [avatarButton] where all opinionated inner content is
+         * displayed in a large size.
+         */
+        public fun largeAvatarButtonStyle(): AvatarButtonStyle =
+            AvatarButtonStyle(
+                labelTypography = Typography.TITLE_MEDIUM,
+                secondaryLabelTypography = Typography.LABEL_SMALL,
+                avatarSizeWeight = 23.15f,
+                avatarPaddingWeight = 2.1f,
+                labelsPaddingWeight = 6f,
+                innerVerticalPadding = padding(vertical = 6f, horizontal = Float.NaN),
+                avatarToLabelsSpaceDp = 8,
+                labelsSpaceDp = 0
+            )
+    }
+}
diff --git a/wear/protolayout/protolayout-material3/src/test/java/androidx/wear/protolayout/material3/ButtonTest.kt b/wear/protolayout/protolayout-material3/src/test/java/androidx/wear/protolayout/material3/ButtonTest.kt
index 7f18399..174d1af 100644
--- a/wear/protolayout/protolayout-material3/src/test/java/androidx/wear/protolayout/material3/ButtonTest.kt
+++ b/wear/protolayout/protolayout-material3/src/test/java/androidx/wear/protolayout/material3/ButtonTest.kt
@@ -83,6 +83,15 @@
     }
 
     @Test
+    fun avatarButton_size_default() {
+        LayoutElementAssertionsProvider(DEFAULT_AVATAR_BUTTON)
+            .onRoot()
+            .assert(hasWidth(expand()))
+            .assert(hasHeight(wrapWithMinTapTargetDimension()))
+            .assert(hasTag(ButtonDefaults.METADATA_TAG_BUTTON))
+    }
+
+    @Test
     fun compactButton_size_default() {
         LayoutElementAssertionsProvider(DEFAULT_COMPACT_BUTTON)
             .onRoot()
@@ -164,6 +173,27 @@
     }
 
     @Test
+    fun avatarButton_hasLabel_asText() {
+        LayoutElementAssertionsProvider(DEFAULT_AVATAR_BUTTON)
+            .onElement(hasText(TEXT))
+            .assertExists()
+    }
+
+    @Test
+    fun avatarButton_hasSecondaryLabel_asText() {
+        LayoutElementAssertionsProvider(DEFAULT_AVATAR_BUTTON)
+            .onElement(hasText(TEXT2))
+            .assertExists()
+    }
+
+    @Test
+    fun avatarButton_hasAvatar_asImage() {
+        LayoutElementAssertionsProvider(DEFAULT_AVATAR_BUTTON)
+            .onElement(hasImage(IMAGE_ID))
+            .assertExists()
+    }
+
+    @Test
     fun compactButton_hasLabel_asText() {
         LayoutElementAssertionsProvider(DEFAULT_COMPACT_BUTTON)
             .onElement(hasText(TEXT))
@@ -295,6 +325,17 @@
                 )
             }
 
+        private val DEFAULT_AVATAR_BUTTON =
+            materialScope(CONTEXT, DEVICE_CONFIGURATION) {
+                avatarButton(
+                    onClick = CLICKABLE,
+                    modifier = LayoutModifier.contentDescription(CONTENT_DESCRIPTION),
+                    labelContent = { text(TEXT.layoutString) },
+                    secondaryLabelContent = { text(TEXT2.layoutString) },
+                    avatarContent = { icon(IMAGE_ID) }
+                )
+            }
+
         private val DEFAULT_COMPACT_BUTTON =
             materialScope(CONTEXT, DEVICE_CONFIGURATION) {
                 compactButton(
diff --git a/wear/tiles/tiles-samples/src/main/java/androidx/wear/tiles/samples/tile/PlaygroundTileService.kt b/wear/tiles/tiles-samples/src/main/java/androidx/wear/tiles/samples/tile/PlaygroundTileService.kt
index 6b0db65..d370263 100644
--- a/wear/tiles/tiles-samples/src/main/java/androidx/wear/tiles/samples/tile/PlaygroundTileService.kt
+++ b/wear/tiles/tiles-samples/src/main/java/androidx/wear/tiles/samples/tile/PlaygroundTileService.kt
@@ -28,6 +28,7 @@
 import androidx.wear.protolayout.TimelineBuilders
 import androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat
 import androidx.wear.protolayout.expression.VersionBuilders.VersionInfo
+import androidx.wear.protolayout.material3.AvatarButtonStyle.Companion.largeAvatarButtonStyle
 import androidx.wear.protolayout.material3.ButtonDefaults.filledVariantButtonColors
 import androidx.wear.protolayout.material3.CardColors
 import androidx.wear.protolayout.material3.CardDefaults.filledTonalCardColors
@@ -39,6 +40,7 @@
 import androidx.wear.protolayout.material3.PrimaryLayoutMargins.Companion.MAX_PRIMARY_LAYOUT_MARGIN
 import androidx.wear.protolayout.material3.TextButtonStyle.Companion.smallTextButtonStyle
 import androidx.wear.protolayout.material3.appCard
+import androidx.wear.protolayout.material3.avatarButton
 import androidx.wear.protolayout.material3.avatarImage
 import androidx.wear.protolayout.material3.button
 import androidx.wear.protolayout.material3.buttonGroup
@@ -140,6 +142,17 @@
         )
     }
 
+private fun MaterialScope.avatarButtonSample() =
+    avatarButton(
+        onClick = clickable(),
+        modifier = LayoutModifier.contentDescription("Avatar button"),
+        avatarContent = { avatarImage(AVATAR_ID) },
+        style = largeAvatarButtonStyle(),
+        horizontalAlignment = LayoutElementBuilders.HORIZONTAL_ALIGN_END,
+        labelContent = { text("Primary label overflowing".layoutString) },
+        secondaryLabelContent = { text("Secondary label overflowing".layoutString) },
+    )
+
 private fun MaterialScope.pillShapeButton() =
     button(
         onClick = clickable(),