Use drawable states for list positions instead of swapping drawables

Replacing drawables interrupts touch feedback of ripples. Implementing
custom state lists allows altering the corner radius without cutting off
ripples and also means we reduce unnecessary object creation.

Fix: 190467676
Test: locally
Change-Id: I10eec042aae57d278f8254327d42df945767c7ac
diff --git a/res/layout/widgets_table_container.xml b/res/layout/widgets_table_container.xml
index c6b70aa..4f70a05 100644
--- a/res/layout/widgets_table_container.xml
+++ b/res/layout/widgets_table_container.xml
@@ -13,7 +13,7 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<TableLayout
+<com.android.launcher3.widget.picker.WidgetsListTableView
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/widgets_table"
     android:layout_width="match_parent"
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 06bdd49..aad7541 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -137,7 +137,6 @@
 
     <dimen name="widget_list_top_bottom_corner_radius">28dp</dimen>
     <dimen name="widget_list_content_corner_radius">4dp</dimen>
-    <dimen name="widget_list_content_joined_corner_radius">0dp</dimen>
 
     <dimen name="widget_list_header_view_vertical_padding">20dp</dimen>
     <dimen name="widget_list_entry_bottom_margin">2dp</dimen>
diff --git a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinderTest.java b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinderTest.java
index 84a03d5..4e2a508 100644
--- a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinderTest.java
+++ b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinderTest.java
@@ -107,7 +107,10 @@
                 /* iconClickListener= */ view -> {},
                 /* iconLongClickListener= */ view -> false);
         mViewHolderBinder = new WidgetsListHeaderViewHolderBinder(
-                LayoutInflater.from(mTestActivity), mOnHeaderClickListener, widgetsListAdapter);
+                LayoutInflater.from(mTestActivity),
+                mOnHeaderClickListener,
+                new WidgetsListDrawableFactory(mTestActivity),
+                widgetsListAdapter);
     }
 
     @After
diff --git a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderViewHolderBinderTest.java b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderViewHolderBinderTest.java
index 075c58d..d6aea55 100644
--- a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderViewHolderBinderTest.java
+++ b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderViewHolderBinderTest.java
@@ -107,7 +107,10 @@
                 /* iconClickListener= */ view -> {},
                 /* iconLongClickListener= */ view -> false);
         mViewHolderBinder = new WidgetsListSearchHeaderViewHolderBinder(
-                LayoutInflater.from(mTestActivity), mOnHeaderClickListener, widgetsListAdapter);
+                LayoutInflater.from(mTestActivity),
+                mOnHeaderClickListener,
+                new WidgetsListDrawableFactory(mTestActivity),
+                widgetsListAdapter);
     }
 
     @After
diff --git a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinderTest.java b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinderTest.java
index 0c6e717..2f1326f 100644
--- a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinderTest.java
+++ b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinderTest.java
@@ -118,6 +118,7 @@
                 mOnIconClickListener,
                 mOnLongClickListener,
                 mWidgetPreviewLoader,
+                new WidgetsListDrawableFactory(mTestActivity),
                 widgetsListAdapter);
     }
 
diff --git a/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java b/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java
index e89aea7..6863c60 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java
@@ -103,18 +103,25 @@
             OnClickListener iconClickListener, OnLongClickListener iconLongClickListener) {
         mLauncher = Launcher.getLauncher(context);
         mDiffReporter = new WidgetsDiffReporter(iconCache, this);
+        WidgetsListDrawableFactory listDrawableFactory = new WidgetsListDrawableFactory(context);
         mWidgetsListTableViewHolderBinder = new WidgetsListTableViewHolderBinder(context,
                 layoutInflater, iconClickListener, iconLongClickListener,
-                widgetPreviewLoader, /* listAdapter= */ this);
+                widgetPreviewLoader, listDrawableFactory, /* listAdapter= */ this);
         mViewHolderBinders.put(VIEW_TYPE_WIDGETS_LIST, mWidgetsListTableViewHolderBinder);
         mViewHolderBinders.put(
                 VIEW_TYPE_WIDGETS_HEADER,
                 new WidgetsListHeaderViewHolderBinder(
-                        layoutInflater, /* onHeaderClickListener= */this, /* listAdapter= */ this));
+                        layoutInflater,
+                        /* onHeaderClickListener= */ this,
+                        listDrawableFactory,
+                        /* listAdapter= */ this));
         mViewHolderBinders.put(
                 VIEW_TYPE_WIDGETS_SEARCH_HEADER,
                 new WidgetsListSearchHeaderViewHolderBinder(
-                        layoutInflater, /*onHeaderClickListener=*/ this, /* listAdapter= */ this));
+                        layoutInflater,
+                        /* onHeaderClickListener= */ this,
+                        listDrawableFactory,
+                        /* listAdapter= */ this));
     }
 
     @Override
