Add StrokeCap Builders.

RelNote: Add StrokeCap support for ArcLine.
Bug: b/274752191
Test: Unit tests added.

Change-Id: I94951c20ab62491fa3d4eafdc119b80a66dc5431
diff --git a/wear/protolayout/protolayout-proto/src/main/proto/layout.proto b/wear/protolayout/protolayout-proto/src/main/proto/layout.proto
index fe23db4..4d0a9d1 100644
--- a/wear/protolayout/protolayout-proto/src/main/proto/layout.proto
+++ b/wear/protolayout/protolayout-proto/src/main/proto/layout.proto
@@ -558,6 +558,10 @@
   // Begin and end contours with a semi-circle extension. The extension size is
   // proportional to the thickness on the path.
   STROKE_CAP_ROUND = 2;
+
+  // Begin and end contours with a half square extension. The extension size is
+  // proportional to the thickness of the path.
+  STROKE_CAP_SQUARE = 3;
 }
 
 // An extensible StrokeCap property.
diff --git a/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/inflater/ProtoLayoutInflater.java b/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/inflater/ProtoLayoutInflater.java
index 625a0d7..c7139bb 100644
--- a/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/inflater/ProtoLayoutInflater.java
+++ b/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/inflater/ProtoLayoutInflater.java
@@ -2604,6 +2604,9 @@
                 case STROKE_CAP_ROUND:
                     lineView.setStrokeCap(Cap.ROUND);
                     break;
+                case STROKE_CAP_SQUARE:
+                    lineView.setStrokeCap(Cap.SQUARE);
+                    break;
                 case UNRECOGNIZED:
                 case STROKE_CAP_UNDEFINED:
                     Log.w(TAG, "Undefined StrokeCap value.");
diff --git a/wear/protolayout/protolayout/api/current.txt b/wear/protolayout/protolayout/api/current.txt
index 69c5e47..208a7d4 100644
--- a/wear/protolayout/protolayout/api/current.txt
+++ b/wear/protolayout/protolayout/api/current.txt
@@ -333,6 +333,10 @@
     field public static final int SPAN_VERTICAL_ALIGN_BOTTOM = 1; // 0x1
     field public static final int SPAN_VERTICAL_ALIGN_TEXT_BASELINE = 2; // 0x2
     field public static final int SPAN_VERTICAL_ALIGN_UNDEFINED = 0; // 0x0
+    field public static final int STROKE_CAP_BUTT = 1; // 0x1
+    field public static final int STROKE_CAP_ROUND = 2; // 0x2
+    field public static final int STROKE_CAP_SQUARE = 3; // 0x3
+    field public static final int STROKE_CAP_UNDEFINED = 0; // 0x0
     field public static final int TEXT_ALIGN_CENTER = 2; // 0x2
     field public static final int TEXT_ALIGN_END = 3; // 0x3
     field public static final int TEXT_ALIGN_START = 1; // 0x1
@@ -403,6 +407,7 @@
     method public androidx.wear.protolayout.DimensionBuilders.AngularLayoutConstraint? getLayoutConstraintsForDynamicLength();
     method public androidx.wear.protolayout.DimensionBuilders.DegreesProp? getLength();
     method public androidx.wear.protolayout.ModifiersBuilders.ArcModifiers? getModifiers();
+    method public androidx.wear.protolayout.LayoutElementBuilders.StrokeCapProp? getStrokeCap();
     method public androidx.wear.protolayout.DimensionBuilders.DpProp? getThickness();
   }
 
@@ -413,6 +418,8 @@
     method public androidx.wear.protolayout.LayoutElementBuilders.ArcLine.Builder setLayoutConstraintsForDynamicLength(androidx.wear.protolayout.DimensionBuilders.AngularLayoutConstraint);
     method public androidx.wear.protolayout.LayoutElementBuilders.ArcLine.Builder setLength(androidx.wear.protolayout.DimensionBuilders.DegreesProp);
     method public androidx.wear.protolayout.LayoutElementBuilders.ArcLine.Builder setModifiers(androidx.wear.protolayout.ModifiersBuilders.ArcModifiers);
+    method public androidx.wear.protolayout.LayoutElementBuilders.ArcLine.Builder setStrokeCap(androidx.wear.protolayout.LayoutElementBuilders.StrokeCapProp);
+    method public androidx.wear.protolayout.LayoutElementBuilders.ArcLine.Builder setStrokeCap(int);
     method public androidx.wear.protolayout.LayoutElementBuilders.ArcLine.Builder setThickness(androidx.wear.protolayout.DimensionBuilders.DpProp);
   }
 
