Copy tiles-material library to protolayout-material
This CL purely copies tiles-material without new API
changes
(https://android.git.corp.google.com/platform/frameworks/support/+/refs/heads/androidx-main/wear/tiles/tiles-material/api/current.txt)
New APIs will be added in a follow up CLs to avoid introducing
many new changes at once.
Note that remaining Tiles references from Javadocs will be update in a
follow up, to minimize changes here.
Bug: 268312818
Test: Copied from tiles-material in child CL
Relnote: "ProtoLayout Material has been added with the same functionalities as Tiles Material."
Change-Id: Ib5e1c673a692aee8c27bd78c74bfcb651ba0393d
diff --git a/wear/protolayout/protolayout-material/api/current.txt b/wear/protolayout/protolayout-material/api/current.txt
index e6f50d0..862495c3 100644
--- a/wear/protolayout/protolayout-material/api/current.txt
+++ b/wear/protolayout/protolayout-material/api/current.txt
@@ -1 +1,298 @@
// Signature format: 4.0
+package androidx.wear.protolayout.material {
+
+ public class Button implements androidx.wear.protolayout.LayoutElementBuilders.LayoutElement {
+ method public static androidx.wear.protolayout.material.Button? fromLayoutElement(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement);
+ method public androidx.wear.protolayout.material.ButtonColors getButtonColors();
+ method public androidx.wear.protolayout.ModifiersBuilders.Clickable getClickable();
+ method public CharSequence? getContentDescription();
+ method public androidx.wear.protolayout.LayoutElementBuilders.LayoutElement? getCustomContent();
+ method public String? getIconContent();
+ method public String? getImageContent();
+ method public androidx.wear.protolayout.DimensionBuilders.ContainerDimension getSize();
+ method public String? getTextContent();
+ }
+
+ public static final class Button.Builder implements androidx.wear.protolayout.LayoutElementBuilders.LayoutElement.Builder {
+ ctor public Button.Builder(android.content.Context, androidx.wear.protolayout.ModifiersBuilders.Clickable);
+ method public androidx.wear.protolayout.material.Button build();
+ method public androidx.wear.protolayout.material.Button.Builder setButtonColors(androidx.wear.protolayout.material.ButtonColors);
+ method public androidx.wear.protolayout.material.Button.Builder setContentDescription(CharSequence);
+ method public androidx.wear.protolayout.material.Button.Builder setCustomContent(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement);
+ method public androidx.wear.protolayout.material.Button.Builder setIconContent(String, androidx.wear.protolayout.DimensionBuilders.DpProp);
+ method public androidx.wear.protolayout.material.Button.Builder setIconContent(String);
+ method public androidx.wear.protolayout.material.Button.Builder setImageContent(String);
+ method public androidx.wear.protolayout.material.Button.Builder setSize(androidx.wear.protolayout.DimensionBuilders.DpProp);
+ method public androidx.wear.protolayout.material.Button.Builder setSize(@Dimension(unit=androidx.annotation.Dimension.DP) float);
+ method public androidx.wear.protolayout.material.Button.Builder setTextContent(String);
+ method public androidx.wear.protolayout.material.Button.Builder setTextContent(String, int);
+ }
+
+ public class ButtonColors {
+ ctor public ButtonColors(@ColorInt int, @ColorInt int);
+ ctor public ButtonColors(androidx.wear.protolayout.ColorBuilders.ColorProp, androidx.wear.protolayout.ColorBuilders.ColorProp);
+ method public androidx.wear.protolayout.ColorBuilders.ColorProp getBackgroundColor();
+ method public androidx.wear.protolayout.ColorBuilders.ColorProp getContentColor();
+ method public static androidx.wear.protolayout.material.ButtonColors primaryButtonColors(androidx.wear.protolayout.material.Colors);
+ method public static androidx.wear.protolayout.material.ButtonColors secondaryButtonColors(androidx.wear.protolayout.material.Colors);
+ }
+
+ public class ButtonDefaults {
+ method public static androidx.wear.protolayout.DimensionBuilders.DpProp recommendedIconSize(androidx.wear.protolayout.DimensionBuilders.DpProp);
+ method public static androidx.wear.protolayout.DimensionBuilders.DpProp recommendedIconSize(@Dimension(unit=androidx.annotation.Dimension.DP) float);
+ field public static final androidx.wear.protolayout.DimensionBuilders.DpProp DEFAULT_SIZE;
+ field public static final androidx.wear.protolayout.DimensionBuilders.DpProp EXTRA_LARGE_SIZE;
+ field public static final androidx.wear.protolayout.DimensionBuilders.DpProp LARGE_SIZE;
+ field public static final androidx.wear.protolayout.material.ButtonColors PRIMARY_COLORS;
+ field public static final androidx.wear.protolayout.material.ButtonColors SECONDARY_COLORS;
+ }
+
+ public class Chip implements androidx.wear.protolayout.LayoutElementBuilders.LayoutElement {
+ method public static androidx.wear.protolayout.material.Chip? fromLayoutElement(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement);
+ method public androidx.wear.protolayout.material.ChipColors getChipColors();
+ method public androidx.wear.protolayout.ModifiersBuilders.Clickable getClickable();
+ method public CharSequence? getContentDescription();
+ method public androidx.wear.protolayout.LayoutElementBuilders.LayoutElement? getCustomContent();
+ method public androidx.wear.protolayout.DimensionBuilders.ContainerDimension getHeight();
+ method public int getHorizontalAlignment();
+ method public String? getIconContent();
+ method public String? getPrimaryLabelContent();
+ method public String? getSecondaryLabelContent();
+ method public androidx.wear.protolayout.DimensionBuilders.ContainerDimension getWidth();
+ }
+
+ public static final class Chip.Builder implements androidx.wear.protolayout.LayoutElementBuilders.LayoutElement.Builder {
+ ctor public Chip.Builder(android.content.Context, androidx.wear.protolayout.ModifiersBuilders.Clickable, androidx.wear.protolayout.DeviceParametersBuilders.DeviceParameters);
+ method public androidx.wear.protolayout.material.Chip build();
+ method public androidx.wear.protolayout.material.Chip.Builder setChipColors(androidx.wear.protolayout.material.ChipColors);
+ method public androidx.wear.protolayout.material.Chip.Builder setContentDescription(CharSequence);
+ method public androidx.wear.protolayout.material.Chip.Builder setCustomContent(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement);
+ method public androidx.wear.protolayout.material.Chip.Builder setHorizontalAlignment(int);
+ method public androidx.wear.protolayout.material.Chip.Builder setIconContent(String);
+ method public androidx.wear.protolayout.material.Chip.Builder setPrimaryLabelContent(String);
+ method public androidx.wear.protolayout.material.Chip.Builder setSecondaryLabelContent(String);
+ method public androidx.wear.protolayout.material.Chip.Builder setWidth(androidx.wear.protolayout.DimensionBuilders.ContainerDimension);
+ method public androidx.wear.protolayout.material.Chip.Builder setWidth(@Dimension(unit=androidx.annotation.Dimension.DP) float);
+ }
+
+ public class ChipColors {
+ ctor public ChipColors(@ColorInt int, @ColorInt int, @ColorInt int, @ColorInt int);
+ ctor public ChipColors(@ColorInt int, @ColorInt int);
+ ctor public ChipColors(androidx.wear.protolayout.ColorBuilders.ColorProp, androidx.wear.protolayout.ColorBuilders.ColorProp, androidx.wear.protolayout.ColorBuilders.ColorProp, androidx.wear.protolayout.ColorBuilders.ColorProp);
+ ctor public ChipColors(androidx.wear.protolayout.ColorBuilders.ColorProp, androidx.wear.protolayout.ColorBuilders.ColorProp);
+ method public androidx.wear.protolayout.ColorBuilders.ColorProp getBackgroundColor();
+ method public androidx.wear.protolayout.ColorBuilders.ColorProp getContentColor();
+ method public androidx.wear.protolayout.ColorBuilders.ColorProp getIconColor();
+ method public androidx.wear.protolayout.ColorBuilders.ColorProp getSecondaryContentColor();
+ method public static androidx.wear.protolayout.material.ChipColors primaryChipColors(androidx.wear.protolayout.material.Colors);
+ method public static androidx.wear.protolayout.material.ChipColors secondaryChipColors(androidx.wear.protolayout.material.Colors);
+ }
+
+ public class ChipDefaults {
+ field public static final androidx.wear.protolayout.material.ChipColors COMPACT_PRIMARY_COLORS;
+ field public static final androidx.wear.protolayout.material.ChipColors COMPACT_SECONDARY_COLORS;
+ field public static final androidx.wear.protolayout.material.ChipColors PRIMARY_COLORS;
+ field public static final androidx.wear.protolayout.material.ChipColors SECONDARY_COLORS;
+ field public static final androidx.wear.protolayout.material.ChipColors TITLE_PRIMARY_COLORS;
+ field public static final androidx.wear.protolayout.material.ChipColors TITLE_SECONDARY_COLORS;
+ }
+
+ public class CircularProgressIndicator implements androidx.wear.protolayout.LayoutElementBuilders.LayoutElement {
+ method public static androidx.wear.protolayout.material.CircularProgressIndicator? fromLayoutElement(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement);
+ method public androidx.wear.protolayout.material.ProgressIndicatorColors getCircularProgressIndicatorColors();
+ method public CharSequence? getContentDescription();
+ method public androidx.wear.protolayout.DimensionBuilders.DegreesProp getEndAngle();
+ method public androidx.wear.protolayout.DimensionBuilders.DegreesProp getProgress();
+ method public androidx.wear.protolayout.DimensionBuilders.DegreesProp getStartAngle();
+ method public androidx.wear.protolayout.DimensionBuilders.DpProp getStrokeWidth();
+ }
+
+ public static final class CircularProgressIndicator.Builder implements androidx.wear.protolayout.LayoutElementBuilders.LayoutElement.Builder {
+ ctor public CircularProgressIndicator.Builder();
+ method public androidx.wear.protolayout.material.CircularProgressIndicator build();
+ method public androidx.wear.protolayout.material.CircularProgressIndicator.Builder setCircularProgressIndicatorColors(androidx.wear.protolayout.material.ProgressIndicatorColors);
+ method public androidx.wear.protolayout.material.CircularProgressIndicator.Builder setContentDescription(CharSequence);
+ method public androidx.wear.protolayout.material.CircularProgressIndicator.Builder setEndAngle(float);
+ method public androidx.wear.protolayout.material.CircularProgressIndicator.Builder setProgress(@FloatRange(from=0, to=1) float);
+ method public androidx.wear.protolayout.material.CircularProgressIndicator.Builder setStartAngle(float);
+ method public androidx.wear.protolayout.material.CircularProgressIndicator.Builder setStrokeWidth(androidx.wear.protolayout.DimensionBuilders.DpProp);
+ method public androidx.wear.protolayout.material.CircularProgressIndicator.Builder setStrokeWidth(@Dimension(unit=androidx.annotation.Dimension.DP) float);
+ }
+
+ public class Colors {
+ ctor public Colors(@ColorInt int, @ColorInt int, @ColorInt int, @ColorInt int);
+ method @ColorInt public int getOnPrimary();
+ method @ColorInt public int getOnSurface();
+ method @ColorInt public int getPrimary();
+ method @ColorInt public int getSurface();
+ field public static final androidx.wear.protolayout.material.Colors DEFAULT;
+ }
+
+ public class CompactChip implements androidx.wear.protolayout.LayoutElementBuilders.LayoutElement {
+ method public static androidx.wear.protolayout.material.CompactChip? fromLayoutElement(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement);
+ method public androidx.wear.protolayout.material.ChipColors getChipColors();
+ method public androidx.wear.protolayout.ModifiersBuilders.Clickable getClickable();
+ method public String getText();
+ }
+
+ public static final class CompactChip.Builder implements androidx.wear.protolayout.LayoutElementBuilders.LayoutElement.Builder {
+ ctor public CompactChip.Builder(android.content.Context, String, androidx.wear.protolayout.ModifiersBuilders.Clickable, androidx.wear.protolayout.DeviceParametersBuilders.DeviceParameters);
+ method public androidx.wear.protolayout.material.CompactChip build();
+ method public androidx.wear.protolayout.material.CompactChip.Builder setChipColors(androidx.wear.protolayout.material.ChipColors);
+ }
+
+ public class ProgressIndicatorColors {
+ ctor public ProgressIndicatorColors(androidx.wear.protolayout.ColorBuilders.ColorProp, androidx.wear.protolayout.ColorBuilders.ColorProp);
+ ctor public ProgressIndicatorColors(@ColorInt int, @ColorInt int);
+ method public androidx.wear.protolayout.ColorBuilders.ColorProp getIndicatorColor();
+ method public androidx.wear.protolayout.ColorBuilders.ColorProp getTrackColor();
+ method public static androidx.wear.protolayout.material.ProgressIndicatorColors progressIndicatorColors(androidx.wear.protolayout.material.Colors);
+ }
+
+ public class ProgressIndicatorDefaults {
+ field public static final androidx.wear.protolayout.material.ProgressIndicatorColors DEFAULT_COLORS;
+ field public static final androidx.wear.protolayout.DimensionBuilders.DpProp DEFAULT_STROKE_WIDTH;
+ field public static final float GAP_END_ANGLE = 156.1f;
+ field public static final float GAP_START_ANGLE = -156.1f;
+ }
+
+ public class Text implements androidx.wear.protolayout.LayoutElementBuilders.LayoutElement {
+ method public static androidx.wear.protolayout.material.Text? fromLayoutElement(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement);
+ method public androidx.wear.protolayout.ColorBuilders.ColorProp getColor();
+ method public androidx.wear.protolayout.LayoutElementBuilders.FontStyle getFontStyle();
+ method public float getLineHeight();
+ method public int getMaxLines();
+ method public androidx.wear.protolayout.ModifiersBuilders.Modifiers getModifiers();
+ method public int getMultilineAlignment();
+ method public int getOverflow();
+ method public String getText();
+ method public int getWeight();
+ method public boolean isItalic();
+ method public boolean isUnderline();
+ }
+
+ public static final class Text.Builder implements androidx.wear.protolayout.LayoutElementBuilders.LayoutElement.Builder {
+ ctor public Text.Builder(android.content.Context, String);
+ method public androidx.wear.protolayout.material.Text build();
+ method public androidx.wear.protolayout.material.Text.Builder setColor(androidx.wear.protolayout.ColorBuilders.ColorProp);
+ method public androidx.wear.protolayout.material.Text.Builder setItalic(boolean);
+ method public androidx.wear.protolayout.material.Text.Builder setMaxLines(@IntRange(from=1) int);
+ method public androidx.wear.protolayout.material.Text.Builder setModifiers(androidx.wear.protolayout.ModifiersBuilders.Modifiers);
+ method public androidx.wear.protolayout.material.Text.Builder setMultilineAlignment(int);
+ method public androidx.wear.protolayout.material.Text.Builder setOverflow(int);
+ method public androidx.wear.protolayout.material.Text.Builder setTypography(int);
+ method public androidx.wear.protolayout.material.Text.Builder setUnderline(boolean);
+ method public androidx.wear.protolayout.material.Text.Builder setWeight(int);
+ }
+
+ public class TitleChip implements androidx.wear.protolayout.LayoutElementBuilders.LayoutElement {
+ method public static androidx.wear.protolayout.material.TitleChip? fromLayoutElement(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement);
+ method public androidx.wear.protolayout.material.ChipColors getChipColors();
+ method public androidx.wear.protolayout.ModifiersBuilders.Clickable getClickable();
+ method public int getHorizontalAlignment();
+ method public String getText();
+ method public androidx.wear.protolayout.DimensionBuilders.ContainerDimension getWidth();
+ }
+
+ public static final class TitleChip.Builder implements androidx.wear.protolayout.LayoutElementBuilders.LayoutElement.Builder {
+ ctor public TitleChip.Builder(android.content.Context, String, androidx.wear.protolayout.ModifiersBuilders.Clickable, androidx.wear.protolayout.DeviceParametersBuilders.DeviceParameters);
+ method public androidx.wear.protolayout.material.TitleChip build();
+ method public androidx.wear.protolayout.material.TitleChip.Builder setChipColors(androidx.wear.protolayout.material.ChipColors);
+ method public androidx.wear.protolayout.material.TitleChip.Builder setHorizontalAlignment(int);
+ method public androidx.wear.protolayout.material.TitleChip.Builder setWidth(androidx.wear.protolayout.DimensionBuilders.ContainerDimension);
+ method public androidx.wear.protolayout.material.TitleChip.Builder setWidth(@Dimension(unit=androidx.annotation.Dimension.DP) float);
+ }
+
+ public class Typography {
+ field public static final int TYPOGRAPHY_BODY1 = 7; // 0x7
+ field public static final int TYPOGRAPHY_BODY2 = 8; // 0x8
+ field public static final int TYPOGRAPHY_BUTTON = 9; // 0x9
+ field public static final int TYPOGRAPHY_CAPTION1 = 10; // 0xa
+ field public static final int TYPOGRAPHY_CAPTION2 = 11; // 0xb
+ field public static final int TYPOGRAPHY_CAPTION3 = 12; // 0xc
+ field public static final int TYPOGRAPHY_DISPLAY1 = 1; // 0x1
+ field public static final int TYPOGRAPHY_DISPLAY2 = 2; // 0x2
+ field public static final int TYPOGRAPHY_DISPLAY3 = 3; // 0x3
+ field public static final int TYPOGRAPHY_TITLE1 = 4; // 0x4
+ field public static final int TYPOGRAPHY_TITLE2 = 5; // 0x5
+ field public static final int TYPOGRAPHY_TITLE3 = 6; // 0x6
+ }
+
+}
+
+package androidx.wear.protolayout.material.layouts {
+
+ public class EdgeContentLayout implements androidx.wear.protolayout.LayoutElementBuilders.LayoutElement {
+ method public static androidx.wear.protolayout.material.layouts.EdgeContentLayout? fromLayoutElement(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement);
+ method public androidx.wear.protolayout.LayoutElementBuilders.LayoutElement? getContent();
+ method public androidx.wear.protolayout.LayoutElementBuilders.LayoutElement? getEdgeContent();
+ method public androidx.wear.protolayout.LayoutElementBuilders.LayoutElement? getPrimaryLabelTextContent();
+ method public androidx.wear.protolayout.LayoutElementBuilders.LayoutElement? getSecondaryLabelTextContent();
+ }
+
+ public static final class EdgeContentLayout.Builder implements androidx.wear.protolayout.LayoutElementBuilders.LayoutElement.Builder {
+ ctor public EdgeContentLayout.Builder(androidx.wear.protolayout.DeviceParametersBuilders.DeviceParameters);
+ method public androidx.wear.protolayout.material.layouts.EdgeContentLayout build();
+ method public androidx.wear.protolayout.material.layouts.EdgeContentLayout.Builder setContent(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement);
+ method public androidx.wear.protolayout.material.layouts.EdgeContentLayout.Builder setEdgeContent(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement);
+ method public androidx.wear.protolayout.material.layouts.EdgeContentLayout.Builder setPrimaryLabelTextContent(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement);
+ method public androidx.wear.protolayout.material.layouts.EdgeContentLayout.Builder setSecondaryLabelTextContent(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement);
+ }
+
+ public class LayoutDefaults {
+ field public static final androidx.wear.protolayout.DimensionBuilders.DpProp DEFAULT_VERTICAL_SPACER_HEIGHT;
+ field public static final float EDGE_CONTENT_LAYOUT_PADDING_ABOVE_MAIN_CONTENT_DP = 6.0f;
+ field public static final float EDGE_CONTENT_LAYOUT_PADDING_BELOW_MAIN_CONTENT_DP = 8.0f;
+ field public static final int MULTI_BUTTON_MAX_NUMBER = 7; // 0x7
+ field public static final androidx.wear.protolayout.DimensionBuilders.DpProp MULTI_SLOT_LAYOUT_HORIZONTAL_SPACER_WIDTH;
+ }
+
+ public class MultiButtonLayout implements androidx.wear.protolayout.LayoutElementBuilders.LayoutElement {
+ method public static androidx.wear.protolayout.material.layouts.MultiButtonLayout? fromLayoutElement(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement);
+ method public java.util.List<androidx.wear.protolayout.LayoutElementBuilders.LayoutElement!> getButtonContents();
+ method public int getFiveButtonDistribution();
+ field public static final int FIVE_BUTTON_DISTRIBUTION_BOTTOM_HEAVY = 2; // 0x2
+ field public static final int FIVE_BUTTON_DISTRIBUTION_TOP_HEAVY = 1; // 0x1
+ }
+
+ public static final class MultiButtonLayout.Builder implements androidx.wear.protolayout.LayoutElementBuilders.LayoutElement.Builder {
+ ctor public MultiButtonLayout.Builder();
+ method public androidx.wear.protolayout.material.layouts.MultiButtonLayout.Builder addButtonContent(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement);
+ method public androidx.wear.protolayout.material.layouts.MultiButtonLayout build();
+ method public androidx.wear.protolayout.material.layouts.MultiButtonLayout.Builder setFiveButtonDistribution(int);
+ }
+
+ public class MultiSlotLayout implements androidx.wear.protolayout.LayoutElementBuilders.LayoutElement {
+ method public static androidx.wear.protolayout.material.layouts.MultiSlotLayout? fromLayoutElement(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement);
+ method @Dimension(unit=androidx.annotation.Dimension.DP) public float getHorizontalSpacerWidth();
+ method public java.util.List<androidx.wear.protolayout.LayoutElementBuilders.LayoutElement!> getSlotContents();
+ }
+
+ public static final class MultiSlotLayout.Builder implements androidx.wear.protolayout.LayoutElementBuilders.LayoutElement.Builder {
+ ctor public MultiSlotLayout.Builder();
+ method public androidx.wear.protolayout.material.layouts.MultiSlotLayout.Builder addSlotContent(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement);
+ method public androidx.wear.protolayout.material.layouts.MultiSlotLayout build();
+ method public androidx.wear.protolayout.material.layouts.MultiSlotLayout.Builder setHorizontalSpacerWidth(@Dimension(unit=androidx.annotation.Dimension.DP) float);
+ }
+
+ public class PrimaryLayout implements androidx.wear.protolayout.LayoutElementBuilders.LayoutElement {
+ method public static androidx.wear.protolayout.material.layouts.PrimaryLayout? fromLayoutElement(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement);
+ method public androidx.wear.protolayout.LayoutElementBuilders.LayoutElement? getContent();
+ method public androidx.wear.protolayout.LayoutElementBuilders.LayoutElement? getPrimaryChipContent();
+ method public androidx.wear.protolayout.LayoutElementBuilders.LayoutElement? getPrimaryLabelTextContent();
+ method public androidx.wear.protolayout.LayoutElementBuilders.LayoutElement? getSecondaryLabelTextContent();
+ method @Dimension(unit=androidx.annotation.Dimension.DP) public float getVerticalSpacerHeight();
+ }
+
+ public static final class PrimaryLayout.Builder implements androidx.wear.protolayout.LayoutElementBuilders.LayoutElement.Builder {
+ ctor public PrimaryLayout.Builder(androidx.wear.protolayout.DeviceParametersBuilders.DeviceParameters);
+ method public androidx.wear.protolayout.material.layouts.PrimaryLayout build();
+ method public androidx.wear.protolayout.material.layouts.PrimaryLayout.Builder setContent(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement);
+ method public androidx.wear.protolayout.material.layouts.PrimaryLayout.Builder setPrimaryChipContent(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement);
+ method public androidx.wear.protolayout.material.layouts.PrimaryLayout.Builder setPrimaryLabelTextContent(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement);
+ method public androidx.wear.protolayout.material.layouts.PrimaryLayout.Builder setSecondaryLabelTextContent(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement);
+ method public androidx.wear.protolayout.material.layouts.PrimaryLayout.Builder setVerticalSpacerHeight(@Dimension(unit=androidx.annotation.Dimension.DP) float);
+ }
+
+}
+
diff --git a/wear/protolayout/protolayout-material/api/public_plus_experimental_current.txt b/wear/protolayout/protolayout-material/api/public_plus_experimental_current.txt
index e6f50d0..862495c3 100644
--- a/wear/protolayout/protolayout-material/api/public_plus_experimental_current.txt
+++ b/wear/protolayout/protolayout-material/api/public_plus_experimental_current.txt
@@ -1 +1,298 @@
// Signature format: 4.0
+package androidx.wear.protolayout.material {
+
+ public class Button implements androidx.wear.protolayout.LayoutElementBuilders.LayoutElement {
+ method public static androidx.wear.protolayout.material.Button? fromLayoutElement(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement);
+ method public androidx.wear.protolayout.material.ButtonColors getButtonColors();
+ method public androidx.wear.protolayout.ModifiersBuilders.Clickable getClickable();
+ method public CharSequence? getContentDescription();
+ method public androidx.wear.protolayout.LayoutElementBuilders.LayoutElement? getCustomContent();
+ method public String? getIconContent();
+ method public String? getImageContent();
+ method public androidx.wear.protolayout.DimensionBuilders.ContainerDimension getSize();
+ method public String? getTextContent();
+ }
+
+ public static final class Button.Builder implements androidx.wear.protolayout.LayoutElementBuilders.LayoutElement.Builder {
+ ctor public Button.Builder(android.content.Context, androidx.wear.protolayout.ModifiersBuilders.Clickable);
+ method public androidx.wear.protolayout.material.Button build();
+ method public androidx.wear.protolayout.material.Button.Builder setButtonColors(androidx.wear.protolayout.material.ButtonColors);
+ method public androidx.wear.protolayout.material.Button.Builder setContentDescription(CharSequence);
+ method public androidx.wear.protolayout.material.Button.Builder setCustomContent(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement);
+ method public androidx.wear.protolayout.material.Button.Builder setIconContent(String, androidx.wear.protolayout.DimensionBuilders.DpProp);
+ method public androidx.wear.protolayout.material.Button.Builder setIconContent(String);
+ method public androidx.wear.protolayout.material.Button.Builder setImageContent(String);
+ method public androidx.wear.protolayout.material.Button.Builder setSize(androidx.wear.protolayout.DimensionBuilders.DpProp);
+ method public androidx.wear.protolayout.material.Button.Builder setSize(@Dimension(unit=androidx.annotation.Dimension.DP) float);
+ method public androidx.wear.protolayout.material.Button.Builder setTextContent(String);
+ method public androidx.wear.protolayout.material.Button.Builder setTextContent(String, int);
+ }
+
+ public class ButtonColors {
+ ctor public ButtonColors(@ColorInt int, @ColorInt int);
+ ctor public ButtonColors(androidx.wear.protolayout.ColorBuilders.ColorProp, androidx.wear.protolayout.ColorBuilders.ColorProp);
+ method public androidx.wear.protolayout.ColorBuilders.ColorProp getBackgroundColor();
+ method public androidx.wear.protolayout.ColorBuilders.ColorProp getContentColor();
+ method public static androidx.wear.protolayout.material.ButtonColors primaryButtonColors(androidx.wear.protolayout.material.Colors);
+ method public static androidx.wear.protolayout.material.ButtonColors secondaryButtonColors(androidx.wear.protolayout.material.Colors);
+ }
+
+ public class ButtonDefaults {
+ method public static androidx.wear.protolayout.DimensionBuilders.DpProp recommendedIconSize(androidx.wear.protolayout.DimensionBuilders.DpProp);
+ method public static androidx.wear.protolayout.DimensionBuilders.DpProp recommendedIconSize(@Dimension(unit=androidx.annotation.Dimension.DP) float);
+ field public static final androidx.wear.protolayout.DimensionBuilders.DpProp DEFAULT_SIZE;
+ field public static final androidx.wear.protolayout.DimensionBuilders.DpProp EXTRA_LARGE_SIZE;
+ field public static final androidx.wear.protolayout.DimensionBuilders.DpProp LARGE_SIZE;
+ field public static final androidx.wear.protolayout.material.ButtonColors PRIMARY_COLORS;
+ field public static final androidx.wear.protolayout.material.ButtonColors SECONDARY_COLORS;
+ }
+
+ public class Chip implements androidx.wear.protolayout.LayoutElementBuilders.LayoutElement {
+ method public static androidx.wear.protolayout.material.Chip? fromLayoutElement(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement);
+ method public androidx.wear.protolayout.material.ChipColors getChipColors();
+ method public androidx.wear.protolayout.ModifiersBuilders.Clickable getClickable();
+ method public CharSequence? getContentDescription();
+ method public androidx.wear.protolayout.LayoutElementBuilders.LayoutElement? getCustomContent();
+ method public androidx.wear.protolayout.DimensionBuilders.ContainerDimension getHeight();
+ method public int getHorizontalAlignment();
+ method public String? getIconContent();
+ method public String? getPrimaryLabelContent();
+ method public String? getSecondaryLabelContent();
+ method public androidx.wear.protolayout.DimensionBuilders.ContainerDimension getWidth();
+ }
+
+ public static final class Chip.Builder implements androidx.wear.protolayout.LayoutElementBuilders.LayoutElement.Builder {
+ ctor public Chip.Builder(android.content.Context, androidx.wear.protolayout.ModifiersBuilders.Clickable, androidx.wear.protolayout.DeviceParametersBuilders.DeviceParameters);
+ method public androidx.wear.protolayout.material.Chip build();
+ method public androidx.wear.protolayout.material.Chip.Builder setChipColors(androidx.wear.protolayout.material.ChipColors);
+ method public androidx.wear.protolayout.material.Chip.Builder setContentDescription(CharSequence);
+ method public androidx.wear.protolayout.material.Chip.Builder setCustomContent(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement);
+ method public androidx.wear.protolayout.material.Chip.Builder setHorizontalAlignment(int);
+ method public androidx.wear.protolayout.material.Chip.Builder setIconContent(String);
+ method public androidx.wear.protolayout.material.Chip.Builder setPrimaryLabelContent(String);
+ method public androidx.wear.protolayout.material.Chip.Builder setSecondaryLabelContent(String);
+ method public androidx.wear.protolayout.material.Chip.Builder setWidth(androidx.wear.protolayout.DimensionBuilders.ContainerDimension);
+ method public androidx.wear.protolayout.material.Chip.Builder setWidth(@Dimension(unit=androidx.annotation.Dimension.DP) float);
+ }
+
+ public class ChipColors {
+ ctor public ChipColors(@ColorInt int, @ColorInt int, @ColorInt int, @ColorInt int);
+ ctor public ChipColors(@ColorInt int, @ColorInt int);
+ ctor public ChipColors(androidx.wear.protolayout.ColorBuilders.ColorProp, androidx.wear.protolayout.ColorBuilders.ColorProp, androidx.wear.protolayout.ColorBuilders.ColorProp, androidx.wear.protolayout.ColorBuilders.ColorProp);
+ ctor public ChipColors(androidx.wear.protolayout.ColorBuilders.ColorProp, androidx.wear.protolayout.ColorBuilders.ColorProp);
+ method public androidx.wear.protolayout.ColorBuilders.ColorProp getBackgroundColor();
+ method public androidx.wear.protolayout.ColorBuilders.ColorProp getContentColor();
+ method public androidx.wear.protolayout.ColorBuilders.ColorProp getIconColor();
+ method public androidx.wear.protolayout.ColorBuilders.ColorProp getSecondaryContentColor();
+ method public static androidx.wear.protolayout.material.ChipColors primaryChipColors(androidx.wear.protolayout.material.Colors);
+ method public static androidx.wear.protolayout.material.ChipColors secondaryChipColors(androidx.wear.protolayout.material.Colors);
+ }
+
+ public class ChipDefaults {
+ field public static final androidx.wear.protolayout.material.ChipColors COMPACT_PRIMARY_COLORS;
+ field public static final androidx.wear.protolayout.material.ChipColors COMPACT_SECONDARY_COLORS;
+ field public static final androidx.wear.protolayout.material.ChipColors PRIMARY_COLORS;
+ field public static final androidx.wear.protolayout.material.ChipColors SECONDARY_COLORS;
+ field public static final androidx.wear.protolayout.material.ChipColors TITLE_PRIMARY_COLORS;
+ field public static final androidx.wear.protolayout.material.ChipColors TITLE_SECONDARY_COLORS;
+ }
+
+ public class CircularProgressIndicator implements androidx.wear.protolayout.LayoutElementBuilders.LayoutElement {
+ method public static androidx.wear.protolayout.material.CircularProgressIndicator? fromLayoutElement(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement);
+ method public androidx.wear.protolayout.material.ProgressIndicatorColors getCircularProgressIndicatorColors();
+ method public CharSequence? getContentDescription();
+ method public androidx.wear.protolayout.DimensionBuilders.DegreesProp getEndAngle();
+ method public androidx.wear.protolayout.DimensionBuilders.DegreesProp getProgress();
+ method public androidx.wear.protolayout.DimensionBuilders.DegreesProp getStartAngle();
+ method public androidx.wear.protolayout.DimensionBuilders.DpProp getStrokeWidth();
+ }
+
+ public static final class CircularProgressIndicator.Builder implements androidx.wear.protolayout.LayoutElementBuilders.LayoutElement.Builder {
+ ctor public CircularProgressIndicator.Builder();
+ method public androidx.wear.protolayout.material.CircularProgressIndicator build();
+ method public androidx.wear.protolayout.material.CircularProgressIndicator.Builder setCircularProgressIndicatorColors(androidx.wear.protolayout.material.ProgressIndicatorColors);
+ method public androidx.wear.protolayout.material.CircularProgressIndicator.Builder setContentDescription(CharSequence);
+ method public androidx.wear.protolayout.material.CircularProgressIndicator.Builder setEndAngle(float);
+ method public androidx.wear.protolayout.material.CircularProgressIndicator.Builder setProgress(@FloatRange(from=0, to=1) float);
+ method public androidx.wear.protolayout.material.CircularProgressIndicator.Builder setStartAngle(float);
+ method public androidx.wear.protolayout.material.CircularProgressIndicator.Builder setStrokeWidth(androidx.wear.protolayout.DimensionBuilders.DpProp);
+ method public androidx.wear.protolayout.material.CircularProgressIndicator.Builder setStrokeWidth(@Dimension(unit=androidx.annotation.Dimension.DP) float);
+ }
+
+ public class Colors {
+ ctor public Colors(@ColorInt int, @ColorInt int, @ColorInt int, @ColorInt int);
+ method @ColorInt public int getOnPrimary();
+ method @ColorInt public int getOnSurface();
+ method @ColorInt public int getPrimary();
+ method @ColorInt public int getSurface();
+ field public static final androidx.wear.protolayout.material.Colors DEFAULT;
+ }
+
+ public class CompactChip implements androidx.wear.protolayout.LayoutElementBuilders.LayoutElement {
+ method public static androidx.wear.protolayout.material.CompactChip? fromLayoutElement(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement);
+ method public androidx.wear.protolayout.material.ChipColors getChipColors();
+ method public androidx.wear.protolayout.ModifiersBuilders.Clickable getClickable();
+ method public String getText();
+ }
+
+ public static final class CompactChip.Builder implements androidx.wear.protolayout.LayoutElementBuilders.LayoutElement.Builder {
+ ctor public CompactChip.Builder(android.content.Context, String, androidx.wear.protolayout.ModifiersBuilders.Clickable, androidx.wear.protolayout.DeviceParametersBuilders.DeviceParameters);
+ method public androidx.wear.protolayout.material.CompactChip build();
+ method public androidx.wear.protolayout.material.CompactChip.Builder setChipColors(androidx.wear.protolayout.material.ChipColors);
+ }
+
+ public class ProgressIndicatorColors {
+ ctor public ProgressIndicatorColors(androidx.wear.protolayout.ColorBuilders.ColorProp, androidx.wear.protolayout.ColorBuilders.ColorProp);
+ ctor public ProgressIndicatorColors(@ColorInt int, @ColorInt int);
+ method public androidx.wear.protolayout.ColorBuilders.ColorProp getIndicatorColor();
+ method public androidx.wear.protolayout.ColorBuilders.ColorProp getTrackColor();
+ method public static androidx.wear.protolayout.material.ProgressIndicatorColors progressIndicatorColors(androidx.wear.protolayout.material.Colors);
+ }
+
+ public class ProgressIndicatorDefaults {
+ field public static final androidx.wear.protolayout.material.ProgressIndicatorColors DEFAULT_COLORS;
+ field public static final androidx.wear.protolayout.DimensionBuilders.DpProp DEFAULT_STROKE_WIDTH;
+ field public static final float GAP_END_ANGLE = 156.1f;
+ field public static final float GAP_START_ANGLE = -156.1f;
+ }
+
+ public class Text implements androidx.wear.protolayout.LayoutElementBuilders.LayoutElement {
+ method public static androidx.wear.protolayout.material.Text? fromLayoutElement(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement);
+ method public androidx.wear.protolayout.ColorBuilders.ColorProp getColor();
+ method public androidx.wear.protolayout.LayoutElementBuilders.FontStyle getFontStyle();
+ method public float getLineHeight();
+ method public int getMaxLines();
+ method public androidx.wear.protolayout.ModifiersBuilders.Modifiers getModifiers();
+ method public int getMultilineAlignment();
+ method public int getOverflow();
+ method public String getText();
+ method public int getWeight();
+ method public boolean isItalic();
+ method public boolean isUnderline();
+ }
+
+ public static final class Text.Builder implements androidx.wear.protolayout.LayoutElementBuilders.LayoutElement.Builder {
+ ctor public Text.Builder(android.content.Context, String);
+ method public androidx.wear.protolayout.material.Text build();
+ method public androidx.wear.protolayout.material.Text.Builder setColor(androidx.wear.protolayout.ColorBuilders.ColorProp);
+ method public androidx.wear.protolayout.material.Text.Builder setItalic(boolean);
+ method public androidx.wear.protolayout.material.Text.Builder setMaxLines(@IntRange(from=1) int);
+ method public androidx.wear.protolayout.material.Text.Builder setModifiers(androidx.wear.protolayout.ModifiersBuilders.Modifiers);
+ method public androidx.wear.protolayout.material.Text.Builder setMultilineAlignment(int);
+ method public androidx.wear.protolayout.material.Text.Builder setOverflow(int);
+ method public androidx.wear.protolayout.material.Text.Builder setTypography(int);
+ method public androidx.wear.protolayout.material.Text.Builder setUnderline(boolean);
+ method public androidx.wear.protolayout.material.Text.Builder setWeight(int);
+ }
+
+ public class TitleChip implements androidx.wear.protolayout.LayoutElementBuilders.LayoutElement {
+ method public static androidx.wear.protolayout.material.TitleChip? fromLayoutElement(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement);
+ method public androidx.wear.protolayout.material.ChipColors getChipColors();
+ method public androidx.wear.protolayout.ModifiersBuilders.Clickable getClickable();
+ method public int getHorizontalAlignment();
+ method public String getText();
+ method public androidx.wear.protolayout.DimensionBuilders.ContainerDimension getWidth();
+ }
+
+ public static final class TitleChip.Builder implements androidx.wear.protolayout.LayoutElementBuilders.LayoutElement.Builder {
+ ctor public TitleChip.Builder(android.content.Context, String, androidx.wear.protolayout.ModifiersBuilders.Clickable, androidx.wear.protolayout.DeviceParametersBuilders.DeviceParameters);
+ method public androidx.wear.protolayout.material.TitleChip build();
+ method public androidx.wear.protolayout.material.TitleChip.Builder setChipColors(androidx.wear.protolayout.material.ChipColors);
+ method public androidx.wear.protolayout.material.TitleChip.Builder setHorizontalAlignment(int);
+ method public androidx.wear.protolayout.material.TitleChip.Builder setWidth(androidx.wear.protolayout.DimensionBuilders.ContainerDimension);
+ method public androidx.wear.protolayout.material.TitleChip.Builder setWidth(@Dimension(unit=androidx.annotation.Dimension.DP) float);
+ }
+
+ public class Typography {
+ field public static final int TYPOGRAPHY_BODY1 = 7; // 0x7
+ field public static final int TYPOGRAPHY_BODY2 = 8; // 0x8
+ field public static final int TYPOGRAPHY_BUTTON = 9; // 0x9
+ field public static final int TYPOGRAPHY_CAPTION1 = 10; // 0xa
+ field public static final int TYPOGRAPHY_CAPTION2 = 11; // 0xb
+ field public static final int TYPOGRAPHY_CAPTION3 = 12; // 0xc
+ field public static final int TYPOGRAPHY_DISPLAY1 = 1; // 0x1
+ field public static final int TYPOGRAPHY_DISPLAY2 = 2; // 0x2
+ field public static final int TYPOGRAPHY_DISPLAY3 = 3; // 0x3
+ field public static final int TYPOGRAPHY_TITLE1 = 4; // 0x4
+ field public static final int TYPOGRAPHY_TITLE2 = 5; // 0x5
+ field public static final int TYPOGRAPHY_TITLE3 = 6; // 0x6
+ }
+
+}
+
+package androidx.wear.protolayout.material.layouts {
+
+ public class EdgeContentLayout implements androidx.wear.protolayout.LayoutElementBuilders.LayoutElement {
+ method public static androidx.wear.protolayout.material.layouts.EdgeContentLayout? fromLayoutElement(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement);
+ method public androidx.wear.protolayout.LayoutElementBuilders.LayoutElement? getContent();
+ method public androidx.wear.protolayout.LayoutElementBuilders.LayoutElement? getEdgeContent();
+ method public androidx.wear.protolayout.LayoutElementBuilders.LayoutElement? getPrimaryLabelTextContent();
+ method public androidx.wear.protolayout.LayoutElementBuilders.LayoutElement? getSecondaryLabelTextContent();
+ }
+
+ public static final class EdgeContentLayout.Builder implements androidx.wear.protolayout.LayoutElementBuilders.LayoutElement.Builder {
+ ctor public EdgeContentLayout.Builder(androidx.wear.protolayout.DeviceParametersBuilders.DeviceParameters);
+ method public androidx.wear.protolayout.material.layouts.EdgeContentLayout build();
+ method public androidx.wear.protolayout.material.layouts.EdgeContentLayout.Builder setContent(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement);
+ method public androidx.wear.protolayout.material.layouts.EdgeContentLayout.Builder setEdgeContent(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement);
+ method public androidx.wear.protolayout.material.layouts.EdgeContentLayout.Builder setPrimaryLabelTextContent(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement);
+ method public androidx.wear.protolayout.material.layouts.EdgeContentLayout.Builder setSecondaryLabelTextContent(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement);
+ }
+
+ public class LayoutDefaults {
+ field public static final androidx.wear.protolayout.DimensionBuilders.DpProp DEFAULT_VERTICAL_SPACER_HEIGHT;
+ field public static final float EDGE_CONTENT_LAYOUT_PADDING_ABOVE_MAIN_CONTENT_DP = 6.0f;
+ field public static final float EDGE_CONTENT_LAYOUT_PADDING_BELOW_MAIN_CONTENT_DP = 8.0f;
+ field public static final int MULTI_BUTTON_MAX_NUMBER = 7; // 0x7
+ field public static final androidx.wear.protolayout.DimensionBuilders.DpProp MULTI_SLOT_LAYOUT_HORIZONTAL_SPACER_WIDTH;
+ }
+
+ public class MultiButtonLayout implements androidx.wear.protolayout.LayoutElementBuilders.LayoutElement {
+ method public static androidx.wear.protolayout.material.layouts.MultiButtonLayout? fromLayoutElement(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement);
+ method public java.util.List<androidx.wear.protolayout.LayoutElementBuilders.LayoutElement!> getButtonContents();
+ method public int getFiveButtonDistribution();
+ field public static final int FIVE_BUTTON_DISTRIBUTION_BOTTOM_HEAVY = 2; // 0x2
+ field public static final int FIVE_BUTTON_DISTRIBUTION_TOP_HEAVY = 1; // 0x1
+ }
+
+ public static final class MultiButtonLayout.Builder implements androidx.wear.protolayout.LayoutElementBuilders.LayoutElement.Builder {
+ ctor public MultiButtonLayout.Builder();
+ method public androidx.wear.protolayout.material.layouts.MultiButtonLayout.Builder addButtonContent(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement);
+ method public androidx.wear.protolayout.material.layouts.MultiButtonLayout build();
+ method public androidx.wear.protolayout.material.layouts.MultiButtonLayout.Builder setFiveButtonDistribution(int);
+ }
+
+ public class MultiSlotLayout implements androidx.wear.protolayout.LayoutElementBuilders.LayoutElement {
+ method public static androidx.wear.protolayout.material.layouts.MultiSlotLayout? fromLayoutElement(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement);
+ method @Dimension(unit=androidx.annotation.Dimension.DP) public float getHorizontalSpacerWidth();
+ method public java.util.List<androidx.wear.protolayout.LayoutElementBuilders.LayoutElement!> getSlotContents();
+ }
+
+ public static final class MultiSlotLayout.Builder implements androidx.wear.protolayout.LayoutElementBuilders.LayoutElement.Builder {
+ ctor public MultiSlotLayout.Builder();
+ method public androidx.wear.protolayout.material.layouts.MultiSlotLayout.Builder addSlotContent(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement);
+ method public androidx.wear.protolayout.material.layouts.MultiSlotLayout build();
+ method public androidx.wear.protolayout.material.layouts.MultiSlotLayout.Builder setHorizontalSpacerWidth(@Dimension(unit=androidx.annotation.Dimension.DP) float);
+ }
+
+ public class PrimaryLayout implements androidx.wear.protolayout.LayoutElementBuilders.LayoutElement {
+ method public static androidx.wear.protolayout.material.layouts.PrimaryLayout? fromLayoutElement(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement);
+ method public androidx.wear.protolayout.LayoutElementBuilders.LayoutElement? getContent();
+ method public androidx.wear.protolayout.LayoutElementBuilders.LayoutElement? getPrimaryChipContent();
+ method public androidx.wear.protolayout.LayoutElementBuilders.LayoutElement? getPrimaryLabelTextContent();
+ method public androidx.wear.protolayout.LayoutElementBuilders.LayoutElement? getSecondaryLabelTextContent();
+ method @Dimension(unit=androidx.annotation.Dimension.DP) public float getVerticalSpacerHeight();
+ }
+
+ public static final class PrimaryLayout.Builder implements androidx.wear.protolayout.LayoutElementBuilders.LayoutElement.Builder {
+ ctor public PrimaryLayout.Builder(androidx.wear.protolayout.DeviceParametersBuilders.DeviceParameters);
+ method public androidx.wear.protolayout.material.layouts.PrimaryLayout build();
+ method public androidx.wear.protolayout.material.layouts.PrimaryLayout.Builder setContent(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement);
+ method public androidx.wear.protolayout.material.layouts.PrimaryLayout.Builder setPrimaryChipContent(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement);
+ method public androidx.wear.protolayout.material.layouts.PrimaryLayout.Builder setPrimaryLabelTextContent(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement);
+ method public androidx.wear.protolayout.material.layouts.PrimaryLayout.Builder setSecondaryLabelTextContent(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement);
+ method public androidx.wear.protolayout.material.layouts.PrimaryLayout.Builder setVerticalSpacerHeight(@Dimension(unit=androidx.annotation.Dimension.DP) float);
+ }
+
+}
+
diff --git a/wear/protolayout/protolayout-material/api/restricted_current.txt b/wear/protolayout/protolayout-material/api/restricted_current.txt
index e6f50d0..862495c3 100644
--- a/wear/protolayout/protolayout-material/api/restricted_current.txt
+++ b/wear/protolayout/protolayout-material/api/restricted_current.txt
@@ -1 +1,298 @@
// Signature format: 4.0
+package androidx.wear.protolayout.material {
+
+ public class Button implements androidx.wear.protolayout.LayoutElementBuilders.LayoutElement {
+ method public static androidx.wear.protolayout.material.Button? fromLayoutElement(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement);
+ method public androidx.wear.protolayout.material.ButtonColors getButtonColors();
+ method public androidx.wear.protolayout.ModifiersBuilders.Clickable getClickable();
+ method public CharSequence? getContentDescription();
+ method public androidx.wear.protolayout.LayoutElementBuilders.LayoutElement? getCustomContent();
+ method public String? getIconContent();
+ method public String? getImageContent();
+ method public androidx.wear.protolayout.DimensionBuilders.ContainerDimension getSize();
+ method public String? getTextContent();
+ }
+
+ public static final class Button.Builder implements androidx.wear.protolayout.LayoutElementBuilders.LayoutElement.Builder {
+ ctor public Button.Builder(android.content.Context, androidx.wear.protolayout.ModifiersBuilders.Clickable);
+ method public androidx.wear.protolayout.material.Button build();
+ method public androidx.wear.protolayout.material.Button.Builder setButtonColors(androidx.wear.protolayout.material.ButtonColors);
+ method public androidx.wear.protolayout.material.Button.Builder setContentDescription(CharSequence);
+ method public androidx.wear.protolayout.material.Button.Builder setCustomContent(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement);
+ method public androidx.wear.protolayout.material.Button.Builder setIconContent(String, androidx.wear.protolayout.DimensionBuilders.DpProp);
+ method public androidx.wear.protolayout.material.Button.Builder setIconContent(String);
+ method public androidx.wear.protolayout.material.Button.Builder setImageContent(String);
+ method public androidx.wear.protolayout.material.Button.Builder setSize(androidx.wear.protolayout.DimensionBuilders.DpProp);
+ method public androidx.wear.protolayout.material.Button.Builder setSize(@Dimension(unit=androidx.annotation.Dimension.DP) float);
+ method public androidx.wear.protolayout.material.Button.Builder setTextContent(String);
+ method public androidx.wear.protolayout.material.Button.Builder setTextContent(String, int);
+ }
+
+ public class ButtonColors {
+ ctor public ButtonColors(@ColorInt int, @ColorInt int);
+ ctor public ButtonColors(androidx.wear.protolayout.ColorBuilders.ColorProp, androidx.wear.protolayout.ColorBuilders.ColorProp);
+ method public androidx.wear.protolayout.ColorBuilders.ColorProp getBackgroundColor();
+ method public androidx.wear.protolayout.ColorBuilders.ColorProp getContentColor();
+ method public static androidx.wear.protolayout.material.ButtonColors primaryButtonColors(androidx.wear.protolayout.material.Colors);
+ method public static androidx.wear.protolayout.material.ButtonColors secondaryButtonColors(androidx.wear.protolayout.material.Colors);
+ }
+
+ public class ButtonDefaults {
+ method public static androidx.wear.protolayout.DimensionBuilders.DpProp recommendedIconSize(androidx.wear.protolayout.DimensionBuilders.DpProp);
+ method public static androidx.wear.protolayout.DimensionBuilders.DpProp recommendedIconSize(@Dimension(unit=androidx.annotation.Dimension.DP) float);
+ field public static final androidx.wear.protolayout.DimensionBuilders.DpProp DEFAULT_SIZE;
+ field public static final androidx.wear.protolayout.DimensionBuilders.DpProp EXTRA_LARGE_SIZE;
+ field public static final androidx.wear.protolayout.DimensionBuilders.DpProp LARGE_SIZE;
+ field public static final androidx.wear.protolayout.material.ButtonColors PRIMARY_COLORS;
+ field public static final androidx.wear.protolayout.material.ButtonColors SECONDARY_COLORS;
+ }
+
+ public class Chip implements androidx.wear.protolayout.LayoutElementBuilders.LayoutElement {
+ method public static androidx.wear.protolayout.material.Chip? fromLayoutElement(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement);
+ method public androidx.wear.protolayout.material.ChipColors getChipColors();
+ method public androidx.wear.protolayout.ModifiersBuilders.Clickable getClickable();
+ method public CharSequence? getContentDescription();
+ method public androidx.wear.protolayout.LayoutElementBuilders.LayoutElement? getCustomContent();
+ method public androidx.wear.protolayout.DimensionBuilders.ContainerDimension getHeight();
+ method public int getHorizontalAlignment();
+ method public String? getIconContent();
+ method public String? getPrimaryLabelContent();
+ method public String? getSecondaryLabelContent();
+ method public androidx.wear.protolayout.DimensionBuilders.ContainerDimension getWidth();
+ }
+
+ public static final class Chip.Builder implements androidx.wear.protolayout.LayoutElementBuilders.LayoutElement.Builder {
+ ctor public Chip.Builder(android.content.Context, androidx.wear.protolayout.ModifiersBuilders.Clickable, androidx.wear.protolayout.DeviceParametersBuilders.DeviceParameters);
+ method public androidx.wear.protolayout.material.Chip build();
+ method public androidx.wear.protolayout.material.Chip.Builder setChipColors(androidx.wear.protolayout.material.ChipColors);
+ method public androidx.wear.protolayout.material.Chip.Builder setContentDescription(CharSequence);
+ method public androidx.wear.protolayout.material.Chip.Builder setCustomContent(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement);
+ method public androidx.wear.protolayout.material.Chip.Builder setHorizontalAlignment(int);
+ method public androidx.wear.protolayout.material.Chip.Builder setIconContent(String);
+ method public androidx.wear.protolayout.material.Chip.Builder setPrimaryLabelContent(String);
+ method public androidx.wear.protolayout.material.Chip.Builder setSecondaryLabelContent(String);
+ method public androidx.wear.protolayout.material.Chip.Builder setWidth(androidx.wear.protolayout.DimensionBuilders.ContainerDimension);
+ method public androidx.wear.protolayout.material.Chip.Builder setWidth(@Dimension(unit=androidx.annotation.Dimension.DP) float);
+ }
+
+ public class ChipColors {
+ ctor public ChipColors(@ColorInt int, @ColorInt int, @ColorInt int, @ColorInt int);
+ ctor public ChipColors(@ColorInt int, @ColorInt int);
+ ctor public ChipColors(androidx.wear.protolayout.ColorBuilders.ColorProp, androidx.wear.protolayout.ColorBuilders.ColorProp, androidx.wear.protolayout.ColorBuilders.ColorProp, androidx.wear.protolayout.ColorBuilders.ColorProp);
+ ctor public ChipColors(androidx.wear.protolayout.ColorBuilders.ColorProp, androidx.wear.protolayout.ColorBuilders.ColorProp);
+ method public androidx.wear.protolayout.ColorBuilders.ColorProp getBackgroundColor();
+ method public androidx.wear.protolayout.ColorBuilders.ColorProp getContentColor();
+ method public androidx.wear.protolayout.ColorBuilders.ColorProp getIconColor();
+ method public androidx.wear.protolayout.ColorBuilders.ColorProp getSecondaryContentColor();
+ method public static androidx.wear.protolayout.material.ChipColors primaryChipColors(androidx.wear.protolayout.material.Colors);
+ method public static androidx.wear.protolayout.material.ChipColors secondaryChipColors(androidx.wear.protolayout.material.Colors);
+ }
+
+ public class ChipDefaults {
+ field public static final androidx.wear.protolayout.material.ChipColors COMPACT_PRIMARY_COLORS;
+ field public static final androidx.wear.protolayout.material.ChipColors COMPACT_SECONDARY_COLORS;
+ field public static final androidx.wear.protolayout.material.ChipColors PRIMARY_COLORS;
+ field public static final androidx.wear.protolayout.material.ChipColors SECONDARY_COLORS;
+ field public static final androidx.wear.protolayout.material.ChipColors TITLE_PRIMARY_COLORS;
+ field public static final androidx.wear.protolayout.material.ChipColors TITLE_SECONDARY_COLORS;
+ }
+
+ public class CircularProgressIndicator implements androidx.wear.protolayout.LayoutElementBuilders.LayoutElement {
+ method public static androidx.wear.protolayout.material.CircularProgressIndicator? fromLayoutElement(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement);
+ method public androidx.wear.protolayout.material.ProgressIndicatorColors getCircularProgressIndicatorColors();
+ method public CharSequence? getContentDescription();
+ method public androidx.wear.protolayout.DimensionBuilders.DegreesProp getEndAngle();
+ method public androidx.wear.protolayout.DimensionBuilders.DegreesProp getProgress();
+ method public androidx.wear.protolayout.DimensionBuilders.DegreesProp getStartAngle();
+ method public androidx.wear.protolayout.DimensionBuilders.DpProp getStrokeWidth();
+ }
+
+ public static final class CircularProgressIndicator.Builder implements androidx.wear.protolayout.LayoutElementBuilders.LayoutElement.Builder {
+ ctor public CircularProgressIndicator.Builder();
+ method public androidx.wear.protolayout.material.CircularProgressIndicator build();
+ method public androidx.wear.protolayout.material.CircularProgressIndicator.Builder setCircularProgressIndicatorColors(androidx.wear.protolayout.material.ProgressIndicatorColors);
+ method public androidx.wear.protolayout.material.CircularProgressIndicator.Builder setContentDescription(CharSequence);
+ method public androidx.wear.protolayout.material.CircularProgressIndicator.Builder setEndAngle(float);
+ method public androidx.wear.protolayout.material.CircularProgressIndicator.Builder setProgress(@FloatRange(from=0, to=1) float);
+ method public androidx.wear.protolayout.material.CircularProgressIndicator.Builder setStartAngle(float);
+ method public androidx.wear.protolayout.material.CircularProgressIndicator.Builder setStrokeWidth(androidx.wear.protolayout.DimensionBuilders.DpProp);
+ method public androidx.wear.protolayout.material.CircularProgressIndicator.Builder setStrokeWidth(@Dimension(unit=androidx.annotation.Dimension.DP) float);
+ }
+
+ public class Colors {
+ ctor public Colors(@ColorInt int, @ColorInt int, @ColorInt int, @ColorInt int);
+ method @ColorInt public int getOnPrimary();
+ method @ColorInt public int getOnSurface();
+ method @ColorInt public int getPrimary();
+ method @ColorInt public int getSurface();
+ field public static final androidx.wear.protolayout.material.Colors DEFAULT;
+ }
+
+ public class CompactChip implements androidx.wear.protolayout.LayoutElementBuilders.LayoutElement {
+ method public static androidx.wear.protolayout.material.CompactChip? fromLayoutElement(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement);
+ method public androidx.wear.protolayout.material.ChipColors getChipColors();
+ method public androidx.wear.protolayout.ModifiersBuilders.Clickable getClickable();
+ method public String getText();
+ }
+
+ public static final class CompactChip.Builder implements androidx.wear.protolayout.LayoutElementBuilders.LayoutElement.Builder {
+ ctor public CompactChip.Builder(android.content.Context, String, androidx.wear.protolayout.ModifiersBuilders.Clickable, androidx.wear.protolayout.DeviceParametersBuilders.DeviceParameters);
+ method public androidx.wear.protolayout.material.CompactChip build();
+ method public androidx.wear.protolayout.material.CompactChip.Builder setChipColors(androidx.wear.protolayout.material.ChipColors);
+ }
+
+ public class ProgressIndicatorColors {
+ ctor public ProgressIndicatorColors(androidx.wear.protolayout.ColorBuilders.ColorProp, androidx.wear.protolayout.ColorBuilders.ColorProp);
+ ctor public ProgressIndicatorColors(@ColorInt int, @ColorInt int);
+ method public androidx.wear.protolayout.ColorBuilders.ColorProp getIndicatorColor();
+ method public androidx.wear.protolayout.ColorBuilders.ColorProp getTrackColor();
+ method public static androidx.wear.protolayout.material.ProgressIndicatorColors progressIndicatorColors(androidx.wear.protolayout.material.Colors);
+ }
+
+ public class ProgressIndicatorDefaults {
+ field public static final androidx.wear.protolayout.material.ProgressIndicatorColors DEFAULT_COLORS;
+ field public static final androidx.wear.protolayout.DimensionBuilders.DpProp DEFAULT_STROKE_WIDTH;
+ field public static final float GAP_END_ANGLE = 156.1f;
+ field public static final float GAP_START_ANGLE = -156.1f;
+ }
+
+ public class Text implements androidx.wear.protolayout.LayoutElementBuilders.LayoutElement {
+ method public static androidx.wear.protolayout.material.Text? fromLayoutElement(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement);
+ method public androidx.wear.protolayout.ColorBuilders.ColorProp getColor();
+ method public androidx.wear.protolayout.LayoutElementBuilders.FontStyle getFontStyle();
+ method public float getLineHeight();
+ method public int getMaxLines();
+ method public androidx.wear.protolayout.ModifiersBuilders.Modifiers getModifiers();
+ method public int getMultilineAlignment();
+ method public int getOverflow();
+ method public String getText();
+ method public int getWeight();
+ method public boolean isItalic();
+ method public boolean isUnderline();
+ }
+
+ public static final class Text.Builder implements androidx.wear.protolayout.LayoutElementBuilders.LayoutElement.Builder {
+ ctor public Text.Builder(android.content.Context, String);
+ method public androidx.wear.protolayout.material.Text build();
+ method public androidx.wear.protolayout.material.Text.Builder setColor(androidx.wear.protolayout.ColorBuilders.ColorProp);
+ method public androidx.wear.protolayout.material.Text.Builder setItalic(boolean);
+ method public androidx.wear.protolayout.material.Text.Builder setMaxLines(@IntRange(from=1) int);
+ method public androidx.wear.protolayout.material.Text.Builder setModifiers(androidx.wear.protolayout.ModifiersBuilders.Modifiers);
+ method public androidx.wear.protolayout.material.Text.Builder setMultilineAlignment(int);
+ method public androidx.wear.protolayout.material.Text.Builder setOverflow(int);
+ method public androidx.wear.protolayout.material.Text.Builder setTypography(int);
+ method public androidx.wear.protolayout.material.Text.Builder setUnderline(boolean);
+ method public androidx.wear.protolayout.material.Text.Builder setWeight(int);
+ }
+
+ public class TitleChip implements androidx.wear.protolayout.LayoutElementBuilders.LayoutElement {
+ method public static androidx.wear.protolayout.material.TitleChip? fromLayoutElement(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement);
+ method public androidx.wear.protolayout.material.ChipColors getChipColors();
+ method public androidx.wear.protolayout.ModifiersBuilders.Clickable getClickable();
+ method public int getHorizontalAlignment();
+ method public String getText();
+ method public androidx.wear.protolayout.DimensionBuilders.ContainerDimension getWidth();
+ }
+
+ public static final class TitleChip.Builder implements androidx.wear.protolayout.LayoutElementBuilders.LayoutElement.Builder {
+ ctor public TitleChip.Builder(android.content.Context, String, androidx.wear.protolayout.ModifiersBuilders.Clickable, androidx.wear.protolayout.DeviceParametersBuilders.DeviceParameters);
+ method public androidx.wear.protolayout.material.TitleChip build();
+ method public androidx.wear.protolayout.material.TitleChip.Builder setChipColors(androidx.wear.protolayout.material.ChipColors);
+ method public androidx.wear.protolayout.material.TitleChip.Builder setHorizontalAlignment(int);
+ method public androidx.wear.protolayout.material.TitleChip.Builder setWidth(androidx.wear.protolayout.DimensionBuilders.ContainerDimension);
+ method public androidx.wear.protolayout.material.TitleChip.Builder setWidth(@Dimension(unit=androidx.annotation.Dimension.DP) float);
+ }
+
+ public class Typography {
+ field public static final int TYPOGRAPHY_BODY1 = 7; // 0x7
+ field public static final int TYPOGRAPHY_BODY2 = 8; // 0x8
+ field public static final int TYPOGRAPHY_BUTTON = 9; // 0x9
+ field public static final int TYPOGRAPHY_CAPTION1 = 10; // 0xa
+ field public static final int TYPOGRAPHY_CAPTION2 = 11; // 0xb
+ field public static final int TYPOGRAPHY_CAPTION3 = 12; // 0xc
+ field public static final int TYPOGRAPHY_DISPLAY1 = 1; // 0x1
+ field public static final int TYPOGRAPHY_DISPLAY2 = 2; // 0x2
+ field public static final int TYPOGRAPHY_DISPLAY3 = 3; // 0x3
+ field public static final int TYPOGRAPHY_TITLE1 = 4; // 0x4
+ field public static final int TYPOGRAPHY_TITLE2 = 5; // 0x5
+ field public static final int TYPOGRAPHY_TITLE3 = 6; // 0x6
+ }
+
+}
+
+package androidx.wear.protolayout.material.layouts {
+
+ public class EdgeContentLayout implements androidx.wear.protolayout.LayoutElementBuilders.LayoutElement {
+ method public static androidx.wear.protolayout.material.layouts.EdgeContentLayout? fromLayoutElement(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement);
+ method public androidx.wear.protolayout.LayoutElementBuilders.LayoutElement? getContent();
+ method public androidx.wear.protolayout.LayoutElementBuilders.LayoutElement? getEdgeContent();
+ method public androidx.wear.protolayout.LayoutElementBuilders.LayoutElement? getPrimaryLabelTextContent();
+ method public androidx.wear.protolayout.LayoutElementBuilders.LayoutElement? getSecondaryLabelTextContent();
+ }
+
+ public static final class EdgeContentLayout.Builder implements androidx.wear.protolayout.LayoutElementBuilders.LayoutElement.Builder {
+ ctor public EdgeContentLayout.Builder(androidx.wear.protolayout.DeviceParametersBuilders.DeviceParameters);
+ method public androidx.wear.protolayout.material.layouts.EdgeContentLayout build();
+ method public androidx.wear.protolayout.material.layouts.EdgeContentLayout.Builder setContent(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement);
+ method public androidx.wear.protolayout.material.layouts.EdgeContentLayout.Builder setEdgeContent(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement);
+ method public androidx.wear.protolayout.material.layouts.EdgeContentLayout.Builder setPrimaryLabelTextContent(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement);
+ method public androidx.wear.protolayout.material.layouts.EdgeContentLayout.Builder setSecondaryLabelTextContent(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement);
+ }
+
+ public class LayoutDefaults {
+ field public static final androidx.wear.protolayout.DimensionBuilders.DpProp DEFAULT_VERTICAL_SPACER_HEIGHT;
+ field public static final float EDGE_CONTENT_LAYOUT_PADDING_ABOVE_MAIN_CONTENT_DP = 6.0f;
+ field public static final float EDGE_CONTENT_LAYOUT_PADDING_BELOW_MAIN_CONTENT_DP = 8.0f;
+ field public static final int MULTI_BUTTON_MAX_NUMBER = 7; // 0x7
+ field public static final androidx.wear.protolayout.DimensionBuilders.DpProp MULTI_SLOT_LAYOUT_HORIZONTAL_SPACER_WIDTH;
+ }
+
+ public class MultiButtonLayout implements androidx.wear.protolayout.LayoutElementBuilders.LayoutElement {
+ method public static androidx.wear.protolayout.material.layouts.MultiButtonLayout? fromLayoutElement(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement);
+ method public java.util.List<androidx.wear.protolayout.LayoutElementBuilders.LayoutElement!> getButtonContents();
+ method public int getFiveButtonDistribution();
+ field public static final int FIVE_BUTTON_DISTRIBUTION_BOTTOM_HEAVY = 2; // 0x2
+ field public static final int FIVE_BUTTON_DISTRIBUTION_TOP_HEAVY = 1; // 0x1
+ }
+
+ public static final class MultiButtonLayout.Builder implements androidx.wear.protolayout.LayoutElementBuilders.LayoutElement.Builder {
+ ctor public MultiButtonLayout.Builder();
+ method public androidx.wear.protolayout.material.layouts.MultiButtonLayout.Builder addButtonContent(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement);
+ method public androidx.wear.protolayout.material.layouts.MultiButtonLayout build();
+ method public androidx.wear.protolayout.material.layouts.MultiButtonLayout.Builder setFiveButtonDistribution(int);
+ }
+
+ public class MultiSlotLayout implements androidx.wear.protolayout.LayoutElementBuilders.LayoutElement {
+ method public static androidx.wear.protolayout.material.layouts.MultiSlotLayout? fromLayoutElement(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement);
+ method @Dimension(unit=androidx.annotation.Dimension.DP) public float getHorizontalSpacerWidth();
+ method public java.util.List<androidx.wear.protolayout.LayoutElementBuilders.LayoutElement!> getSlotContents();
+ }
+
+ public static final class MultiSlotLayout.Builder implements androidx.wear.protolayout.LayoutElementBuilders.LayoutElement.Builder {
+ ctor public MultiSlotLayout.Builder();
+ method public androidx.wear.protolayout.material.layouts.MultiSlotLayout.Builder addSlotContent(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement);
+ method public androidx.wear.protolayout.material.layouts.MultiSlotLayout build();
+ method public androidx.wear.protolayout.material.layouts.MultiSlotLayout.Builder setHorizontalSpacerWidth(@Dimension(unit=androidx.annotation.Dimension.DP) float);
+ }
+
+ public class PrimaryLayout implements androidx.wear.protolayout.LayoutElementBuilders.LayoutElement {
+ method public static androidx.wear.protolayout.material.layouts.PrimaryLayout? fromLayoutElement(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement);
+ method public androidx.wear.protolayout.LayoutElementBuilders.LayoutElement? getContent();
+ method public androidx.wear.protolayout.LayoutElementBuilders.LayoutElement? getPrimaryChipContent();
+ method public androidx.wear.protolayout.LayoutElementBuilders.LayoutElement? getPrimaryLabelTextContent();
+ method public androidx.wear.protolayout.LayoutElementBuilders.LayoutElement? getSecondaryLabelTextContent();
+ method @Dimension(unit=androidx.annotation.Dimension.DP) public float getVerticalSpacerHeight();
+ }
+
+ public static final class PrimaryLayout.Builder implements androidx.wear.protolayout.LayoutElementBuilders.LayoutElement.Builder {
+ ctor public PrimaryLayout.Builder(androidx.wear.protolayout.DeviceParametersBuilders.DeviceParameters);
+ method public androidx.wear.protolayout.material.layouts.PrimaryLayout build();
+ method public androidx.wear.protolayout.material.layouts.PrimaryLayout.Builder setContent(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement);
+ method public androidx.wear.protolayout.material.layouts.PrimaryLayout.Builder setPrimaryChipContent(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement);
+ method public androidx.wear.protolayout.material.layouts.PrimaryLayout.Builder setPrimaryLabelTextContent(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement);
+ method public androidx.wear.protolayout.material.layouts.PrimaryLayout.Builder setSecondaryLabelTextContent(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement);
+ method public androidx.wear.protolayout.material.layouts.PrimaryLayout.Builder setVerticalSpacerHeight(@Dimension(unit=androidx.annotation.Dimension.DP) float);
+ }
+
+}
+
diff --git a/wear/protolayout/protolayout-material/build.gradle b/wear/protolayout/protolayout-material/build.gradle
index 7241009..fdd52a9 100644
--- a/wear/protolayout/protolayout-material/build.gradle
+++ b/wear/protolayout/protolayout-material/build.gradle
@@ -23,10 +23,46 @@
dependencies {
annotationProcessor(libs.nullaway)
- // Add dependencies here
+ api("androidx.annotation:annotation:1.2.0")
+ implementation(project(":wear:protolayout:protolayout"))
+ implementation(project(":wear:protolayout:protolayout-proto"))
+ implementation(project(":wear:protolayout:protolayout-expression"))
+ androidTestImplementation(libs.junit)
+ androidTestImplementation(libs.testCore)
+ androidTestImplementation(libs.testExtJunit)
+ androidTestImplementation(libs.testRules)
+ androidTestImplementation(libs.testRunner)
+ androidTestImplementation("androidx.core:core:1.7.0")
+ androidTestImplementation(project(":test:screenshot:screenshot"))
+ androidTestImplementation(project(":wear:tiles:tiles-renderer"))
+ androidTestRuntimeOnly(project(path: ":wear:protolayout:protolayout-proto", configuration: "shadow"))
+ androidTestImplementation(libs.protobuf)
+
+ testImplementation(libs.junit)
+ testImplementation(libs.mockitoCore4)
+ testImplementation(libs.robolectric)
+ testImplementation(libs.testExtJunit)
+ testImplementation(libs.testExtTruth)
+ testImplementation(libs.testCore)
+ testImplementation(libs.testRunner)
+ testImplementation(libs.testRules)
+ testImplementation(libs.truth)
}
android {
+ defaultConfig {
+ minSdkVersion 26
+ }
+
+ sourceSets {
+ // TODO(b/268312818): Update to protolayout location and copy images.
+ androidTest.assets.srcDirs += project.rootDir.absolutePath + "/../../golden/wear/wear-tiles-material"
+ }
+
+ defaultConfig {
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ }
+
namespace "androidx.wear.protolayout.material"
}
diff --git a/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/Button.java b/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/Button.java
new file mode 100644
index 0000000..996cae8
--- /dev/null
+++ b/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/Button.java
@@ -0,0 +1,565 @@
+/*
+ * 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.protolayout.material;
+
+import static androidx.annotation.Dimension.DP;
+import static androidx.wear.protolayout.DimensionBuilders.dp;
+import static androidx.wear.protolayout.LayoutElementBuilders.CONTENT_SCALE_MODE_FILL_BOUNDS;
+import static androidx.wear.protolayout.material.ButtonDefaults.DEFAULT_SIZE;
+import static androidx.wear.protolayout.material.ButtonDefaults.EXTRA_LARGE_SIZE;
+import static androidx.wear.protolayout.material.ButtonDefaults.LARGE_SIZE;
+import static androidx.wear.protolayout.material.ButtonDefaults.PRIMARY_COLORS;
+import static androidx.wear.protolayout.material.Helper.checkNotNull;
+import static androidx.wear.protolayout.material.Helper.checkTag;
+import static androidx.wear.protolayout.material.Helper.getMetadataTagName;
+import static androidx.wear.protolayout.material.Helper.getTagBytes;
+import static androidx.wear.protolayout.material.Helper.radiusOf;
+
+import android.content.Context;
+
+import androidx.annotation.Dimension;
+import androidx.annotation.IntDef;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
+import androidx.annotation.RestrictTo.Scope;
+import androidx.wear.protolayout.ColorBuilders.ColorProp;
+import androidx.wear.protolayout.DimensionBuilders.ContainerDimension;
+import androidx.wear.protolayout.DimensionBuilders.DpProp;
+import androidx.wear.protolayout.LayoutElementBuilders.Box;
+import androidx.wear.protolayout.LayoutElementBuilders.ColorFilter;
+import androidx.wear.protolayout.LayoutElementBuilders.Image;
+import androidx.wear.protolayout.LayoutElementBuilders.LayoutElement;
+import androidx.wear.protolayout.ModifiersBuilders;
+import androidx.wear.protolayout.ModifiersBuilders.Background;
+import androidx.wear.protolayout.ModifiersBuilders.Clickable;
+import androidx.wear.protolayout.ModifiersBuilders.Corner;
+import androidx.wear.protolayout.ModifiersBuilders.ElementMetadata;
+import androidx.wear.protolayout.ModifiersBuilders.Modifiers;
+import androidx.wear.protolayout.ModifiersBuilders.Semantics;
+import androidx.wear.protolayout.expression.Fingerprint;
+import androidx.wear.protolayout.material.Typography.TypographyName;
+import androidx.wear.protolayout.proto.LayoutElementProto;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Tiles component {@link Button} that represents clickable button with the given content.
+ *
+ * <p>The Button is circular in shape. The recommended sizes are defined in {@link ButtonDefaults}.
+ *
+ * <p>The recommended set of {@link ButtonColors} styles can be obtained from {@link
+ * ButtonDefaults}., e.g. {@link ButtonDefaults#PRIMARY_COLORS} to get a color scheme for a primary
+ * {@link Button}.
+ *
+ * <p>When accessing the contents of a container for testing, note that this element can't be simply
+ * casted back to the original type, i.e.:
+ *
+ * <pre>{@code
+ * Button button = new Button...
+ * Box box = new Box.Builder().addContent(button).build();
+ *
+ * Button myButton = (Button) box.getContents().get(0);
+ * }</pre>
+ *
+ * will fail.
+ *
+ * <p>To be able to get {@link Button} object from any layout element, {@link #fromLayoutElement}
+ * method should be used, i.e.:
+ *
+ * <pre>{@code
+ * Button myButton = Button.fromLayoutElement(box.getContents().get(0));
+ * }</pre>
+ */
+public class Button implements LayoutElement {
+ /** Tool tag for Metadata in Modifiers, so we know that Box is actually a Button with text. */
+ static final String METADATA_TAG_TEXT = "TXTBTN";
+ /** Tool tag for Metadata in Modifiers, so we know that Box is actually a Button with icon. */
+ static final String METADATA_TAG_ICON = "ICNBTN";
+ /** Tool tag for Metadata in Modifiers, so we know that Box is actually a Button with image. */
+ static final String METADATA_TAG_IMAGE = "IMGBTN";
+ /**
+ * Tool tag for Metadata in Modifiers, so we know that Box is actually a Button with custom
+ * content.
+ */
+ static final String METADATA_TAG_CUSTOM_CONTENT = "CSTBTN";
+
+ @NonNull private final Box mElement;
+
+ Button(@NonNull Box element) {
+ mElement = element;
+ }
+
+ /** Builder class for {@link Button}. */
+ public static final class Builder implements LayoutElement.Builder {
+ private static final int NOT_SET = -1;
+ private static final int ICON = 0;
+ private static final int TEXT = 1;
+ private static final int IMAGE = 2;
+ private static final int CUSTOM_CONTENT = 3;
+
+ @NonNull static final Map<Integer, String> TYPE_TO_TAG = new HashMap<>();
+
+ @RestrictTo(Scope.LIBRARY)
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({NOT_SET, ICON, TEXT, IMAGE, CUSTOM_CONTENT})
+ @interface ButtonType {}
+
+ @NonNull private final Context mContext;
+ @Nullable private LayoutElement mCustomContent;
+ @NonNull private final Clickable mClickable;
+ @NonNull private CharSequence mContentDescription = "";
+ @NonNull private DpProp mSize = DEFAULT_SIZE;
+ @Nullable private String mText = null;
+ @Nullable private Integer mTypographyName = null;
+ @Nullable private String mIcon = null;
+ @Nullable private DpProp mIconSize = null;
+ @Nullable private String mImage = null;
+ @NonNull private ButtonColors mButtonColors = PRIMARY_COLORS;
+ @ButtonType private int mType = NOT_SET;
+
+ static {
+ TYPE_TO_TAG.put(ICON, METADATA_TAG_ICON);
+ TYPE_TO_TAG.put(TEXT, METADATA_TAG_TEXT);
+ TYPE_TO_TAG.put(IMAGE, METADATA_TAG_IMAGE);
+ TYPE_TO_TAG.put(CUSTOM_CONTENT, METADATA_TAG_CUSTOM_CONTENT);
+ }
+
+ /**
+ * Creates a builder for the {@link Button} from the given content. Custom content should be
+ * later set with one of the following ({@link #setIconContent}, {@link #setTextContent},
+ * {@link #setImageContent}.
+ *
+ * @param context The application's context.
+ * @param clickable Associated {@link Clickable} for click events. When the Button is
+ * clicked it will fire the associated action.
+ */
+ public Builder(@NonNull Context context, @NonNull Clickable clickable) {
+ mClickable = clickable;
+ mContext = context;
+ }
+
+ /**
+ * Sets the content description for the {@link Button}. It is highly recommended to provide
+ * this for button containing icon or image.
+ */
+ @NonNull
+ public Builder setContentDescription(@NonNull CharSequence contentDescription) {
+ this.mContentDescription = contentDescription;
+ return this;
+ }
+
+ /**
+ * Sets the size for the {@link Button}. Strongly recommended values are {@link
+ * ButtonDefaults#DEFAULT_SIZE}, {@link ButtonDefaults#LARGE_SIZE} and {@link
+ * ButtonDefaults#EXTRA_LARGE_SIZE}. If not set, {@link ButtonDefaults#DEFAULT_SIZE} will be
+ * used.
+ */
+ @NonNull
+ public Builder setSize(@NonNull DpProp size) {
+ mSize = size;
+ return this;
+ }
+
+ /**
+ * Sets the size for the {@link Button}. Strongly recommended values are {@link
+ * ButtonDefaults#DEFAULT_SIZE}, {@link ButtonDefaults#LARGE_SIZE} and {@link
+ * ButtonDefaults#EXTRA_LARGE_SIZE}. If not set, {@link ButtonDefaults#DEFAULT_SIZE} will be
+ * used.
+ */
+ @NonNull
+ public Builder setSize(@Dimension(unit = DP) float size) {
+ mSize = dp(size);
+ return this;
+ }
+
+ /**
+ * Sets the colors for the {@link Button}. If not set, {@link ButtonDefaults#PRIMARY_COLORS}
+ * will be used.
+ */
+ @NonNull
+ public Builder setButtonColors(@NonNull ButtonColors buttonColors) {
+ mButtonColors = buttonColors;
+ return this;
+ }
+
+ /**
+ * Sets the custom content for this Button. Any previously added content will be overridden.
+ */
+ @NonNull
+ public Builder setCustomContent(@NonNull LayoutElement content) {
+ resetContent();
+ this.mCustomContent = content;
+ this.mType = CUSTOM_CONTENT;
+ return this;
+ }
+
+ /**
+ * Sets the content of this Button to be the given icon with the given size. Any previously
+ * added content will be overridden. Provided icon will be tinted to the given content color
+ * from {@link ButtonColors} and with the given size. This icon should be image with chosen
+ * alpha channel and not an actual image.
+ */
+ @NonNull
+ public Builder setIconContent(@NonNull String imageResourceId, @NonNull DpProp size) {
+ resetContent();
+ this.mIcon = imageResourceId;
+ this.mType = ICON;
+ this.mIconSize = size;
+ return this;
+ }
+
+ /**
+ * Sets the content of this Button to be the given icon with the default size that is half
+ * of the set size of the button. Any previously added content will be overridden. Provided
+ * icon will be tinted to the given content color from {@link ButtonColors}. This icon
+ * should be image with chosen alpha channel and not an actual image.
+ */
+ @NonNull
+ public Builder setIconContent(@NonNull String imageResourceId) {
+ resetContent();
+ this.mIcon = imageResourceId;
+ this.mType = ICON;
+ return this;
+ }
+
+ /**
+ * Sets the content of this Button to be the given text with the default font for the set
+ * size (for the {@link ButtonDefaults#DEFAULT_SIZE}, {@link ButtonDefaults#LARGE_SIZE} and
+ * {@link ButtonDefaults#EXTRA_LARGE_SIZE} is {@link Typography#TYPOGRAPHY_TITLE2}, {@link
+ * Typography#TYPOGRAPHY_TITLE1} and {@link Typography#TYPOGRAPHY_DISPLAY3} respectively).
+ * Any previously added content will be overridden. Text should contain no more than 3
+ * characters, otherwise it will overflow from the edges.
+ */
+ @NonNull
+ public Builder setTextContent(@NonNull String text) {
+ resetContent();
+ this.mText = text;
+ this.mType = TEXT;
+ return this;
+ }
+
+ /**
+ * Sets the content of this Button to be the given text with the given font. If you need
+ * more font related customization, consider using {@link #setCustomContent} with {@link
+ * Text} component. Any previously added content will be overridden. Text should contain no
+ * more than 3 characters, otherwise it will overflow from the edges.
+ */
+ @NonNull
+ public Builder setTextContent(@NonNull String text, @TypographyName int typographyName) {
+ resetContent();
+ this.mText = text;
+ this.mTypographyName = typographyName;
+ this.mType = TEXT;
+ return this;
+ }
+
+ /**
+ * Sets the content of this Button to be the given image, i.e. contacts photo. Any
+ * previously added content will be overridden.
+ */
+ @NonNull
+ public Builder setImageContent(@NonNull String imageResourceId) {
+ resetContent();
+ this.mImage = imageResourceId;
+ this.mType = IMAGE;
+ return this;
+ }
+
+ private void resetContent() {
+ this.mText = null;
+ this.mTypographyName = null;
+ this.mIcon = null;
+ this.mImage = null;
+ this.mCustomContent = null;
+ this.mIconSize = null;
+ }
+
+ /** Constructs and returns {@link Button} with the provided field and look. */
+ @NonNull
+ @Override
+ public Button build() {
+ Modifiers.Builder modifiers =
+ new Modifiers.Builder()
+ .setClickable(mClickable)
+ .setBackground(
+ new Background.Builder()
+ .setColor(mButtonColors.getBackgroundColor())
+ .setCorner(
+ new Corner.Builder()
+ .setRadius(radiusOf(mSize))
+ .build())
+ .build())
+ .setMetadata(
+ new ElementMetadata.Builder()
+ .setTagData(
+ getTagBytes(
+ checkNotNull(TYPE_TO_TAG.get(mType))))
+ .build());
+ if (mContentDescription.length() > 0) {
+ modifiers.setSemantics(
+ new ModifiersBuilders.Semantics.Builder()
+ .setContentDescription(mContentDescription.toString())
+ .build());
+ }
+
+ Box.Builder element =
+ new Box.Builder()
+ .setHeight(mSize)
+ .setWidth(mSize)
+ .setModifiers(modifiers.build());
+
+ element.addContent(getCorrectContent());
+
+ return new Button(element.build());
+ }
+
+ @NonNull
+ private LayoutElement getCorrectContent() {
+ LayoutElement.Builder content;
+ switch (mType) {
+ case ICON:
+ {
+ DpProp iconSize =
+ mIconSize != null
+ ? mIconSize
+ : ButtonDefaults.recommendedIconSize(mSize);
+ content =
+ new Image.Builder()
+ .setResourceId(checkNotNull(mIcon))
+ .setHeight(checkNotNull(iconSize))
+ .setWidth(iconSize)
+ .setContentScaleMode(CONTENT_SCALE_MODE_FILL_BOUNDS)
+ .setColorFilter(
+ new ColorFilter.Builder()
+ .setTint(mButtonColors.getContentColor())
+ .build());
+
+ return content.build();
+ }
+ case TEXT:
+ {
+ @TypographyName
+ int typographyName =
+ mTypographyName != null
+ ? mTypographyName
+ : getDefaultTypographyForSize(mSize);
+ content =
+ new Text.Builder(mContext, checkNotNull(mText))
+ .setMaxLines(1)
+ .setTypography(typographyName)
+ .setColor(mButtonColors.getContentColor());
+
+ return content.build();
+ }
+ case IMAGE:
+ {
+ content =
+ new Image.Builder()
+ .setResourceId(checkNotNull(mImage))
+ .setHeight(mSize)
+ .setWidth(mSize)
+ .setContentScaleMode(CONTENT_SCALE_MODE_FILL_BOUNDS);
+ return content.build();
+ }
+ case CUSTOM_CONTENT:
+ return checkNotNull(mCustomContent);
+ case NOT_SET:
+ // Shouldn't happen.
+ default:
+ // Shouldn't happen.
+ throw new IllegalArgumentException("Wrong Button type");
+ }
+ }
+
+ private static @TypographyName int getDefaultTypographyForSize(@NonNull DpProp size) {
+ if (size.getValue() == LARGE_SIZE.getValue()) {
+ return Typography.TYPOGRAPHY_TITLE1;
+ } else if (size.getValue() == EXTRA_LARGE_SIZE.getValue()) {
+ return Typography.TYPOGRAPHY_DISPLAY3;
+ } else {
+ return Typography.TYPOGRAPHY_TITLE2;
+ }
+ }
+ }
+
+ /**
+ * Returns the custom content of this Button if it has been added. Otherwise, it returns null.
+ */
+ @Nullable
+ public LayoutElement getCustomContent() {
+ if (!getMetadataTag().equals(METADATA_TAG_CUSTOM_CONTENT)) {
+ return null;
+ }
+ return getAnyContent();
+ }
+
+ /** Returns the icon content of this Button if it has been added. Otherwise, it returns null. */
+ @Nullable
+ public String getIconContent() {
+ Image icon = getIconContentObject();
+ return icon != null ? checkNotNull(icon.getResourceId()).getValue() : null;
+ }
+
+ /**
+ * Returns the image content of this Button if it has been added. Otherwise, it returns null.
+ */
+ @Nullable
+ public String getImageContent() {
+ Image image = getImageContentObject();
+ return image != null ? checkNotNull(image.getResourceId()).getValue() : null;
+ }
+
+ /** Returns the text content of this Button if it has been added. Otherwise, it returns null. */
+ @Nullable
+ public String getTextContent() {
+ Text text = getTextContentObject();
+ return text != null ? text.getText() : null;
+ }
+
+ @NonNull
+ private LayoutElement getAnyContent() {
+ return checkNotNull(mElement.getContents().get(0));
+ }
+
+ @Nullable
+ private Image getIconContentObject() {
+ if (!getMetadataTag().equals(METADATA_TAG_ICON)) {
+ return null;
+ }
+ return (Image) getAnyContent();
+ }
+
+ @Nullable
+ private Text getTextContentObject() {
+ if (!getMetadataTag().equals(METADATA_TAG_TEXT)) {
+ return null;
+ }
+ return Text.fromLayoutElement(getAnyContent());
+ }
+
+ @Nullable
+ private Image getImageContentObject() {
+ if (!getMetadataTag().equals(METADATA_TAG_IMAGE)) {
+ return null;
+ }
+ return (Image) getAnyContent();
+ }
+
+ /** Returns click event action associated with this Button. */
+ @NonNull
+ public Clickable getClickable() {
+ return checkNotNull(checkNotNull(mElement.getModifiers()).getClickable());
+ }
+
+ /** Returns content description for this Button. */
+ @Nullable
+ public CharSequence getContentDescription() {
+ Semantics semantics = checkNotNull(mElement.getModifiers()).getSemantics();
+ if (semantics == null) {
+ return null;
+ }
+ return semantics.getContentDescription();
+ }
+
+ /** Returns size for this Button. */
+ @NonNull
+ public ContainerDimension getSize() {
+ return checkNotNull(mElement.getWidth());
+ }
+
+ private ColorProp getBackgroundColor() {
+ return checkNotNull(
+ checkNotNull(checkNotNull(mElement.getModifiers()).getBackground()).getColor());
+ }
+
+ /** Returns button color of this Button. */
+ @NonNull
+ public ButtonColors getButtonColors() {
+ ColorProp backgroundColor = getBackgroundColor();
+ ColorProp contentColor = null;
+
+ switch (getMetadataTag()) {
+ case METADATA_TAG_TEXT:
+ contentColor = checkNotNull(getTextContentObject()).getColor();
+ break;
+ case METADATA_TAG_ICON:
+ contentColor =
+ checkNotNull(checkNotNull(getIconContentObject()).getColorFilter())
+ .getTint();
+ break;
+ case METADATA_TAG_IMAGE:
+ contentColor =
+ checkNotNull(checkNotNull(getImageContentObject()).getColorFilter())
+ .getTint();
+ break;
+ case METADATA_TAG_CUSTOM_CONTENT:
+ break;
+ }
+
+ if (contentColor == null) {
+ contentColor = new ColorProp.Builder(0).build();
+ }
+
+ return new ButtonColors(backgroundColor, contentColor);
+ }
+
+ /** Returns metadata tag set to this Button. */
+ @NonNull
+ String getMetadataTag() {
+ return getMetadataTagName(
+ checkNotNull(checkNotNull(mElement.getModifiers()).getMetadata()));
+ }
+
+ /**
+ * Returns Button object from the given LayoutElement (e.g. one retrieved from a container's
+ * content with {@code container.getContents().get(index)}) if that element can be converted to
+ * Button. Otherwise, it will return null.
+ */
+ @Nullable
+ public static Button fromLayoutElement(@NonNull LayoutElement element) {
+ if (element instanceof Button) {
+ return (Button) element;
+ }
+ if (!(element instanceof Box)) {
+ return null;
+ }
+ Box boxElement = (Box) element;
+ if (!checkTag(boxElement.getModifiers(), Builder.TYPE_TO_TAG.values())) {
+ return null;
+ }
+ // Now we are sure that this element is a Button.
+ return new Button(boxElement);
+ }
+
+ @NonNull
+ @Override
+ @RestrictTo(Scope.LIBRARY_GROUP)
+ public LayoutElementProto.LayoutElement toLayoutElementProto() {
+ return checkNotNull(mElement.toLayoutElementProto());
+ }
+
+ @Nullable
+ @Override
+ @RestrictTo(Scope.LIBRARY_GROUP)
+ public Fingerprint getFingerprint() {
+ return mElement.getFingerprint();
+ }
+}
diff --git a/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/ButtonColors.java b/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/ButtonColors.java
new file mode 100644
index 0000000..3ebfc62
--- /dev/null
+++ b/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/ButtonColors.java
@@ -0,0 +1,89 @@
+/*
+ * 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.protolayout.material;
+
+import static androidx.wear.protolayout.ColorBuilders.argb;
+
+import androidx.annotation.ColorInt;
+import androidx.annotation.NonNull;
+import androidx.wear.protolayout.ColorBuilders.ColorProp;
+
+/**
+ * Represents the background and content colors used in a button Tiles component.
+ *
+ * <p>See {@link ButtonDefaults#PRIMARY_COLORS} for the default colors used in a primary styled
+ * {@link Button}. See {@link ButtonDefaults#SECONDARY_COLORS} for the default colors used in a
+ * secondary styled {@link Button}.
+ */
+public class ButtonColors {
+ @NonNull private final ColorProp mBackgroundColor;
+ @NonNull private final ColorProp mContentColor;
+
+ /**
+ * Constructor for {@link ButtonColors} object.
+ *
+ * @param backgroundColor The background color to be used for a button Tiles component. Should
+ * be in ARGB format.
+ * @param contentColor The content color or tint color to be used for a button Tiles component.
+ * Should be in ARGB format.
+ */
+ public ButtonColors(@ColorInt int backgroundColor, @ColorInt int contentColor) {
+ mBackgroundColor = argb(backgroundColor);
+ mContentColor = argb(contentColor);
+ }
+
+ /**
+ * Constructor for {@link ButtonColors} object.
+ *
+ * @param backgroundColor The background color to be used for a button.
+ * @param contentColor The content color or tint color to be used for a button.
+ */
+ public ButtonColors(@NonNull ColorProp backgroundColor, @NonNull ColorProp contentColor) {
+ mBackgroundColor = backgroundColor;
+ mContentColor = contentColor;
+ }
+
+ /**
+ * Returns a {@link ButtonColors} object, using the current Primary colors from the given {@link
+ * Colors}.
+ */
+ @NonNull
+ public static ButtonColors primaryButtonColors(@NonNull Colors colors) {
+ return new ButtonColors(colors.getPrimary(), colors.getOnPrimary());
+ }
+
+ /**
+ * Returns a {@link ButtonColors} object, using the current Surface colors from the given {@link
+ * Colors}.
+ */
+ @NonNull
+ public static ButtonColors secondaryButtonColors(@NonNull Colors colors) {
+ return new ButtonColors(colors.getSurface(), colors.getOnSurface());
+ }
+
+ /** The background color to be used on a button Tiles components. */
+ @NonNull
+ public ColorProp getBackgroundColor() {
+ return mBackgroundColor;
+ }
+
+ /** The content or tint color to be used on a button Tiles components. */
+ @NonNull
+ public ColorProp getContentColor() {
+ return mContentColor;
+ }
+}
diff --git a/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/ButtonDefaults.java b/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/ButtonDefaults.java
new file mode 100644
index 0000000..e98e212
--- /dev/null
+++ b/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/ButtonDefaults.java
@@ -0,0 +1,60 @@
+/*
+ * 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.protolayout.material;
+
+import static androidx.annotation.Dimension.DP;
+import static androidx.wear.protolayout.DimensionBuilders.dp;
+
+import androidx.annotation.Dimension;
+import androidx.annotation.NonNull;
+import androidx.wear.protolayout.DimensionBuilders.DpProp;
+
+/** Contains the default values used by button Tiles components. */
+public class ButtonDefaults {
+ private ButtonDefaults() {}
+
+ /** The default size for standard {@link Button}. */
+ @NonNull public static final DpProp DEFAULT_SIZE = dp(52);
+
+ /** The recommended size for large {@link Button}. */
+ @NonNull public static final DpProp LARGE_SIZE = dp(60);
+
+ /** The recommended size for extra large {@link Button}. */
+ @NonNull public static final DpProp EXTRA_LARGE_SIZE = dp(88);
+
+ /** Returns the recommended icon size for the given size of a {@link Button}. */
+ @NonNull
+ public static DpProp recommendedIconSize(@NonNull DpProp buttonSize) {
+ return recommendedIconSize(buttonSize.getValue());
+ }
+
+ /** Returns the recommended icon size for the given size of a {@link Button}. */
+ @NonNull
+ public static DpProp recommendedIconSize(@Dimension(unit = DP) float buttonSize) {
+ return dp(buttonSize / 2);
+ }
+
+ /** The recommended colors for a primary {@link Button}. */
+ @NonNull
+ public static final ButtonColors PRIMARY_COLORS =
+ ButtonColors.primaryButtonColors(Colors.DEFAULT);
+
+ /** The recommended colors for a secondary {@link Button}. */
+ @NonNull
+ public static final ButtonColors SECONDARY_COLORS =
+ ButtonColors.secondaryButtonColors(Colors.DEFAULT);
+}
diff --git a/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/Chip.java b/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/Chip.java
new file mode 100644
index 0000000..74732d5
--- /dev/null
+++ b/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/Chip.java
@@ -0,0 +1,682 @@
+/*
+ * 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.protolayout.material;
+
+import static androidx.annotation.Dimension.DP;
+import static androidx.wear.protolayout.DimensionBuilders.dp;
+import static androidx.wear.protolayout.LayoutElementBuilders.HORIZONTAL_ALIGN_CENTER;
+import static androidx.wear.protolayout.LayoutElementBuilders.HORIZONTAL_ALIGN_START;
+import static androidx.wear.protolayout.LayoutElementBuilders.HORIZONTAL_ALIGN_UNDEFINED;
+import static androidx.wear.protolayout.material.ChipDefaults.DEFAULT_HEIGHT;
+import static androidx.wear.protolayout.material.ChipDefaults.DEFAULT_MARGIN_PERCENT;
+import static androidx.wear.protolayout.material.ChipDefaults.HORIZONTAL_PADDING;
+import static androidx.wear.protolayout.material.ChipDefaults.ICON_SIZE;
+import static androidx.wear.protolayout.material.ChipDefaults.ICON_SPACER_WIDTH;
+import static androidx.wear.protolayout.material.ChipDefaults.PRIMARY_COLORS;
+import static androidx.wear.protolayout.material.Helper.checkNotNull;
+import static androidx.wear.protolayout.material.Helper.checkTag;
+import static androidx.wear.protolayout.material.Helper.getMetadataTagName;
+import static androidx.wear.protolayout.material.Helper.getTagBytes;
+import static androidx.wear.protolayout.material.Helper.radiusOf;
+
+import android.content.Context;
+
+import androidx.annotation.Dimension;
+import androidx.annotation.IntDef;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
+import androidx.annotation.RestrictTo.Scope;
+import androidx.wear.protolayout.ColorBuilders.ColorProp;
+import androidx.wear.protolayout.DeviceParametersBuilders.DeviceParameters;
+import androidx.wear.protolayout.DimensionBuilders.ContainerDimension;
+import androidx.wear.protolayout.DimensionBuilders.DpProp;
+import androidx.wear.protolayout.LayoutElementBuilders;
+import androidx.wear.protolayout.LayoutElementBuilders.Box;
+import androidx.wear.protolayout.LayoutElementBuilders.ColorFilter;
+import androidx.wear.protolayout.LayoutElementBuilders.Column;
+import androidx.wear.protolayout.LayoutElementBuilders.HorizontalAlignment;
+import androidx.wear.protolayout.LayoutElementBuilders.Image;
+import androidx.wear.protolayout.LayoutElementBuilders.LayoutElement;
+import androidx.wear.protolayout.LayoutElementBuilders.Row;
+import androidx.wear.protolayout.LayoutElementBuilders.Spacer;
+import androidx.wear.protolayout.ModifiersBuilders.Background;
+import androidx.wear.protolayout.ModifiersBuilders.Clickable;
+import androidx.wear.protolayout.ModifiersBuilders.Corner;
+import androidx.wear.protolayout.ModifiersBuilders.ElementMetadata;
+import androidx.wear.protolayout.ModifiersBuilders.Modifiers;
+import androidx.wear.protolayout.ModifiersBuilders.Padding;
+import androidx.wear.protolayout.ModifiersBuilders.Semantics;
+import androidx.wear.protolayout.expression.Fingerprint;
+import androidx.wear.protolayout.material.Typography.TypographyName;
+import androidx.wear.protolayout.proto.LayoutElementProto;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Tiles component {@link Chip} that represents clickable object with the text, optional label and
+ * optional icon or with custom content.
+ *
+ * <p>The Chip is Stadium shape and has a max height designed to take no more than two lines of text
+ * of {@link Typography#TYPOGRAPHY_BUTTON} style. The {@link Chip} can have an icon horizontally
+ * parallel to the two lines of text. Width of chip can very, and the recommended size is screen
+ * dependent with the recommended margin being applied.
+ *
+ * <p>The recommended set of {@link ChipColors} styles can be obtained from {@link ChipDefaults}.,
+ * e.g. {@link ChipDefaults#PRIMARY_COLORS} to get a color scheme for a primary {@link Chip}.
+ *
+ * <p>When accessing the contents of a container for testing, note that this element can't be simply
+ * casted back to the original type, i.e.:
+ *
+ * <pre>{@code
+ * Chip chip = new Chip...
+ * Box box = new Box.Builder().addContent(chip).build();
+ *
+ * Chip myChip = (Chip) box.getContents().get(0);
+ * }</pre>
+ *
+ * will fail.
+ *
+ * <p>To be able to get {@link Chip} object from any layout element, {@link #fromLayoutElement}
+ * method should be used, i.e.:
+ *
+ * <pre>{@code
+ * Chip myChip = Chip.fromLayoutElement(box.getContents().get(0));
+ * }</pre>
+ *
+ * @see androidx.wear.protolayout.material.layouts.PrimaryLayout.Builder#setContent if this Chip is
+ * used inside of {@link androidx.wear.protolayout.material.layouts.PrimaryLayout}.
+ */
+public class Chip implements LayoutElement {
+ /**
+ * Tool tag for Metadata in Modifiers, so we know that Box is actually a Chip with only text.
+ */
+ static final String METADATA_TAG_TEXT = "TXTCHP";
+ /** Tool tag for Metadata in Modifiers, so we know that Box is actually a Chip with icon. */
+ static final String METADATA_TAG_ICON = "ICNCHP";
+ /**
+ * Tool tag for Metadata in Modifiers, so we know that Box is actually a Chip with custom
+ * content.
+ */
+ static final String METADATA_TAG_CUSTOM_CONTENT = "CSTCHP";
+
+ @NonNull private final Box mElement;
+
+ Chip(@NonNull Box element) {
+ mElement = element;
+ }
+ /** Builder class for {@link androidx.wear.protolayout.material.Chip}. */
+ public static final class Builder implements LayoutElement.Builder {
+ private static final int NOT_SET = 0;
+ private static final int TEXT = 1;
+ private static final int ICON = 2;
+ private static final int CUSTOM_CONTENT = 3;
+
+ @RestrictTo(Scope.LIBRARY)
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({NOT_SET, TEXT, ICON, CUSTOM_CONTENT})
+ @interface ChipType {}
+
+ @NonNull private final Context mContext;
+ @Nullable private LayoutElement mCustomContent;
+ @Nullable private String mImageResourceId = null;
+ @Nullable private String mPrimaryLabel = null;
+ @Nullable private String mSecondaryLabel = null;
+ @NonNull private final Clickable mClickable;
+ @NonNull private CharSequence mContentDescription = "";
+ @NonNull private ContainerDimension mWidth;
+ @NonNull private DpProp mHeight = DEFAULT_HEIGHT;
+ @NonNull private ChipColors mChipColors = PRIMARY_COLORS;
+ @HorizontalAlignment private int mHorizontalAlign = HORIZONTAL_ALIGN_UNDEFINED;
+ @TypographyName private int mPrimaryLabelTypography;
+ @NonNull private DpProp mHorizontalPadding = HORIZONTAL_PADDING;
+ private boolean mIsScalable = true;
+ private int mMaxLines = 0; // 0 indicates that is not set.
+ @NonNull private String mMetadataTag = "";
+
+ @NonNull static final Map<Integer, String> TYPE_TO_TAG = new HashMap<>();
+
+ static {
+ TYPE_TO_TAG.put(ICON, METADATA_TAG_ICON);
+ TYPE_TO_TAG.put(TEXT, METADATA_TAG_TEXT);
+ TYPE_TO_TAG.put(CUSTOM_CONTENT, METADATA_TAG_CUSTOM_CONTENT);
+ }
+
+ /**
+ * Creates a builder for the {@link Chip} with associated action. It is required to add
+ * content later with setters.
+ *
+ * @param context The application's context.
+ * @param clickable Associated {@link Clickable} for click events. When the Chip is clicked
+ * it will fire the associated action.
+ * @param deviceParameters The device parameters used to derive defaults for this Chip.
+ */
+ public Builder(
+ @NonNull Context context,
+ @NonNull Clickable clickable,
+ @NonNull DeviceParameters deviceParameters) {
+ mContext = context;
+ mClickable = clickable;
+ mWidth =
+ dp(
+ (100 - 2 * DEFAULT_MARGIN_PERCENT)
+ * deviceParameters.getScreenWidthDp()
+ / 100);
+ mPrimaryLabelTypography = Typography.TYPOGRAPHY_BUTTON;
+ }
+
+ /**
+ * Sets the width of {@link Chip}. If not set, default value will be set to fill the screen.
+ */
+ @NonNull
+ public Builder setWidth(@NonNull ContainerDimension width) {
+ mWidth = width;
+ return this;
+ }
+
+ /**
+ * Sets the width of {@link TitleChip}. If not set, default value will be set to fill the
+ * screen.
+ */
+ @NonNull
+ public Builder setWidth(@Dimension(unit = DP) float width) {
+ mWidth = dp(width);
+ return this;
+ }
+
+ /**
+ * Sets the custom content for the {@link Chip}. Any previously added content will be
+ * overridden.
+ */
+ @NonNull
+ public Builder setCustomContent(@NonNull LayoutElement content) {
+ this.mCustomContent = content;
+ this.mPrimaryLabel = "";
+ this.mSecondaryLabel = "";
+ this.mImageResourceId = "";
+ return this;
+ }
+
+ /**
+ * Sets the content description for the {@link Chip}. It is highly recommended to provide
+ * this for chip containing icon.
+ */
+ @NonNull
+ public Builder setContentDescription(@NonNull CharSequence contentDescription) {
+ this.mContentDescription = contentDescription;
+ return this;
+ }
+
+ /**
+ * Sets the primary label for the {@link Chip}. Any previously added custom content will be
+ * overridden. Primary label can be on 1 or 2 lines, depending on the length and existence
+ * of secondary label.
+ */
+ @NonNull
+ public Builder setPrimaryLabelContent(@NonNull String primaryLabel) {
+ this.mPrimaryLabel = primaryLabel;
+ this.mCustomContent = null;
+ return this;
+ }
+
+ /**
+ * Used for creating CompactChip and TitleChip.
+ *
+ * <p>Sets the font for the primary label and should only be used internally.
+ */
+ @NonNull
+ Builder setPrimaryLabelTypography(@TypographyName int typography) {
+ this.mPrimaryLabelTypography = typography;
+ return this;
+ }
+
+ /**
+ * Used for creating CompactChip and TitleChip.
+ *
+ * <p>Sets whether the font for the primary label is scalable.
+ */
+ @NonNull
+ Builder setIsPrimaryLabelScalable(boolean isScalable) {
+ this.mIsScalable = isScalable;
+ return this;
+ }
+
+ /**
+ * Sets the secondary label for the {@link Chip}. Any previously added custom content will
+ * be overridden. If secondary label is set, primary label must be set too with {@link
+ * #setPrimaryLabelContent}.
+ */
+ @NonNull
+ public Builder setSecondaryLabelContent(@NonNull String secondaryLabel) {
+ this.mSecondaryLabel = secondaryLabel;
+ this.mCustomContent = null;
+ return this;
+ }
+
+ /**
+ * Sets the icon for the {@link Chip}. Any previously added custom content will be
+ * overridden. Provided icon will be tinted to the given content color from {@link
+ * ChipColors}. This icon should be image with chosen alpha channel and not an actual image.
+ * If icon is set, primary label must be set too with {@link #setPrimaryLabelContent}.
+ */
+ @NonNull
+ public Builder setIconContent(@NonNull String imageResourceId) {
+ this.mImageResourceId = imageResourceId;
+ this.mCustomContent = null;
+ return this;
+ }
+
+ /**
+ * Sets the colors for the {@link Chip}. If set, {@link ChipColors#getBackgroundColor()}
+ * will be used for the background of the button, {@link ChipColors#getContentColor()} for
+ * main text, {@link ChipColors#getSecondaryContentColor()} for label text and {@link
+ * ChipColors#getIconColor()} will be used as color for the icon itself. If not set, {@link
+ * ChipDefaults#PRIMARY_COLORS} will be used.
+ */
+ @NonNull
+ public Builder setChipColors(@NonNull ChipColors chipColors) {
+ mChipColors = chipColors;
+ return this;
+ }
+
+ /**
+ * Sets the horizontal alignment in the chip. It is strongly recommended that the content of
+ * the chip is start-aligned if there is more than primary text in it. By default, {@link
+ * HorizontalAlignment#HORIZONTAL_ALIGN_CENTER} will be used when only a primary label is
+ * present. Otherwise {@link HorizontalAlignment#HORIZONTAL_ALIGN_START} will be used.
+ */
+ @NonNull
+ public Builder setHorizontalAlignment(@HorizontalAlignment int horizontalAlignment) {
+ mHorizontalAlign = horizontalAlignment;
+ return this;
+ }
+
+ /** Used for creating CompactChip and TitleChip. */
+ @NonNull
+ Builder setHorizontalPadding(@NonNull DpProp horizontalPadding) {
+ this.mHorizontalPadding = horizontalPadding;
+ return this;
+ }
+
+ /** Used for creating CompactChip and TitleChip. */
+ @NonNull
+ Builder setHeight(@NonNull DpProp height) {
+ this.mHeight = height;
+ return this;
+ }
+
+ /** Used for creating CompactChip and TitleChip. */
+ @NonNull
+ Builder setMaxLines(int maxLines) {
+ this.mMaxLines = maxLines;
+ return this;
+ }
+
+ /** Used for setting the correct tag in CompactChip and TitleChip. */
+ @NonNull
+ Builder setMetadataTag(@NonNull String metadataTag) {
+ this.mMetadataTag = metadataTag;
+ return this;
+ }
+
+ /** Constructs and returns {@link Chip} with the provided content and look. */
+ @NonNull
+ @Override
+ public Chip build() {
+ Modifiers.Builder modifiers =
+ new Modifiers.Builder()
+ .setClickable(mClickable)
+ .setPadding(
+ new Padding.Builder()
+ .setStart(mHorizontalPadding)
+ .setEnd(mHorizontalPadding)
+ .build())
+ .setBackground(
+ new Background.Builder()
+ .setColor(mChipColors.getBackgroundColor())
+ .setCorner(
+ new Corner.Builder()
+ .setRadius(radiusOf(mHeight))
+ .build())
+ .build())
+ .setMetadata(
+ new ElementMetadata.Builder()
+ .setTagData(getTagBytes(getCorrectMetadataTag()))
+ .build())
+ .setSemantics(
+ new Semantics.Builder()
+ .setContentDescription(getCorrectContentDescription())
+ .build());
+
+ Box.Builder element =
+ new Box.Builder()
+ .setWidth(mWidth)
+ .setHeight(mHeight)
+ .setHorizontalAlignment(getCorrectHorizontalAlignment())
+ .addContent(getCorrectContent())
+ .setModifiers(modifiers.build());
+
+ return new Chip(element.build());
+ }
+
+ @NonNull
+ private String getCorrectContentDescription() {
+ if (mContentDescription.length() == 0) {
+ mContentDescription = "";
+ if (mPrimaryLabel != null) {
+ mContentDescription += mPrimaryLabel;
+ }
+ if (mSecondaryLabel != null) {
+ mContentDescription += "\n" + mSecondaryLabel;
+ }
+ }
+ return mContentDescription.toString();
+ }
+
+ @HorizontalAlignment
+ private int getCorrectHorizontalAlignment() {
+ if (mHorizontalAlign != HORIZONTAL_ALIGN_UNDEFINED) {
+ return mHorizontalAlign;
+ }
+ if (mPrimaryLabel != null && mSecondaryLabel == null && mImageResourceId == null) {
+ return HORIZONTAL_ALIGN_CENTER;
+ } else {
+ return HORIZONTAL_ALIGN_START;
+ }
+ }
+
+ private String getCorrectMetadataTag() {
+ if (!mMetadataTag.isEmpty()) {
+ return mMetadataTag;
+ }
+ if (mCustomContent != null) {
+ return METADATA_TAG_CUSTOM_CONTENT;
+ }
+ if (mImageResourceId != null) {
+ return METADATA_TAG_ICON;
+ }
+ return METADATA_TAG_TEXT;
+ }
+
+ @NonNull
+ private LayoutElement getCorrectContent() {
+ if (mCustomContent != null) {
+ return mCustomContent;
+ }
+
+ Text mainTextElement =
+ new Text.Builder(mContext, checkNotNull(mPrimaryLabel))
+ .setTypography(mPrimaryLabelTypography)
+ .setColor(mChipColors.getContentColor())
+ .setMaxLines(getCorrectMaxLines())
+ .setOverflow(LayoutElementBuilders.TEXT_OVERFLOW_ELLIPSIZE_END)
+ .setMultilineAlignment(LayoutElementBuilders.TEXT_ALIGN_START)
+ .setIsScalable(mIsScalable)
+ .build();
+
+ // Placeholder for text.
+ Column.Builder column =
+ new Column.Builder()
+ .setHorizontalAlignment(HORIZONTAL_ALIGN_START)
+ .addContent(putLayoutInBox(mainTextElement).build());
+
+ if (mSecondaryLabel != null) {
+ Text labelTextElement =
+ new Text.Builder(mContext, mSecondaryLabel)
+ .setTypography(Typography.TYPOGRAPHY_CAPTION2)
+ .setColor(mChipColors.getSecondaryContentColor())
+ .setMaxLines(1)
+ .setOverflow(LayoutElementBuilders.TEXT_OVERFLOW_ELLIPSIZE_END)
+ .setMultilineAlignment(LayoutElementBuilders.TEXT_ALIGN_START)
+ .build();
+ column.addContent(putLayoutInBox(labelTextElement).build());
+ }
+
+ Box texts = putLayoutInBox(column.build()).build();
+ if (mImageResourceId == null) {
+ return texts;
+ } else {
+ return new Row.Builder()
+ .addContent(
+ new Image.Builder()
+ .setResourceId(mImageResourceId)
+ .setWidth(ICON_SIZE)
+ .setHeight(ICON_SIZE)
+ .setColorFilter(
+ new ColorFilter.Builder()
+ .setTint(mChipColors.getIconColor())
+ .build())
+ .build())
+ .addContent(
+ new Spacer.Builder()
+ .setHeight(mHeight)
+ .setWidth(ICON_SPACER_WIDTH)
+ .build())
+ .addContent(texts)
+ .setVerticalAlignment(LayoutElementBuilders.VERTICAL_ALIGN_CENTER)
+ .build();
+ }
+ }
+
+ private int getCorrectMaxLines() {
+ if (mMaxLines > 0) {
+ return mMaxLines;
+ }
+ return mSecondaryLabel != null ? 1 : 2;
+ }
+
+ private Box.Builder putLayoutInBox(@NonNull LayoutElement element) {
+ // Wrapped and centered content are default.
+ return new Box.Builder().addContent(element);
+ }
+ }
+
+ /** Returns height of this Chip. */
+ @NonNull
+ public ContainerDimension getHeight() {
+ return checkNotNull(mElement.getHeight());
+ }
+
+ /** Returns width of this Chip. */
+ @NonNull
+ public ContainerDimension getWidth() {
+ return checkNotNull(mElement.getWidth());
+ }
+
+ /** Returns click event action associated with this Chip. */
+ @NonNull
+ public Clickable getClickable() {
+ return checkNotNull(checkNotNull(mElement.getModifiers()).getClickable());
+ }
+
+ /** Returns background color of this Chip. */
+ @NonNull
+ private ColorProp getBackgroundColor() {
+ return checkNotNull(
+ checkNotNull(checkNotNull(mElement.getModifiers()).getBackground()).getColor());
+ }
+
+ /** Returns chip colors of this Chip. */
+ @NonNull
+ public ChipColors getChipColors() {
+ ColorProp backgroundColor = getBackgroundColor();
+ ColorProp contentColor = null;
+ ColorProp secondaryContentColor = null;
+ ColorProp iconTintColor = null;
+
+ if (!getMetadataTag().equals(METADATA_TAG_CUSTOM_CONTENT)) {
+ if (getMetadataTag().equals(METADATA_TAG_ICON)) {
+ Image icon = checkNotNull(getIconContentObject());
+ iconTintColor = checkNotNull(checkNotNull(icon.getColorFilter()).getTint());
+ }
+
+ contentColor = checkNotNull(getPrimaryLabelContentObject()).getColor();
+ Text label = getSecondaryLabelContentObject();
+ if (label != null) {
+ secondaryContentColor = label.getColor();
+ }
+ }
+
+ // Populate other colors if they are not found.
+ if (contentColor == null) {
+ contentColor = new ColorProp.Builder(0).build();
+ }
+ if (secondaryContentColor == null) {
+ secondaryContentColor = contentColor;
+ }
+ if (iconTintColor == null) {
+ iconTintColor = contentColor;
+ }
+
+ return new ChipColors(backgroundColor, iconTintColor, contentColor, secondaryContentColor);
+ }
+
+ /** Returns content description of this Chip. */
+ @Nullable
+ public CharSequence getContentDescription() {
+ Semantics semantics = checkNotNull(mElement.getModifiers()).getSemantics();
+ if (semantics == null) {
+ return null;
+ }
+ return semantics.getContentDescription();
+ }
+
+ /** Returns custom content from this Chip if it has been added. Otherwise, it returns null. */
+ @Nullable
+ public LayoutElement getCustomContent() {
+ if (getMetadataTag().equals(METADATA_TAG_CUSTOM_CONTENT)) {
+ return checkNotNull(checkNotNull(mElement.getContents()).get(0));
+ }
+ return null;
+ }
+
+ /** Returns primary label from this Chip if it has been added. Otherwise, it returns null. */
+ @Nullable
+ public String getPrimaryLabelContent() {
+ Text primaryLabel = getPrimaryLabelContentObject();
+ return primaryLabel != null ? primaryLabel.getText() : null;
+ }
+
+ /** Returns secondary label from this Chip if it has been added. Otherwise, it returns null. */
+ @Nullable
+ public String getSecondaryLabelContent() {
+ Text label = getSecondaryLabelContentObject();
+ return label != null ? label.getText() : null;
+ }
+
+ /** Returns icon id from this Chip if it has been added. Otherwise, it returns null. */
+ @Nullable
+ public String getIconContent() {
+ Image icon = getIconContentObject();
+ return icon != null ? checkNotNull(icon.getResourceId()).getValue() : null;
+ }
+
+ @Nullable
+ private Text getPrimaryLabelContentObject() {
+ return getPrimaryOrSecondaryLabelContent(0);
+ }
+
+ @Nullable
+ private Text getSecondaryLabelContentObject() {
+ return getPrimaryOrSecondaryLabelContent(1);
+ }
+
+ @Nullable
+ private Image getIconContentObject() {
+ if (!getMetadataTag().equals(METADATA_TAG_ICON)) {
+ return null;
+ }
+ return ((Image) ((Row) mElement.getContents().get(0)).getContents().get(0));
+ }
+
+ @Nullable
+ private Text getPrimaryOrSecondaryLabelContent(int index) {
+ String metadataTag = getMetadataTag();
+ if (metadataTag.equals(METADATA_TAG_CUSTOM_CONTENT)) {
+ return null;
+ }
+ // In any other case, text (either primary or primary + label) must be present.
+ Column content;
+ if (metadataTag.equals(METADATA_TAG_ICON)) {
+ content =
+ (Column)
+ ((Box) ((Row) mElement.getContents().get(0)).getContents().get(2))
+ .getContents()
+ .get(0);
+ } else {
+ content = (Column) ((Box) mElement.getContents().get(0)).getContents().get(0);
+ }
+
+ // We need to check this as this can be the case when we called for label, which doesn't
+ // exist.
+ return index < content.getContents().size()
+ ? Text.fromLayoutElement(
+ ((Box) content.getContents().get(index)).getContents().get(0))
+ : null;
+ }
+
+ /** Returns the horizontal alignment of the content in this Chip. */
+ @HorizontalAlignment
+ public int getHorizontalAlignment() {
+ return checkNotNull(mElement.getHorizontalAlignment()).getValue();
+ }
+
+ /** Returns metadata tag set to this Chip. */
+ @NonNull
+ String getMetadataTag() {
+ return getMetadataTagName(
+ checkNotNull(checkNotNull(mElement.getModifiers()).getMetadata()));
+ }
+
+ /**
+ * Returns Chip object from the given LayoutElement (e.g. one retrieved from a container's
+ * content with {@code container.getContents().get(index)}) if that element can be converted to
+ * Chip. Otherwise, it will return null.
+ */
+ @Nullable
+ public static Chip fromLayoutElement(@NonNull LayoutElement element) {
+ if (element instanceof Chip) {
+ return (Chip) element;
+ }
+ if (!(element instanceof Box)) {
+ return null;
+ }
+ Box boxElement = (Box) element;
+ if (!checkTag(boxElement.getModifiers(), Builder.TYPE_TO_TAG.values())) {
+ return null;
+ }
+ // Now we are sure that this element is a Chip.
+ return new Chip(boxElement);
+ }
+
+ @NonNull
+ @Override
+ @RestrictTo(Scope.LIBRARY_GROUP)
+ public LayoutElementProto.LayoutElement toLayoutElementProto() {
+ return mElement.toLayoutElementProto();
+ }
+
+ @Nullable
+ @Override
+ @RestrictTo(Scope.LIBRARY_GROUP)
+ public Fingerprint getFingerprint() {
+ return mElement.getFingerprint();
+ }
+}
diff --git a/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/ChipColors.java b/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/ChipColors.java
new file mode 100644
index 0000000..a24e6c4
--- /dev/null
+++ b/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/ChipColors.java
@@ -0,0 +1,150 @@
+/*
+ * 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.protolayout.material;
+
+import static androidx.wear.protolayout.ColorBuilders.argb;
+
+import androidx.annotation.ColorInt;
+import androidx.annotation.NonNull;
+import androidx.wear.protolayout.ColorBuilders.ColorProp;
+
+/**
+ * Represents the background and content colors used in a chip Tiles component.
+ *
+ * <p>See {@link ChipDefaults#PRIMARY_COLORS} for the default colors used in a primary styled {@link
+ * Chip}. See {@link ChipDefaults#SECONDARY_COLORS} for the default colors used in a secondary
+ * styled {@link Chip}.
+ */
+public class ChipColors {
+ @NonNull private final ColorProp mBackgroundColor;
+ @NonNull private final ColorProp mIconColor;
+ @NonNull private final ColorProp mContentColor;
+ @NonNull private final ColorProp mSecondaryContentColor;
+
+ /**
+ * Constructor for the {@link ChipColors} object.
+ *
+ * @param backgroundColor The background color to be used for a chip Tiles component. Should be
+ * in ARGB format.
+ * @param iconColor The color to be used for an icon in a chip Tiles component. Should be in
+ * ARGB format.
+ * @param contentColor The text color to be used for a main text in a chip Tiles component.
+ * Should be in ARGB format.
+ * @param secondaryContentColor The text color to be used for a label text in a chip Tiles
+ * component. Should be in ARGB format.
+ */
+ public ChipColors(
+ @ColorInt int backgroundColor,
+ @ColorInt int iconColor,
+ @ColorInt int contentColor,
+ @ColorInt int secondaryContentColor) {
+ mBackgroundColor = argb(backgroundColor);
+ mIconColor = argb(iconColor);
+ mContentColor = argb(contentColor);
+ mSecondaryContentColor = argb(secondaryContentColor);
+ }
+
+ /**
+ * Constructor for the {@link ChipColors} object.
+ *
+ * @param backgroundColor The background color to be used for a chip Tiles component. Should be
+ * in ARGB format.
+ * @param contentColor The content color to be used for all items inside a chip Tiles component.
+ * Should be in ARGB format.
+ */
+ public ChipColors(@ColorInt int backgroundColor, @ColorInt int contentColor) {
+ mBackgroundColor = argb(backgroundColor);
+ mIconColor = argb(contentColor);
+ mContentColor = argb(contentColor);
+ mSecondaryContentColor = argb(contentColor);
+ }
+
+ /**
+ * Constructor for the {@link ChipColors} object.
+ *
+ * @param backgroundColor The background color to be used for a chip Tiles component.
+ * @param iconColor The color to be used for an icon in a chip Tiles component.
+ * @param contentColor The text color to be used for a main text in a chip Tiles component.
+ * @param secondaryContentColor The text color to be used for a label text in a chip Tiles
+ * component.
+ */
+ public ChipColors(
+ @NonNull ColorProp backgroundColor,
+ @NonNull ColorProp iconColor,
+ @NonNull ColorProp contentColor,
+ @NonNull ColorProp secondaryContentColor) {
+ mBackgroundColor = backgroundColor;
+ mIconColor = iconColor;
+ mContentColor = contentColor;
+ mSecondaryContentColor = secondaryContentColor;
+ }
+
+ /**
+ * Constructor for the {@link ChipColors} object.
+ *
+ * @param backgroundColor The background color to be used for a chip Tiles component.
+ * @param contentColor The content color to be used for all items inside a chip Tiles component.
+ */
+ public ChipColors(@NonNull ColorProp backgroundColor, @NonNull ColorProp contentColor) {
+ mBackgroundColor = backgroundColor;
+ mIconColor = contentColor;
+ mContentColor = contentColor;
+ mSecondaryContentColor = contentColor;
+ }
+
+ /**
+ * Returns a {@link ChipColors} object, using the current Primary colors from the given {@link
+ * Colors}.
+ */
+ @NonNull
+ public static ChipColors primaryChipColors(@NonNull Colors colors) {
+ return new ChipColors(colors.getPrimary(), colors.getOnPrimary());
+ }
+
+ /**
+ * Returns a {@link ChipColors} object, using the current Surface colors from the given {@link
+ * Colors}.
+ */
+ @NonNull
+ public static ChipColors secondaryChipColors(@NonNull Colors colors) {
+ return new ChipColors(colors.getSurface(), colors.getOnSurface());
+ }
+
+ /** The background color to be used on a chip Tiles components. */
+ @NonNull
+ public ColorProp getBackgroundColor() {
+ return mBackgroundColor;
+ }
+
+ /** The icon color to be used on a chip Tiles components. */
+ @NonNull
+ public ColorProp getIconColor() {
+ return mIconColor;
+ }
+
+ /** The main text color to be used on a chip Tiles components. */
+ @NonNull
+ public ColorProp getContentColor() {
+ return mContentColor;
+ }
+
+ /** The label text color to be used on a chip Tiles components. */
+ @NonNull
+ public ColorProp getSecondaryContentColor() {
+ return mSecondaryContentColor;
+ }
+}
diff --git a/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/ChipDefaults.java b/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/ChipDefaults.java
new file mode 100644
index 0000000..8059369
--- /dev/null
+++ b/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/ChipDefaults.java
@@ -0,0 +1,137 @@
+/*
+ * 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.protolayout.material;
+
+import static androidx.wear.protolayout.DimensionBuilders.dp;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
+import androidx.annotation.RestrictTo.Scope;
+import androidx.wear.protolayout.DimensionBuilders.DpProp;
+
+/** Contains the default values used by chip Tiles components. */
+public class ChipDefaults {
+ private ChipDefaults() {}
+
+ /**
+ * The default height for standard {@link Chip}
+ *
+ */
+ @RestrictTo(Scope.LIBRARY_GROUP)
+ @NonNull
+ public static final DpProp DEFAULT_HEIGHT = dp(52);
+
+ /**
+ * The default height for standard {@link CompactChip}
+ *
+ */
+ @RestrictTo(Scope.LIBRARY_GROUP)
+ @NonNull
+ public static final DpProp COMPACT_HEIGHT = dp(32);
+
+ /**
+ * The default height of tappable area for standard {@link CompactChip}
+ *
+ */
+ @RestrictTo(Scope.LIBRARY_GROUP)
+ @NonNull
+ public static final DpProp COMPACT_HEIGHT_TAPPABLE = dp(48);
+
+ /**
+ * The default height for standard {@link TitleChip}
+ *
+ */
+ @RestrictTo(Scope.LIBRARY_GROUP)
+ @NonNull
+ public static final DpProp TITLE_HEIGHT = dp(60);
+
+ /**
+ * The recommended horizontal margin used for width for standard {@link Chip}
+ *
+ */
+ @RestrictTo(Scope.LIBRARY_GROUP)
+ public static final float DEFAULT_MARGIN_PERCENT = 5.2f;
+
+ /**
+ * The recommended horizontal padding for standard {@link Chip}
+ *
+ */
+ @RestrictTo(Scope.LIBRARY_GROUP)
+ @NonNull
+ public static final DpProp HORIZONTAL_PADDING = dp(14);
+
+ /**
+ * The recommended horizontal padding for standard {@link CompactChip}
+ *
+ */
+ @RestrictTo(Scope.LIBRARY_GROUP)
+ @NonNull
+ public static final DpProp COMPACT_HORIZONTAL_PADDING = dp(12);
+
+ /**
+ * The recommended horizontal padding for standard {@link TitleChip}
+ *
+ */
+ @RestrictTo(Scope.LIBRARY_GROUP)
+ @NonNull
+ public static final DpProp TITLE_HORIZONTAL_PADDING = dp(16);
+
+ /**
+ * The recommended vertical space between icon and text in standard {@link Chip}
+ *
+ */
+ @RestrictTo(Scope.LIBRARY_GROUP)
+ @NonNull
+ public static final DpProp ICON_SPACER_WIDTH = dp(6);
+
+ /**
+ * The icon size used in standard {@link Chip}
+ *
+ */
+ @RestrictTo(Scope.LIBRARY_GROUP)
+ @NonNull
+ public static final DpProp ICON_SIZE = dp(24);
+
+ /** The recommended colors for a primary {@link Chip}. */
+ @NonNull
+ public static final ChipColors PRIMARY_COLORS = ChipColors.primaryChipColors(Colors.DEFAULT);
+
+ /** The recommended colors for a secondary {@link Chip}. */
+ @NonNull
+ public static final ChipColors SECONDARY_COLORS =
+ ChipColors.secondaryChipColors(Colors.DEFAULT);
+
+ /** The recommended colors for a primary {@link CompactChip}. */
+ @NonNull
+ public static final ChipColors COMPACT_PRIMARY_COLORS =
+ ChipColors.primaryChipColors(Colors.DEFAULT);
+
+ /** The recommended colors for a secondary {@link CompactChip}. */
+ @NonNull
+ public static final ChipColors COMPACT_SECONDARY_COLORS =
+ ChipColors.secondaryChipColors(Colors.DEFAULT);
+
+ /** The recommended colors for a primary {@link TitleChip}. */
+ @NonNull
+ public static final ChipColors TITLE_PRIMARY_COLORS =
+ ChipColors.primaryChipColors(Colors.DEFAULT);
+
+ /** The recommended colors for a secondary {@link TitleChip}. */
+ @NonNull
+ public static final ChipColors TITLE_SECONDARY_COLORS =
+ ChipColors.secondaryChipColors(Colors.DEFAULT);
+}
diff --git a/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/CircularProgressIndicator.java b/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/CircularProgressIndicator.java
new file mode 100644
index 0000000..820be12
--- /dev/null
+++ b/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/CircularProgressIndicator.java
@@ -0,0 +1,354 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.protolayout.material;
+
+import static androidx.annotation.Dimension.DP;
+import static androidx.wear.protolayout.DimensionBuilders.degrees;
+import static androidx.wear.protolayout.DimensionBuilders.dp;
+import static androidx.wear.protolayout.material.Helper.checkNotNull;
+import static androidx.wear.protolayout.material.Helper.checkTag;
+import static androidx.wear.protolayout.material.Helper.getMetadataTagName;
+import static androidx.wear.protolayout.material.Helper.getTagBytes;
+import static androidx.wear.protolayout.material.ProgressIndicatorDefaults.DEFAULT_COLORS;
+import static androidx.wear.protolayout.material.ProgressIndicatorDefaults.DEFAULT_END_ANGLE;
+import static androidx.wear.protolayout.material.ProgressIndicatorDefaults.DEFAULT_PADDING;
+import static androidx.wear.protolayout.material.ProgressIndicatorDefaults.DEFAULT_START_ANGLE;
+import static androidx.wear.protolayout.material.ProgressIndicatorDefaults.DEFAULT_STROKE_WIDTH;
+
+import androidx.annotation.Dimension;
+import androidx.annotation.FloatRange;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
+import androidx.annotation.RestrictTo.Scope;
+import androidx.wear.protolayout.DimensionBuilders.DegreesProp;
+import androidx.wear.protolayout.DimensionBuilders.DpProp;
+import androidx.wear.protolayout.LayoutElementBuilders;
+import androidx.wear.protolayout.LayoutElementBuilders.Arc;
+import androidx.wear.protolayout.LayoutElementBuilders.ArcLine;
+import androidx.wear.protolayout.LayoutElementBuilders.ArcSpacer;
+import androidx.wear.protolayout.LayoutElementBuilders.LayoutElement;
+import androidx.wear.protolayout.ModifiersBuilders;
+import androidx.wear.protolayout.ModifiersBuilders.ElementMetadata;
+import androidx.wear.protolayout.ModifiersBuilders.Modifiers;
+import androidx.wear.protolayout.ModifiersBuilders.Padding;
+import androidx.wear.protolayout.ModifiersBuilders.Semantics;
+import androidx.wear.protolayout.expression.Fingerprint;
+import androidx.wear.protolayout.proto.LayoutElementProto;
+
+/**
+ * Tiles component {@link CircularProgressIndicator} that represents circular progress indicator
+ * which supports a gap in the circular track between startAngle and endAngle. [Progress Indicator
+ * doc] (https://developer.android.com/training/wearables/components/progress-indicator)
+ *
+ * <p>The CircularProgressIndicator is a colored arc around the edge of the screen with the given
+ * start and end angles, which can describe a full or partial circle. Behind it is an arc with
+ * optional gap representing full progress. The recommended sizes are defined in {@link
+ * ProgressIndicatorDefaults}. Unless specified, the CircularProgressIndicator will have the full
+ * length.
+ *
+ * <p>The recommended set of {@link ProgressIndicatorColors} can be obtained from {@link
+ * ProgressIndicatorDefaults}, e.g. {@link ProgressIndicatorDefaults#DEFAULT_COLORS} to get a
+ * default color scheme for a {@link CircularProgressIndicator}.
+ *
+ * <p>When accessing the contents of a container for testing, note that this element can't be simply
+ * casted back to the original type, i.e.:
+ *
+ * <pre>{@code
+ * CircularProgressIndicator cpi = new CircularProgressIndicator...
+ * Box box = new Box.Builder().addContent(cpi).build();
+ *
+ * CircularProgressIndicator myCpi = (CircularProgressIndicator) box.getContents().get(0);
+ * }</pre>
+ *
+ * will fail.
+ *
+ * <p>To be able to get {@link CircularProgressIndicator} object from any layout element, {@link
+ * #fromLayoutElement} method should be used, i.e.:
+ *
+ * <pre>{@code
+ * CircularProgressIndicator myCpi =
+ * CircularProgressIndicator.fromLayoutElement(box.getContents().get(0));
+ * }</pre>
+ */
+public class CircularProgressIndicator implements LayoutElement {
+ /**
+ * Tool tag for Metadata in Modifiers, so we know that Arc is actually a
+ * CircularProgressIndicator.
+ */
+ static final String METADATA_TAG = "CPI";
+
+ @NonNull private final Arc mElement;
+ @NonNull private final ArcLine mProgress;
+ @NonNull private final ArcLine mBackground;
+
+ CircularProgressIndicator(@NonNull Arc element) {
+ this.mElement = element;
+ this.mBackground = (ArcLine) element.getContents().get(0);
+ this.mProgress = (ArcLine) element.getContents().get(2);
+ }
+
+ /** Builder class for {@link CircularProgressIndicator} */
+ public static final class Builder implements LayoutElement.Builder {
+ @NonNull private ProgressIndicatorColors mCircularProgressIndicatorColors = DEFAULT_COLORS;
+ @NonNull private DpProp mStrokeWidth = DEFAULT_STROKE_WIDTH;
+ @NonNull private CharSequence mContentDescription = "";
+ @NonNull private DegreesProp mStartAngle = degrees(DEFAULT_START_ANGLE);
+ @NonNull private DegreesProp mEndAngle = degrees(DEFAULT_END_ANGLE);
+
+ @FloatRange(from = 0, to = 1)
+ private float mProgress = 0;
+
+ /** Creates a builder for the {@link CircularProgressIndicator}. */
+ public Builder() {}
+
+ /**
+ * Sets the progress of the {@link CircularProgressIndicator}. Progress should be percentage
+ * from 0 to 1. Progress will be colored in {@link ProgressIndicatorColors#getTrackColor}.
+ * If not set, 0 will be used.
+ */
+ @NonNull
+ public Builder setProgress(@FloatRange(from = 0, to = 1) float progressPercentage) {
+ this.mProgress = progressPercentage;
+ return this;
+ }
+
+ /**
+ * Sets the start angle of the {@link CircularProgressIndicator}'s background arc, where
+ * angle 0 is 12 o'clock. Start angle doesn't need to be within 0-360 range. I.e. -90 is to
+ * start arc from the 9 o'clock. If not set 0 will be used and the indicator will have full
+ * length.
+ */
+ @NonNull
+ public Builder setStartAngle(float startAngle) {
+ this.mStartAngle = degrees(startAngle);
+ return this;
+ }
+
+ /**
+ * Sets the end angle of the {@link CircularProgressIndicator}'s background arc, where angle
+ * 0 is 12 o'clock. End angle doesn't need to be within 0-360 range, but it must be larger
+ * than start angle. If not set 360 will be used and the indicator will have full length.
+ */
+ @NonNull
+ public Builder setEndAngle(float endAngle) {
+ this.mEndAngle = degrees(endAngle);
+ return this;
+ }
+
+ /**
+ * Sets the content description of the {@link CircularProgressIndicator} to be used for
+ * accessibility support.
+ */
+ @NonNull
+ public Builder setContentDescription(@NonNull CharSequence contentDescription) {
+ this.mContentDescription = contentDescription;
+ return this;
+ }
+
+ /**
+ * Sets the colors for the {@link CircularProgressIndicator}. If set, {@link
+ * ProgressIndicatorColors#getIndicatorColor()} will be used for a progress that has been
+ * made, while {@link ProgressIndicatorColors#getTrackColor()} will be used for a background
+ * full size arc. If not set, {@link ProgressIndicatorDefaults#DEFAULT_COLORS} will be used.
+ */
+ @NonNull
+ public Builder setCircularProgressIndicatorColors(
+ @NonNull ProgressIndicatorColors circularProgressIndicatorColors) {
+ this.mCircularProgressIndicatorColors = circularProgressIndicatorColors;
+ return this;
+ }
+
+ /**
+ * Sets the stroke width of the {@link CircularProgressIndicator}. Strongly recommended
+ * value is {@link ProgressIndicatorDefaults#DEFAULT_STROKE_WIDTH}.
+ */
+ @NonNull
+ public Builder setStrokeWidth(@NonNull DpProp strokeWidth) {
+ this.mStrokeWidth = strokeWidth;
+ return this;
+ }
+
+ /**
+ * Sets the stroke width of the {@link CircularProgressIndicator}. Strongly recommended
+ * value is {@link ProgressIndicatorDefaults#DEFAULT_STROKE_WIDTH}.
+ */
+ @NonNull
+ public Builder setStrokeWidth(@Dimension(unit = DP) float strokeWidth) {
+ this.mStrokeWidth = dp(strokeWidth);
+ return this;
+ }
+
+ /**
+ * Constructs and returns {@link CircularProgressIndicator} with the provided field and
+ * look.
+ */
+ @NonNull
+ @Override
+ public CircularProgressIndicator build() {
+ checkAngles();
+
+ DegreesProp length = getLength();
+ Modifiers.Builder modifiers =
+ new Modifiers.Builder()
+ .setPadding(new Padding.Builder().setAll(DEFAULT_PADDING).build())
+ .setMetadata(
+ new ElementMetadata.Builder()
+ .setTagData(getTagBytes(METADATA_TAG))
+ .build());
+
+ if (mContentDescription.length() > 0) {
+ modifiers.setSemantics(
+ new ModifiersBuilders.Semantics.Builder()
+ .setContentDescription(mContentDescription.toString())
+ .build());
+ }
+
+ Arc.Builder element =
+ new Arc.Builder()
+ .setAnchorType(LayoutElementBuilders.ARC_ANCHOR_START)
+ .setAnchorAngle(mStartAngle)
+ .setModifiers(modifiers.build())
+ .addContent(
+ new ArcLine.Builder()
+ .setColor(
+ mCircularProgressIndicatorColors
+ .getTrackColor())
+ .setThickness(mStrokeWidth)
+ .setLength(length)
+ .build())
+ .addContent(
+ // Fill in the space to make a full circle, so that progress is
+ // correctly aligned.
+ new ArcSpacer.Builder()
+ .setLength(degrees(360 - length.getValue()))
+ .build())
+ .addContent(
+ new ArcLine.Builder()
+ .setColor(
+ mCircularProgressIndicatorColors
+ .getIndicatorColor())
+ .setThickness(mStrokeWidth)
+ .setLength(degrees(mProgress * length.getValue()))
+ .build());
+ return new CircularProgressIndicator(element.build());
+ }
+
+ private void checkAngles() {
+ if (mEndAngle.getValue() < mStartAngle.getValue()) {
+ throw new IllegalArgumentException("End angle must be bigger than start angle.");
+ }
+ }
+
+ @NonNull
+ private DegreesProp getLength() {
+ float startAngle = mStartAngle.getValue();
+ float endAngle = mEndAngle.getValue();
+ if (endAngle <= startAngle) {
+ endAngle += 360;
+ }
+ return degrees(endAngle - startAngle);
+ }
+ }
+
+ /** Returns angle representing progressed part of this CircularProgressIndicator. */
+ @NonNull
+ public DegreesProp getProgress() {
+ return checkNotNull(mProgress.getLength());
+ }
+
+ /** Returns stroke width of this CircularProgressIndicator. */
+ @NonNull
+ public DpProp getStrokeWidth() {
+ return checkNotNull(mProgress.getThickness());
+ }
+
+ /** Returns start angle of this CircularProgressIndicator. */
+ @NonNull
+ public DegreesProp getStartAngle() {
+ return checkNotNull(mElement.getAnchorAngle());
+ }
+
+ /** Returns start angle of this CircularProgressIndicator. */
+ @NonNull
+ public DegreesProp getEndAngle() {
+ float backArcLength = checkNotNull(mBackground.getLength()).getValue();
+ return degrees(getStartAngle().getValue() + backArcLength);
+ }
+
+ /** Returns main arc color of this CircularProgressIndicator. */
+ @NonNull
+ public ProgressIndicatorColors getCircularProgressIndicatorColors() {
+ return new ProgressIndicatorColors(
+ checkNotNull(mProgress.getColor()), checkNotNull(mBackground.getColor()));
+ }
+
+ /** Returns content description of this CircularProgressIndicator. */
+ @Nullable
+ public CharSequence getContentDescription() {
+ Semantics semantics = checkNotNull(mElement.getModifiers()).getSemantics();
+ if (semantics == null) {
+ return null;
+ }
+ return semantics.getContentDescription();
+ }
+
+ /**
+ * Returns metadata tag set to this CircularProgressIndicator, which should be {@link
+ * #METADATA_TAG}.
+ */
+ @NonNull
+ String getMetadataTag() {
+ return getMetadataTagName(
+ checkNotNull(checkNotNull(mElement.getModifiers()).getMetadata()));
+ }
+
+ /**
+ * Returns CircularProgressIndicator object from the given LayoutElement (e.g. one retrieved
+ * from a container's content with {@code container.getContents().get(index)}) if that element
+ * can be converted to CircularProgressIndicator. Otherwise, it will return null.
+ */
+ @Nullable
+ public static CircularProgressIndicator fromLayoutElement(@NonNull LayoutElement element) {
+ if (element instanceof CircularProgressIndicator) {
+ return (CircularProgressIndicator) element;
+ }
+ if (!(element instanceof Arc)) {
+ return null;
+ }
+ Arc arcElement = (Arc) element;
+ if (!checkTag(arcElement.getModifiers(), METADATA_TAG)) {
+ return null;
+ }
+ // Now we are sure that this element is a CircularProgressIndicator.
+ return new CircularProgressIndicator(arcElement);
+ }
+
+ @NonNull
+ @Override
+ @RestrictTo(Scope.LIBRARY_GROUP)
+ public LayoutElementProto.LayoutElement toLayoutElementProto() {
+ return mElement.toLayoutElementProto();
+ }
+
+ @Nullable
+ @Override
+ @RestrictTo(Scope.LIBRARY_GROUP)
+ public Fingerprint getFingerprint() {
+ return mElement.getFingerprint();
+ }
+}
diff --git a/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/Colors.java b/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/Colors.java
new file mode 100644
index 0000000..84026bd
--- /dev/null
+++ b/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/Colors.java
@@ -0,0 +1,119 @@
+/*
+ * 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.protolayout.material;
+
+import androidx.annotation.ColorInt;
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
+import androidx.annotation.RestrictTo.Scope;
+
+/**
+ * Represent the container for default color scheme in your Tile, that can be used to create color
+ * objects for all Material components.
+ *
+ * <p>See {@link #DEFAULT} for default color scheme.
+ */
+public class Colors {
+
+ /**
+ * The default color used for primary elements (i.e. background color).
+ *
+ */
+ @RestrictTo(Scope.LIBRARY_GROUP)
+ @ColorInt
+ public static final int PRIMARY = 0xFFAECBFA;
+
+ /**
+ * The default color used on primary elements (i.e. content color).
+ *
+ */
+ @RestrictTo(Scope.LIBRARY_GROUP)
+ @ColorInt
+ public static final int ON_PRIMARY = 0xFF303133;
+
+ /**
+ * The default color used for secondary elements (i.e. background color).
+ *
+ */
+ @RestrictTo(Scope.LIBRARY_GROUP)
+ @ColorInt
+ public static final int SURFACE = 0xFF303133;
+
+ /**
+ * The default color used on secondary elements (i.e. content color).
+ *
+ */
+ @RestrictTo(Scope.LIBRARY_GROUP)
+ @ColorInt
+ public static final int ON_SURFACE = 0xFFFFFFFF;
+
+ /** The default color scheme to be used in Tiles Material components. */
+ @NonNull
+ public static final Colors DEFAULT = new Colors(PRIMARY, ON_PRIMARY, SURFACE, ON_SURFACE);
+
+ private @ColorInt final int mPrimary;
+ private @ColorInt final int mOnPrimary;
+ private @ColorInt final int mSurface;
+ private @ColorInt final int mOnSurface;
+
+ /**
+ * Constructor for {@link Colors} object.
+ *
+ * @param primary The background color to be used for primary components. Should be in ARGB
+ * format.
+ * @param onPrimary The content color or tint color to be used for primary components. Should be
+ * in ARGB format.
+ * @param surface The background color to be used for secondary components. Should be in ARGB
+ * format.
+ * @param onSurface The content color or tint color to be used for secondary components. Should
+ * be in ARGB format.
+ */
+ public Colors(
+ @ColorInt int primary,
+ @ColorInt int onPrimary,
+ @ColorInt int surface,
+ @ColorInt int onSurface) {
+ this.mPrimary = primary;
+ this.mOnPrimary = onPrimary;
+ this.mSurface = surface;
+ this.mOnSurface = onSurface;
+ }
+
+ /** The primary color to be used on components. */
+ @ColorInt
+ public int getPrimary() {
+ return mPrimary;
+ }
+
+ /** The onPrimary color to be used on components. */
+ @ColorInt
+ public int getOnPrimary() {
+ return mOnPrimary;
+ }
+
+ /** The surface color to be used on components. */
+ @ColorInt
+ public int getSurface() {
+ return mSurface;
+ }
+
+ /** The onSurface color to be used on components. */
+ @ColorInt
+ public int getOnSurface() {
+ return mOnSurface;
+ }
+}
diff --git a/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/CompactChip.java b/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/CompactChip.java
new file mode 100644
index 0000000..58bf3b8
--- /dev/null
+++ b/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/CompactChip.java
@@ -0,0 +1,234 @@
+/*
+ * 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.protolayout.material;
+
+import static androidx.wear.protolayout.DimensionBuilders.wrap;
+import static androidx.wear.protolayout.LayoutElementBuilders.HORIZONTAL_ALIGN_CENTER;
+import static androidx.wear.protolayout.material.ChipDefaults.COMPACT_HEIGHT;
+import static androidx.wear.protolayout.material.ChipDefaults.COMPACT_HEIGHT_TAPPABLE;
+import static androidx.wear.protolayout.material.ChipDefaults.COMPACT_HORIZONTAL_PADDING;
+import static androidx.wear.protolayout.material.ChipDefaults.COMPACT_PRIMARY_COLORS;
+import static androidx.wear.protolayout.material.Helper.checkNotNull;
+import static androidx.wear.protolayout.material.Helper.checkTag;
+import static androidx.wear.protolayout.material.Helper.getTagBytes;
+
+import android.content.Context;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
+import androidx.annotation.RestrictTo.Scope;
+import androidx.wear.protolayout.DeviceParametersBuilders.DeviceParameters;
+import androidx.wear.protolayout.LayoutElementBuilders;
+import androidx.wear.protolayout.LayoutElementBuilders.Box;
+import androidx.wear.protolayout.LayoutElementBuilders.LayoutElement;
+import androidx.wear.protolayout.ModifiersBuilders.Clickable;
+import androidx.wear.protolayout.ModifiersBuilders.ElementMetadata;
+import androidx.wear.protolayout.ModifiersBuilders.Modifiers;
+import androidx.wear.protolayout.expression.Fingerprint;
+import androidx.wear.protolayout.proto.LayoutElementProto;
+
+/**
+ * Tiles component {@link CompactChip} that represents clickable object with the text.
+ *
+ * <p>The Chip is Stadium shape and has a max height designed to take no more than one line of text
+ * of {@link Typography#TYPOGRAPHY_CAPTION1} style. Width of the chip is adjustable to the text
+ * size.
+ *
+ * <p>The recommended set of {@link ChipColors} styles can be obtained from {@link ChipDefaults}.,
+ * e.g. {@link ChipDefaults#COMPACT_PRIMARY_COLORS} to get a color scheme for a primary {@link
+ * CompactChip}.
+ *
+ * <p>When accessing the contents of a container for testing, note that this element can't be simply
+ * casted back to the original type, i.e.:
+ *
+ * <pre>{@code
+ * CompactChip chip = new CompactChip...
+ * Box box = new Box.Builder().addContent(chip).build();
+ *
+ * CompactChip myChip = (CompactChip) box.getContents().get(0);
+ * }</pre>
+ *
+ * will fail.
+ *
+ * <p>To be able to get {@link CompactChip} object from any layout element, {@link
+ * #fromLayoutElement} method should be used, i.e.:
+ *
+ * <pre>{@code
+ * CompactChip myChip = CompactChip.fromLayoutElement(box.getContents().get(0));
+ * }</pre>
+ */
+public class CompactChip implements LayoutElement {
+ /** Tool tag for Metadata in Modifiers, so we know that Box is actually a CompactChip. */
+ static final String METADATA_TAG = "CMPCHP";
+
+ @NonNull private final Box mImpl;
+ @NonNull private final Chip mElement;
+
+ CompactChip(@NonNull Box element) {
+ this.mImpl = element;
+ // We know for sure that content of the Box is Chip.
+ this.mElement = new Chip((Box) element.getContents().get(0));
+ }
+
+ /** Builder class for {@link androidx.wear.protolayout.material.CompactChip}. */
+ public static final class Builder implements LayoutElement.Builder {
+ @NonNull private final Context mContext;
+ @NonNull private final String mText;
+ @NonNull private final Clickable mClickable;
+ @NonNull private final DeviceParameters mDeviceParameters;
+ @NonNull private ChipColors mChipColors = COMPACT_PRIMARY_COLORS;
+
+ /**
+ * Creates a builder for the {@link CompactChip} with associated action and the given text
+ *
+ * @param context The application's context.
+ * @param text The text to be displayed in this compact chip.
+ * @param clickable Associated {@link Clickable} for click events. When the CompactChip is
+ * clicked it will fire the associated action.
+ * @param deviceParameters The device parameters used for styling text.
+ */
+ public Builder(
+ @NonNull Context context,
+ @NonNull String text,
+ @NonNull Clickable clickable,
+ @NonNull DeviceParameters deviceParameters) {
+ this.mContext = context;
+ this.mText = text;
+ this.mClickable = clickable;
+ this.mDeviceParameters = deviceParameters;
+ }
+
+ /**
+ * Sets the colors for the {@link CompactChip}. If set, {@link
+ * ChipColors#getBackgroundColor()} will be used for the background of the button and {@link
+ * ChipColors#getContentColor()} for the text. If not set, {@link
+ * ChipDefaults#COMPACT_PRIMARY_COLORS} will be used.
+ */
+ @NonNull
+ public Builder setChipColors(@NonNull ChipColors chipColors) {
+ mChipColors = chipColors;
+ return this;
+ }
+
+ /** Constructs and returns {@link CompactChip} with the provided content and look. */
+ @NonNull
+ @Override
+ public CompactChip build() {
+ Chip.Builder chipBuilder =
+ new Chip.Builder(mContext, mClickable, mDeviceParameters)
+ .setMetadataTag(METADATA_TAG)
+ .setChipColors(mChipColors)
+ .setContentDescription(mText)
+ .setHorizontalAlignment(HORIZONTAL_ALIGN_CENTER)
+ .setWidth(wrap())
+ .setHeight(COMPACT_HEIGHT)
+ .setMaxLines(1)
+ .setHorizontalPadding(COMPACT_HORIZONTAL_PADDING)
+ .setPrimaryLabelContent(mText)
+ .setPrimaryLabelTypography(Typography.TYPOGRAPHY_CAPTION1)
+ .setIsPrimaryLabelScalable(false);
+
+ Box tappableChip =
+ new Box.Builder()
+ .setModifiers(
+ new Modifiers.Builder()
+ .setClickable(mClickable)
+ .setMetadata(
+ new ElementMetadata.Builder()
+ .setTagData(getTagBytes(METADATA_TAG))
+ .build())
+ .build())
+ .setWidth(wrap())
+ .setHeight(COMPACT_HEIGHT_TAPPABLE)
+ .setVerticalAlignment(LayoutElementBuilders.VERTICAL_ALIGN_CENTER)
+ .addContent(chipBuilder.build())
+ .build();
+
+ return new CompactChip(tappableChip);
+ }
+ }
+
+ /** Returns click event action associated with this Chip. */
+ @NonNull
+ public Clickable getClickable() {
+ return mElement.getClickable();
+ }
+
+ /** Returns chip color of this Chip. */
+ @NonNull
+ public ChipColors getChipColors() {
+ return mElement.getChipColors();
+ }
+
+ /** Returns text content of this Chip. */
+ @NonNull
+ public String getText() {
+ return checkNotNull(mElement.getPrimaryLabelContent());
+ }
+
+ /** Returns metadata tag set to this CompactChip, which should be {@link #METADATA_TAG}. */
+ @NonNull
+ String getMetadataTag() {
+ return mElement.getMetadataTag();
+ }
+
+ /**
+ * Returns CompactChip object from the given LayoutElement (e.g. one retrieved from a
+ * container's content with {@code container.getContents().get(index)}) if that element can be
+ * converted to CompactChip. Otherwise, it will return null.
+ */
+ @Nullable
+ public static CompactChip fromLayoutElement(@NonNull LayoutElement element) {
+ if (element instanceof CompactChip) {
+ return (CompactChip) element;
+ }
+ if (!(element instanceof Box)) {
+ return null;
+ }
+ Box boxElement = (Box) element;
+ if (!checkTag(boxElement.getModifiers(), METADATA_TAG)) {
+ return null;
+ }
+ // Now to check that inner content of the Box is CompactChip's Chip.
+ LayoutElement innerElement = boxElement.getContents().get(0);
+ if (!(innerElement instanceof Box)) {
+ return null;
+ }
+ Box innerBoxElement = (Box) innerElement;
+ if (!checkTag(innerBoxElement.getModifiers(), METADATA_TAG)) {
+ return null;
+ }
+
+ // Now we are sure that this element is a CompactChip.
+ return new CompactChip(boxElement);
+ }
+
+ @RestrictTo(Scope.LIBRARY_GROUP)
+ @NonNull
+ @Override
+ public LayoutElementProto.LayoutElement toLayoutElementProto() {
+ return mImpl.toLayoutElementProto();
+ }
+
+ @Nullable
+ @Override
+ @RestrictTo(Scope.LIBRARY_GROUP)
+ public Fingerprint getFingerprint() {
+ return mImpl.getFingerprint();
+ }
+}
diff --git a/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/DeleteMe.kt b/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/DeleteMe.kt
new file mode 100644
index 0000000..8f2cad2
--- /dev/null
+++ b/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/DeleteMe.kt
@@ -0,0 +1,17 @@
+package androidx.wear.protolayout.material/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// This file exists to trick AGP/lint to work around b/234865137
diff --git a/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/Helper.java b/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/Helper.java
new file mode 100644
index 0000000..630bf29
--- /dev/null
+++ b/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/Helper.java
@@ -0,0 +1,134 @@
+/*
+ * 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.protolayout.material;
+
+import static androidx.wear.protolayout.DimensionBuilders.dp;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
+import androidx.annotation.RestrictTo.Scope;
+import androidx.wear.protolayout.DeviceParametersBuilders;
+import androidx.wear.protolayout.DeviceParametersBuilders.DeviceParameters;
+import androidx.wear.protolayout.DimensionBuilders.DpProp;
+import androidx.wear.protolayout.ModifiersBuilders.ElementMetadata;
+import androidx.wear.protolayout.ModifiersBuilders.Modifiers;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.Collection;
+
+/**
+ * Helper class used for Tiles Material.
+ *
+ */
+@RestrictTo(Scope.LIBRARY_GROUP)
+public class Helper {
+ private Helper() {}
+
+ /**
+ * Returns given value if not null or throws {@code NullPointerException} otherwise.
+ *
+ */
+ @RestrictTo(Scope.LIBRARY_GROUP)
+ @NonNull
+ public static <T> T checkNotNull(@Nullable T value) {
+ if (value == null) {
+ throw new NullPointerException();
+ }
+ return value;
+ }
+
+ /** Returns radius in {@link DpProp} of the given diameter. */
+ @NonNull
+ static DpProp radiusOf(DpProp diameter) {
+ return dp(diameter.getValue() / 2);
+ }
+
+ /**
+ * Returns true if the given DeviceParameters belong to the round screen device.
+ *
+ */
+ @RestrictTo(Scope.LIBRARY_GROUP)
+ public static boolean isRoundDevice(@NonNull DeviceParameters deviceParameters) {
+ return deviceParameters.getScreenShape() == DeviceParametersBuilders.SCREEN_SHAPE_ROUND;
+ }
+
+ /**
+ * Returns String representation of tag from byte array.
+ *
+ */
+ @RestrictTo(Scope.LIBRARY_GROUP)
+ @NonNull
+ public static String getTagName(@NonNull byte[] tagData) {
+ return new String(tagData, StandardCharsets.UTF_8);
+ }
+
+ /**
+ * Returns byte array representation of tag from String.
+ *
+ */
+ @RestrictTo(Scope.LIBRARY_GROUP)
+ @NonNull
+ public static byte[] getTagBytes(@NonNull String tagName) {
+ return tagName.getBytes(StandardCharsets.UTF_8);
+ }
+
+ /** Returns the String representation of metadata tag from the given ElementMetadata. */
+ @NonNull
+ public static String getMetadataTagName(@NonNull ElementMetadata metadata) {
+ return getTagName(getMetadataTagBytes(metadata));
+ }
+
+ /** Returns the metadata tag from the given ElementMetadata. */
+ @NonNull
+ public static byte[] getMetadataTagBytes(@NonNull ElementMetadata metadata) {
+ return checkNotNull(metadata).getTagData();
+ }
+
+ /** Returns true if the given Modifiers have Metadata tag set to the given String value. */
+ public static boolean checkTag(@Nullable Modifiers modifiers, @NonNull String validTag) {
+ return modifiers != null
+ && modifiers.getMetadata() != null
+ && validTag.equals(getMetadataTagName(modifiers.getMetadata()));
+ }
+
+ /**
+ * Returns true if the given Modifiers have Metadata tag set to any of the value in the given
+ * String collection.
+ */
+ public static boolean checkTag(
+ @Nullable Modifiers modifiers, @NonNull Collection<String> validTags) {
+ return modifiers != null
+ && modifiers.getMetadata() != null
+ && validTags.contains(getMetadataTagName(modifiers.getMetadata()));
+ }
+
+ /**
+ * Returns true if the given Modifiers have Metadata tag set with prefix that is equal to the
+ * given String and its length is of the given base array.
+ */
+ public static boolean checkTag(
+ @Nullable Modifiers modifiers, @NonNull String validPrefix, @NonNull byte[] validBase) {
+ if (modifiers == null || modifiers.getMetadata() == null) {
+ return false;
+ }
+ byte[] metadataTag = getMetadataTagBytes(modifiers.getMetadata());
+ byte[] tag = Arrays.copyOf(metadataTag, validPrefix.length());
+ return metadataTag.length == validBase.length && validPrefix.equals(getTagName(tag));
+ }
+}
diff --git a/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/ProgressIndicatorColors.java b/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/ProgressIndicatorColors.java
new file mode 100644
index 0000000..668fe3e
--- /dev/null
+++ b/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/ProgressIndicatorColors.java
@@ -0,0 +1,83 @@
+/*
+ * 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.protolayout.material;
+
+import static androidx.wear.protolayout.ColorBuilders.argb;
+
+import androidx.annotation.ColorInt;
+import androidx.annotation.NonNull;
+import androidx.wear.protolayout.ColorBuilders.ColorProp;
+
+/**
+ * Represents the indicator and track colors used in a progress indicator Tiles component.
+ *
+ * <p>See {@link ProgressIndicatorDefaults#DEFAULT_COLORS} for the default colors used in a {@link
+ * CircularProgressIndicator}.
+ */
+public class ProgressIndicatorColors {
+ @NonNull private final ColorProp mIndicatorColor;
+ @NonNull private final ColorProp mTrackColor;
+
+ /**
+ * Constructor for {@link ProgressIndicatorColors} object.
+ *
+ * @param indicatorColor The indicator color to be used for a progress indicator Tiles
+ * component.
+ * @param trackColor The background track color to be used for a progress indicator Tiles
+ * component.
+ */
+ public ProgressIndicatorColors(
+ @NonNull ColorProp indicatorColor, @NonNull ColorProp trackColor) {
+ this.mIndicatorColor = indicatorColor;
+ this.mTrackColor = trackColor;
+ }
+
+ /**
+ * Constructor for {@link ProgressIndicatorColors} object.
+ *
+ * @param indicatorColor The indicator color to be used for a progress indicator Tiles
+ * component. Should be in ARGB format.
+ * @param trackColor The background track color to be used for a progress indicator Tiles
+ * component. Should be in ARGB format.
+ */
+ public ProgressIndicatorColors(@ColorInt int indicatorColor, @ColorInt int trackColor) {
+ this.mIndicatorColor = argb(indicatorColor);
+ this.mTrackColor = argb(trackColor);
+ }
+
+ /**
+ * Returns a {@link ProgressIndicatorColors} object, using the current Primary color for
+ * indicator color and the current Surface color for the track color from the given {@link
+ * Colors}.
+ */
+ @NonNull
+ public static ProgressIndicatorColors progressIndicatorColors(@NonNull Colors colors) {
+ return new ProgressIndicatorColors(colors.getPrimary(), colors.getSurface());
+ }
+
+ /** The indicator color to be used for a progress indicator Tiles component. */
+ @NonNull
+ public ColorProp getIndicatorColor() {
+ return mIndicatorColor;
+ }
+
+ /** The background track color to be used for a progress indicator Tiles component. */
+ @NonNull
+ public ColorProp getTrackColor() {
+ return mTrackColor;
+ }
+}
diff --git a/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/ProgressIndicatorDefaults.java b/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/ProgressIndicatorDefaults.java
new file mode 100644
index 0000000..dd2f9cf
--- /dev/null
+++ b/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/ProgressIndicatorDefaults.java
@@ -0,0 +1,59 @@
+/*
+ * 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.protolayout.material;
+
+import static androidx.wear.protolayout.DimensionBuilders.dp;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
+import androidx.annotation.RestrictTo.Scope;
+import androidx.wear.protolayout.DimensionBuilders.DpProp;
+
+/** Contains the default values used by {@link CircularProgressIndicator} Tiles components. */
+public class ProgressIndicatorDefaults {
+ private ProgressIndicatorDefaults() {}
+
+ /** The default stroke width for {@link CircularProgressIndicator} */
+ @NonNull public static final DpProp DEFAULT_STROKE_WIDTH = dp(8);
+
+ /**
+ * The default padding for {@link CircularProgressIndicator}
+ *
+ */
+ @RestrictTo(Scope.LIBRARY_GROUP)
+ @NonNull
+ public static final DpProp DEFAULT_PADDING = dp(6);
+
+ /** The recommended colors for {@link CircularProgressIndicator}. */
+ @NonNull
+ public static final ProgressIndicatorColors DEFAULT_COLORS =
+ ProgressIndicatorColors.progressIndicatorColors(Colors.DEFAULT);
+
+ static final float DEFAULT_GAP_LENGTH = 47.8f;
+
+ /** The recommended start angle for {@link CircularProgressIndicator} if there's a gap. */
+ public static final float GAP_START_ANGLE = 180 + DEFAULT_GAP_LENGTH / 2 - 360;
+
+ /** The recommended end angle for {@link CircularProgressIndicator} if there's a gap. */
+ public static final float GAP_END_ANGLE = 180 - DEFAULT_GAP_LENGTH / 2;
+
+ /** Start angle for full length {@link CircularProgressIndicator}. */
+ static final float DEFAULT_START_ANGLE = 0;
+
+ /** End angle for full length {@link CircularProgressIndicator}. */
+ static final float DEFAULT_END_ANGLE = 360;
+}
diff --git a/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/Text.java b/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/Text.java
new file mode 100644
index 0000000..ed5e309
--- /dev/null
+++ b/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/Text.java
@@ -0,0 +1,318 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.protolayout.material;
+
+import static androidx.wear.protolayout.ColorBuilders.argb;
+import static androidx.wear.protolayout.LayoutElementBuilders.TEXT_ALIGN_CENTER;
+import static androidx.wear.protolayout.LayoutElementBuilders.TEXT_OVERFLOW_ELLIPSIZE_END;
+import static androidx.wear.protolayout.material.Helper.checkNotNull;
+import static androidx.wear.protolayout.material.Typography.TYPOGRAPHY_DISPLAY1;
+import static androidx.wear.protolayout.material.Typography.getFontStyleBuilder;
+import static androidx.wear.protolayout.material.Typography.getLineHeightForTypography;
+
+import android.content.Context;
+
+import androidx.annotation.IntRange;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
+import androidx.annotation.RestrictTo.Scope;
+import androidx.wear.protolayout.ColorBuilders.ColorProp;
+import androidx.wear.protolayout.LayoutElementBuilders;
+import androidx.wear.protolayout.LayoutElementBuilders.FontStyle;
+import androidx.wear.protolayout.LayoutElementBuilders.FontWeight;
+import androidx.wear.protolayout.LayoutElementBuilders.LayoutElement;
+import androidx.wear.protolayout.LayoutElementBuilders.TextAlignment;
+import androidx.wear.protolayout.LayoutElementBuilders.TextOverflow;
+import androidx.wear.protolayout.ModifiersBuilders.Modifiers;
+import androidx.wear.protolayout.expression.Fingerprint;
+import androidx.wear.protolayout.material.Typography.TypographyName;
+import androidx.wear.protolayout.proto.LayoutElementProto;
+
+/**
+ * Tiles component {@link Text} that represents text object holding any information.
+ *
+ * <p>There are pre-built typography styles that can be obtained from constants in {@link
+ * FontStyle}.
+ *
+ * <p>When accessing the contents of a container for testing, note that this element can't be simply
+ * casted back to the original type, i.e.:
+ *
+ * <pre>{@code
+ * Text text = new Text...
+ * Box box = new Box.Builder().addContent(text).build();
+ *
+ * Text myText = (Text) box.getContents().get(0);
+ * }</pre>
+ *
+ * will fail.
+ *
+ * <p>To be able to get {@link Text} object from any layout element, {@link #fromLayoutElement}
+ * method should be used, i.e.:
+ *
+ * <pre>{@code
+ * Text myText = Text.fromLayoutElement(box.getContents().get(0));
+ * }</pre>
+ */
+public class Text implements LayoutElement {
+
+ @NonNull private final LayoutElementBuilders.Text mText;
+
+ Text(@NonNull LayoutElementBuilders.Text mText) {
+ this.mText = mText;
+ }
+
+ /** Builder class for {@link Text}. */
+ public static final class Builder implements LayoutElement.Builder {
+ @NonNull private final Context mContext;
+ @NonNull private String mTextContent = "";
+ @NonNull private ColorProp mColor = argb(Colors.DEFAULT.getOnPrimary());
+ private @TypographyName int mTypographyName = TYPOGRAPHY_DISPLAY1;
+ private boolean mItalic = false;
+ private int mMaxLines = 1;
+ private boolean mUnderline = false;
+ @TextAlignment private int mMultilineAlignment = TEXT_ALIGN_CENTER;
+ @NonNull private Modifiers mModifiers = new Modifiers.Builder().build();
+ private @TextOverflow int mOverflow = TEXT_OVERFLOW_ELLIPSIZE_END;
+ private boolean mIsScalable = true;
+ @Nullable private Integer mCustomWeight = null;
+
+ /**
+ * Creates a builder for {@link Text}.
+ *
+ * @param context The application's context.
+ * @param text The text content for this component.
+ */
+ public Builder(@NonNull Context context, @NonNull String text) {
+ mContext = context;
+ mTextContent = text;
+ }
+
+ /**
+ * Sets the typography for the {@link Text}. If not set, {@link
+ * Typography#TYPOGRAPHY_DISPLAY1} will be used.
+ */
+ @NonNull
+ @SuppressWarnings("MissingGetterMatchingBuilder")
+ // There is getFontStyle matching getter for this setter as the serialized format of the
+ // Tiles do not allow for a direct reconstruction of the all arguments, but it has FontStyle
+ // object of that text.
+ public Builder setTypography(@TypographyName int typography) {
+ this.mTypographyName = typography;
+ return this;
+ }
+
+ /**
+ * Sets whether the text size will change if user has changed the default font size. If not
+ * set, true will be used.
+ */
+ Builder setIsScalable(boolean isScalable) {
+ this.mIsScalable = isScalable;
+ return this;
+ }
+
+ /**
+ * Sets the color for the {@link Text}. If not set, onPrimary color from the {@link
+ * Colors#DEFAULT} will be used.
+ */
+ @NonNull
+ public Builder setColor(@NonNull ColorProp color) {
+ this.mColor = color;
+ return this;
+ }
+
+ /** Sets the text to be italic. If not set, false will be used. */
+ @NonNull
+ public Builder setItalic(boolean italic) {
+ this.mItalic = italic;
+ return this;
+ }
+
+ /** Sets the text to be underlined. If not set, false will be used. */
+ @NonNull
+ public Builder setUnderline(boolean underline) {
+ this.mUnderline = underline;
+ return this;
+ }
+
+ /** Sets the maximum lines of text. If not set, 1 will be used. */
+ @NonNull
+ public Builder setMaxLines(@IntRange(from = 1) int maxLines) {
+ this.mMaxLines = maxLines;
+ return this;
+ }
+
+ /**
+ * Sets the multiline alignment for text within bounds of the Text element. Note that this
+ * option has no effect for single line of text, and for that, alignment on the outer
+ * container should be used. If not set, {@link TextAlignment#TEXT_ALIGN_CENTER} will be
+ * used.
+ */
+ @NonNull
+ public Builder setMultilineAlignment(@TextAlignment int multilineAlignment) {
+ this.mMultilineAlignment = multilineAlignment;
+ return this;
+ }
+
+ /** Sets the modifiers of text. */
+ @NonNull
+ public Builder setModifiers(@NonNull Modifiers modifiers) {
+ this.mModifiers = modifiers;
+ return this;
+ }
+
+ /**
+ * Sets the overflow for text. If not set, {@link TextAlignment#TEXT_OVERFLOW_ELLIPSIZE_END}
+ * will be used.
+ */
+ @NonNull
+ public Builder setOverflow(@TextOverflow int overflow) {
+ this.mOverflow = overflow;
+ return this;
+ }
+
+ /**
+ * Sets the weight of the font. If not set, default weight for the chosen Typography will be
+ * used.
+ */
+ @NonNull
+ public Builder setWeight(@FontWeight int weight) {
+ this.mCustomWeight = weight;
+ return this;
+ }
+
+ /** Constructs and returns {@link Text} with the provided content and look. */
+ @NonNull
+ @Override
+ public Text build() {
+ FontStyle.Builder fontStyleBuilder =
+ getFontStyleBuilder(mTypographyName, mContext, mIsScalable)
+ .setColor(mColor)
+ .setItalic(mItalic)
+ .setUnderline(mUnderline);
+ if (mCustomWeight != null) {
+ fontStyleBuilder.setWeight(mCustomWeight);
+ }
+
+ LayoutElementBuilders.Text.Builder text =
+ new LayoutElementBuilders.Text.Builder()
+ .setText(mTextContent)
+ .setFontStyle(fontStyleBuilder.build())
+ .setLineHeight(getLineHeightForTypography(mTypographyName))
+ .setMaxLines(mMaxLines)
+ .setMultilineAlignment(mMultilineAlignment)
+ .setModifiers(mModifiers)
+ .setOverflow(mOverflow);
+ return new Text(text.build());
+ }
+ }
+
+ /** Returns the text of this Text element. */
+ @NonNull
+ public String getText() {
+ return checkNotNull(checkNotNull(mText.getText()).getValue());
+ }
+
+ /** Returns the color of this Text element. */
+ @NonNull
+ public ColorProp getColor() {
+ return checkNotNull(checkNotNull(mText.getFontStyle()).getColor());
+ }
+
+ /** Returns the font style of this Text element. */
+ @NonNull
+ public FontStyle getFontStyle() {
+ return checkNotNull(mText.getFontStyle());
+ }
+
+ /** Returns the line height of this Text element. */
+ public float getLineHeight() {
+ return checkNotNull(mText.getLineHeight()).getValue();
+ }
+
+ /** Returns the max lines of text of this Text element. */
+ public int getMaxLines() {
+ return checkNotNull(mText.getMaxLines()).getValue();
+ }
+
+ /** Returns the multiline alignment of this Text element. */
+ @TextAlignment
+ public int getMultilineAlignment() {
+ return checkNotNull(mText.getMultilineAlignment()).getValue();
+ }
+
+ /** Returns the modifiers of this Text element. */
+ @NonNull
+ public Modifiers getModifiers() {
+ return checkNotNull(mText.getModifiers());
+ }
+
+ /** Returns the overflow of this Text element. */
+ @TextOverflow
+ public int getOverflow() {
+ return checkNotNull(mText.getOverflow()).getValue();
+ }
+
+ /** Returns the overflow of this Text element. */
+ @FontWeight
+ public int getWeight() {
+ return checkNotNull(checkNotNull(mText.getFontStyle()).getWeight()).getValue();
+ }
+
+ /** Returns whether the Text is in italic. */
+ public boolean isItalic() {
+ return checkNotNull(checkNotNull(mText.getFontStyle()).getItalic()).getValue();
+ }
+
+ /** Returns whether the Text is underlined. */
+ public boolean isUnderline() {
+ return checkNotNull(checkNotNull(mText.getFontStyle()).getUnderline()).getValue();
+ }
+
+ /**
+ * Returns Material Text object from the given LayoutElement (e.g. one retrieved from a
+ * container's content with {@code container.getContents().get(index)}) if that element can be
+ * converted to Material Text. Otherwise, it will return null.
+ */
+ @Nullable
+ public static Text fromLayoutElement(@NonNull LayoutElement element) {
+ if (element instanceof Text) {
+ return (Text) element;
+ }
+ if (!(element instanceof LayoutElementBuilders.Text)) {
+ return null;
+ }
+ LayoutElementBuilders.Text textElement = (LayoutElementBuilders.Text) element;
+ // We don't need to check the tag as LayoutElement.Text will have the same fields as our
+ // getters even if it's not Material.
+ return new Text(textElement);
+ }
+
+ @NonNull
+ @Override
+ @RestrictTo(Scope.LIBRARY_GROUP)
+ public LayoutElementProto.LayoutElement toLayoutElementProto() {
+ return mText.toLayoutElementProto();
+ }
+
+ @Nullable
+ @Override
+ @RestrictTo(Scope.LIBRARY_GROUP)
+ public Fingerprint getFingerprint() {
+ return mText.getFingerprint();
+ }
+}
diff --git a/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/TitleChip.java b/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/TitleChip.java
new file mode 100644
index 0000000..009dd8c
--- /dev/null
+++ b/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/TitleChip.java
@@ -0,0 +1,254 @@
+/*
+ * 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.protolayout.material;
+
+import static androidx.annotation.Dimension.DP;
+import static androidx.wear.protolayout.DimensionBuilders.dp;
+import static androidx.wear.protolayout.LayoutElementBuilders.HORIZONTAL_ALIGN_CENTER;
+import static androidx.wear.protolayout.material.ChipDefaults.TITLE_HEIGHT;
+import static androidx.wear.protolayout.material.ChipDefaults.TITLE_HORIZONTAL_PADDING;
+import static androidx.wear.protolayout.material.ChipDefaults.TITLE_PRIMARY_COLORS;
+import static androidx.wear.protolayout.material.Helper.checkNotNull;
+import static androidx.wear.protolayout.material.Helper.checkTag;
+
+import android.content.Context;
+
+import androidx.annotation.Dimension;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
+import androidx.annotation.RestrictTo.Scope;
+import androidx.wear.protolayout.DeviceParametersBuilders.DeviceParameters;
+import androidx.wear.protolayout.DimensionBuilders.ContainerDimension;
+import androidx.wear.protolayout.LayoutElementBuilders.Box;
+import androidx.wear.protolayout.LayoutElementBuilders.HorizontalAlignment;
+import androidx.wear.protolayout.LayoutElementBuilders.LayoutElement;
+import androidx.wear.protolayout.ModifiersBuilders.Clickable;
+import androidx.wear.protolayout.expression.Fingerprint;
+import androidx.wear.protolayout.proto.LayoutElementProto;
+
+/**
+ * Tiles component {@link TitleChip} that represents clickable object with the text.
+ *
+ * <p>The Title Chip is Stadium shaped object with a larger height then standard Chip and it will
+ * take one line of text of {@link Typography#TYPOGRAPHY_TITLE2} style.
+ *
+ * <p>The recommended set of {@link ChipColors} styles can be obtained from {@link ChipDefaults},
+ * e.g. {@link ChipDefaults#TITLE_PRIMARY_COLORS} to get a color scheme for a primary {@link
+ * TitleChip}.
+ *
+ * <p>When accessing the contents of a container for testing, note that this element can't be simply
+ * casted back to the original type, i.e.:
+ *
+ * <pre>{@code
+ * TitleChip chip = new TitleChip...
+ * Box box = new Box.Builder().addContent(chip).build();
+ *
+ * TitleChip myChip = (TitleChip) box.getContents().get(0);
+ * }</pre>
+ *
+ * will fail.
+ *
+ * <p>To be able to get {@link TitleChip} object from any layout element, {@link #fromLayoutElement}
+ * method should be used, i.e.:
+ *
+ * <pre>{@code
+ * TitleChip myChip = TitleChip.fromLayoutElement(box.getContents().get(0));
+ * }</pre>
+ *
+ * @see androidx.wear.protolayout.material.layouts.PrimaryLayout.Builder#setContent if this
+ * TitleChip is used inside of {@link androidx.wear.protolayout.material.layouts.PrimaryLayout}.
+ */
+public class TitleChip implements LayoutElement {
+ /** Tool tag for Metadata in Modifiers, so we know that Box is actually a TitleChip. */
+ static final String METADATA_TAG = "TTLCHP";
+
+ @NonNull private final Chip mElement;
+
+ TitleChip(@NonNull Chip element) {
+ this.mElement = element;
+ }
+
+ /** Builder class for {@link TitleChip}. */
+ public static final class Builder implements LayoutElement.Builder {
+ @NonNull private final Context mContext;
+ @NonNull private final String mText;
+ @NonNull private final Clickable mClickable;
+ @NonNull private final DeviceParameters mDeviceParameters;
+ @NonNull private ChipColors mChipColors = TITLE_PRIMARY_COLORS;
+ @HorizontalAlignment private int mHorizontalAlign = HORIZONTAL_ALIGN_CENTER;
+
+ // Indicates that the width isn't set, so it will be automatically set by Chip.Builder
+ // constructor.
+ @Nullable private ContainerDimension mWidth = null;
+
+ /**
+ * Creates a builder for the {@link TitleChip} with associated action and the given text
+ *
+ * @param context The application's context.
+ * @param text The text to be displayed in this title chip. Text will be displayed in 1 line
+ * and truncated if it doesn't fit.
+ * @param clickable Associated {@link Clickable} for click events. When the TitleChip is
+ * clicked it will fire the associated action.
+ * @param deviceParameters The device parameters used for styling text.
+ */
+ public Builder(
+ @NonNull Context context,
+ @NonNull String text,
+ @NonNull Clickable clickable,
+ @NonNull DeviceParameters deviceParameters) {
+ this.mContext = context;
+ this.mText = text;
+ this.mClickable = clickable;
+ this.mDeviceParameters = deviceParameters;
+ }
+
+ /**
+ * Sets the colors for the {@link TitleChip}. If set, {@link
+ * ChipColors#getBackgroundColor()} will be used for the background of the button and {@link
+ * ChipColors#getContentColor()} for the text. If not set, {@link
+ * ChipDefaults#TITLE_PRIMARY_COLORS} will be used.
+ */
+ @NonNull
+ public Builder setChipColors(@NonNull ChipColors chipColors) {
+ mChipColors = chipColors;
+ return this;
+ }
+
+ /** Sets the horizontal alignment in the chip. If not set, content will be centered. */
+ @NonNull
+ public Builder setHorizontalAlignment(@HorizontalAlignment int horizontalAlignment) {
+ mHorizontalAlign = horizontalAlignment;
+ return this;
+ }
+
+ /**
+ * Sets the width of {@link TitleChip}. If not set, default value will be set to fill the
+ * screen.
+ */
+ @NonNull
+ public Builder setWidth(@NonNull ContainerDimension width) {
+ mWidth = width;
+ return this;
+ }
+
+ /**
+ * Sets the width of {@link TitleChip}. If not set, default value will be set to fill the
+ * screen.
+ */
+ @NonNull
+ public Builder setWidth(@Dimension(unit = DP) float width) {
+ mWidth = dp(width);
+ return this;
+ }
+
+ /** Constructs and returns {@link TitleChip} with the provided content and look. */
+ @NonNull
+ @Override
+ public TitleChip build() {
+ Chip.Builder chipBuilder =
+ new Chip.Builder(mContext, mClickable, mDeviceParameters)
+ .setMetadataTag(METADATA_TAG)
+ .setChipColors(mChipColors)
+ .setContentDescription(mText)
+ .setHorizontalAlignment(mHorizontalAlign)
+ .setHeight(TITLE_HEIGHT)
+ .setMaxLines(1)
+ .setHorizontalPadding(TITLE_HORIZONTAL_PADDING)
+ .setPrimaryLabelContent(mText)
+ .setPrimaryLabelTypography(Typography.TYPOGRAPHY_TITLE2)
+ .setIsPrimaryLabelScalable(false);
+
+ if (mWidth != null) {
+ chipBuilder.setWidth(mWidth);
+ }
+
+ return new TitleChip(chipBuilder.build());
+ }
+ }
+
+ /** Returns width of this Chip. */
+ @NonNull
+ public ContainerDimension getWidth() {
+ return mElement.getWidth();
+ }
+
+ /** Returns click event action associated with this Chip. */
+ @NonNull
+ public Clickable getClickable() {
+ return mElement.getClickable();
+ }
+
+ /** Returns chip color of this Chip. */
+ @NonNull
+ public ChipColors getChipColors() {
+ return mElement.getChipColors();
+ }
+
+ /** Returns text content of this Chip. */
+ @NonNull
+ public String getText() {
+ return checkNotNull(mElement.getPrimaryLabelContent());
+ }
+
+ /** Returns the horizontal alignment of the content in this Chip. */
+ @HorizontalAlignment
+ public int getHorizontalAlignment() {
+ return mElement.getHorizontalAlignment();
+ }
+
+ /** Returns metadata tag set to this TitleChip, which should be {@link #METADATA_TAG}. */
+ @NonNull
+ String getMetadataTag() {
+ return mElement.getMetadataTag();
+ }
+
+ /**
+ * Returns TitleChip object from the given LayoutElement (e.g. one retrieved from a container's
+ * content with {@code container.getContents().get(index)}) if that element can be converted to
+ * TitleChip. Otherwise, it will return null.
+ */
+ @Nullable
+ public static TitleChip fromLayoutElement(@NonNull LayoutElement element) {
+ if (element instanceof TitleChip) {
+ return (TitleChip) element;
+ }
+ if (!(element instanceof Box)) {
+ return null;
+ }
+ Box boxElement = (Box) element;
+ if (!checkTag(boxElement.getModifiers(), METADATA_TAG)) {
+ return null;
+ }
+ // Now we are sure that this element is a TitleChip.
+ return new TitleChip(new Chip(boxElement));
+ }
+
+ @RestrictTo(Scope.LIBRARY_GROUP)
+ @NonNull
+ @Override
+ public LayoutElementProto.LayoutElement toLayoutElementProto() {
+ return mElement.toLayoutElementProto();
+ }
+
+ @Nullable
+ @Override
+ @RestrictTo(Scope.LIBRARY_GROUP)
+ public Fingerprint getFingerprint() {
+ return mElement.getFingerprint();
+ }
+}
diff --git a/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/Typography.java b/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/Typography.java
new file mode 100644
index 0000000..d5b5b9f
--- /dev/null
+++ b/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/Typography.java
@@ -0,0 +1,298 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.protolayout.material;
+
+import static androidx.annotation.Dimension.DP;
+import static androidx.annotation.Dimension.SP;
+import static androidx.wear.protolayout.DimensionBuilders.sp;
+import static androidx.wear.protolayout.LayoutElementBuilders.FONT_VARIANT_BODY;
+import static androidx.wear.protolayout.LayoutElementBuilders.FONT_VARIANT_TITLE;
+import static androidx.wear.protolayout.LayoutElementBuilders.FONT_WEIGHT_BOLD;
+import static androidx.wear.protolayout.LayoutElementBuilders.FONT_WEIGHT_MEDIUM;
+import static androidx.wear.protolayout.LayoutElementBuilders.FONT_WEIGHT_NORMAL;
+import static androidx.wear.protolayout.material.Helper.checkNotNull;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.util.DisplayMetrics;
+
+import androidx.annotation.Dimension;
+import androidx.annotation.IntDef;
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
+import androidx.wear.protolayout.DimensionBuilders;
+import androidx.wear.protolayout.DimensionBuilders.SpProp;
+import androidx.wear.protolayout.LayoutElementBuilders.FontStyle;
+import androidx.wear.protolayout.LayoutElementBuilders.FontVariant;
+import androidx.wear.protolayout.LayoutElementBuilders.FontWeight;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.HashMap;
+import java.util.Map;
+
+/** Typography styles, currently set up to match Wear's styling. */
+public class Typography {
+ /** Typography for large display text. */
+ public static final int TYPOGRAPHY_DISPLAY1 = 1;
+
+ /** Typography for medium display text. */
+ public static final int TYPOGRAPHY_DISPLAY2 = 2;
+
+ /** Typography for small display text. */
+ public static final int TYPOGRAPHY_DISPLAY3 = 3;
+
+ /** Typography for large title text. */
+ public static final int TYPOGRAPHY_TITLE1 = 4;
+
+ /** Typography for medium title text. */
+ public static final int TYPOGRAPHY_TITLE2 = 5;
+
+ /** Typography for small title text. */
+ public static final int TYPOGRAPHY_TITLE3 = 6;
+
+ /** Typography for large body text. */
+ public static final int TYPOGRAPHY_BODY1 = 7;
+
+ /** Typography for medium body text. */
+ public static final int TYPOGRAPHY_BODY2 = 8;
+
+ /** Typography for bold button text. */
+ public static final int TYPOGRAPHY_BUTTON = 9;
+
+ /** Typography for large caption text. */
+ public static final int TYPOGRAPHY_CAPTION1 = 10;
+
+ /** Typography for medium caption text. */
+ public static final int TYPOGRAPHY_CAPTION2 = 11;
+
+ /** Typography for small caption text. */
+ public static final int TYPOGRAPHY_CAPTION3 = 12;
+
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ TYPOGRAPHY_DISPLAY1,
+ TYPOGRAPHY_DISPLAY2,
+ TYPOGRAPHY_DISPLAY3,
+ TYPOGRAPHY_TITLE1,
+ TYPOGRAPHY_TITLE2,
+ TYPOGRAPHY_TITLE3,
+ TYPOGRAPHY_BODY1,
+ TYPOGRAPHY_BODY2,
+ TYPOGRAPHY_BUTTON,
+ TYPOGRAPHY_CAPTION1,
+ TYPOGRAPHY_CAPTION2,
+ TYPOGRAPHY_CAPTION3
+ })
+ @interface TypographyName {}
+
+ /** Mapping for line height for different typography. */
+ @NonNull
+ private static final Map<Integer, Float> TYPOGRAPHY_TO_LINE_HEIGHT_SP = new HashMap<>();
+
+ static {
+ TYPOGRAPHY_TO_LINE_HEIGHT_SP.put(TYPOGRAPHY_DISPLAY1, 46f);
+ TYPOGRAPHY_TO_LINE_HEIGHT_SP.put(TYPOGRAPHY_DISPLAY2, 40f);
+ TYPOGRAPHY_TO_LINE_HEIGHT_SP.put(TYPOGRAPHY_DISPLAY3, 36f);
+ TYPOGRAPHY_TO_LINE_HEIGHT_SP.put(TYPOGRAPHY_TITLE1, 28f);
+ TYPOGRAPHY_TO_LINE_HEIGHT_SP.put(TYPOGRAPHY_TITLE2, 24f);
+ TYPOGRAPHY_TO_LINE_HEIGHT_SP.put(TYPOGRAPHY_TITLE3, 20f);
+ TYPOGRAPHY_TO_LINE_HEIGHT_SP.put(TYPOGRAPHY_BODY1, 20f);
+ TYPOGRAPHY_TO_LINE_HEIGHT_SP.put(TYPOGRAPHY_BODY2, 18f);
+ TYPOGRAPHY_TO_LINE_HEIGHT_SP.put(TYPOGRAPHY_BUTTON, 19f);
+ TYPOGRAPHY_TO_LINE_HEIGHT_SP.put(TYPOGRAPHY_CAPTION1, 18f);
+ TYPOGRAPHY_TO_LINE_HEIGHT_SP.put(TYPOGRAPHY_CAPTION2, 16f);
+ TYPOGRAPHY_TO_LINE_HEIGHT_SP.put(TYPOGRAPHY_CAPTION3, 14f);
+ }
+ /**
+ * Returns the {@link FontStyle.Builder} for the given FontStyle code with the recommended size,
+ * weight and letter spacing. Font will be scalable.
+ */
+ @NonNull
+ static FontStyle.Builder getFontStyleBuilder(
+ @TypographyName int fontStyleCode, @NonNull Context context) {
+ return getFontStyleBuilder(fontStyleCode, context, true);
+ }
+
+ private Typography() {}
+
+ /**
+ * Returns the {@link FontStyle.Builder} for the given Typography code with the recommended
+ * size, weight and letter spacing, with the option to make this font not scalable.
+ */
+ @NonNull
+ static FontStyle.Builder getFontStyleBuilder(
+ @TypographyName int typographyCode, @NonNull Context context, boolean isScalable) {
+ switch (typographyCode) {
+ case TYPOGRAPHY_BODY1:
+ return body1(isScalable, context);
+ case TYPOGRAPHY_BODY2:
+ return body2(isScalable, context);
+ case TYPOGRAPHY_BUTTON:
+ return button(isScalable, context);
+ case TYPOGRAPHY_CAPTION1:
+ return caption1(isScalable, context);
+ case TYPOGRAPHY_CAPTION2:
+ return caption2(isScalable, context);
+ case TYPOGRAPHY_CAPTION3:
+ return caption3(isScalable, context);
+ case TYPOGRAPHY_DISPLAY1:
+ return display1(isScalable, context);
+ case TYPOGRAPHY_DISPLAY2:
+ return display2(isScalable, context);
+ case TYPOGRAPHY_DISPLAY3:
+ return display3(isScalable, context);
+ case TYPOGRAPHY_TITLE1:
+ return title1(isScalable, context);
+ case TYPOGRAPHY_TITLE2:
+ return title2(isScalable, context);
+ case TYPOGRAPHY_TITLE3:
+ return title3(isScalable, context);
+ default:
+ // Shouldn't happen.
+ throw new IllegalArgumentException(
+ "Typography " + typographyCode + " doesn't exist.");
+ }
+ }
+
+ /**
+ * Returns the recommended line height for the given Typography to be added to the Text
+ * component.
+ */
+ @NonNull
+ static SpProp getLineHeightForTypography(@TypographyName int typography) {
+ if (!TYPOGRAPHY_TO_LINE_HEIGHT_SP.containsKey(typography)) {
+ throw new IllegalArgumentException("Typography " + typography + " doesn't exist.");
+ }
+ return sp(checkNotNull(TYPOGRAPHY_TO_LINE_HEIGHT_SP.get(typography)).intValue());
+ }
+
+ @NonNull
+ @SuppressLint("ResourceType")
+ @SuppressWarnings("deprecation")
+ // This is a helper function to make the font not scalable. It should interpret in value as DP
+ // and convert it to SP which is needed to be passed in as a font size. However, we will pass an
+ // SP object to it, because the default style is defined in it, but for the case when the font
+ // size on device in 1, so the DP is equal to SP.
+ private static SpProp dpToSp(@NonNull Context context, @Dimension(unit = DP) float valueDp) {
+ DisplayMetrics metrics = context.getResources().getDisplayMetrics();
+ float scaledSp = (valueDp / metrics.scaledDensity) * metrics.density;
+ return sp(scaledSp);
+ }
+
+ // The @Dimension(unit = SP) on sp() is seemingly being ignored, so lint complains that we're
+ // passing SP to something expecting PX. Just suppress the warning for now.
+ @SuppressLint("ResourceType")
+ private static FontStyle.Builder createFontStyleBuilder(
+ @Dimension(unit = SP) int size,
+ @FontWeight int weight,
+ @FontVariant int variant,
+ float letterSpacing,
+ boolean isScalable,
+ @NonNull Context context) {
+ return new FontStyle.Builder()
+ .setSize(isScalable ? DimensionBuilders.sp(size) : dpToSp(context, size))
+ .setLetterSpacing(DimensionBuilders.em(letterSpacing))
+ .setVariant(variant)
+ .setWeight(weight);
+ }
+
+ /** Font style for large display text. */
+ @NonNull
+ private static FontStyle.Builder display1(boolean isScalable, @NonNull Context context) {
+ return createFontStyleBuilder(
+ 40, FONT_WEIGHT_MEDIUM, FONT_VARIANT_TITLE, 0.01f, isScalable, context);
+ }
+
+ /** Font style for medium display text. */
+ @NonNull
+ private static FontStyle.Builder display2(boolean isScalable, @NonNull Context context) {
+ return createFontStyleBuilder(
+ 34, FONT_WEIGHT_MEDIUM, FONT_VARIANT_TITLE, 0.03f, isScalable, context);
+ }
+
+ /** Font style for small display text. */
+ @NonNull
+ private static FontStyle.Builder display3(boolean isScalable, @NonNull Context context) {
+ return createFontStyleBuilder(
+ 30, FONT_WEIGHT_MEDIUM, FONT_VARIANT_TITLE, 0.03f, isScalable, context);
+ }
+
+ /** Font style for large title text. */
+ @NonNull
+ private static FontStyle.Builder title1(boolean isScalable, @NonNull Context context) {
+ return createFontStyleBuilder(
+ 24, FONT_WEIGHT_MEDIUM, FONT_VARIANT_TITLE, 0.008f, isScalable, context);
+ }
+
+ /** Font style for medium title text. */
+ @NonNull
+ private static FontStyle.Builder title2(boolean isScalable, @NonNull Context context) {
+ return createFontStyleBuilder(
+ 20, FONT_WEIGHT_MEDIUM, FONT_VARIANT_TITLE, 0.01f, isScalable, context);
+ }
+
+ /** Font style for small title text. */
+ @NonNull
+ private static FontStyle.Builder title3(boolean isScalable, @NonNull Context context) {
+ return createFontStyleBuilder(
+ 16, FONT_WEIGHT_MEDIUM, FONT_VARIANT_TITLE, 0.01f, isScalable, context);
+ }
+
+ /** Font style for normal body text. */
+ @NonNull
+ private static FontStyle.Builder body1(boolean isScalable, @NonNull Context context) {
+ return createFontStyleBuilder(
+ 16, FONT_WEIGHT_NORMAL, FONT_VARIANT_BODY, 0.01f, isScalable, context);
+ }
+
+ /** Font style for small body text. */
+ @NonNull
+ private static FontStyle.Builder body2(boolean isScalable, @NonNull Context context) {
+ return createFontStyleBuilder(
+ 14, FONT_WEIGHT_NORMAL, FONT_VARIANT_BODY, 0.014f, isScalable, context);
+ }
+
+ /** Font style for bold button text. */
+ @NonNull
+ private static FontStyle.Builder button(boolean isScalable, @NonNull Context context) {
+ return createFontStyleBuilder(
+ 15, FONT_WEIGHT_BOLD, FONT_VARIANT_BODY, 0.03f, isScalable, context);
+ }
+
+ /** Font style for large caption text. */
+ @NonNull
+ private static FontStyle.Builder caption1(boolean isScalable, @NonNull Context context) {
+ return createFontStyleBuilder(
+ 14, FONT_WEIGHT_MEDIUM, FONT_VARIANT_BODY, 0.01f, isScalable, context);
+ }
+
+ /** Font style for medium caption text. */
+ @NonNull
+ private static FontStyle.Builder caption2(boolean isScalable, @NonNull Context context) {
+ return createFontStyleBuilder(
+ 12, FONT_WEIGHT_MEDIUM, FONT_VARIANT_BODY, 0.01f, isScalable, context);
+ }
+
+ /** Font style for small caption text. */
+ @NonNull
+ private static FontStyle.Builder caption3(boolean isScalable, @NonNull Context context) {
+ return createFontStyleBuilder(
+ 10, FONT_WEIGHT_MEDIUM, FONT_VARIANT_BODY, 0.01f, isScalable, context);
+ }
+}
diff --git a/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/androidx-wear-protolayout-protolayout-material-documentation.md b/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/androidx-wear-protolayout-protolayout-material-documentation.md
new file mode 100644
index 0000000..0b7dc77
--- /dev/null
+++ b/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/androidx-wear-protolayout-protolayout-material-documentation.md
@@ -0,0 +1,8 @@
+# Module root
+
+Wear ProtoLayout Material
+
+# Package androidx.wear.protolayout.material
+
+This package provides helpers for Wear ProtoLayout, to enable you to use Wear Material components
+such as Buttons and Chips within your ProtoLayout.
diff --git a/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/layouts/EdgeContentLayout.java b/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/layouts/EdgeContentLayout.java
new file mode 100644
index 0000000..e3e78d2
--- /dev/null
+++ b/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/layouts/EdgeContentLayout.java
@@ -0,0 +1,381 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.protolayout.material.layouts;
+
+import static androidx.wear.protolayout.DimensionBuilders.dp;
+import static androidx.wear.protolayout.DimensionBuilders.expand;
+import static androidx.wear.protolayout.material.Helper.checkNotNull;
+import static androidx.wear.protolayout.material.Helper.checkTag;
+import static androidx.wear.protolayout.material.Helper.getMetadataTagBytes;
+import static androidx.wear.protolayout.material.Helper.getTagBytes;
+import static androidx.wear.protolayout.material.Helper.isRoundDevice;
+import static androidx.wear.protolayout.material.ProgressIndicatorDefaults.DEFAULT_PADDING;
+import static androidx.wear.protolayout.material.layouts.LayoutDefaults.EDGE_CONTENT_LAYOUT_MARGIN_HORIZONTAL_ROUND_DP;
+import static androidx.wear.protolayout.material.layouts.LayoutDefaults.EDGE_CONTENT_LAYOUT_MARGIN_HORIZONTAL_SQUARE_DP;
+import static androidx.wear.protolayout.material.layouts.LayoutDefaults.EDGE_CONTENT_LAYOUT_PADDING_ABOVE_MAIN_CONTENT_DP;
+import static androidx.wear.protolayout.material.layouts.LayoutDefaults.EDGE_CONTENT_LAYOUT_PADDING_BELOW_MAIN_CONTENT_DP;
+
+import androidx.annotation.IntDef;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
+import androidx.annotation.RestrictTo.Scope;
+import androidx.wear.protolayout.DeviceParametersBuilders.DeviceParameters;
+import androidx.wear.protolayout.DimensionBuilders.DpProp;
+import androidx.wear.protolayout.LayoutElementBuilders;
+import androidx.wear.protolayout.LayoutElementBuilders.Box;
+import androidx.wear.protolayout.LayoutElementBuilders.Column;
+import androidx.wear.protolayout.LayoutElementBuilders.LayoutElement;
+import androidx.wear.protolayout.LayoutElementBuilders.Spacer;
+import androidx.wear.protolayout.ModifiersBuilders.ElementMetadata;
+import androidx.wear.protolayout.ModifiersBuilders.Modifiers;
+import androidx.wear.protolayout.ModifiersBuilders.Padding;
+import androidx.wear.protolayout.expression.Fingerprint;
+import androidx.wear.protolayout.material.CircularProgressIndicator;
+import androidx.wear.protolayout.proto.LayoutElementProto;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Tiles layout that represents the suggested layout style for Material Tiles, which has content
+ * around the edge of the screen (e.g. a ProgressIndicator) and the given content inside of it with
+ * the recommended margin and padding applied. Optional primary or secondary label can be added
+ * above and below the main content, respectively.
+ *
+ * <p>For additional examples and suggested layouts see <a
+ * href="/training/wearables/design/tiles-design-system">Tiles Design System</a>.
+ *
+ * <p>When accessing the contents of a container for testing, note that this element can't be simply
+ * casted back to the original type, i.e.:
+ *
+ * <pre>{@code
+ * EdgeContentLayout ecl = new EdgeContentLayout...
+ * Box box = new Box.Builder().addContent(ecl).build();
+ *
+ * EdgeContentLayout myEcl = (EdgeContentLayout) box.getContents().get(0);
+ * }</pre>
+ *
+ * will fail.
+ *
+ * <p>To be able to get {@link EdgeContentLayout} object from any layout element, {@link
+ * #fromLayoutElement} method should be used, i.e.:
+ *
+ * <pre>{@code
+ * EdgeContentLayout myEcl =
+ * EdgeContentLayout.fromLayoutElement(box.getContents().get(0));
+ * }</pre>
+ */
+public class EdgeContentLayout implements LayoutElement {
+ /**
+ * Prefix tool tag for Metadata in Modifiers, so we know that Box is actually a
+ * EdgeContentLayout.
+ */
+ static final String METADATA_TAG_PREFIX = "ECL_";
+
+ /**
+ * Index for byte array that contains bits to check whether the content and indicator are
+ * present or not.
+ */
+ static final int FLAG_INDEX = METADATA_TAG_PREFIX.length();
+
+ /**
+ * Base tool tag for Metadata in Modifiers, so we know that Box is actually a EdgeContentLayout
+ * and what optional content is added.
+ */
+ static final byte[] METADATA_TAG_BASE =
+ Arrays.copyOf(getTagBytes(METADATA_TAG_PREFIX), FLAG_INDEX + 1);
+
+ /**
+ * Bit position in a byte on {@link #FLAG_INDEX} index in metadata byte array to check whether
+ * the edge content is present or not.
+ */
+ static final int EDGE_CONTENT_PRESENT = 0x1;
+ /**
+ * Bit position in a byte on {@link #FLAG_INDEX} index in metadata byte array to check whether
+ * the primary label is present or not.
+ */
+ static final int PRIMARY_LABEL_PRESENT = 0x2;
+ /**
+ * Bit position in a byte on {@link #FLAG_INDEX} index in metadata byte array to check whether
+ * the secondary label is present or not.
+ */
+ static final int SECONDARY_LABEL_PRESENT = 0x4;
+ /**
+ * Bit position in a byte on {@link #FLAG_INDEX} index in metadata byte array to check whether
+ * the main content is present or not.
+ */
+ static final int CONTENT_PRESENT = 0x8;
+
+ @RestrictTo(Scope.LIBRARY)
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(
+ flag = true,
+ value = {
+ EDGE_CONTENT_PRESENT,
+ PRIMARY_LABEL_PRESENT,
+ CONTENT_PRESENT,
+ SECONDARY_LABEL_PRESENT
+ })
+ @interface ContentBits {}
+
+ @NonNull private final Box mImpl;
+
+ // This contains inner columns and edge content.
+ @NonNull private final List<LayoutElement> mContents;
+
+ // This contains optional labels, spacers and main content.
+ @NonNull private final List<LayoutElement> mInnerColumn;
+
+ EdgeContentLayout(@NonNull Box layoutElement) {
+ this.mImpl = layoutElement;
+ this.mContents = mImpl.getContents();
+ this.mInnerColumn = ((Column) ((Box) mContents.get(0)).getContents().get(0)).getContents();
+ }
+
+ /** Builder class for {@link EdgeContentLayout}. */
+ public static final class Builder implements LayoutElement.Builder {
+ @NonNull private final DeviceParameters mDeviceParameters;
+ @Nullable private LayoutElement mEdgeContent = null;
+ @Nullable private LayoutElement mPrimaryLabelText = null;
+ @Nullable private LayoutElement mSecondaryLabelText = null;
+ @Nullable private LayoutElement mContent = null;
+ private byte mMetadataContentByte = 0;
+
+ /**
+ * Creates a builder for the {@link EdgeContentLayout}t. Custom content inside of it can
+ * later be set with ({@link #setContent}.
+ */
+ public Builder(@NonNull DeviceParameters deviceParameters) {
+ this.mDeviceParameters = deviceParameters;
+ }
+
+ /**
+ * Sets the content to be around the edges. This can be {@link CircularProgressIndicator}.
+ */
+ @NonNull
+ public Builder setEdgeContent(@NonNull LayoutElement edgeContent) {
+ this.mEdgeContent = edgeContent;
+ mMetadataContentByte = (byte) (mMetadataContentByte | EDGE_CONTENT_PRESENT);
+ return this;
+ }
+
+ /** Sets the content in the primary label slot which will be above the main content. */
+ @NonNull
+ public Builder setPrimaryLabelTextContent(@NonNull LayoutElement primaryLabelText) {
+ this.mPrimaryLabelText = primaryLabelText;
+ mMetadataContentByte = (byte) (mMetadataContentByte | PRIMARY_LABEL_PRESENT);
+ return this;
+ }
+
+ /**
+ * Sets the content in the secondary label slot which will be below the main content. It is
+ * highly recommended to have primary label set when having secondary label.
+ */
+ @NonNull
+ public Builder setSecondaryLabelTextContent(@NonNull LayoutElement secondaryLabelText) {
+ this.mSecondaryLabelText = secondaryLabelText;
+ mMetadataContentByte = (byte) (mMetadataContentByte | SECONDARY_LABEL_PRESENT);
+ return this;
+ }
+
+ /** Sets the additional content to this layout, inside of the screen. */
+ @NonNull
+ public Builder setContent(@NonNull LayoutElement content) {
+ this.mContent = content;
+ mMetadataContentByte = (byte) (mMetadataContentByte | CONTENT_PRESENT);
+ return this;
+ }
+
+ /** Constructs and returns {@link EdgeContentLayout} with the provided content and look. */
+ @NonNull
+ @Override
+ public EdgeContentLayout build() {
+ float thicknessDp =
+ mEdgeContent instanceof CircularProgressIndicator
+ ? ((CircularProgressIndicator) mEdgeContent).getStrokeWidth().getValue()
+ : 0;
+ float horizontalPaddingDp =
+ isRoundDevice(mDeviceParameters)
+ ? EDGE_CONTENT_LAYOUT_MARGIN_HORIZONTAL_ROUND_DP
+ : EDGE_CONTENT_LAYOUT_MARGIN_HORIZONTAL_SQUARE_DP;
+ float indicatorWidth = 2 * (thicknessDp + DEFAULT_PADDING.getValue());
+ float mainContentHeightDp = mDeviceParameters.getScreenHeightDp() - indicatorWidth;
+ float mainContentWidthDp = mDeviceParameters.getScreenWidthDp() - indicatorWidth;
+
+ DpProp mainContentHeight = dp(Math.min(mainContentHeightDp, mainContentWidthDp));
+ DpProp mainContentWidth = dp(Math.min(mainContentHeightDp, mainContentWidthDp));
+
+ Modifiers modifiers =
+ new Modifiers.Builder()
+ .setPadding(
+ new Padding.Builder()
+ .setStart(dp(horizontalPaddingDp))
+ .setEnd(dp(horizontalPaddingDp))
+ .build())
+ .build();
+
+ byte[] metadata = METADATA_TAG_BASE.clone();
+ metadata[FLAG_INDEX] = mMetadataContentByte;
+ Box.Builder mainBoxBuilder =
+ new Box.Builder()
+ .setWidth(expand())
+ .setHeight(expand())
+ .setModifiers(
+ new Modifiers.Builder()
+ .setMetadata(
+ new ElementMetadata.Builder()
+ .setTagData(metadata)
+ .build())
+ .build())
+ .setHorizontalAlignment(LayoutElementBuilders.HORIZONTAL_ALIGN_CENTER)
+ .setVerticalAlignment(LayoutElementBuilders.VERTICAL_ALIGN_CENTER);
+
+ Column.Builder innerContentBuilder =
+ new Column.Builder()
+ .setHorizontalAlignment(LayoutElementBuilders.HORIZONTAL_ALIGN_CENTER);
+
+ if (mPrimaryLabelText != null) {
+ innerContentBuilder.addContent(mPrimaryLabelText);
+ innerContentBuilder.addContent(
+ new Spacer.Builder()
+ .setHeight(dp(EDGE_CONTENT_LAYOUT_PADDING_ABOVE_MAIN_CONTENT_DP))
+ .build());
+ }
+
+ if (mContent != null) {
+ innerContentBuilder.addContent(
+ new Box.Builder()
+ .setVerticalAlignment(LayoutElementBuilders.VERTICAL_ALIGN_CENTER)
+ .addContent(mContent)
+ .build());
+ }
+
+ if (mSecondaryLabelText != null) {
+ innerContentBuilder.addContent(
+ new Spacer.Builder()
+ .setHeight(dp(EDGE_CONTENT_LAYOUT_PADDING_BELOW_MAIN_CONTENT_DP))
+ .build());
+ innerContentBuilder.addContent(mSecondaryLabelText);
+ }
+
+ mainBoxBuilder.addContent(
+ new Box.Builder()
+ .setModifiers(modifiers)
+ .setVerticalAlignment(LayoutElementBuilders.VERTICAL_ALIGN_CENTER)
+ .setHorizontalAlignment(LayoutElementBuilders.HORIZONTAL_ALIGN_CENTER)
+ .setHeight(mainContentHeight)
+ .setWidth(mainContentWidth)
+ .addContent(innerContentBuilder.build())
+ .build());
+
+ if (mEdgeContent != null) {
+ mainBoxBuilder.addContent(mEdgeContent);
+ }
+
+ return new EdgeContentLayout(mainBoxBuilder.build());
+ }
+ }
+
+ private boolean areElementsPresent(@ContentBits int elementFlag) {
+ return (getMetadataTag()[FLAG_INDEX] & elementFlag) == elementFlag;
+ }
+
+ /** Returns metadata tag set to this EdgeContentLayout. */
+ @NonNull
+ byte[] getMetadataTag() {
+ return getMetadataTagBytes(checkNotNull(checkNotNull(mImpl.getModifiers()).getMetadata()));
+ }
+
+ /** Returns the inner content from this layout. */
+ @Nullable
+ public LayoutElement getContent() {
+ if (!areElementsPresent(CONTENT_PRESENT)) {
+ return null;
+ }
+ // By tag we know that content exists. It will be at position 0 if there is no primary
+ // label, or at position 2 (primary label, spacer - content) otherwise.
+ int contentPosition = areElementsPresent(PRIMARY_LABEL_PRESENT) ? 2 : 0;
+ return ((Box) mInnerColumn.get(contentPosition)).getContents().get(0);
+ }
+
+ /** Get the primary label content from this layout. */
+ @Nullable
+ public LayoutElement getPrimaryLabelTextContent() {
+ if (!areElementsPresent(PRIMARY_LABEL_PRESENT)) {
+ return null;
+ }
+ // By tag we know that primary label exists. It will always be at position 0.
+ return mInnerColumn.get(0);
+ }
+
+ /** Get the secondary label content from this layout. */
+ @Nullable
+ public LayoutElement getSecondaryLabelTextContent() {
+ if (!areElementsPresent(SECONDARY_LABEL_PRESENT)) {
+ return null;
+ }
+ // By tag we know that secondary label exists. It will always be at last position.
+ return mInnerColumn.get(mInnerColumn.size() - 1);
+ }
+
+ /** Returns the edge content from this layout. */
+ @Nullable
+ public LayoutElement getEdgeContent() {
+ if (areElementsPresent(EDGE_CONTENT_PRESENT)) {
+ return mContents.get(1);
+ }
+ return null;
+ }
+
+ /**
+ * Returns EdgeContentLayout object from the given LayoutElement (e.g. one retrieved from a
+ * container's content with {@code container.getContents().get(index)}) if that element can be
+ * converted to EdgeContentLayout. Otherwise, it will return null.
+ */
+ @Nullable
+ public static EdgeContentLayout fromLayoutElement(@NonNull LayoutElement element) {
+ if (element instanceof EdgeContentLayout) {
+ return (EdgeContentLayout) element;
+ }
+ if (!(element instanceof Box)) {
+ return null;
+ }
+ Box boxElement = (Box) element;
+ if (!checkTag(boxElement.getModifiers(), METADATA_TAG_PREFIX, METADATA_TAG_BASE)) {
+ return null;
+ }
+ // Now we are sure that this element is a EdgeContentLayout.
+ return new EdgeContentLayout(boxElement);
+ }
+
+ @NonNull
+ @Override
+ @RestrictTo(Scope.LIBRARY_GROUP)
+ public LayoutElementProto.LayoutElement toLayoutElementProto() {
+ return mImpl.toLayoutElementProto();
+ }
+
+ @Nullable
+ @Override
+ @RestrictTo(Scope.LIBRARY_GROUP)
+ public Fingerprint getFingerprint() {
+ return mImpl.getFingerprint();
+ }
+}
diff --git a/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/layouts/LayoutDefaults.java b/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/layouts/LayoutDefaults.java
new file mode 100644
index 0000000..42a3381
--- /dev/null
+++ b/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/layouts/LayoutDefaults.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.protolayout.material.layouts;
+
+import static androidx.wear.protolayout.DimensionBuilders.dp;
+
+import androidx.wear.protolayout.DimensionBuilders.DpProp;
+import androidx.wear.protolayout.material.ButtonDefaults;
+
+/** Contains the default values used by layout templates for Tiles. */
+public class LayoutDefaults {
+ private LayoutDefaults() {}
+
+ /**
+ * The default percentage for the bottom margin for primary chip in the {@link PrimaryLayout}.
+ */
+ static final float PRIMARY_LAYOUT_MARGIN_BOTTOM_ROUND_PERCENT = 2.1f / 100;
+
+ /**
+ * The default percentage for the bottom margin for primary chip in the {@link PrimaryLayout}.
+ */
+ static final float PRIMARY_LAYOUT_MARGIN_BOTTOM_SQUARE_PERCENT = 0;
+
+ /**
+ * The default percentage for the top margin for primary chip in the {@link PrimaryLayout} on
+ * round devices.
+ */
+ static final float PRIMARY_LAYOUT_MARGIN_TOP_ROUND_PERCENT = 16.7f / 100;
+
+ /**
+ * The default percentage for the top margin for primary chip in the {@link PrimaryLayout} on
+ * square devices.
+ */
+ static final float PRIMARY_LAYOUT_MARGIN_TOP_SQUARE_PERCENT = 13.3f / 100;
+
+ /**
+ * The default spacer above primary label in {@link PrimaryLayout} to make space for Tile icon
+ * on round devices.
+ */
+ static final DpProp PRIMARY_LAYOUT_PRIMARY_LABEL_SPACER_HEIGHT_ROUND_DP = dp(0);
+
+ /**
+ * The default spacer above primary label in {@link PrimaryLayout} to make space for Tile icon
+ * on square devices.
+ */
+ static final DpProp PRIMARY_LAYOUT_PRIMARY_LABEL_SPACER_HEIGHT_SQUARE_DP = dp(4);
+
+ /**
+ * The default percentage for the horizontal margin for primary chip in the {@link
+ * PrimaryLayout}.
+ */
+ static final float PRIMARY_LAYOUT_MARGIN_HORIZONTAL_ROUND_PERCENT = 6.3f / 100;
+
+ /**
+ * The default percentage for the horizontal margin for primary chip in the {@link
+ * PrimaryLayout}.
+ */
+ static final float PRIMARY_LAYOUT_MARGIN_HORIZONTAL_SQUARE_PERCENT = 2.8f / 100;
+
+ /**
+ * The padding for the primary chip in {@link PrimaryLayout} so it doesn't bleed off screen if
+ * text is too big.
+ */
+ static final float PRIMARY_LAYOUT_CHIP_HORIZONTAL_PADDING_ROUND_DP = 30;
+
+ /**
+ * The padding for the primary chip in {@link PrimaryLayout} so it doesn't bleed off screen if
+ * text is too big.
+ */
+ static final float PRIMARY_LAYOUT_CHIP_HORIZONTAL_PADDING_SQUARE_DP = 0;
+
+ /** The default horizontal margin in the {@link EdgeContentLayout}. */
+ static final float EDGE_CONTENT_LAYOUT_MARGIN_HORIZONTAL_ROUND_DP = 14;
+
+ /** The default horizontal margin in the {@link EdgeContentLayout}. */
+ static final float EDGE_CONTENT_LAYOUT_MARGIN_HORIZONTAL_SQUARE_DP = 16;
+
+ /**
+ * The recommended padding that should be above the main content (text) in the {@link
+ * EdgeContentLayout}.
+ */
+ public static final float EDGE_CONTENT_LAYOUT_PADDING_ABOVE_MAIN_CONTENT_DP = 6;
+
+ /**
+ * The recommended padding that should be below the main content (text) in the {@link
+ * EdgeContentLayout}.
+ */
+ public static final float EDGE_CONTENT_LAYOUT_PADDING_BELOW_MAIN_CONTENT_DP = 8;
+
+ /** The default spacer width for slots in a {@link MultiSlotLayout}. */
+ public static final DpProp MULTI_SLOT_LAYOUT_HORIZONTAL_SPACER_WIDTH = dp(8);
+
+ /** The recommended space between the main content and additional labels in layouts. */
+ public static final DpProp DEFAULT_VERTICAL_SPACER_HEIGHT = dp(8);
+
+ /** The maximum number of button that can be added to the {@link MultiButtonLayout}. */
+ public static final int MULTI_BUTTON_MAX_NUMBER = 7;
+
+ /**
+ * The default size of button in case when there are 3 or more buttons in the {@link
+ * MultiButtonLayout}.
+ */
+ static final DpProp MULTI_BUTTON_3_PLUS_SIZE = ButtonDefaults.DEFAULT_SIZE;
+
+ /** The default size of button in case when there 2 buttons in the {@link MultiButtonLayout}. */
+ static final DpProp MULTI_BUTTON_2_SIZE = ButtonDefaults.LARGE_SIZE;
+
+ /**
+ * The default size of button in case when there is 1 button in the {@link MultiButtonLayout}.
+ */
+ static final DpProp MULTI_BUTTON_1_SIZE = ButtonDefaults.EXTRA_LARGE_SIZE;
+
+ /** The default width for vertical spacer between buttons in the {@link MultiButtonLayout}. */
+ static final DpProp MULTI_BUTTON_SPACER_WIDTH = dp(6);
+
+ /**
+ * The default height for horizontal spacer between buttons in the {@link MultiButtonLayout}.
+ */
+ static final DpProp MULTI_BUTTON_SPACER_HEIGHT = dp(4);
+}
diff --git a/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/layouts/MultiButtonLayout.java b/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/layouts/MultiButtonLayout.java
new file mode 100644
index 0000000..e4186c5
--- /dev/null
+++ b/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/layouts/MultiButtonLayout.java
@@ -0,0 +1,399 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.protolayout.material.layouts;
+
+import static androidx.wear.protolayout.DimensionBuilders.wrap;
+import static androidx.wear.protolayout.material.Helper.checkNotNull;
+import static androidx.wear.protolayout.material.Helper.checkTag;
+import static androidx.wear.protolayout.material.Helper.getMetadataTagName;
+import static androidx.wear.protolayout.material.Helper.getTagBytes;
+import static androidx.wear.protolayout.material.layouts.LayoutDefaults.MULTI_BUTTON_1_SIZE;
+import static androidx.wear.protolayout.material.layouts.LayoutDefaults.MULTI_BUTTON_2_SIZE;
+import static androidx.wear.protolayout.material.layouts.LayoutDefaults.MULTI_BUTTON_3_PLUS_SIZE;
+import static androidx.wear.protolayout.material.layouts.LayoutDefaults.MULTI_BUTTON_MAX_NUMBER;
+import static androidx.wear.protolayout.material.layouts.LayoutDefaults.MULTI_BUTTON_SPACER_HEIGHT;
+import static androidx.wear.protolayout.material.layouts.LayoutDefaults.MULTI_BUTTON_SPACER_WIDTH;
+
+import androidx.annotation.IntDef;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
+import androidx.annotation.RestrictTo.Scope;
+import androidx.wear.protolayout.DimensionBuilders.DpProp;
+import androidx.wear.protolayout.LayoutElementBuilders.Box;
+import androidx.wear.protolayout.LayoutElementBuilders.Column;
+import androidx.wear.protolayout.LayoutElementBuilders.LayoutElement;
+import androidx.wear.protolayout.LayoutElementBuilders.Row;
+import androidx.wear.protolayout.LayoutElementBuilders.Spacer;
+import androidx.wear.protolayout.ModifiersBuilders.ElementMetadata;
+import androidx.wear.protolayout.ModifiersBuilders.Modifiers;
+import androidx.wear.protolayout.expression.Fingerprint;
+import androidx.wear.protolayout.material.Button;
+import androidx.wear.protolayout.proto.LayoutElementProto;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Opinionated Tiles layout, that can contain between 1 and {@link
+ * LayoutDefaults#MULTI_BUTTON_MAX_NUMBER} number of buttons arranged inline with the Material
+ * guidelines. Can be used as a content passed in to the {@link PrimaryLayout}, but if there is
+ * {@link LayoutDefaults#MULTI_BUTTON_MAX_NUMBER} buttons it should be used on its own.
+ *
+ * <p>For additional examples and suggested layouts see <a
+ * href="/training/wearables/design/tiles-design-system">Tiles Design System</a>.
+ *
+ * <p>When accessing the contents of a container for testing, note that this element can't be simply
+ * casted back to the original type, i.e.:
+ *
+ * <pre>{@code
+ * MultiButtonLayout mbl = new MultiButtonLayout...
+ * Box box = new Box.Builder().addContent(mbl).build();
+ *
+ * MultiButtonLayout myMbl = (MultiButtonLayout) box.getContents().get(0);
+ * }</pre>
+ *
+ * will fail.
+ *
+ * <p>To be able to get {@link MultiButtonLayout} object from any layout element, {@link
+ * #fromLayoutElement} method should be used, i.e.:
+ *
+ * <pre>{@code
+ * MultiButtonLayout myMbl = MultiButtonLayout.fromLayoutElement(box.getContents().get(0));
+ * }</pre>
+ */
+public class MultiButtonLayout implements LayoutElement {
+ /** Tool tag for Metadata in Modifiers, so we know that Box is actually a MultiButtonLayout. */
+ static final String METADATA_TAG = "MBL";
+
+ /** Button distribution where the first row has more buttons than other rows. */
+ public static final int FIVE_BUTTON_DISTRIBUTION_TOP_HEAVY = 1;
+
+ /** Button distribution where the last row has more buttons than other rows. */
+ public static final int FIVE_BUTTON_DISTRIBUTION_BOTTOM_HEAVY = 2;
+
+ @RestrictTo(Scope.LIBRARY)
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({FIVE_BUTTON_DISTRIBUTION_TOP_HEAVY, FIVE_BUTTON_DISTRIBUTION_BOTTOM_HEAVY})
+ public @interface ButtonDistribution {}
+
+ @NonNull private final Box mElement;
+
+ MultiButtonLayout(@NonNull Box mElement) {
+ this.mElement = mElement;
+ }
+
+ /** Builder class for {@link MultiButtonLayout}. */
+ public static final class Builder implements LayoutElement.Builder {
+ @NonNull private final List<LayoutElement> mButtonsContent = new ArrayList<>();
+ private @ButtonDistribution int mFiveButtonDistribution =
+ FIVE_BUTTON_DISTRIBUTION_BOTTOM_HEAVY;
+
+ /**
+ * Creates a builder for the {@link MultiButtonLayout}. Content inside of it can later be
+ * added with {@link #addButtonContent}.
+ */
+ public Builder() {}
+
+ /**
+ * Add one new button to the layout. Note that it is accepted to pass in any {@link
+ * LayoutElement}, but it is strongly recommended to add a {@link Button} as the layout is
+ * optimized for it. Any button added after {@link LayoutDefaults#MULTI_BUTTON_MAX_NUMBER}
+ * is reached will be discarded.
+ */
+ @NonNull
+ @SuppressWarnings("MissingGetterMatchingBuilder")
+ // There is no direct matching getter for this setter, but there is a getter that gets all
+ // added buttons.
+ public Builder addButtonContent(@NonNull LayoutElement buttonContent) {
+ mButtonsContent.add(buttonContent);
+ return this;
+ }
+
+ /**
+ * Sets the button distribution for this layout. Button distribution is used in case when
+ * there is 5 buttons in the layout to determine whether the 3 buttons row is at the top or
+ * bottom.
+ */
+ @NonNull
+ public Builder setFiveButtonDistribution(@ButtonDistribution int fiveButtonDistribution) {
+ this.mFiveButtonDistribution = fiveButtonDistribution;
+ return this;
+ }
+
+ /** Constructs and returns {@link MultiButtonLayout} with the provided content and look. */
+ @NonNull
+ @Override
+ public MultiButtonLayout build() {
+ int buttonNum = mButtonsContent.size();
+ if (buttonNum > MULTI_BUTTON_MAX_NUMBER) {
+ throw new IllegalArgumentException(
+ "Too many buttons are added. Maximum number is "
+ + MULTI_BUTTON_MAX_NUMBER
+ + ".");
+ }
+
+ LayoutElement buttons = buildButtons(buttonNum);
+ Box.Builder elementBuilder =
+ new Box.Builder()
+ .setModifiers(
+ new Modifiers.Builder()
+ .setMetadata(
+ new ElementMetadata.Builder()
+ .setTagData(getTagBytes(METADATA_TAG))
+ .build())
+ .build())
+ .addContent(buttons);
+
+ return new MultiButtonLayout(elementBuilder.build());
+ }
+
+ @NonNull
+ private LayoutElement buildButtons(int buttonNum) {
+ switch (buttonNum) {
+ case 1:
+ return wrapButton(mButtonsContent.get(0), MULTI_BUTTON_1_SIZE);
+ case 2:
+ return build2ButtonRow(
+ mButtonsContent.get(0), mButtonsContent.get(1), MULTI_BUTTON_2_SIZE);
+ case 3:
+ return build3ButtonRow(
+ mButtonsContent.get(0), mButtonsContent.get(1), mButtonsContent.get(2));
+ case 4:
+ return new Column.Builder()
+ .addContent(
+ build2ButtonRow(
+ mButtonsContent.get(0),
+ mButtonsContent.get(1),
+ MULTI_BUTTON_3_PLUS_SIZE))
+ .addContent(buildVerticalSpacer())
+ .addContent(
+ build2ButtonRow(
+ mButtonsContent.get(2),
+ mButtonsContent.get(3),
+ MULTI_BUTTON_3_PLUS_SIZE))
+ .build();
+ case 5:
+ return new Column.Builder()
+ .addContent(
+ mFiveButtonDistribution == FIVE_BUTTON_DISTRIBUTION_TOP_HEAVY
+ ? build3ButtonRow(
+ mButtonsContent.get(0),
+ mButtonsContent.get(1),
+ mButtonsContent.get(2))
+ : build2ButtonRow(
+ mButtonsContent.get(0),
+ mButtonsContent.get(1),
+ MULTI_BUTTON_3_PLUS_SIZE))
+ .addContent(buildVerticalSpacer())
+ .addContent(
+ mFiveButtonDistribution == FIVE_BUTTON_DISTRIBUTION_TOP_HEAVY
+ ? build2ButtonRow(
+ mButtonsContent.get(3),
+ mButtonsContent.get(4),
+ MULTI_BUTTON_3_PLUS_SIZE)
+ : build3ButtonRow(
+ mButtonsContent.get(2),
+ mButtonsContent.get(3),
+ mButtonsContent.get(4)))
+ .build();
+ case 6:
+ return new Column.Builder()
+ .addContent(
+ build3ButtonRow(
+ mButtonsContent.get(0),
+ mButtonsContent.get(1),
+ mButtonsContent.get(2)))
+ .addContent(buildVerticalSpacer())
+ .addContent(
+ build3ButtonRow(
+ mButtonsContent.get(3),
+ mButtonsContent.get(4),
+ mButtonsContent.get(5)))
+ .build();
+ case 7:
+ return new Column.Builder()
+ .addContent(
+ build2ButtonRow(
+ mButtonsContent.get(0),
+ mButtonsContent.get(1),
+ MULTI_BUTTON_3_PLUS_SIZE))
+ .addContent(buildVerticalSpacer())
+ .addContent(
+ build3ButtonRow(
+ mButtonsContent.get(2),
+ mButtonsContent.get(3),
+ mButtonsContent.get(4)))
+ .addContent(buildVerticalSpacer())
+ .addContent(
+ build2ButtonRow(
+ mButtonsContent.get(5),
+ mButtonsContent.get(6),
+ MULTI_BUTTON_3_PLUS_SIZE))
+ .build();
+ }
+ // This shouldn't happen, but return an empty Box instead of having this method nullable
+ // and checks above.
+ return new Box.Builder().build();
+ }
+
+ @NonNull
+ private Row build3ButtonRow(
+ @NonNull LayoutElement button1,
+ @NonNull LayoutElement button2,
+ @NonNull LayoutElement button3) {
+ return new Row.Builder()
+ .setWidth(wrap())
+ .setHeight(wrap())
+ .addContent(wrapButton(button1, MULTI_BUTTON_3_PLUS_SIZE))
+ .addContent(buildHorizontalSpacer())
+ .addContent(wrapButton(button2, MULTI_BUTTON_3_PLUS_SIZE))
+ .addContent(buildHorizontalSpacer())
+ .addContent(wrapButton(button3, MULTI_BUTTON_3_PLUS_SIZE))
+ .build();
+ }
+
+ @NonNull
+ private Row build2ButtonRow(
+ @NonNull LayoutElement button1,
+ @NonNull LayoutElement button2,
+ @NonNull DpProp size) {
+ return new Row.Builder()
+ .setWidth(wrap())
+ .setHeight(wrap())
+ .addContent(wrapButton(button1, size))
+ .addContent(buildHorizontalSpacer())
+ .addContent(wrapButton(button2, size))
+ .build();
+ }
+
+ @NonNull
+ private Spacer buildHorizontalSpacer() {
+ return new Spacer.Builder().setWidth(MULTI_BUTTON_SPACER_WIDTH).build();
+ }
+
+ @NonNull
+ private Spacer buildVerticalSpacer() {
+ return new Spacer.Builder().setHeight(MULTI_BUTTON_SPACER_HEIGHT).build();
+ }
+
+ @NonNull
+ private Box wrapButton(@NonNull LayoutElement button, @NonNull DpProp size) {
+ return new Box.Builder().setWidth(size).setHeight(size).addContent(button).build();
+ }
+ }
+
+ /** Gets the content from this layout, containing all buttons that were added. */
+ @NonNull
+ public List<LayoutElement> getButtonContents() {
+ List<LayoutElement> buttons = new ArrayList<>();
+ List<LayoutElement> contents = mElement.getContents();
+ if (contents.isEmpty()) {
+ return buttons;
+ }
+ LayoutElement innerContent = contents.get(0);
+ if (innerContent instanceof Column) {
+ for (LayoutElement row : ((Column) innerContent).getContents()) {
+ if (row instanceof Row) {
+ buttons.addAll(getButtonsFromRow((Row) row));
+ }
+ }
+ } else if (innerContent instanceof Row) {
+ return getButtonsFromRow((Row) innerContent);
+ } else if (innerContent instanceof Box) {
+ buttons.add(((Box) innerContent).getContents().get(0));
+ }
+
+ return buttons;
+ }
+
+ /** Returns metadata tag set to this MultiButtonLayouts. */
+ @NonNull
+ String getMetadataTag() {
+ return getMetadataTagName(
+ checkNotNull(checkNotNull(mElement.getModifiers()).getMetadata()));
+ }
+
+ /**
+ * Gets the button distribution from this layout for the case when there is 5 buttons in the
+ * layout. If there is more or less buttons than 5, default {@link
+ * #FIVE_BUTTON_DISTRIBUTION_BOTTOM_HEAVY} will be returned.
+ */
+ public int getFiveButtonDistribution() {
+ List<LayoutElement> contents = mElement.getContents();
+ if (getButtonContents().size() != 5) {
+ return FIVE_BUTTON_DISTRIBUTION_BOTTOM_HEAVY;
+ }
+ LayoutElement innerContent = contents.get(0);
+ if (innerContent instanceof Column && ((Column) innerContent).getContents().size() == 3) {
+ // 1st and 3rd row are buttons. Check whether the first row has 5 (3 buttons + 2 spacer)
+ // - top heavy or 3 (2 buttons + spacer) - bottom heavy elements.
+ LayoutElement firstElement = ((Column) innerContent).getContents().get(0);
+ if (firstElement instanceof Row && ((Row) firstElement).getContents().size() == 5) {
+ return FIVE_BUTTON_DISTRIBUTION_TOP_HEAVY;
+ }
+ }
+ return FIVE_BUTTON_DISTRIBUTION_BOTTOM_HEAVY;
+ }
+
+ private List<LayoutElement> getButtonsFromRow(Row row) {
+ List<LayoutElement> buttons = new ArrayList<>();
+ for (LayoutElement element : row.getContents()) {
+ if (element instanceof Box) {
+ buttons.add(((Box) element).getContents().get(0));
+ }
+ }
+ return buttons;
+ }
+
+ /**
+ * Returns MultiButtonLayout object from the given LayoutElement (e.g. one retrieved from a
+ * container's content with {@code container.getContents().get(index)}) if that element can be
+ * converted to MultiButtonLayout. Otherwise, it will return null.
+ */
+ @Nullable
+ public static MultiButtonLayout fromLayoutElement(@NonNull LayoutElement element) {
+ if (element instanceof MultiButtonLayout) {
+ return (MultiButtonLayout) element;
+ }
+ if (!(element instanceof Box)) {
+ return null;
+ }
+ Box boxElement = (Box) element;
+ if (!checkTag(boxElement.getModifiers(), METADATA_TAG)) {
+ return null;
+ }
+ // Now we are sure that this element is a MultiButtonLayout.
+ return new MultiButtonLayout(boxElement);
+ }
+
+ @NonNull
+ @Override
+ @RestrictTo(Scope.LIBRARY_GROUP)
+ public LayoutElementProto.LayoutElement toLayoutElementProto() {
+ return mElement.toLayoutElementProto();
+ }
+
+ @Nullable
+ @Override
+ @RestrictTo(Scope.LIBRARY_GROUP)
+ public Fingerprint getFingerprint() {
+ return mElement.getFingerprint();
+ }
+}
diff --git a/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/layouts/MultiSlotLayout.java b/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/layouts/MultiSlotLayout.java
new file mode 100644
index 0000000..8f9e903
--- /dev/null
+++ b/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/layouts/MultiSlotLayout.java
@@ -0,0 +1,237 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.protolayout.material.layouts;
+
+import static androidx.annotation.Dimension.DP;
+import static androidx.wear.protolayout.DimensionBuilders.dp;
+import static androidx.wear.protolayout.DimensionBuilders.wrap;
+import static androidx.wear.protolayout.material.Helper.checkNotNull;
+import static androidx.wear.protolayout.material.Helper.checkTag;
+import static androidx.wear.protolayout.material.Helper.getMetadataTagName;
+import static androidx.wear.protolayout.material.Helper.getTagBytes;
+import static androidx.wear.protolayout.material.layouts.LayoutDefaults.MULTI_SLOT_LAYOUT_HORIZONTAL_SPACER_WIDTH;
+
+import android.annotation.SuppressLint;
+
+import androidx.annotation.Dimension;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
+import androidx.annotation.RestrictTo.Scope;
+import androidx.wear.protolayout.DimensionBuilders.DpProp;
+import androidx.wear.protolayout.DimensionBuilders.SpacerDimension;
+import androidx.wear.protolayout.LayoutElementBuilders;
+import androidx.wear.protolayout.LayoutElementBuilders.Box;
+import androidx.wear.protolayout.LayoutElementBuilders.LayoutElement;
+import androidx.wear.protolayout.LayoutElementBuilders.Row;
+import androidx.wear.protolayout.LayoutElementBuilders.Spacer;
+import androidx.wear.protolayout.ModifiersBuilders.ElementMetadata;
+import androidx.wear.protolayout.ModifiersBuilders.Modifiers;
+import androidx.wear.protolayout.expression.Fingerprint;
+import androidx.wear.protolayout.proto.LayoutElementProto;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Opinionated Tiles layout, row like style with horizontally aligned and spaced slots (for icons or
+ * other small content). Should be used as a content passed in to the {@link PrimaryLayout}.
+ *
+ * <p>Recommended number of added slots is 1 to 3. Their width will be the width of an element
+ * passed in, with the {@link LayoutDefaults#MULTI_SLOT_LAYOUT_HORIZONTAL_SPACER_WIDTH} space
+ * between.
+ *
+ * <p>For additional examples and suggested layouts see <a
+ * href="/training/wearables/design/tiles-design-system">Tiles Design System</a>.
+ *
+ * <p>When accessing the contents of a container for testing, note that this element can't be simply
+ * casted back to the original type, i.e.:
+ *
+ * <pre>{@code
+ * MultiSlotLayout msl = new MultiSlotLayout...
+ * Box box = new Box.Builder().addContent(msl).build();
+ *
+ * MultiSlotLayout myMsl = (MultiSlotLayout) box.getContents().get(0);
+ * }</pre>
+ *
+ * will fail.
+ *
+ * <p>To be able to get {@link MultiSlotLayout} object from any layout element, {@link
+ * #fromLayoutElement} method should be used, i.e.:
+ *
+ * <pre>{@code
+ * MultiSlotLayout myMsl = MultiSlotLayout.fromLayoutElement(box.getContents().get(0));
+ * }</pre>
+ */
+public class MultiSlotLayout implements LayoutElement {
+ /** Tool tag for Metadata in Modifiers, so we know that Row is actually a MultiSlotLayout. */
+ static final String METADATA_TAG = "MSL";
+
+ @NonNull private final Row mElement;
+
+ MultiSlotLayout(@NonNull Row mElement) {
+ this.mElement = mElement;
+ }
+
+ /** Builder class for {@link MultiSlotLayout}. */
+ public static final class Builder implements LayoutElement.Builder {
+
+ @NonNull private final List<LayoutElement> mSlotsContent = new ArrayList<>();
+ @NonNull private DpProp mHorizontalSpacerWidth = MULTI_SLOT_LAYOUT_HORIZONTAL_SPACER_WIDTH;
+
+ /**
+ * Creates a builder for the {@link MultiSlotLayout}. Content inside of it can later be
+ * added with {@link #addSlotContent}.
+ */
+ public Builder() {}
+
+ /** Add one new slot to the layout with the given content inside. */
+ @NonNull
+ @SuppressWarnings("MissingGetterMatchingBuilder")
+ // There is no direct matching getter for this setter, but there is a getter that gets all
+ // added slots.
+ public Builder addSlotContent(@NonNull LayoutElement slotContent) {
+ mSlotsContent.add(slotContent);
+ return this;
+ }
+
+ /**
+ * Sets the horizontal spacer width which is used as a space between slots if there is more
+ * than one slot. If not set, {@link
+ * LayoutDefaults#MULTI_SLOT_LAYOUT_HORIZONTAL_SPACER_WIDTH} will be used.
+ */
+ @NonNull
+ public Builder setHorizontalSpacerWidth(@Dimension(unit = DP) float width) {
+ this.mHorizontalSpacerWidth = dp(width);
+ return this;
+ }
+
+ /** Constructs and returns {@link MultiSlotLayout} with the provided content and look. */
+ @NonNull
+ @Override
+ // The @Dimension(unit = DP) on mVerticalSpacerHeight.getValue() is seemingly being ignored,
+ // so lint complains that we're passing PX to something expecting DP. Just suppress the
+ // warning for now.
+ @SuppressLint("ResourceType")
+ public MultiSlotLayout build() {
+ Row.Builder rowBuilder =
+ new Row.Builder()
+ .setHeight(wrap())
+ .setVerticalAlignment(LayoutElementBuilders.VERTICAL_ALIGN_CENTER)
+ .setWidth(wrap())
+ .setModifiers(
+ new Modifiers.Builder()
+ .setMetadata(
+ new ElementMetadata.Builder()
+ .setTagData(getTagBytes(METADATA_TAG))
+ .build())
+ .build());
+ if (!mSlotsContent.isEmpty()) {
+
+ boolean isFirst = true;
+ for (LayoutElement slot : mSlotsContent) {
+ if (!isFirst) {
+ rowBuilder.addContent(
+ new Spacer.Builder().setWidth(mHorizontalSpacerWidth).build());
+ } else {
+ isFirst = false;
+ }
+ rowBuilder.addContent(
+ new Box.Builder()
+ .setWidth(wrap())
+ .setHeight(wrap())
+ .addContent(slot)
+ .build());
+ }
+ }
+
+ return new MultiSlotLayout(rowBuilder.build());
+ }
+ }
+
+ /** Gets the content from this layout, containing all slots that were added. */
+ @NonNull
+ public List<LayoutElement> getSlotContents() {
+ List<LayoutElement> slots = new ArrayList<>();
+ for (LayoutElement slot : mElement.getContents()) {
+ if (slot instanceof Box) {
+ slots.add(((Box) slot).getContents().get(0));
+ }
+ }
+ return slots;
+ }
+
+ /** Gets the width of horizontal spacer that is between slots. */
+ // The @Dimension(unit = DP) on getLinearDimension.getValue() is seemingly being ignored, so
+ // lint complains that we're passing PX to something expecting DP. Just suppress the warning for
+ // now.
+ @SuppressLint("ResourceType")
+ @Dimension(unit = DP)
+ public float getHorizontalSpacerWidth() {
+ for (LayoutElement slot : mElement.getContents()) {
+ if (slot instanceof Spacer) {
+ SpacerDimension width = ((Spacer) slot).getWidth();
+ if (width instanceof DpProp) {
+ return ((DpProp) width).getValue();
+ }
+ }
+ }
+ return LayoutDefaults.MULTI_SLOT_LAYOUT_HORIZONTAL_SPACER_WIDTH.getValue();
+ }
+
+ /** Returns metadata tag set to this MultiSlotLayout. */
+ @NonNull
+ String getMetadataTag() {
+ return getMetadataTagName(
+ checkNotNull(checkNotNull(mElement.getModifiers()).getMetadata()));
+ }
+
+ /**
+ * Returns MultiSlotLayout object from the given LayoutElement (e.g. one retrieved from a
+ * container's content with {@code container.getContents().get(index)}) if that element can be
+ * converted to MultiSlotLayout. Otherwise, it will return null.
+ */
+ @Nullable
+ public static MultiSlotLayout fromLayoutElement(@NonNull LayoutElement element) {
+ if (element instanceof MultiSlotLayout) {
+ return (MultiSlotLayout) element;
+ }
+ if (!(element instanceof Row)) {
+ return null;
+ }
+ Row rowElement = (Row) element;
+ if (!checkTag(rowElement.getModifiers(), METADATA_TAG)) {
+ return null;
+ }
+ // Now we are sure that this element is a MultiSlotLayout.
+ return new MultiSlotLayout(rowElement);
+ }
+
+ @NonNull
+ @Override
+ @RestrictTo(Scope.LIBRARY_GROUP)
+ public LayoutElementProto.LayoutElement toLayoutElementProto() {
+ return mElement.toLayoutElementProto();
+ }
+
+ @Nullable
+ @Override
+ @RestrictTo(Scope.LIBRARY_GROUP)
+ public Fingerprint getFingerprint() {
+ return mElement.getFingerprint();
+ }
+}
diff --git a/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/layouts/PrimaryLayout.java b/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/layouts/PrimaryLayout.java
new file mode 100644
index 0000000..2bbbac5
--- /dev/null
+++ b/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/layouts/PrimaryLayout.java
@@ -0,0 +1,540 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.protolayout.material.layouts;
+
+import static androidx.annotation.Dimension.DP;
+import static androidx.wear.protolayout.DimensionBuilders.dp;
+import static androidx.wear.protolayout.DimensionBuilders.expand;
+import static androidx.wear.protolayout.DimensionBuilders.wrap;
+import static androidx.wear.protolayout.material.ChipDefaults.COMPACT_HEIGHT_TAPPABLE;
+import static androidx.wear.protolayout.material.Helper.checkNotNull;
+import static androidx.wear.protolayout.material.Helper.checkTag;
+import static androidx.wear.protolayout.material.Helper.getMetadataTagBytes;
+import static androidx.wear.protolayout.material.Helper.getTagBytes;
+import static androidx.wear.protolayout.material.Helper.isRoundDevice;
+import static androidx.wear.protolayout.material.layouts.LayoutDefaults.DEFAULT_VERTICAL_SPACER_HEIGHT;
+import static androidx.wear.protolayout.material.layouts.LayoutDefaults.PRIMARY_LAYOUT_CHIP_HORIZONTAL_PADDING_ROUND_DP;
+import static androidx.wear.protolayout.material.layouts.LayoutDefaults.PRIMARY_LAYOUT_CHIP_HORIZONTAL_PADDING_SQUARE_DP;
+import static androidx.wear.protolayout.material.layouts.LayoutDefaults.PRIMARY_LAYOUT_MARGIN_BOTTOM_ROUND_PERCENT;
+import static androidx.wear.protolayout.material.layouts.LayoutDefaults.PRIMARY_LAYOUT_MARGIN_BOTTOM_SQUARE_PERCENT;
+import static androidx.wear.protolayout.material.layouts.LayoutDefaults.PRIMARY_LAYOUT_MARGIN_HORIZONTAL_ROUND_PERCENT;
+import static androidx.wear.protolayout.material.layouts.LayoutDefaults.PRIMARY_LAYOUT_MARGIN_HORIZONTAL_SQUARE_PERCENT;
+import static androidx.wear.protolayout.material.layouts.LayoutDefaults.PRIMARY_LAYOUT_MARGIN_TOP_ROUND_PERCENT;
+import static androidx.wear.protolayout.material.layouts.LayoutDefaults.PRIMARY_LAYOUT_MARGIN_TOP_SQUARE_PERCENT;
+import static androidx.wear.protolayout.material.layouts.LayoutDefaults.PRIMARY_LAYOUT_PRIMARY_LABEL_SPACER_HEIGHT_ROUND_DP;
+import static androidx.wear.protolayout.material.layouts.LayoutDefaults.PRIMARY_LAYOUT_PRIMARY_LABEL_SPACER_HEIGHT_SQUARE_DP;
+
+import android.annotation.SuppressLint;
+
+import androidx.annotation.Dimension;
+import androidx.annotation.IntDef;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
+import androidx.annotation.RestrictTo.Scope;
+import androidx.wear.protolayout.DeviceParametersBuilders.DeviceParameters;
+import androidx.wear.protolayout.DimensionBuilders.DpProp;
+import androidx.wear.protolayout.DimensionBuilders.SpacerDimension;
+import androidx.wear.protolayout.LayoutElementBuilders;
+import androidx.wear.protolayout.LayoutElementBuilders.Box;
+import androidx.wear.protolayout.LayoutElementBuilders.Column;
+import androidx.wear.protolayout.LayoutElementBuilders.LayoutElement;
+import androidx.wear.protolayout.LayoutElementBuilders.Spacer;
+import androidx.wear.protolayout.ModifiersBuilders.ElementMetadata;
+import androidx.wear.protolayout.ModifiersBuilders.Modifiers;
+import androidx.wear.protolayout.ModifiersBuilders.Padding;
+import androidx.wear.protolayout.expression.Fingerprint;
+import androidx.wear.protolayout.material.CompactChip;
+import androidx.wear.protolayout.proto.LayoutElementProto;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Tiles layout that represents a suggested layout style for Material Tiles with the primary
+ * (compact) chip at the bottom with the given content in the center and the recommended margin and
+ * padding applied. There is a fixed slot for an optional primary label above or optional secondary
+ * label below the main content area.
+ *
+ * <p>It is highly recommended that main content has max lines between 2 and 4 (dependant on labels
+ * present), i.e.: * No labels are present: content with max 4 lines, * 1 label is present: content
+ * with max 3 lines, * 2 labels are present: content with max 2 lines.
+ *
+ * <p>For additional examples and suggested layouts see <a
+ * href="/training/wearables/design/tiles-design-system">Tiles Design System</a>.
+ *
+ * <p>When accessing the contents of a container for testing, note that this element can't be simply
+ * casted back to the original type, i.e.:
+ *
+ * <pre>{@code
+ * PrimaryLayout pl = new PrimaryLayout...
+ * Box box = new Box.Builder().addContent(pl).build();
+ *
+ * PrimaryLayout myPl = (PrimaryLayout) box.getContents().get(0);
+ * }</pre>
+ *
+ * will fail.
+ *
+ * <p>To be able to get {@link PrimaryLayout} object from any layout element, {@link
+ * #fromLayoutElement} method should be used, i.e.:
+ *
+ * <pre>{@code
+ * PrimaryLayout myPl = PrimaryLayout.fromLayoutElement(box.getContents().get(0));
+ * }</pre>
+ */
+public class PrimaryLayout implements LayoutElement {
+ /**
+ * Prefix tool tag for Metadata in Modifiers, so we know that Box is actually a PrimaryLayout.
+ */
+ static final String METADATA_TAG_PREFIX = "PL_";
+
+ /** Index for byte array that contains bits to check whether the contents are present or not. */
+ static final int FLAG_INDEX = METADATA_TAG_PREFIX.length();
+
+ /**
+ * Base tool tag for Metadata in Modifiers, so we know that Box is actually a PrimaryLayout and
+ * what optional content is added.
+ */
+ static final byte[] METADATA_TAG_BASE =
+ Arrays.copyOf(getTagBytes(METADATA_TAG_PREFIX), FLAG_INDEX + 1);
+
+ /**
+ * Bit position in a byte on {@link #FLAG_INDEX} index in metadata byte array to check whether
+ * the primary chip is present or not.
+ */
+ static final int CHIP_PRESENT = 0x1;
+ /**
+ * Bit position in a byte on {@link #FLAG_INDEX} index in metadata byte array to check whether
+ * the primary label is present or not.
+ */
+ static final int PRIMARY_LABEL_PRESENT = 0x2;
+ /**
+ * Bit position in a byte on {@link #FLAG_INDEX} index in metadata byte array to check whether
+ * the secondary label is present or not.
+ */
+ static final int SECONDARY_LABEL_PRESENT = 0x4;
+ /**
+ * Bit position in a byte on {@link #FLAG_INDEX} index in metadata byte array to check whether
+ * the content is present or not.
+ */
+ static final int CONTENT_PRESENT = 0x8;
+
+ /** Position of the primary label in its own inner column if exists. */
+ static final int PRIMARY_LABEL_POSITION = 1;
+ /** Position of the content in its own inner column. */
+ static final int CONTENT_ONLY_POSITION = 0;
+ /** Position of the primary chip in main layout column. */
+ static final int PRIMARY_CHIP_POSITION = 1;
+
+ @RestrictTo(Scope.LIBRARY)
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(
+ flag = true,
+ value = {CHIP_PRESENT, PRIMARY_LABEL_PRESENT, SECONDARY_LABEL_PRESENT, CONTENT_PRESENT})
+ @interface ContentBits {}
+
+ @NonNull private final Box mImpl;
+
+ // This contains inner columns and primary chip.
+ @NonNull private final List<LayoutElement> mAllContent;
+ // This contains optional labels, spacers and main content.
+ @NonNull private final List<LayoutElement> mPrimaryLabel;
+ // This contains optional labels, spacers and main content.
+ @NonNull private final List<LayoutElement> mContentAndSecondaryLabel;
+
+ PrimaryLayout(@NonNull Box layoutElement) {
+ this.mImpl = layoutElement;
+ this.mAllContent = ((Column) layoutElement.getContents().get(0)).getContents();
+ List<LayoutElement> innerContent = ((Column) mAllContent.get(0)).getContents();
+ this.mPrimaryLabel = ((Column) innerContent.get(0)).getContents();
+ this.mContentAndSecondaryLabel =
+ ((Column) ((Box) innerContent.get(1)).getContents().get(0)).getContents();
+ }
+
+ /** Builder class for {@link PrimaryLayout}. */
+ public static final class Builder implements LayoutElement.Builder {
+ @NonNull private final DeviceParameters mDeviceParameters;
+ @Nullable private LayoutElement mPrimaryChip = null;
+ @Nullable private LayoutElement mPrimaryLabelText = null;
+ @Nullable private LayoutElement mSecondaryLabelText = null;
+ @NonNull private LayoutElement mContent = new Box.Builder().build();
+ @NonNull private DpProp mVerticalSpacerHeight = DEFAULT_VERTICAL_SPACER_HEIGHT;
+ private byte mMetadataContentByte = 0;
+
+ /**
+ * Creates a builder for the {@link PrimaryLayout} from the given content. Content inside of
+ * it can later be set with {@link #setContent}, {@link #setPrimaryChipContent}, {@link
+ * #setPrimaryLabelTextContent} and {@link #setSecondaryLabelTextContent}.
+ */
+ public Builder(@NonNull DeviceParameters deviceParameters) {
+ this.mDeviceParameters = deviceParameters;
+ }
+
+ /**
+ * Sets the element which is in the slot at the bottom of the layout. Note that it is
+ * accepted to pass in any {@link LayoutElement}, but it is strongly recommended to add a
+ * {@link CompactChip} as the layout is optimized for it.
+ */
+ @NonNull
+ public Builder setPrimaryChipContent(@NonNull LayoutElement compactChip) {
+ this.mPrimaryChip = compactChip;
+ mMetadataContentByte = (byte) (mMetadataContentByte | CHIP_PRESENT);
+ return this;
+ }
+
+ /** Sets the content in the primary label slot which will be above the main content. */
+ @NonNull
+ public Builder setPrimaryLabelTextContent(@NonNull LayoutElement primaryLabelText) {
+ this.mPrimaryLabelText = primaryLabelText;
+ mMetadataContentByte = (byte) (mMetadataContentByte | PRIMARY_LABEL_PRESENT);
+ return this;
+ }
+
+ /**
+ * Sets the content in the primary label slot which will be below the main content. It is
+ * highly recommended to have primary label set when having secondary label.
+ */
+ @NonNull
+ public Builder setSecondaryLabelTextContent(@NonNull LayoutElement secondaryLabelText) {
+ this.mSecondaryLabelText = secondaryLabelText;
+ mMetadataContentByte = (byte) (mMetadataContentByte | SECONDARY_LABEL_PRESENT);
+ return this;
+ }
+
+ /**
+ * Sets the additional content to this layout, above the primary chip.
+ *
+ * The content slot will wrap the elements' height, so the height of the given content must
+ * be fixed or set to wrap ({@code expand} can't be used).
+ *
+ * This layout has built-in horizontal margins, so the given content should have width set
+ * to {@code expand} to use all the available space, rather than an explicit width which may
+ * lead to clipping.
+ */
+ @NonNull
+ public Builder setContent(@NonNull LayoutElement content) {
+ this.mContent = content;
+ mMetadataContentByte = (byte) (mMetadataContentByte | CONTENT_PRESENT);
+ return this;
+ }
+
+ /**
+ * Sets the vertical spacer height which is used as a space between main content and
+ * secondary label if there is any. If not set, {@link
+ * LayoutDefaults#DEFAULT_VERTICAL_SPACER_HEIGHT} will be used.
+ */
+ @NonNull
+ // The @Dimension(unit = DP) on dp() is seemingly being ignored, so lint complains that
+ // we're passing PX to something expecting DP. Just suppress the warning for now.
+ @SuppressLint("ResourceType")
+ public Builder setVerticalSpacerHeight(@Dimension(unit = DP) float height) {
+ this.mVerticalSpacerHeight = dp(height);
+ return this;
+ }
+
+ /** Constructs and returns {@link PrimaryLayout} with the provided content and look. */
+ // The @Dimension(unit = DP) on dp() is seemingly being ignored, so lint complains that
+ // we're passing DP to something expecting PX. Just suppress the warning for now.
+ @SuppressLint("ResourceType")
+ @NonNull
+ @Override
+ public PrimaryLayout build() {
+ float topPadding = getTopPadding();
+ float bottomPadding = getBottomPadding();
+ float horizontalPadding = getHorizontalPadding();
+ float horizontalChipPadding = getChipHorizontalPadding();
+
+ float primaryChipHeight = mPrimaryChip != null ? COMPACT_HEIGHT_TAPPABLE.getValue() : 0;
+
+ DpProp mainContentHeight =
+ dp(
+ mDeviceParameters.getScreenHeightDp()
+ - primaryChipHeight
+ - bottomPadding
+ - topPadding);
+
+ // Layout organization: column(column(primary label + spacer + (box(column(content +
+ // secondary label))) + chip)
+
+ // First column that has all other content and chip.
+ Column.Builder layoutBuilder = new Column.Builder();
+
+ // Contains primary label, main content and secondary label. Primary label will be
+ // wrapped, while other content will be expanded so it can be centered in the remaining
+ // space.
+ Column.Builder contentAreaBuilder =
+ new Column.Builder()
+ .setWidth(expand())
+ .setHeight(mainContentHeight)
+ .setHorizontalAlignment(LayoutElementBuilders.HORIZONTAL_ALIGN_CENTER);
+
+ // Contains main content and secondary label with wrapped height so it can be put inside
+ // of the Box to be centered.
+ Column.Builder contentSecondaryLabelBuilder =
+ new Column.Builder()
+ .setWidth(expand())
+ .setHeight(wrap())
+ .setHorizontalAlignment(LayoutElementBuilders.HORIZONTAL_ALIGN_CENTER);
+
+ // Needs to be in column because of the spacers.
+ Column.Builder primaryLabelBuilder =
+ new Column.Builder().setWidth(expand()).setHeight(wrap());
+
+ if (mPrimaryLabelText != null) {
+ primaryLabelBuilder.addContent(
+ new Spacer.Builder().setHeight(getPrimaryLabelTopSpacerHeight()).build());
+ primaryLabelBuilder.addContent(mPrimaryLabelText);
+ }
+
+ contentAreaBuilder.addContent(primaryLabelBuilder.build());
+
+ contentSecondaryLabelBuilder.addContent(
+ new Box.Builder()
+ .setVerticalAlignment(LayoutElementBuilders.VERTICAL_ALIGN_CENTER)
+ .setWidth(expand())
+ .setHeight(wrap())
+ .addContent(mContent)
+ .build());
+
+ if (mSecondaryLabelText != null) {
+ contentSecondaryLabelBuilder.addContent(
+ new Spacer.Builder().setHeight(mVerticalSpacerHeight).build());
+ contentSecondaryLabelBuilder.addContent(mSecondaryLabelText);
+ }
+
+ contentAreaBuilder.addContent(
+ new Box.Builder()
+ .setVerticalAlignment(LayoutElementBuilders.VERTICAL_ALIGN_CENTER)
+ .setWidth(expand())
+ .setHeight(expand())
+ .addContent(contentSecondaryLabelBuilder.build())
+ .build());
+
+ layoutBuilder
+ .setModifiers(
+ new Modifiers.Builder()
+ .setPadding(
+ new Padding.Builder()
+ .setStart(dp(horizontalPadding))
+ .setEnd(dp(horizontalPadding))
+ .setTop(dp(topPadding))
+ .setBottom(dp(bottomPadding))
+ .build())
+ .build())
+ .setWidth(expand())
+ .setHeight(expand())
+ .setHorizontalAlignment(LayoutElementBuilders.HORIZONTAL_ALIGN_CENTER);
+
+ layoutBuilder.addContent(contentAreaBuilder.build());
+
+ if (mPrimaryChip != null) {
+ layoutBuilder.addContent(
+ new Box.Builder()
+ .setVerticalAlignment(LayoutElementBuilders.VERTICAL_ALIGN_BOTTOM)
+ .setWidth(expand())
+ .setHeight(wrap())
+ .setModifiers(
+ new Modifiers.Builder()
+ .setPadding(
+ new Padding.Builder()
+ .setStart(dp(horizontalChipPadding))
+ .setEnd(dp(horizontalChipPadding))
+ .build())
+ .build())
+ .addContent(mPrimaryChip)
+ .build());
+ }
+
+ byte[] metadata = METADATA_TAG_BASE.clone();
+ metadata[FLAG_INDEX] = mMetadataContentByte;
+
+ Box.Builder element =
+ new Box.Builder()
+ .setWidth(expand())
+ .setHeight(expand())
+ .setModifiers(
+ new Modifiers.Builder()
+ .setMetadata(
+ new ElementMetadata.Builder()
+ .setTagData(metadata)
+ .build())
+ .build())
+ .setVerticalAlignment(LayoutElementBuilders.VERTICAL_ALIGN_BOTTOM)
+ .addContent(layoutBuilder.build());
+
+ return new PrimaryLayout(element.build());
+ }
+
+ /**
+ * Returns the recommended bottom padding, based on percentage values in {@link
+ * LayoutDefaults}.
+ */
+ private float getBottomPadding() {
+ return mPrimaryChip != null
+ ? (mDeviceParameters.getScreenHeightDp()
+ * (isRoundDevice(mDeviceParameters)
+ ? PRIMARY_LAYOUT_MARGIN_BOTTOM_ROUND_PERCENT
+ : PRIMARY_LAYOUT_MARGIN_BOTTOM_SQUARE_PERCENT))
+ : getTopPadding();
+ }
+
+ /**
+ * Returns the recommended top padding, based on percentage values in {@link
+ * LayoutDefaults}.
+ */
+ @Dimension(unit = DP)
+ private float getTopPadding() {
+ return mDeviceParameters.getScreenHeightDp()
+ * (isRoundDevice(mDeviceParameters)
+ ? PRIMARY_LAYOUT_MARGIN_TOP_ROUND_PERCENT
+ : PRIMARY_LAYOUT_MARGIN_TOP_SQUARE_PERCENT);
+ }
+
+ /**
+ * Returns the recommended horizontal padding, based on percentage values in {@link
+ * LayoutDefaults}.
+ */
+ @Dimension(unit = DP)
+ private float getHorizontalPadding() {
+ return mDeviceParameters.getScreenWidthDp()
+ * (isRoundDevice(mDeviceParameters)
+ ? PRIMARY_LAYOUT_MARGIN_HORIZONTAL_ROUND_PERCENT
+ : PRIMARY_LAYOUT_MARGIN_HORIZONTAL_SQUARE_PERCENT);
+ }
+
+ /**
+ * Returns the recommended horizontal padding for primary chip, based on percentage values
+ * and DP values in {@link LayoutDefaults}.
+ */
+ @Dimension(unit = DP)
+ private float getChipHorizontalPadding() {
+ return isRoundDevice(mDeviceParameters)
+ ? PRIMARY_LAYOUT_CHIP_HORIZONTAL_PADDING_ROUND_DP
+ : PRIMARY_LAYOUT_CHIP_HORIZONTAL_PADDING_SQUARE_DP;
+ }
+
+ /** Returns the spacer height to be placed above primary label to accommodate Tile icon. */
+ @NonNull
+ private DpProp getPrimaryLabelTopSpacerHeight() {
+ return isRoundDevice(mDeviceParameters)
+ ? PRIMARY_LAYOUT_PRIMARY_LABEL_SPACER_HEIGHT_ROUND_DP
+ : PRIMARY_LAYOUT_PRIMARY_LABEL_SPACER_HEIGHT_SQUARE_DP;
+ }
+ }
+
+ /** Get the primary label content from this layout. */
+ @Nullable
+ public LayoutElement getPrimaryLabelTextContent() {
+ if (!areElementsPresent(PRIMARY_LABEL_PRESENT)) {
+ return null;
+ }
+ return mPrimaryLabel.get(PRIMARY_LABEL_POSITION);
+ }
+
+ /** Get the secondary label content from this layout. */
+ @Nullable
+ public LayoutElement getSecondaryLabelTextContent() {
+ if (!areElementsPresent(SECONDARY_LABEL_PRESENT)) {
+ return null;
+ }
+ // By tag we know that secondary label exists. It will always be at last position.
+ return mContentAndSecondaryLabel.get(mContentAndSecondaryLabel.size() - 1);
+ }
+
+ /** Get the inner content from this layout. */
+ @Nullable
+ public LayoutElement getContent() {
+ if (!areElementsPresent(CONTENT_PRESENT)) {
+ return null;
+ }
+ return ((Box) mContentAndSecondaryLabel.get(CONTENT_ONLY_POSITION)).getContents().get(0);
+ }
+
+ /** Get the primary chip content from this layout. */
+ @Nullable
+ public LayoutElement getPrimaryChipContent() {
+ if (areElementsPresent(CHIP_PRESENT)) {
+ return ((Box) mAllContent.get(PRIMARY_CHIP_POSITION)).getContents().get(0);
+ }
+ return null;
+ }
+
+ /** Get the vertical spacer height from this layout. */
+ // The @Dimension(unit = DP) on getValue() is seemingly being ignored, so lint complains that
+ // we're passing PX to something expecting DP. Just suppress the warning for now.
+ @SuppressLint("ResourceType")
+ @Dimension(unit = DP)
+ public float getVerticalSpacerHeight() {
+ if (areElementsPresent(SECONDARY_LABEL_PRESENT)) {
+ LayoutElement element = mContentAndSecondaryLabel.get(CONTENT_ONLY_POSITION + 1);
+ if (element instanceof Spacer) {
+ SpacerDimension height = ((Spacer) element).getHeight();
+ if (height instanceof DpProp) {
+ return ((DpProp) height).getValue();
+ }
+ }
+ }
+ return DEFAULT_VERTICAL_SPACER_HEIGHT.getValue();
+ }
+
+ private boolean areElementsPresent(@ContentBits int elementFlag) {
+ return (getMetadataTag()[FLAG_INDEX] & elementFlag) == elementFlag;
+ }
+
+ /** Returns metadata tag set to this PrimaryLayout. */
+ @NonNull
+ byte[] getMetadataTag() {
+ return getMetadataTagBytes(checkNotNull(checkNotNull(mImpl.getModifiers()).getMetadata()));
+ }
+
+ /**
+ * Returns PrimaryLayout object from the given LayoutElement (e.g. one retrieved from a
+ * container's content with {@code container.getContents().get(index)}) if that element can be
+ * converted to PrimaryLayout. Otherwise, it will return null.
+ */
+ @Nullable
+ public static PrimaryLayout fromLayoutElement(@NonNull LayoutElement element) {
+ if (element instanceof PrimaryLayout) {
+ return (PrimaryLayout) element;
+ }
+ if (!(element instanceof Box)) {
+ return null;
+ }
+ Box boxElement = (Box) element;
+ if (!checkTag(boxElement.getModifiers(), METADATA_TAG_PREFIX, METADATA_TAG_BASE)) {
+ return null;
+ }
+ // Now we are sure that this element is a PrimaryLayout.
+ return new PrimaryLayout(boxElement);
+ }
+
+ @NonNull
+ @Override
+ @RestrictTo(Scope.LIBRARY_GROUP)
+ public LayoutElementProto.LayoutElement toLayoutElementProto() {
+ return mImpl.toLayoutElementProto();
+ }
+
+ @Nullable
+ @Override
+ @RestrictTo(Scope.LIBRARY_GROUP)
+ public Fingerprint getFingerprint() {
+ return mImpl.getFingerprint();
+ }
+}