Add radio button functionality to car list item

Bug: 146382566
Test: Manual test with CarUiListItemActivity

Change-Id: Ia6374c0815f770460afd74f6d7b576d5b53dd468
diff --git a/car-ui-lib/res/layout/car_ui_list_item.xml b/car-ui-lib/res/layout/car_ui_list_item.xml
index 5934fce..6be7e82 100644
--- a/car-ui-lib/res/layout/car_ui_list_item.xml
+++ b/car-ui-lib/res/layout/car_ui_list_item.xml
@@ -126,6 +126,14 @@
             android:clickable="false"
             android:focusable="false" />
 
+        <RadioButton
+            android:id="@+id/radio_button_widget"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:clickable="false"
+            android:focusable="false" />
+
         <ImageView
             android:id="@+id/supplemental_icon"
             android:layout_width="@dimen/car_ui_list_item_supplemental_icon_size"
diff --git a/car-ui-lib/src/com/android/car/ui/recyclerview/CarUiContentListItem.java b/car-ui-lib/src/com/android/car/ui/recyclerview/CarUiContentListItem.java
index b1ff760..fd5bf50 100644
--- a/car-ui-lib/src/com/android/car/ui/recyclerview/CarUiContentListItem.java
+++ b/car-ui-lib/src/com/android/car/ui/recyclerview/CarUiContentListItem.java
@@ -57,6 +57,11 @@
          */
         CHECK_BOX,
         /**
+         * For an action value of CHECK_BOX, a radio button is shown for the action element of the
+         * list item.
+         */
+        RADIO_BUTTON,
+        /**
          * For an action value of ICON, an icon is shown for the action element of the list item.
          */
         ICON
@@ -142,8 +147,9 @@
      * @param checked the checked state for the item.
      */
     public void setChecked(boolean checked) {
-        // Checked state can only be set when action type is checkbox or switch.
-        if (mAction == Action.CHECK_BOX || mAction == Action.SWITCH) {
+        // Checked state can only be set when action type is checkbox, radio button or switch.
+        if (mAction == Action.CHECK_BOX || mAction == Action.SWITCH
+                || mAction == Action.RADIO_BUTTON) {
             mIsChecked = checked;
         }
     }
@@ -179,8 +185,10 @@
     public void setAction(Action action) {
         mAction = action;
 
-        // Cannot have checked state be true when there action type is not checkbox or switch.
-        if (mAction != Action.CHECK_BOX && mAction != Action.SWITCH) {
+        // Cannot have checked state be true when there action type is not checkbox, radio button or
+        // switch.
+        if (mAction != Action.CHECK_BOX && mAction != Action.SWITCH
+                && mAction != Action.RADIO_BUTTON) {
             mIsChecked = false;
         }
     }
diff --git a/car-ui-lib/src/com/android/car/ui/recyclerview/CarUiListItemAdapter.java b/car-ui-lib/src/com/android/car/ui/recyclerview/CarUiListItemAdapter.java
index 4994e54..b39bbd9 100644
--- a/car-ui-lib/src/com/android/car/ui/recyclerview/CarUiListItemAdapter.java
+++ b/car-ui-lib/src/com/android/car/ui/recyclerview/CarUiListItemAdapter.java
@@ -23,6 +23,7 @@
 import android.view.ViewGroup;
 import android.widget.CheckBox;
 import android.widget.ImageView;
+import android.widget.RadioButton;
 import android.widget.Switch;
 import android.widget.TextView;
 
@@ -156,6 +157,7 @@
         private final View mActionDivider;
         private final Switch mSwitch;
         private final CheckBox mCheckBox;
+        private final RadioButton mRadioButton;
         private final ImageView mSupplementalIcon;
         private final View mTouchInterceptor;
         private final View mReducedTouchInterceptor;
@@ -171,6 +173,7 @@
             mActionDivider = itemView.requireViewById(R.id.action_divider);
             mSwitch = itemView.requireViewById(R.id.switch_widget);
             mCheckBox = itemView.requireViewById(R.id.checkbox_widget);
+            mRadioButton = itemView.requireViewById(R.id.radio_button_widget);
             mSupplementalIcon = itemView.requireViewById(R.id.supplemental_icon);
             mReducedTouchInterceptor = itemView.requireViewById(R.id.reduced_touch_interceptor);
             mTouchInterceptor = itemView.requireViewById(R.id.touch_interceptor);
@@ -204,6 +207,11 @@
             mActionDivider.setVisibility(
                     item.isActionDividerVisible() ? View.VISIBLE : View.GONE);
 
+            mSwitch.setVisibility(View.GONE);
+            mCheckBox.setVisibility(View.GONE);
+            mRadioButton.setVisibility(View.GONE);
+            mSupplementalIcon.setVisibility(View.GONE);
+
             switch (item.getAction()) {
                 case NONE:
                     mActionContainer.setVisibility(View.GONE);
@@ -229,14 +237,9 @@
                     // Clicks anywhere on the item should toggle the switch state. Use full touch
                     // interceptor.
                     mTouchInterceptor.setVisibility(View.VISIBLE);
-                    mTouchInterceptor.setOnClickListener(
-                            v -> mSwitch.setChecked(!item.isChecked()));
+                    mTouchInterceptor.setOnClickListener(v -> mSwitch.toggle());
                     mReducedTouchInterceptor.setVisibility(View.GONE);
 
-                    // Only the switch should be displayed in the action container.
-                    mCheckBox.setVisibility(View.GONE);
-                    mSupplementalIcon.setVisibility(View.GONE);
-
                     mActionContainer.setVisibility(View.VISIBLE);
                     mActionContainer.setClickable(false);
                     break;
@@ -256,13 +259,30 @@
                     // Clicks anywhere on the item should toggle the checkbox state. Use full touch
                     // interceptor.
                     mTouchInterceptor.setVisibility(View.VISIBLE);
-                    mTouchInterceptor.setOnClickListener(
-                            v -> mCheckBox.setChecked(!item.isChecked()));
+                    mTouchInterceptor.setOnClickListener(v -> mCheckBox.toggle());
                     mReducedTouchInterceptor.setVisibility(View.GONE);
 
-                    // Only the checkbox should be displayed in the action container.
-                    mSwitch.setVisibility(View.GONE);
-                    mSupplementalIcon.setVisibility(View.GONE);
+                    mActionContainer.setVisibility(View.VISIBLE);
+                    mActionContainer.setClickable(false);
+                    break;
+                case RADIO_BUTTON:
+                    mRadioButton.setVisibility(View.VISIBLE);
+                    mRadioButton.setChecked(item.isChecked());
+                    mRadioButton.setOnCheckedChangeListener(
+                            (buttonView, isChecked) -> {
+                                item.setChecked(isChecked);
+                                CarUiContentListItem.OnCheckedChangedListener itemListener =
+                                        item.getOnCheckedChangedListener();
+                                if (itemListener != null) {
+                                    itemListener.onCheckedChanged(isChecked);
+                                }
+                            });
+
+                    // Clicks anywhere on the item should toggle the switch state. Use full touch
+                    // interceptor.
+                    mTouchInterceptor.setVisibility(View.VISIBLE);
+                    mTouchInterceptor.setOnClickListener(v -> mRadioButton.toggle());
+                    mReducedTouchInterceptor.setVisibility(View.GONE);
 
                     mActionContainer.setVisibility(View.VISIBLE);
                     mActionContainer.setClickable(false);
@@ -278,10 +298,6 @@
                                 }
                             });
 