@@ -715,6 +722,16 @@
     method public androidx.wear.protolayout.LayoutElementBuilders.Spannable.Builder setOverflow(int);
   }
 
+  public static final class LayoutElementBuilders.StrokeCapProp {
+    method public int getValue();
+  }
+
+  public static final class LayoutElementBuilders.StrokeCapProp.Builder {
+    ctor public LayoutElementBuilders.StrokeCapProp.Builder();
+    method public androidx.wear.protolayout.LayoutElementBuilders.StrokeCapProp build();
+    method public androidx.wear.protolayout.LayoutElementBuilders.StrokeCapProp.Builder setValue(int);
+  }
+
   public static final class LayoutElementBuilders.Text implements androidx.wear.protolayout.LayoutElementBuilders.LayoutElement {
     method public androidx.wear.protolayout.LayoutElementBuilders.FontStyle? getFontStyle();
     method public androidx.wear.protolayout.TypeBuilders.StringLayoutConstraint? getLayoutConstraintsForDynamicText();
diff --git a/wear/protolayout/protolayout/api/public_plus_experimental_current.txt b/wear/protolayout/protolayout/api/public_plus_experimental_current.txt
index 56d09d9..5d8554a 100644
--- a/wear/protolayout/protolayout/api/public_plus_experimental_current.txt
+++ b/wear/protolayout/protolayout/api/public_plus_experimental_current.txt
@@ -346,6 +346,10 @@
     field public static final int SPAN_VERTICAL_ALIGN_BOTTOM = 1; // 0x1
     field public static final int SPAN_VERTICAL_ALIGN_TEXT_BASELINE = 2; // 0x2
     field public static final int SPAN_VERTICAL_ALIGN_UNDEFINED = 0; // 0x0
+    field public static final int STROKE_CAP_BUTT = 1; // 0x1
+    field public static final int STROKE_CAP_ROUND = 2; // 0x2
+    field public static final int STROKE_CAP_SQUARE = 3; // 0x3
+    field public static final int STROKE_CAP_UNDEFINED = 0; // 0x0
     field public static final int TEXT_ALIGN_CENTER = 2; // 0x2
     field public static final int TEXT_ALIGN_END = 3; // 0x3
     field public static final int TEXT_ALIGN_START = 1; // 0x1
@@ -427,6 +431,7 @@
     method public androidx.wear.protolayout.DimensionBuilders.AngularLayoutConstraint? getLayoutConstraintsForDynamicLength();
     method public androidx.wear.protolayout.DimensionBuilders.DegreesProp? getLength();
     method public androidx.wear.protolayout.ModifiersBuilders.ArcModifiers? getModifiers();
+    method public androidx.wear.protolayout.LayoutElementBuilders.StrokeCapProp? getStrokeCap();
     method public androidx.wear.protolayout.DimensionBuilders.DpProp? getThickness();
   }
 
@@ -437,6 +442,8 @@
     method public androidx.wear.protolayout.LayoutElementBuilders.ArcLine.Builder setLayoutConstraintsForDynamicLength(androidx.wear.protolayout.DimensionBuilders.AngularLayoutConstraint);
     method public androidx.wear.protolayout.LayoutElementBuilders.ArcLine.Builder setLength(androidx.wear.protolayout.DimensionBuilders.DegreesProp);
     method public androidx.wear.protolayout.LayoutElementBuilders.ArcLine.Builder setModifiers(androidx.wear.protolayout.ModifiersBuilders.ArcModifiers);
+    method public androidx.wear.protolayout.LayoutElementBuilders.ArcLine.Builder setStrokeCap(androidx.wear.protolayout.LayoutElementBuilders.StrokeCapProp);
+    method public androidx.wear.protolayout.LayoutElementBuilders.ArcLine.Builder setStrokeCap(int);
     method public androidx.wear.protolayout.LayoutElementBuilders.ArcLine.Builder setThickness(androidx.wear.protolayout.DimensionBuilders.DpProp);
   }
 
@@ -758,6 +765,16 @@
     method public androidx.wear.protolayout.LayoutElementBuilders.Spannable.Builder setOverflow(int);
   }
 