diff --git a/src/com/android/launcher3/widget/picker/WidgetsListDrawableFactory.java b/src/com/android/launcher3/widget/picker/WidgetsListDrawableFactory.java
new file mode 100644
index 0000000..c61e3a4
--- /dev/null
+++ b/src/com/android/launcher3/widget/picker/WidgetsListDrawableFactory.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 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 com.android.launcher3.widget.picker;
+
+import static com.android.launcher3.widget.picker.WidgetsListDrawableState.FIRST;
+import static com.android.launcher3.widget.picker.WidgetsListDrawableState.FIRST_EXPANDED;
+import static com.android.launcher3.widget.picker.WidgetsListDrawableState.LAST;
+import static com.android.launcher3.widget.picker.WidgetsListDrawableState.MIDDLE;
+import static com.android.launcher3.widget.picker.WidgetsListDrawableState.MIDDLE_EXPANDED;
+import static com.android.launcher3.widget.picker.WidgetsListDrawableState.SINGLE;
+
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.GradientDrawable;
+import android.graphics.drawable.RippleDrawable;
+import android.graphics.drawable.StateListDrawable;
+
+import com.android.launcher3.R;
+import com.android.launcher3.util.Themes;
+
+/** Factory for creating drawables to use as background for list elements. */
+final class WidgetsListDrawableFactory {
+
+    private final float mTopBottomCornerRadius;
+    private final float mMiddleCornerRadius;
+    private final ColorStateList mSurfaceColor;
+    private final ColorStateList mRippleColor;
+
+    WidgetsListDrawableFactory(Context context) {
+        Resources res = context.getResources();
+        mTopBottomCornerRadius = res.getDimension(R.dimen.widget_list_top_bottom_corner_radius);
+        mMiddleCornerRadius = res.getDimension(R.dimen.widget_list_content_corner_radius);
+        mSurfaceColor = context.getColorStateList(R.color.surface);
+        mRippleColor = ColorStateList.valueOf(
+                Themes.getAttrColor(context, android.R.attr.colorControlHighlight));
+    }
+
+    /**
+     * Creates a drawable for widget header list items. This drawable supports all positions
+     * in {@link WidgetsListDrawableState}.
+     */
+    Drawable createHeaderBackgroundDrawable() {
+        StateListDrawable stateList = new StateListDrawable();
+        stateList.addState(
+                SINGLE.mStateSet,
+                createRoundedRectDrawable(mTopBottomCornerRadius, mTopBottomCornerRadius));
+        stateList.addState(
+                FIRST_EXPANDED.mStateSet,
+                createRoundedRectDrawable(mTopBottomCornerRadius, 0));
+        stateList.addState(
+                FIRST.mStateSet,
+                createRoundedRectDrawable(mTopBottomCornerRadius, mMiddleCornerRadius));
+        stateList.addState(
+                MIDDLE_EXPANDED.mStateSet,
+                createRoundedRectDrawable(mMiddleCornerRadius, 0));
+        stateList.addState(
+                MIDDLE.mStateSet,
+                createRoundedRectDrawable(mMiddleCornerRadius, mMiddleCornerRadius));
+        stateList.addState(
+                LAST.mStateSet,
+                createRoundedRectDrawable(mMiddleCornerRadius, mTopBottomCornerRadius));
+        return new RippleDrawable(mRippleColor, /* content= */ stateList, /* mask= */ stateList);
+    }
+
+    /**
+     * Creates a drawable for widget content list items. This state list supports the middle and
+     * last states.
+     */
+    Drawable createContentBackgroundDrawable() {
+        StateListDrawable stateList = new StateListDrawable();
+        stateList.addState(
+                MIDDLE.mStateSet,
+                createRoundedRectDrawable(0, mMiddleCornerRadius));
+        stateList.addState(
+                LAST.mStateSet,
+                createRoundedRectDrawable(0, mTopBottomCornerRadius));
+        return new RippleDrawable(mRippleColor, /* content= */ stateList, /* mask= */ stateList);
+    }
+
+    /** Creates a rounded-rect drawable with the specified radii. */
+    private Drawable createRoundedRectDrawable(float topRadius, float bottomRadius) {
+        GradientDrawable backgroundMask = new GradientDrawable();
+        backgroundMask.setColor(mSurfaceColor);
+        backgroundMask.setShape(GradientDrawable.RECTANGLE);
+        backgroundMask.setCornerRadii(
+                new float[]{
+                        topRadius,
+                        topRadius,
+                        topRadius,
+                        topRadius,
+                        bottomRadius,
+                        bottomRadius,
+                        bottomRadius,
+                        bottomRadius
+                });
+        return backgroundMask;
+    }
+}
diff --git a/src/com/android/launcher3/widget/picker/WidgetsListDrawableState.java b/src/com/android/launcher3/widget/picker/WidgetsListDrawableState.java
new file mode 100644
index 0000000..94f292b
--- /dev/null
+++ b/src/com/android/launcher3/widget/picker/WidgetsListDrawableState.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 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 com.android.launcher3.widget.picker;
+
+/**
+ * Different possible list position states for an item in the widgets list to have. Note that only
+ * headers use the expanded state.
+ */
+enum WidgetsListDrawableState {
+    FIRST(new int[]{android.R.attr.state_first}),
+    FIRST_EXPANDED(new int[]{android.R.attr.state_first, android.R.attr.state_expanded}),
+    MIDDLE(new int[]{android.R.attr.state_middle}),
+    MIDDLE_EXPANDED(new int[]{android.R.attr.state_middle, android.R.attr.state_expanded}),
+    LAST(new int[]{android.R.attr.state_last}),
+    SINGLE(new int[]{android.R.attr.state_single});
+
+    final int[] mStateSet;
+
+    WidgetsListDrawableState(int[] stateSet) {
+        mStateSet = stateSet;
+    }
+
+    static WidgetsListDrawableState obtain(boolean isFirst, boolean isLast, boolean isExpanded) {
+        if (isFirst && isLast) return SINGLE;
+        if (isFirst && isExpanded) return FIRST_EXPANDED;
+        if (isFirst) return FIRST;
+        if (isLast) return LAST;
+        if (isExpanded) return MIDDLE_EXPANDED;
+        return MIDDLE;
+    }
+}
diff --git a/src/com/android/launcher3/widget/picker/WidgetsListDrawables.java b/src/com/android/launcher3/widget/picker/WidgetsListDrawables.java
deleted file mode 100644
index b3bb544..0000000
--- a/src/com/android/launcher3/widget/picker/WidgetsListDrawables.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (C) 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 com.android.launcher3.widget.picker;
-
-import android.content.Context;
-import android.content.res.ColorStateList;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.GradientDrawable;
-import android.graphics.drawable.RippleDrawable;
-
-import com.android.launcher3.R;
-import com.android.launcher3.util.Themes;
-
-/** Helper class for creating drawables to use as background for list elements. */
-final class WidgetsListDrawables {
-
-    private WidgetsListDrawables() {}
-
-    /** Creates a list background drawable with the specified radii. */
-    static Drawable createListBackgroundDrawable(
-            Context context,
-            float topRadius,
-            float bottomRadius) {
-        GradientDrawable backgroundMask = new GradientDrawable();
-        backgroundMask.setColor(context.getColorStateList(R.color.surface));
-        backgroundMask.setShape(GradientDrawable.RECTANGLE);
-
-        backgroundMask.setCornerRadii(
-                new float[]{
-                        topRadius,
-                        topRadius,
-                        topRadius,
-                        topRadius,
-                        bottomRadius,
-                        bottomRadius,
-                        bottomRadius,
-                        bottomRadius
-                });
-
-        return new RippleDrawable(
-                /* color= */ ColorStateList.valueOf(
-                        Themes.getAttrColor(context, android.R.attr.colorControlHighlight)),
-                /* content= */ backgroundMask,
-                /* mask= */ backgroundMask);
-    }
-
-}
diff --git a/src/com/android/launcher3/widget/picker/WidgetsListHeader.java b/src/com/android/launcher3/widget/picker/WidgetsListHeader.java
index fece359..cdab964 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsListHeader.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsListHeader.java
@@ -60,9 +60,6 @@
     @Nullable private Drawable mIconDrawable;
     private final int mIconSize;
     private final int mBottomMarginSize;