-                    // Only the supplemental icon should be displayed in the action container.
-                    mSwitch.setVisibility(View.GONE);
-                    mCheckBox.setVisibility(View.GONE);
-
                     // If the icon has a click listener, use a reduced touch interceptor to create
                     // two distinct touch area; the action container and the remainder of the list
                     // item. Each touch area will have its own ripple effect. If the icon has no
diff --git a/car-ui-lib/tests/paintbooth/src/com/android/car/ui/paintbooth/caruirecyclerview/CarUiListItemActivity.java b/car-ui-lib/tests/paintbooth/src/com/android/car/ui/paintbooth/caruirecyclerview/CarUiListItemActivity.java
index 3b5cf7a..c307cb3 100644
--- a/car-ui-lib/tests/paintbooth/src/com/android/car/ui/paintbooth/caruirecyclerview/CarUiListItemActivity.java
+++ b/car-ui-lib/tests/paintbooth/src/com/android/car/ui/paintbooth/caruirecyclerview/CarUiListItemActivity.java
@@ -37,6 +37,7 @@
 public class CarUiListItemActivity extends Activity {
 
     private final ArrayList<CarUiListItem> mData = new ArrayList<>();
+    private CarUiListItemAdapter mAdapter;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
@@ -44,8 +45,8 @@
         setContentView(R.layout.car_ui_recycler_view_activity);
         CarUiRecyclerView recyclerView = findViewById(R.id.list);
 
-        CarUiListItemAdapter adapter = new CarUiListItemAdapter(generateDummyData());
-        recyclerView.setAdapter(adapter);
+        mAdapter = new CarUiListItemAdapter(generateDummyData());
+        recyclerView.setAdapter(mAdapter);
         recyclerView.setLayoutManager(new CarUiListItemLayoutManager(this));
     }
 
@@ -106,6 +107,33 @@
         item.setChecked(true);
         mData.add(item);
 
+        CarUiContentListItem radioItem1 = new CarUiContentListItem();
+        CarUiContentListItem radioItem2 = new CarUiContentListItem();
+
+        radioItem1.setTitle("Title -- Item with radio button");
+        radioItem1.setBody("Item is initially unchecked checked");
+        radioItem1.setAction(CarUiContentListItem.Action.RADIO_BUTTON);
+        radioItem1.setChecked(false);
+        radioItem1.setOnCheckedChangedListener(isChecked -> {
+            if (isChecked) {
+                radioItem2.setChecked(false);
+                mAdapter.notifyItemChanged(mData.indexOf(radioItem2));
+            }
+        });
+        mData.add(radioItem1);
+
+        radioItem2.setIcon(getDrawable(R.drawable.ic_launcher));
+        radioItem2.setTitle("Item is mutually exclusive with item above");
+        radioItem2.setAction(CarUiContentListItem.Action.RADIO_BUTTON);
+        radioItem2.setChecked(true);
+        radioItem2.setOnCheckedChangedListener(isChecked -> {
+            if (isChecked) {
+                radioItem1.setChecked(false);
+                mAdapter.notifyItemChanged(mData.indexOf(radioItem1));
+            }
+        });
+        mData.add(radioItem2);
+
         item = new CarUiContentListItem();
         item.setIcon(getDrawable(R.drawable.ic_launcher));
         item.setTitle("Title");