+  public static final class LayoutElementBuilders.StrokeCapProp {
+    method public int getValue();
+  }
+
+  public static final class LayoutElementBuilders.StrokeCapProp.Builder {
+    ctor public LayoutElementBuilders.StrokeCapProp.Builder();
+    method public androidx.wear.protolayout.LayoutElementBuilders.StrokeCapProp build();
+    method public androidx.wear.protolayout.LayoutElementBuilders.StrokeCapProp.Builder setValue(int);
+  }
+
   public static final class LayoutElementBuilders.Text implements androidx.wear.protolayout.LayoutElementBuilders.LayoutElement {
     method @androidx.wear.protolayout.expression.ProtoLayoutExperimental public androidx.wear.protolayout.LayoutElementBuilders.AndroidTextStyle? getAndroidTextStyle();
     method public androidx.wear.protolayout.LayoutElementBuilders.FontStyle? getFontStyle();
diff --git a/wear/protolayout/protolayout/api/restricted_current.txt b/wear/protolayout/protolayout/api/restricted_current.txt
index 69c5e47..208a7d4 100644
--- a/wear/protolayout/protolayout/api/restricted_current.txt
+++ b/wear/protolayout/protolayout/api/restricted_current.txt
@@ -333,6 +333,10 @@
     field public static final int SPAN_VERTICAL_ALIGN_BOTTOM = 1; // 0x1
     field public static final int SPAN_VERTICAL_ALIGN_TEXT_BASELINE = 2; // 0x2
     field public static final int SPAN_VERTICAL_ALIGN_UNDEFINED = 0; // 0x0
+    field public static final int STROKE_CAP_BUTT = 1; // 0x1
+    field public static final int STROKE_CAP_ROUND = 2; // 0x2
+    field public static final int STROKE_CAP_SQUARE = 3; // 0x3
+    field public static final int STROKE_CAP_UNDEFINED = 0; // 0x0
     field public static final int TEXT_ALIGN_CENTER = 2; // 0x2
     field public static final int TEXT_ALIGN_END = 3; // 0x3
     field public static final int TEXT_ALIGN_START = 1; // 0x1
@@ -403,6 +407,7 @@
     method public androidx.wear.protolayout.DimensionBuilders.AngularLayoutConstraint? getLayoutConstraintsForDynamicLength();
     method public androidx.wear.protolayout.DimensionBuilders.DegreesProp? getLength();
     method public androidx.wear.protolayout.ModifiersBuilders.ArcModifiers? getModifiers();
+    method public androidx.wear.protolayout.LayoutElementBuilders.StrokeCapProp? getStrokeCap();
     method public androidx.wear.protolayout.DimensionBuilders.DpProp? getThickness();
   }
 
@@ -413,6 +418,8 @@
     method public androidx.wear.protolayout.LayoutElementBuilders.ArcLine.Builder setLayoutConstraintsForDynamicLength(androidx.wear.protolayout.DimensionBuilders.AngularLayoutConstraint);
     method public androidx.wear.protolayout.LayoutElementBuilders.ArcLine.Builder setLength(androidx.wear.protolayout.DimensionBuilders.DegreesProp);
     method public androidx.wear.protolayout.LayoutElementBuilders.ArcLine.Builder setModifiers(androidx.wear.protolayout.ModifiersBuilders.ArcModifiers);
+    method public androidx.wear.protolayout.LayoutElementBuilders.ArcLine.Builder setStrokeCap(androidx.wear.protolayout.LayoutElementBuilders.StrokeCapProp);
+    method public androidx.wear.protolayout.LayoutElementBuilders.ArcLine.Builder setStrokeCap(int);
     method public androidx.wear.protolayout.LayoutElementBuilders.ArcLine.Builder setThickness(androidx.wear.protolayout.DimensionBuilders.DpProp);
   }
 
@@ -715,6 +722,16 @@
     method public androidx.wear.protolayout.LayoutElementBuilders.Spannable.Builder setOverflow(int);
   }
 