-    private final float mTopBottomCornerRadius;
-    private final float mMiddleCornerRadius;
-    private final float mJoinedCornerRadius;
 
     private ImageView mAppIcon;
     private TextView mTitle;
@@ -70,6 +67,7 @@
 
     private CheckBox mExpandToggle;
     private boolean mIsExpanded = false;
+    @Nullable private WidgetsListDrawableState mListDrawableState;
 
     public WidgetsListHeader(Context context) {
         this(context, /* attrs= */ null);
@@ -90,12 +88,6 @@
                 grid.iconSizePx);
         mBottomMarginSize =
                 getResources().getDimensionPixelSize(R.dimen.widget_list_entry_bottom_margin);
-        mTopBottomCornerRadius =
-                getResources().getDimension(R.dimen.widget_list_top_bottom_corner_radius);
-        mMiddleCornerRadius =
-                getResources().getDimension(R.dimen.widget_list_content_corner_radius);
-        mJoinedCornerRadius =
-                getResources().getDimension(R.dimen.widget_list_content_joined_corner_radius);
     }
 
     @Override
@@ -163,6 +155,14 @@
         }
     }
 
+    /** Sets the {@link WidgetsListDrawableState} and refreshes the background drawable. */
+    @UiThread
+    public void setListDrawableState(WidgetsListDrawableState state) {
+        if (state == mListDrawableState) return;
+        this.mListDrawableState = state;
+        refreshDrawableState();
+    }
+
     /** Apply app icon, labels and tag using a generic {@link WidgetsListHeaderEntry}. */
     @UiThread
     public void applyFromItemInfoWithIcon(WidgetsListHeaderEntry entry) {
@@ -263,20 +263,6 @@
         verifyHighRes();
     }
 
