Handle case where LayoutManager set via XML for CarUiRecyclerView

Item decoration setup must only be performed after stylized attributes
have been read and item decorations initialized. This change also
ensures that a LayoutManger set via XML is not overridden by the init()
method.

Bug: 167458099
Test: atest CarUILibUnitTests
Change-Id: I5fa4b5264ca6eb8d59208788050d22aadfe6c2ba
(cherry picked from commit d4bddf66a7002f874d4d16806bd45700b72652ac)
Merged-In: I5fa4b5264ca6eb8d59208788050d22aadfe6c2ba
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/CarUiRecyclerView.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/CarUiRecyclerView.java
index b755213..fa2e65c 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/CarUiRecyclerView.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/CarUiRecyclerView.java
@@ -60,9 +60,9 @@
 import java.util.Objects;
 
 /**
- * View that extends a {@link RecyclerView} and wraps itself into a {@link LinearLayout} which
- * could potentially include a scrollbar that has page up and down arrows. Interaction with this
- * view is similar to a {@code RecyclerView} as it takes the same adapter and the layout manager.
+ * View that extends a {@link RecyclerView} and wraps itself into a {@link LinearLayout} which could
+ * potentially include a scrollbar that has page up and down arrows. Interaction with this view is
+ * similar to a {@code RecyclerView} as it takes the same adapter and the layout manager.
  */
 public final class CarUiRecyclerView extends RecyclerView {
 
@@ -83,17 +83,17 @@
     @Nullable
     private ScrollBar mScrollBar;
 
-    @NonNull
+    @Nullable
     private GridOffsetItemDecoration mTopOffsetItemDecorationGrid;
-    @NonNull
+    @Nullable
     private GridOffsetItemDecoration mBottomOffsetItemDecorationGrid;
-    @NonNull
+    @Nullable
     private RecyclerView.ItemDecoration mTopOffsetItemDecorationLinear;
-    @NonNull
+    @Nullable
     private RecyclerView.ItemDecoration mBottomOffsetItemDecorationLinear;
-    @NonNull
+    @Nullable
     private GridDividerItemDecoration mDividerItemDecorationGrid;
-    @NonNull
+    @Nullable
     private RecyclerView.ItemDecoration mDividerItemDecorationLinear;
     private int mNumOfColumns;
     private boolean mInstallingExtScrollBar = false;
@@ -105,6 +105,8 @@
     @Nullable
     private LinearLayout mContainer;
 
+    // Set to true when when styled attributes are read and initialized.
+    private boolean mIsInitialized;
     private boolean mEnableDividers;
     private int mTopOffset;
     private int mBottomOffset;
@@ -132,12 +134,14 @@
     @Retention(SOURCE)
     public @interface CarUiRecyclerViewLayout {
         /**
-         * Arranges items either horizontally in a single row or vertically in a single column.
-         * This is default.
+         * Arranges items either horizontally in a single row or vertically in a single column. This
+         * is default.
          */
         int LINEAR = 0;
 
-        /** Arranges items in a Grid. */
+        /**
+         * Arranges items in a Grid.
+         */
         int GRID = 2;
     }
 
@@ -165,8 +169,7 @@
 
         /**
          * Sets the maximum number of items available in the adapter. A value less than '0' means
-         * the
-         * list should not be capped.
+         * the list should not be capped.
          */
         void setMaxItems(int maxItems);
     }
@@ -230,15 +233,19 @@
         mBottomOffsetItemDecorationGrid =
                 new GridOffsetItemDecoration(mBottomOffset, mNumOfColumns,
                         OffsetPosition.END);
-        if (carUiRecyclerViewLayout == CarUiRecyclerViewLayout.LINEAR) {
+
+        mIsInitialized = true;
+
+        // Check if a layout manager has already been set via XML
+        boolean isLayoutMangerSet = getLayoutManager() != null;
+        if (!isLayoutMangerSet && carUiRecyclerViewLayout == CarUiRecyclerViewLayout.LINEAR) {
             setLayoutManager(new LinearLayoutManager(getContext()));
-        } else {
+        } else if (!isLayoutMangerSet && carUiRecyclerViewLayout == CarUiRecyclerViewLayout.GRID) {
             setLayoutManager(new GridLayoutManager(getContext(), mNumOfColumns));
         }
 
         a.recycle();
 
-
         if (!mScrollBarEnabled) {
             return;
         }
@@ -252,33 +259,38 @@
     }
 
     @Override