+  public static final class LayoutElementBuilders.StrokeCapProp {
+    method public int getValue();
+  }
+
+  public static final class LayoutElementBuilders.StrokeCapProp.Builder {
+    ctor public LayoutElementBuilders.StrokeCapProp.Builder();
+    method public androidx.wear.protolayout.LayoutElementBuilders.StrokeCapProp build();
+    method public androidx.wear.protolayout.LayoutElementBuilders.StrokeCapProp.Builder setValue(int);
+  }
+
   public static final class LayoutElementBuilders.Text implements androidx.wear.protolayout.LayoutElementBuilders.LayoutElement {
     method public androidx.wear.protolayout.LayoutElementBuilders.FontStyle? getFontStyle();
     method public androidx.wear.protolayout.TypeBuilders.StringLayoutConstraint? getLayoutConstraintsForDynamicText();
diff --git a/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/LayoutElementBuilders.java b/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/LayoutElementBuilders.java
index ddfea03..4b2cf21 100644
--- a/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/LayoutElementBuilders.java
+++ b/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/LayoutElementBuilders.java
@@ -219,6 +219,46 @@
      */
     public static final int CONTENT_SCALE_MODE_FILL_BOUNDS = 3;
 
+    /**
+     * Styles to use for path endings.
+     *
+     * @since 1.2
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
+    @IntDef({STROKE_CAP_UNDEFINED, STROKE_CAP_BUTT, STROKE_CAP_ROUND, STROKE_CAP_SQUARE})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface StrokeCap {}
+
+    /**
+     * {@code StrokeCap} is undefined.
+     *
+     * @since 1.2
+     */
+    public static final int STROKE_CAP_UNDEFINED = 0;
+
+    /**
+     * Begin and end contours with a flat edge and no extension.
+     *
+     * @since 1.2
+     */
+    public static final int STROKE_CAP_BUTT = 1;
+
+    /**
+     * Begin and end contours with a semi-circle extension. The extension size is proportional to
+     * the thickness of the path.
+     *
+     * @since 1.2
+     */
+    public static final int STROKE_CAP_ROUND = 2;
+
+    /**
+     * Begin and end contours with a half square extension. The extension size is proportional to
+     * the thickness of the path.
+     *
+     * @since 1.2
+     */
+    public static final int STROKE_CAP_SQUARE = 3;
+
     /** An extensible {@code FontWeight} property. */
     public static final class FontWeightProp {
         private final LayoutElementProto.FontWeightProp mImpl;
@@ -3690,6 +3730,20 @@
             }
         }
 
+        /**
+         * Gets the line stroke cap. If not defined, defaults to STROKE_CAP_ROUND.
+         *
+         * @since 1.2
+         */
+        @Nullable
+        public StrokeCapProp getStrokeCap() {
+            if (mImpl.hasStrokeCap()) {
+                return StrokeCapProp.fromProto(mImpl.getStrokeCap());
+            } else {
+                return null;
+            }
+        }
+
         @Override
         @RestrictTo(Scope.LIBRARY_GROUP)
         @Nullable
@@ -3718,7 +3772,7 @@
         public static final class Builder implements ArcLayoutElement.Builder {
             private final LayoutElementProto.ArcLine.Builder mImpl =
                     LayoutElementProto.ArcLine.newBuilder();
-            private final Fingerprint mFingerprint = new Fingerprint(-1371793535);
+            private final Fingerprint mFingerprint = new Fingerprint(846148011);
 
             public Builder() {}
 
@@ -3798,6 +3852,33 @@
                 return this;
             }
 