-    /** Updates the list to have a background drawable with the appropriate corner radii. */
-    @UiThread
-    public void updateListBackground(boolean isFirst, boolean isLast, boolean isExpanded) {
-        float topRadius = isFirst ? mTopBottomCornerRadius : mMiddleCornerRadius;
-        float bottomRadius = isLast
-                ? mTopBottomCornerRadius
-                : isExpanded
-                        ? mJoinedCornerRadius
-                        : mMiddleCornerRadius;
-        setBackground(
-                WidgetsListDrawables.createListBackgroundDrawable(
-                        getContext(), topRadius, bottomRadius));
-    }
-
     private void setTitles(WidgetsListSearchHeaderEntry entry) {
         mTitle.setText(entry.mPkgItem.title);
 
@@ -300,6 +286,17 @@
         }
     }
 
+    @Override
+    protected int[] onCreateDrawableState(int extraSpace) {
+        if (mListDrawableState == null) return super.onCreateDrawableState(extraSpace);
+        // Augment the state set from the super implementation with the custom states from
+        // mListDrawableState.
+        int[] drawableState =
+                super.onCreateDrawableState(extraSpace + mListDrawableState.mStateSet.length);
+        mergeDrawableStates(drawableState, mListDrawableState.mStateSet);
+        return drawableState;
+    }
+
     /** Verifies that the current icon is high-res otherwise posts a request to load the icon. */
     public void verifyHighRes() {
         if (mIconLoadRequest != null) {
diff --git a/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinder.java b/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinder.java
index 22d6d22..2f8f1ba 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinder.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinder.java
@@ -30,13 +30,16 @@
         ViewHolderBinder<WidgetsListHeaderEntry, WidgetsListHeaderHolder> {
     private final LayoutInflater mLayoutInflater;
     private final OnHeaderClickListener mOnHeaderClickListener;
+    private final WidgetsListDrawableFactory mListDrawableFactory;
     private final WidgetsListAdapter mWidgetsListAdapter;
 
     public WidgetsListHeaderViewHolderBinder(LayoutInflater layoutInflater,
             OnHeaderClickListener onHeaderClickListener,
+            WidgetsListDrawableFactory listDrawableFactory,
             WidgetsListAdapter listAdapter) {
         mLayoutInflater = layoutInflater;
         mOnHeaderClickListener = onHeaderClickListener;
+        mListDrawableFactory = listDrawableFactory;
         mWidgetsListAdapter = listAdapter;
     }
 
@@ -44,7 +47,7 @@
     public WidgetsListHeaderHolder newViewHolder(ViewGroup parent) {
         WidgetsListHeader header = (WidgetsListHeader) mLayoutInflater.inflate(
                 R.layout.widgets_list_row_header, parent, false);
-
+        header.setBackground(mListDrawableFactory.createHeaderBackgroundDrawable());
         return new WidgetsListHeaderHolder(header);
     }
 
@@ -52,12 +55,13 @@
     public void bindViewHolder(WidgetsListHeaderHolder viewHolder, WidgetsListHeaderEntry data,
             int position) {
         WidgetsListHeader widgetsListHeader = viewHolder.mWidgetsListHeader;
-        widgetsListHeader.updateListBackground(
-                /* isFirst= */ position == 0,
-                /* isLast= */ position == mWidgetsListAdapter.getItemCount() - 1,
-                /* isExpanded= */ data.isWidgetListShown());
         widgetsListHeader.applyFromItemInfoWithIcon(data);
         widgetsListHeader.setExpanded(data.isWidgetListShown());
+        widgetsListHeader.setListDrawableState(
+                WidgetsListDrawableState.obtain(
+                        /* isFirst= */ position == 0,
+                        /* isLast= */ position == mWidgetsListAdapter.getItemCount() - 1,
+                        /* isExpanded= */ data.isWidgetListShown()));
         widgetsListHeader.setOnExpandChangeListener(isExpanded ->
                 mOnHeaderClickListener.onHeaderClicked(
                         isExpanded,
diff --git a/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderViewHolderBinder.java b/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderViewHolderBinder.java
index d5e03a4..31dd9ee 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderViewHolderBinder.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderViewHolderBinder.java
@@ -31,13 +31,16 @@
         ViewHolderBinder<WidgetsListSearchHeaderEntry, WidgetsListSearchHeaderHolder> {
     private final LayoutInflater mLayoutInflater;
     private final OnHeaderClickListener mOnHeaderClickListener;
+    private final WidgetsListDrawableFactory mListDrawableFactory;
     private final WidgetsListAdapter mWidgetsListAdapter;
 
     public WidgetsListSearchHeaderViewHolderBinder(LayoutInflater layoutInflater,
             OnHeaderClickListener onHeaderClickListener,
+            WidgetsListDrawableFactory listDrawableFactory,
             WidgetsListAdapter listAdapter) {
         mLayoutInflater = layoutInflater;
         mOnHeaderClickListener = onHeaderClickListener;
+        mListDrawableFactory = listDrawableFactory;
         mWidgetsListAdapter = listAdapter;
     }
 
@@ -45,7 +48,7 @@
     public WidgetsListSearchHeaderHolder newViewHolder(ViewGroup parent) {
         WidgetsListHeader header = (WidgetsListHeader) mLayoutInflater.inflate(
                 R.layout.widgets_list_row_header, parent, false);
-
+        header.setBackground(mListDrawableFactory.createHeaderBackgroundDrawable());
         return new WidgetsListSearchHeaderHolder(header);
     }
 
@@ -53,12 +56,13 @@
     public void bindViewHolder(WidgetsListSearchHeaderHolder viewHolder,
             WidgetsListSearchHeaderEntry data, int position) {
         WidgetsListHeader widgetsListHeader = viewHolder.mWidgetsListHeader;
-        widgetsListHeader.updateListBackground(
-                /* isFirst= */ position == 0,
-                /* isLast= */ position == mWidgetsListAdapter.getItemCount() - 1,
-                /* isExpanded= */ data.isWidgetListShown());
         widgetsListHeader.applyFromItemInfoWithIcon(data);
         widgetsListHeader.setExpanded(data.isWidgetListShown());
+        widgetsListHeader.setListDrawableState(
+                WidgetsListDrawableState.obtain(
+                        /* isFirst= */ position == 0,
+                        /* isLast= */ position == mWidgetsListAdapter.getItemCount() - 1,
+                        /* isExpanded= */ data.isWidgetListShown()));
         widgetsListHeader.setOnExpandChangeListener(isExpanded ->
                 mOnHeaderClickListener.onHeaderClicked(isExpanded,
                         new PackageUserKey(data.mPkgItem.packageName, data.mPkgItem.user)));
diff --git a/src/com/android/launcher3/widget/picker/WidgetsListTableView.java b/src/com/android/launcher3/widget/picker/WidgetsListTableView.java
new file mode 100644
index 0000000..d30e7b6
--- /dev/null
+++ b/src/com/android/launcher3/widget/picker/WidgetsListTableView.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 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 com.android.launcher3.widget.picker;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.TableLayout;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.UiThread;
+
+/**
+ * Extension of {@link TableLayout} to support the drawable states used by
+ * {@link WidgetsListDrawableState}.
+ */
+public class WidgetsListTableView extends TableLayout {
+
+    @Nullable private WidgetsListDrawableState mListDrawableState;
+
+    public WidgetsListTableView(Context context) {
+        super(context);
+    }
+
+    public WidgetsListTableView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    /** Sets the {@link WidgetsListDrawableState} and refreshes the background drawable. */
+    @UiThread
+    public void setListDrawableState(WidgetsListDrawableState state) {
+        if (state == mListDrawableState) return;
+        mListDrawableState = state;
+        refreshDrawableState();
+    }
+
+    @Override
+    protected int[] onCreateDrawableState(int extraSpace) {
+        if (mListDrawableState == null) return super.onCreateDrawableState(extraSpace);
+        // Augment the state set from the super implementation with the custom states from
+        // mListDrawableState.
+        int[] drawableState =
+                super.onCreateDrawableState(extraSpace + mListDrawableState.mStateSet.length);
+        mergeDrawableStates(drawableState, mListDrawableState.mStateSet);
+        return drawableState;
+    }
+}
diff --git a/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinder.java b/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinder.java
index 8e310c5..7e8c55b 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinder.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinder.java
@@ -15,8 +15,10 @@
  */
 package com.android.launcher3.widget.picker;
 
+import static com.android.launcher3.widget.picker.WidgetsListDrawableState.LAST;
+import static com.android.launcher3.widget.picker.WidgetsListDrawableState.MIDDLE;
+
 import android.content.Context;
-import android.content.res.Resources;
 import android.util.Log;
 import android.view.Gravity;
 import android.view.LayoutInflater;
@@ -51,10 +53,8 @@
     private final OnClickListener mIconClickListener;
     private final OnLongClickListener mIconLongClickListener;
     private final WidgetPreviewLoader mWidgetPreviewLoader;
+    private final WidgetsListDrawableFactory mListDrawableFactory;
     private final WidgetsListAdapter mWidgetsListAdapter;
-    private final float mTopBottomCornerRadius;
-    private final float mMiddleCornerRadius;
-    private final float mJoinedCornerRadius;
     private boolean mApplyBitmapDeferred = false;
 
     public WidgetsListTableViewHolderBinder(
@@ -63,19 +63,14 @@
             OnClickListener iconClickListener,
             OnLongClickListener iconLongClickListener,
             WidgetPreviewLoader widgetPreviewLoader,
+            WidgetsListDrawableFactory listDrawableFactory,
             WidgetsListAdapter listAdapter) {
         mLayoutInflater = layoutInflater;
         mIconClickListener = iconClickListener;
         mIconLongClickListener = iconLongClickListener;
         mWidgetPreviewLoader = widgetPreviewLoader;
+        mListDrawableFactory = listDrawableFactory;
         mWidgetsListAdapter = listAdapter;
-        Resources resources = context.getResources();
-        mTopBottomCornerRadius =
-                resources.getDimension(R.dimen.widget_list_top_bottom_corner_radius);
-        mMiddleCornerRadius =
-                resources.getDimension(R.dimen.widget_list_content_corner_radius);
-        mJoinedCornerRadius =
-                resources.getDimension(R.dimen.widget_list_content_joined_corner_radius);
     }
 
     /**
@@ -97,28 +92,25 @@
             Log.v(TAG, "\nonCreateViewHolder");
         }
 
-        ViewGroup container = (ViewGroup) mLayoutInflater.inflate(
-                R.layout.widgets_table_container, parent, false);
-        return new WidgetsRowViewHolder(container);
+        WidgetsRowViewHolder viewHolder =
+                new WidgetsRowViewHolder(mLayoutInflater.inflate(
+                        R.layout.widgets_table_container, parent, false));
+        viewHolder.mTableContainer.setBackgroundDrawable(
+                mListDrawableFactory.createContentBackgroundDrawable());
+        return viewHolder;
     }
 
     @Override
     public void bindViewHolder(WidgetsRowViewHolder holder, WidgetsListContentEntry entry,
             int position) {
-        TableLayout table = holder.mTableContainer;
+        WidgetsListTableView table = holder.mTableContainer;
         if (DEBUG) {
             Log.d(TAG, String.format("onBindViewHolder [widget#=%d, table.getChildCount=%d]",
                     entry.mWidgets.size(), table.getChildCount()));
         }
 
-        // The content is always joined to an expanded header above.
-        float topRadius = mJoinedCornerRadius;
-        float bottomRadius = position == mWidgetsListAdapter.getItemCount() - 1
-                ? mTopBottomCornerRadius
-                : mMiddleCornerRadius;
-        table.setBackgroundDrawable(
-                WidgetsListDrawables.createListBackgroundDrawable(
-                        holder.itemView.getContext(), topRadius, bottomRadius));
+        table.setListDrawableState(
+                position == mWidgetsListAdapter.getItemCount() - 1 ? LAST : MIDDLE);
 
         List<ArrayList<WidgetItem>> widgetItemsTable =
                 WidgetsTableUtils.groupWidgetItemsIntoTable(entry.mWidgets, mMaxSpansPerRow);
diff --git a/src/com/android/launcher3/widget/picker/WidgetsRowViewHolder.java b/src/com/android/launcher3/widget/picker/WidgetsRowViewHolder.java
index aef1103..618e2cb 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsRowViewHolder.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsRowViewHolder.java
@@ -15,8 +15,7 @@
  */
 package com.android.launcher3.widget.picker;
 
-import android.view.ViewGroup;
-import android.widget.TableLayout;
+import android.view.View;
 
 import androidx.recyclerview.widget.RecyclerView.ViewHolder;
 
@@ -25,9 +24,9 @@
 /** A {@link ViewHolder} for showing widgets of an app in the full widget picker. */
 public final class WidgetsRowViewHolder extends ViewHolder {
 
-    public final TableLayout mTableContainer;
+    public final WidgetsListTableView mTableContainer;
 
-    public WidgetsRowViewHolder(ViewGroup v) {
+    public WidgetsRowViewHolder(View v) {
         super(v);
 
         mTableContainer = v.findViewById(R.id.widgets_table);