-    public void setLayoutManager(@Nullable LayoutManager layout) {
-        addItemDecorations(layout);
-        super.setLayoutManager(layout);
+    public void setLayoutManager(@Nullable LayoutManager layoutManager) {
+        // Cannot setup item decorations before stylized attributes have been read.
+        if (mIsInitialized) {
+            addItemDecorations(layoutManager);
+        }
+        super.setLayoutManager(layoutManager);
     }
 
-    private void addItemDecorations(LayoutManager layout) {
-        // remove existing Item decorations
-        removeItemDecoration(mDividerItemDecorationGrid);
-        removeItemDecoration(mTopOffsetItemDecorationGrid);
-        removeItemDecoration(mBottomOffsetItemDecorationGrid);
-        removeItemDecoration(mDividerItemDecorationLinear);
-        removeItemDecoration(mTopOffsetItemDecorationLinear);
-        removeItemDecoration(mBottomOffsetItemDecorationLinear);
+    // This method should not be invoked before item decorations are initialized by the #init()
+    // method.
+    private void addItemDecorations(LayoutManager layoutManager) {
+        // remove existing Item decorations.
+        removeItemDecoration(Objects.requireNonNull(mDividerItemDecorationGrid));
+        removeItemDecoration(Objects.requireNonNull(mTopOffsetItemDecorationGrid));
+        removeItemDecoration(Objects.requireNonNull(mBottomOffsetItemDecorationGrid));
+        removeItemDecoration(Objects.requireNonNull(mDividerItemDecorationLinear));
+        removeItemDecoration(Objects.requireNonNull(mTopOffsetItemDecorationLinear));
+        removeItemDecoration(Objects.requireNonNull(mBottomOffsetItemDecorationLinear));
 
-        if (layout instanceof GridLayoutManager) {
+        if (layoutManager instanceof GridLayoutManager) {
             if (mEnableDividers) {
-                addItemDecoration(mDividerItemDecorationGrid);
+                addItemDecoration(Objects.requireNonNull(mDividerItemDecorationGrid));
             }
-            addItemDecoration(mTopOffsetItemDecorationGrid);
-            addItemDecoration(mBottomOffsetItemDecorationGrid);
-            setNumOfColumns(((GridLayoutManager) layout).getSpanCount());
+            addItemDecoration(Objects.requireNonNull(mTopOffsetItemDecorationGrid));
+            addItemDecoration(Objects.requireNonNull(mBottomOffsetItemDecorationGrid));
+            setNumOfColumns(((GridLayoutManager) layoutManager).getSpanCount());
         } else {
             if (mEnableDividers) {
-                addItemDecoration(mDividerItemDecorationLinear);
+                addItemDecoration(Objects.requireNonNull(mDividerItemDecorationLinear));
             }
-            addItemDecoration(mTopOffsetItemDecorationLinear);
-            addItemDecoration(mBottomOffsetItemDecorationLinear);
+            addItemDecoration(Objects.requireNonNull(mTopOffsetItemDecorationLinear));
+            addItemDecoration(Objects.requireNonNull(mBottomOffsetItemDecorationLinear));
         }
     }
 
@@ -379,8 +391,8 @@
 
     /**
      * This method will detach the current recycler view from its parent and attach it to the
-     * container which is a LinearLayout. Later the entire container is attached to the
-     * parent where the recycler view was set with the same layout params.
+     * container which is a LinearLayout. Later the entire container is attached to the parent where
+     * the recycler view was set with the same layout params.
      */
     private void installExternalScrollBar() {
         LayoutInflater inflater = LayoutInflater.from(getContext());
@@ -488,8 +500,8 @@
     }
 
     /**
-     * Sets the scrollbar's padding top and bottom.
-     * This padding is applied in addition to the padding of the RecyclerView.
+     * Sets the scrollbar's padding top and bottom. This padding is applied in addition to the
+     * padding of the RecyclerView.
      */
     public void setScrollBarPadding(int paddingTop, int paddingBottom) {
         if (mScrollBarEnabled) {