+            /**
+             * Sets the line stroke cap. If not defined, defaults to STROKE_CAP_ROUND.
+             *
+             * @since 1.2
+             */
+            @NonNull
+            public Builder setStrokeCap(@NonNull StrokeCapProp strokeCap) {
+                mImpl.setStrokeCap(strokeCap.toProto());
+                mFingerprint.recordPropertyUpdate(
+                        6, checkNotNull(strokeCap.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+
+            /**
+             * Sets the line stroke cap. If not defined, defaults to STROKE_CAP_ROUND.
+             *
+             * @since 1.2
+             */
+            @NonNull
+            public Builder setStrokeCap(@StrokeCap int strokeCap) {
+                mImpl.setStrokeCap(
+                        LayoutElementProto.StrokeCapProp.newBuilder()
+                                .setValue(LayoutElementProto.StrokeCap.forNumber(strokeCap)));
+                mFingerprint.recordPropertyUpdate(6, strokeCap);
+                return this;
+            }
+
             @Override
             @NonNull
             public ArcLine build() {
@@ -3812,6 +3893,86 @@
         }
     }
 
+    /**
+     * An extensible {@code StrokeCap} property.
+     *
+     * @since 1.2
+     */
+    public static final class StrokeCapProp {
+        private final LayoutElementProto.StrokeCapProp mImpl;
+        @Nullable private final Fingerprint mFingerprint;
+
+        StrokeCapProp(LayoutElementProto.StrokeCapProp impl, @Nullable Fingerprint fingerprint) {
+            this.mImpl = impl;
+            this.mFingerprint = fingerprint;
+        }
+
+        /**
+         * Gets the value.
+         *
+         * @since 1.2
+         */
+        @StrokeCap
+        public int getValue() {
+            return mImpl.getValue().getNumber();
+        }
+
+        /** Get the fingerprint for this object, or null if unknown. */
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @Nullable
+        public Fingerprint getFingerprint() {
+            return mFingerprint;
+        }
+
+        /** Creates a new wrapper instance from the proto. */
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @NonNull
+        public static StrokeCapProp fromProto(
+                @NonNull LayoutElementProto.StrokeCapProp proto,
+                @Nullable Fingerprint fingerprint) {
+            return new StrokeCapProp(proto, fingerprint);
+        }
+
+        @NonNull
+        static StrokeCapProp fromProto(@NonNull LayoutElementProto.StrokeCapProp proto) {
+            return fromProto(proto, null);
+        }
+
+        /** Returns the internal proto instance. */
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @NonNull
+        public LayoutElementProto.StrokeCapProp toProto() {
+            return mImpl;
+        }
+
+        /** Builder for {@link StrokeCapProp} */
+        public static final class Builder {
+            private final LayoutElementProto.StrokeCapProp.Builder mImpl =
+                    LayoutElementProto.StrokeCapProp.newBuilder();
+            private final Fingerprint mFingerprint = new Fingerprint(-956183418);
+
+            public Builder() {}
+
+            /**
+             * Sets the value.
+             *
+             * @since 1.2
+             */
+            @NonNull
+            public Builder setValue(@StrokeCap int value) {
+                mImpl.setValue(LayoutElementProto.StrokeCap.forNumber(value));
+                mFingerprint.recordPropertyUpdate(1, value);
+                return this;
+            }
+
+            /** Builds an instance from accumulated values. */
+            @NonNull
+            public StrokeCapProp build() {
+                return new StrokeCapProp(mImpl.build(), mFingerprint);
+            }
+        }
+    }
+
     /** A simple spacer used to provide padding between adjacent elements in an {@link Arc}. */
     public static final class ArcSpacer implements ArcLayoutElement {
         private final LayoutElementProto.ArcSpacer mImpl;
diff --git a/wear/protolayout/protolayout/src/test/java/androidx/wear/protolayout/LayoutElementBuildersTest.java b/wear/protolayout/protolayout/src/test/java/androidx/wear/protolayout/LayoutElementBuildersTest.java
index 78945dd..3b6107e 100644
--- a/wear/protolayout/protolayout/src/test/java/androidx/wear/protolayout/LayoutElementBuildersTest.java
+++ b/wear/protolayout/protolayout/src/test/java/androidx/wear/protolayout/LayoutElementBuildersTest.java
@@ -22,6 +22,7 @@
 
 import androidx.wear.protolayout.expression.DynamicBuilders;
 import androidx.wear.protolayout.proto.DimensionProto;
+import androidx.wear.protolayout.proto.LayoutElementProto;
 import androidx.wear.protolayout.proto.TypesProto;
 
 import org.junit.Test;
@@ -79,6 +80,34 @@
     }
 
     @Test
+    public void testArcLineSetStrokeCap() {
+        LayoutElementBuilders.ArcLine arcLine =
+                new LayoutElementBuilders.ArcLine.Builder()
+                        .setStrokeCap(LayoutElementBuilders.STROKE_CAP_BUTT)
+                        .build();
+
+        LayoutElementProto.StrokeCapProp strokeCapProp = arcLine.toProto().getStrokeCap();
+
+        assertThat(strokeCapProp.getValue())
+                .isEqualTo(LayoutElementProto.StrokeCap.STROKE_CAP_BUTT);
+    }
+
+    @Test
+    public void testArcLineSetStrokeCapProp() {
+        LayoutElementBuilders.ArcLine arcLine =
+                new LayoutElementBuilders.ArcLine.Builder()
+                        .setStrokeCap(new LayoutElementBuilders.StrokeCapProp.Builder()
+                                .setValue(LayoutElementBuilders.STROKE_CAP_BUTT)
+                                .build())
+                        .build();
+
+        LayoutElementProto.StrokeCapProp strokeCapProp = arcLine.toProto().getStrokeCap();
+
+        assertThat(strokeCapProp.getValue())
+                .isEqualTo(LayoutElementProto.StrokeCap.STROKE_CAP_BUTT);
+    }
+
+    @Test
     public void arcLineSetLength_withoutLayoutConstraint_throws() {
         assertThrows(
                 IllegalStateException.class,