[BatteryTip] Implement new CardPreference to apply new style

- Implement a new CardPreference to apply SettingsCard
- Apply CardPreference on battery tips

Bug: 315748218
Test: atest SettingsSpaUnitTests:com.android.settings.widget.CardPreferenceTest
Test: atest SettingsRoboTest:com.android.settings.fuelgauge
Change-Id: I9a57e7739275854278b2c586793af718b0680d23
diff --git a/src/com/android/settings/fuelgauge/batterytip/tips/BatteryDefenderTip.java b/src/com/android/settings/fuelgauge/batterytip/tips/BatteryDefenderTip.java
index 047bf13..319ba7a 100644
--- a/src/com/android/settings/fuelgauge/batterytip/tips/BatteryDefenderTip.java
+++ b/src/com/android/settings/fuelgauge/batterytip/tips/BatteryDefenderTip.java
@@ -16,12 +16,14 @@
 
 package com.android.settings.fuelgauge.batterytip.tips;
 
+import android.app.Activity;
 import android.app.settings.SettingsEnums;
 import android.content.Context;
 import android.content.Intent;
 import android.os.Parcel;
 import android.util.Log;
 
+import androidx.core.app.ActivityCompat;
 import androidx.preference.Preference;
 
 import com.android.settings.R;
@@ -30,6 +32,8 @@
 import com.android.settingslib.HelpUtils;
 import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
 
+import kotlin.Unit;
+
 /** Tip to show current battery is overheated */
 public class BatteryDefenderTip extends BatteryTip {
 
@@ -83,28 +87,39 @@
         }
 
         cardPreference.setSelectable(false);
+        cardPreference.setIconResId(getIconId());
         cardPreference.setPrimaryButtonText(context.getString(R.string.learn_more));
-        cardPreference.setPrimaryButtonClickListener(
-                button ->
-                        button.startActivityForResult(
-                                HelpUtils.getHelpIntent(
-                                        context,
-                                        context.getString(R.string.help_url_battery_defender),
-                                        /* backupContext */ ""), /* requestCode */
-                                0));
-        cardPreference.setPrimaryButtonVisible(true);
+        cardPreference.setPrimaryButtonAction(
+                () -> {
+                    var helpIntent =
+                            HelpUtils.getHelpIntent(
+                                    context,
+                                    context.getString(R.string.help_url_battery_defender),
+                                    /* backupContext= */ "");
+                    ActivityCompat.startActivityForResult(
+                            (Activity) preference.getContext(),
+                            helpIntent,
+                            /* requestCode= */ 0,
+                            /* options= */ null);
+
+                    return Unit.INSTANCE;
+                });
+        cardPreference.setPrimaryButtonVisibility(true);
         cardPreference.setPrimaryButtonContentDescription(
                 context.getString(
                         R.string.battery_tip_limited_temporarily_sec_button_content_description));
 
         cardPreference.setSecondaryButtonText(
                 context.getString(R.string.battery_tip_charge_to_full_button));
-        cardPreference.setSecondaryButtonClickListener(
-                unused -> {
+        cardPreference.setSecondaryButtonAction(
+                () -> {
                     resumeCharging(context);
                     preference.setVisible(false);
+
+                    return Unit.INSTANCE;
                 });
-        cardPreference.setSecondaryButtonVisible(mIsPluggedIn);
+        cardPreference.setSecondaryButtonVisibility(mIsPluggedIn);
+        cardPreference.buildContent();
     }
 
     private void resumeCharging(Context context) {
diff --git a/src/com/android/settings/fuelgauge/batterytip/tips/IncompatibleChargerTip.java b/src/com/android/settings/fuelgauge/batterytip/tips/IncompatibleChargerTip.java
index 882b755..c9ff864 100644
--- a/src/com/android/settings/fuelgauge/batterytip/tips/IncompatibleChargerTip.java
+++ b/src/com/android/settings/fuelgauge/batterytip/tips/IncompatibleChargerTip.java
@@ -16,11 +16,13 @@
 
 package com.android.settings.fuelgauge.batterytip.tips;
 
+import android.app.Activity;
 import android.app.settings.SettingsEnums;
 import android.content.Context;
 import android.os.Parcel;
 import android.util.Log;
 
+import androidx.core.app.ActivityCompat;
 import androidx.preference.Preference;
 
 import com.android.settings.R;
@@ -28,6 +30,8 @@
 import com.android.settingslib.HelpUtils;
 import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
 
+import kotlin.Unit;
+
 /** Tip to show incompatible charger state */
 public final class IncompatibleChargerTip extends BatteryTip {
     private static final String TAG = "IncompatibleChargerTip";
@@ -77,18 +81,27 @@
         }
 
         cardPreference.setSelectable(false);
+        cardPreference.setIconResId(getIconId());
         cardPreference.setPrimaryButtonText(context.getString(R.string.learn_more));
-        cardPreference.setPrimaryButtonClickListener(
-                button ->
-                        button.startActivityForResult(
-                                HelpUtils.getHelpIntent(
-                                        context,
-                                        context.getString(R.string.help_url_incompatible_charging),
-                                        /* backupContext */ ""), /* requestCode */
-                                0));
-        cardPreference.setPrimaryButtonVisible(true);
+        cardPreference.setPrimaryButtonAction(
+                () -> {
+                    var helpIntent =
+                            HelpUtils.getHelpIntent(
+                                    context,
+                                    context.getString(R.string.help_url_incompatible_charging),
+                                    /* backupContext */ "");
+                    ActivityCompat.startActivityForResult(
+                            (Activity) context,
+                            helpIntent,
+                            /* requestCode= */ 0,
+                            /* options= */ null);
+
+                    return Unit.INSTANCE;
+                });
+        cardPreference.setPrimaryButtonVisibility(true);
         cardPreference.setPrimaryButtonContentDescription(
                 context.getString(R.string.battery_tip_incompatible_charging_content_description));
+        cardPreference.buildContent();
     }
 
     public static final Creator CREATOR =
diff --git a/src/com/android/settings/widget/CardPreference.java b/src/com/android/settings/widget/CardPreference.java
deleted file mode 100644
index 61114d9..0000000
--- a/src/com/android/settings/widget/CardPreference.java
+++ /dev/null
@@ -1,170 +0,0 @@
-/*
- * Copyright (C) 2019 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.settings.widget;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.View;
-import android.widget.Button;
-
-import androidx.preference.Preference;
-import androidx.preference.PreferenceViewHolder;
-
-import com.android.settings.R;
-
-import com.google.android.material.card.MaterialCardView;
-
-import java.util.Optional;
-
-/** Preference that wrapped by {@link MaterialCardView} */
-public class CardPreference extends Preference {
-
-    private View.OnClickListener mPrimaryBtnClickListener = null;
-    private View.OnClickListener mSecondaryBtnClickListener = null;
-
-    private String mPrimaryButtonText = null;
-    private String mSecondaryButtonText = null;
-
-    private Optional<Button> mPrimaryButton = Optional.empty();
-    private Optional<Button> mSecondaryButton = Optional.empty();
-    private Optional<View> mButtonsGroup = Optional.empty();
-
-    private boolean mPrimaryButtonVisible = false;
-    private boolean mSecondaryButtonVisible = false;
-
-    public CardPreference(Context context) {
-        this(context, null /* attrs */);
-    }
-
-    public CardPreference(Context context, AttributeSet attrs) {
-        super(context, attrs, R.attr.cardPreferenceStyle);
-    }
-
-    @Override
-    public void onBindViewHolder(PreferenceViewHolder holder) {
-        super.onBindViewHolder(holder);
-
-        initButtonsAndLayout(holder);
-    }
-
-    private void initButtonsAndLayout(PreferenceViewHolder holder) {
-        mPrimaryButton = Optional.ofNullable((Button) holder.findViewById(android.R.id.button1));
-        mSecondaryButton = Optional.ofNullable((Button) holder.findViewById(android.R.id.button2));
-        mButtonsGroup = Optional.ofNullable(holder.findViewById(R.id.card_preference_buttons));
-
-        setPrimaryButtonText(mPrimaryButtonText);
-        setPrimaryButtonClickListener(mPrimaryBtnClickListener);
-        setPrimaryButtonVisible(mPrimaryButtonVisible);
-        setSecondaryButtonText(mSecondaryButtonText);
-        setSecondaryButtonClickListener(mSecondaryBtnClickListener);
-        setSecondaryButtonVisible(mSecondaryButtonVisible);
-    }
-
-    /** Clear layout state if needed */
-    public void resetLayoutState() {
-        setPrimaryButtonVisible(false);
-        setSecondaryButtonVisible(false);
-    }
-
-    /**
-     * Register a callback to be invoked when the primary button is clicked.
-     *
-     * @param l the callback that will run
-     */
-    public void setPrimaryButtonClickListener(View.OnClickListener l) {
-        mPrimaryButton.ifPresent(button -> button.setOnClickListener(l));
-        mPrimaryBtnClickListener = l;
-    }
-
-    /**
-     * Register a callback to be invoked when the secondary button is clicked.
-     *
-     * @param l the callback that will run
-     */
-    public void setSecondaryButtonClickListener(View.OnClickListener l) {
-        mSecondaryButton.ifPresent(button -> button.setOnClickListener(l));
-        mSecondaryBtnClickListener = l;
-    }
-
-    /**
-     * Sets the text to be displayed on primary button.
-     *
-     * @param text text to be displayed
-     */
-    public void setPrimaryButtonText(String text) {
-        mPrimaryButton.ifPresent(button -> button.setText(text));
-        mPrimaryButtonText = text;
-    }
-
-    /**
-     * Sets the text to be displayed on secondary button.
-     *
-     * @param text text to be displayed
-     */
-    public void setSecondaryButtonText(String text) {
-        mSecondaryButton.ifPresent(button -> button.setText(text));
-        mSecondaryButtonText = text;
-    }
-
-    /**
-     * Set the visible on the primary button.
-     *
-     * @param visible {@code true} for visible
-     */
-    public void setPrimaryButtonVisible(boolean visible) {
-        mPrimaryButton.ifPresent(
-                button -> button.setVisibility(visible ? View.VISIBLE : View.GONE));
-        mPrimaryButtonVisible = visible;
-        updateButtonGroupsVisibility();
-    }
-
-    /**
-     * Set the visible on the secondary button.
-     *
-     * @param visible {@code true} for visible
-     */
-    public void setSecondaryButtonVisible(boolean visible) {
-        mSecondaryButton.ifPresent(
-                button -> button.setVisibility(visible ? View.VISIBLE : View.GONE));
-        mSecondaryButtonVisible = visible;
-        updateButtonGroupsVisibility();
-    }
-
-    /**
-     * Sets the text of content description on primary button.
-     *
-     * @param text text for the content description
-     */
-    public void setPrimaryButtonContentDescription(String text) {
-        mPrimaryButton.ifPresent(button -> button.setContentDescription(text));
-    }
-
-    /**
-     * Sets the text of content description on secondary button.
-     *
-     * @param text text for the content description
-     */
-    public void setSecondaryButtonContentDescription(String text) {
-        mSecondaryButton.ifPresent(button -> button.setContentDescription(text));
-    }
-
-    private void updateButtonGroupsVisibility() {
-        int visibility =
-                (mPrimaryButtonVisible || mSecondaryButtonVisible) ? View.VISIBLE : View.GONE;
-        mButtonsGroup.ifPresent(group -> group.setVisibility(visibility));
-    }
-}
diff --git a/src/com/android/settings/widget/CardPreference.kt b/src/com/android/settings/widget/CardPreference.kt
new file mode 100644
index 0000000..7122ac6
--- /dev/null
+++ b/src/com/android/settings/widget/CardPreference.kt
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2024 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.settings.widget
+
+import android.content.Context
+import android.content.res.Resources
+import android.util.AttributeSet
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.res.vectorResource
+import com.android.settings.spa.preference.ComposePreference
+import com.android.settingslib.spa.widget.card.CardButton
+import com.android.settingslib.spa.widget.card.CardModel
+import com.android.settingslib.spa.widget.card.SettingsCard
+
+/** A preference for settings banner tips card. */
+class CardPreference
+@JvmOverloads
+constructor(
+    context: Context,
+    attr: AttributeSet? = null,
+) : ComposePreference(context, attr) {
+
+    /** A icon resource id for displaying icon on tips card. */
+    var iconResId: Int? = null
+
+    /** The primary button's text. */
+    var primaryButtonText: String = ""
+
+    /** The accessibility content description of the primary button. */
+    var primaryButtonContentDescription: String? = null
+
+    /** The action for click on primary button. */
+    var primaryButtonAction: () -> Unit = {}
+
+    /** The visibility of primary button on tips card. The default value is `false`. */
+    var primaryButtonVisibility: Boolean = false
+
+    /** The text on the second button of this [SettingsCard]. */
+    var secondaryButtonText: String = ""
+
+    /** The accessibility content description of the secondary button. */
+    var secondaryButtonContentDescription: String? = null
+
+    /** The action for click on secondary button. */
+    var secondaryButtonAction: () -> Unit = {}
+
+    /** The visibility of secondary button on tips card. The default value is `false`. */
+    var secondaryButtonVisibility: Boolean = false
+
+    private var onDismiss: (() -> Unit)? = null
+
+    /** Enable the dismiss button on tips card. */
+    fun enableDismiss(enable: Boolean) =
+        if (enable) onDismiss = { isVisible = false } else onDismiss = null
+
+    /** Clear layout state if needed. */
+    fun resetLayoutState() {
+        primaryButtonVisibility = false
+        secondaryButtonVisibility = false
+        notifyChanged()
+    }
+
+    /** Build the tips card content to apply any changes of this card's property. */
+    fun buildContent() {
+        setContent {
+            SettingsCard(
+                CardModel(
+                    title = title?.toString() ?: "",
+                    text = summary?.toString() ?: "",
+                    buttons = listOfNotNull(configPrimaryButton(), configSecondaryButton()),
+                    onDismiss = onDismiss,
+                    imageVector =
+                        iconResId
+                            ?.takeIf { it != Resources.ID_NULL }
+                            ?.let { ImageVector.vectorResource(it) },
+                )
+            )
+        }
+    }
+
+    private fun configPrimaryButton(): CardButton? {
+        return if (primaryButtonVisibility)
+            CardButton(
+                text = primaryButtonText,
+                contentDescription = primaryButtonContentDescription,
+                onClick = primaryButtonAction,
+            )
+        else null
+    }
+
+    private fun configSecondaryButton(): CardButton? {
+        return if (secondaryButtonVisibility)
+            CardButton(
+                text = secondaryButtonText,
+                contentDescription = secondaryButtonContentDescription,
+                onClick = secondaryButtonAction,
+            )
+        else null
+    }
+
+    override fun notifyChanged() {
+        buildContent()
+        super.notifyChanged()
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/BatteryDefenderTipTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/BatteryDefenderTipTest.java
index 296306d..3f89f9b 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/BatteryDefenderTipTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/BatteryDefenderTipTest.java
@@ -124,7 +124,7 @@
     public void updatePreference_shouldSetPrimaryButtonVisible() {
         mBatteryDefenderTip.updatePreference(mCardPreference);
 
-        verify(mCardPreference).setPrimaryButtonVisible(true);
+        verify(mCardPreference).setPrimaryButtonVisibility(true);
     }
 
     @Test
@@ -134,14 +134,14 @@
 
         mBatteryDefenderTip.updatePreference(mCardPreference);
 
-        verify(mCardPreference).setPrimaryButtonVisible(true);
+        verify(mCardPreference).setPrimaryButtonVisibility(true);
     }
 
     @Test
     public void updatePreference_whenNotCharging_setSecondaryButtonVisibleToBeFalse() {
         mBatteryDefenderTip.updatePreference(mCardPreference);
 
-        verify(mCardPreference).setSecondaryButtonVisible(false);
+        verify(mCardPreference).setSecondaryButtonVisibility(false);
     }
 
     @Test
@@ -150,7 +150,7 @@
 
         mBatteryDefenderTip.updatePreference(mCardPreference);
 
-        verify(mCardPreference).setSecondaryButtonVisible(false);
+        verify(mCardPreference).setSecondaryButtonVisibility(false);
     }
 
     private void fakeGetChargingStatusFailed() {
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/BatteryTipTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/BatteryTipTest.java
index 45fdc1f..ea72ff6 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/BatteryTipTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/BatteryTipTest.java
@@ -91,7 +91,7 @@
                                 mContext, R.layout.card_preference_layout, /* parent= */ null));
         CardPreference cardPreference = new CardPreference(mContext);
         cardPreference.onBindViewHolder(holder);
-        cardPreference.setPrimaryButtonVisible(true);
+        cardPreference.setPrimaryButtonVisibility(true);
 
         mBatteryTip.updatePreference(cardPreference);
 
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/IncompatibleChargerTipTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/IncompatibleChargerTipTest.java
index c1d039b..c66cf37 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/IncompatibleChargerTipTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/IncompatibleChargerTipTest.java
@@ -113,7 +113,7 @@
     @Test
     public void updatePreference_shouldSetSecondaryButtonVisible() {
         mIncompatibleChargerTip.updatePreference(mCardPreference);
-        verify(mCardPreference).setPrimaryButtonVisible(true);
+        verify(mCardPreference).setPrimaryButtonVisibility(true);
     }
 
     private String getLastErrorLog() {
diff --git a/tests/robotests/src/com/android/settings/widget/CardPreferenceTest.java b/tests/robotests/src/com/android/settings/widget/CardPreferenceTest.java
deleted file mode 100644
index e26643f..0000000
--- a/tests/robotests/src/com/android/settings/widget/CardPreferenceTest.java
+++ /dev/null
@@ -1,344 +0,0 @@
-/*
- * Copyright (C) 2019 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
- *Visibility_setGoneForPrimaryButton_buttonGroupIsGone
- * 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.settings.widget;
-
-import static android.view.View.GONE;
-import static android.view.View.VISIBLE;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.content.Context;
-import android.view.View;
-import android.widget.Button;
-
-import androidx.preference.PreferenceViewHolder;
-import androidx.test.core.app.ApplicationProvider;
-
-import com.android.settings.R;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.robolectric.RobolectricTestRunner;
-
-@RunWith(RobolectricTestRunner.class)
-public class CardPreferenceTest {
-
-    private CardPreference mCardPreference;
-    private PreferenceViewHolder mHolder;
-
-    @Before
-    public void setUp() {
-        Context context = ApplicationProvider.getApplicationContext();
-        context.setTheme(R.style.Theme_Settings);
-        mCardPreference = new CardPreference(context);
-
-        mHolder = PreferenceViewHolder.createInstanceForTests(
-                View.inflate(context, R.layout.card_preference_layout, /* parent= */ null));
-    }
-
-    @Test
-    public void newACardPreference_layoutResourceShouldBeCardPreferenceLayout() {
-        Context context = ApplicationProvider.getApplicationContext();
-        context.setTheme(R.style.SettingsPreferenceTheme);
-
-        CardPreference cardPreference = new CardPreference(context);
-
-        assertThat(cardPreference.getLayoutResource()).isEqualTo(R.layout.card_preference_layout);
-    }
-
-    @Test
-    public void onBindViewHolder_noButtonVisible_buttonsLayoutIsGone() {
-        mCardPreference.onBindViewHolder(mHolder);
-
-        assertThat(getCardPreferenceButtonsView().getVisibility()).isEqualTo(GONE);
-    }
-
-    @Test
-    public void onBindViewHolder_setPrimaryButtonVisibility_buttonsLayoutIsVisible() {
-        mCardPreference.setPrimaryButtonVisible(true);
-
-        mCardPreference.onBindViewHolder(mHolder);
-
-        assertThat(getCardPreferenceButtonsView().getVisibility()).isEqualTo(VISIBLE);
-    }
-
-    @Test
-    public void onBindViewHolder_setPrimaryButtonVisibilityToVisible() {
-        mCardPreference.setPrimaryButtonVisible(true);
-
-        mCardPreference.onBindViewHolder(mHolder);
-
-        assertThat(getPrimaryButton().getVisibility()).isEqualTo(VISIBLE);
-    }
-
-    @Test
-    public void onBindViewHolder_setSecondaryButtonVisibility_buttonsLayoutIsVisible() {
-        mCardPreference.setSecondaryButtonVisible(true);
-
-        mCardPreference.onBindViewHolder(mHolder);
-
-        assertThat(getCardPreferenceButtonsView().getVisibility()).isEqualTo(VISIBLE);
-    }
-
-    @Test
-    public void onBindViewHolder_setSecondaryButtonVisibilityToVisible() {
-        mCardPreference.setSecondaryButtonVisible(true);
-
-        mCardPreference.onBindViewHolder(mHolder);
-
-        assertThat(getSecondaryButton().getVisibility()).isEqualTo(VISIBLE);
-    }
-
-    @Test
-    public void onBindViewHolder_setPrimaryButtonTextToExpectedText() {
-        String expectedText = "primary-button";
-        mCardPreference.setPrimaryButtonText(expectedText);
-
-        mCardPreference.onBindViewHolder(mHolder);
-
-        assertThat(getPrimaryButton().getText().toString()).isEqualTo(expectedText);
-    }
-
-    @Test
-    public void onBindViewHolder_setSecondaryButtonTextToExpectedText() {
-        String expectedText = "secondary-button";
-        mCardPreference.setSecondaryButtonText(expectedText);
-
-        mCardPreference.onBindViewHolder(mHolder);
-
-        assertThat(getSecondaryButton().getText().toString()).isEqualTo(expectedText);
-    }
-
-    @Test
-    public void onBindViewHolder_initialTextForPrimaryButtonShouldBeEmpty() {
-        mCardPreference.onBindViewHolder(mHolder);
-
-        assertThat(getPrimaryButton().getText().toString()).isEqualTo("");
-    }
-
-    @Test
-    public void onBindViewHolder_initialTextForSecondaryButtonShouldBeEmpty() {
-        mCardPreference.onBindViewHolder(mHolder);
-
-        assertThat(getSecondaryButton().getText().toString()).isEqualTo("");
-    }
-
-    @Test
-    public void performClickOnPrimaryButton_callClickListener() {
-        final boolean[] hasCalled = {false};
-        View.OnClickListener clickListener = v -> hasCalled[0] = true;
-        mCardPreference.setPrimaryButtonClickListener(clickListener);
-
-        mCardPreference.onBindViewHolder(mHolder);
-        getPrimaryButton().performClick();
-
-        assertThat(hasCalled[0]).isTrue();
-    }
-
-    @Test
-    public void performClickOnSecondaryButton_callClickListener() {
-        final boolean[] hasCalled = {false};
-        View.OnClickListener clickListener = v -> hasCalled[0] = true;
-        mCardPreference.setSecondaryButtonClickListener(clickListener);
-
-        mCardPreference.onBindViewHolder(mHolder);
-        getSecondaryButton().performClick();
-
-        assertThat(hasCalled[0]).isTrue();
-    }
-
-    @Test
-    public void onBindViewHolder_primaryButtonDefaultIsGone() {
-        mCardPreference.onBindViewHolder(mHolder);
-
-        assertThat(getPrimaryButton().getVisibility()).isEqualTo(GONE);
-    }
-
-    @Test
-    public void onBindViewHolder_secondaryButtonDefaultIsGone() {
-        mCardPreference.onBindViewHolder(mHolder);
-
-        assertThat(getSecondaryButton().getVisibility()).isEqualTo(GONE);
-    }
-
-    @Test
-    public void setPrimaryButtonVisibility_setTrueAfterBindViewHolder_isVisible() {
-        mCardPreference.setPrimaryButtonVisible(false);
-        mCardPreference.onBindViewHolder(mHolder);
-
-        mCardPreference.setPrimaryButtonVisible(true);
-
-        assertThat(getPrimaryButton().getVisibility()).isEqualTo(VISIBLE);
-    }
-
-    @Test
-    public void setPrimaryButtonText_setAfterBindViewHolder_setOnUi() {
-        String expectedText = "123456";
-        mCardPreference.onBindViewHolder(mHolder);
-
-        mCardPreference.setPrimaryButtonText(expectedText);
-
-        assertThat(getPrimaryButton().getText().toString()).isEqualTo(expectedText);
-    }
-
-    @Test
-    public void setPrimaryButtonText_setNull_isEmptyText() {
-        final String emptyString = "";
-        mCardPreference.setPrimaryButtonText("1234");
-        mCardPreference.onBindViewHolder(mHolder);
-
-        mCardPreference.setPrimaryButtonText(null);
-
-        assertThat(getPrimaryButton().getText().toString()).isEqualTo(emptyString);
-    }
-
-    @Test
-    public void setPrimaryButtonClickListener_setAfterOnBindViewHolder() {
-        final String[] hasCalled = {""};
-        String expectedClickedResult = "was called";
-        View.OnClickListener clickListener = v -> hasCalled[0] = expectedClickedResult;
-        mCardPreference.onBindViewHolder(mHolder);
-
-        mCardPreference.setPrimaryButtonClickListener(clickListener);
-        getPrimaryButton().performClick();
-
-        assertThat(hasCalled[0]).isEqualTo(expectedClickedResult);
-    }
-
-    @Test
-    public void setPrimaryButtonClickListener_setNull_clearTheOnClickListener() {
-        final String[] hasCalled = {"not called"};
-        View.OnClickListener clickListener = v -> hasCalled[0] = "called once";
-        mCardPreference.setPrimaryButtonClickListener(clickListener);
-        mCardPreference.onBindViewHolder(mHolder);
-
-        mCardPreference.setPrimaryButtonClickListener(null);
-        getPrimaryButton().performClick();
-
-        assertThat(hasCalled[0]).isEqualTo("not called");
-    }
-
-    @Test
-    public void setSecondaryButtonVisibility_setTrueAfterBindViewHolder_isVisible() {
-        mCardPreference.setSecondaryButtonVisible(false);
-        mCardPreference.onBindViewHolder(mHolder);
-
-        mCardPreference.setSecondaryButtonVisible(true);
-
-        assertThat(getSecondaryButton().getVisibility()).isEqualTo(VISIBLE);
-    }
-
-    @Test
-    public void setSecondaryButtonText_setAfterBindViewHolder_setOnUi() {
-        String expectedText = "10101010";
-        mCardPreference.onBindViewHolder(mHolder);
-
-        mCardPreference.setSecondaryButtonText(expectedText);
-
-        assertThat(getSecondaryButton().getText().toString()).isEqualTo(expectedText);
-    }
-
-    @Test
-    public void setSecondaryButtonText_setNull_isEmptyText() {
-        String emptyString = "";
-        mCardPreference.setSecondaryButtonText("1234");
-        mCardPreference.onBindViewHolder(mHolder);
-
-        mCardPreference.setSecondaryButtonText(null);
-
-        assertThat(getSecondaryButton().getText().toString()).isEqualTo(emptyString);
-    }
-
-    @Test
-    public void setSecondaryButtonClickListener_setAfterOnBindViewHolder() {
-        final String[] hasCalled = {""};
-        String expectedClickedResult = "2nd was called";
-        View.OnClickListener clickListener = v -> hasCalled[0] = expectedClickedResult;
-        mCardPreference.onBindViewHolder(mHolder);
-
-        mCardPreference.setSecondaryButtonClickListener(clickListener);
-        getSecondaryButton().performClick();
-
-        assertThat(hasCalled[0]).isEqualTo(expectedClickedResult);
-    }
-
-    @Test
-    public void setSecondaryButtonClickListener_setNull_clearTheOnClickListener() {
-        final String[] hasCalled = {"not called"};
-        View.OnClickListener clickListener = v -> hasCalled[0] = "called once";
-        mCardPreference.setSecondaryButtonClickListener(clickListener);
-        mCardPreference.onBindViewHolder(mHolder);
-
-        mCardPreference.setSecondaryButtonClickListener(null);
-        getSecondaryButton().performClick();
-
-        assertThat(hasCalled[0]).isEqualTo("not called");
-    }
-
-    @Test
-    public void setPrimaryButtonVisibility_setGoneForSecondaryButton_buttonGroupIsGone() {
-        mCardPreference.setPrimaryButtonVisible(true);
-        mCardPreference.setSecondaryButtonVisible(false);
-        mCardPreference.onBindViewHolder(mHolder);
-        assertWithMessage("PreCondition: buttonsView should be Visible")
-                .that(getCardPreferenceButtonsView().getVisibility())
-                .isEqualTo(VISIBLE);
-
-        mCardPreference.setPrimaryButtonVisible(false);
-
-        assertThat(getCardPreferenceButtonsView().getVisibility()).isEqualTo(GONE);
-    }
-
-    @Test
-    public void setSecondaryButtonVisibility_setGoneForPrimaryButton_buttonGroupIsGone() {
-        mCardPreference.setPrimaryButtonVisible(false);
-        mCardPreference.setSecondaryButtonVisible(true);
-        mCardPreference.onBindViewHolder(mHolder);
-        assertWithMessage("PreCondition: buttonsView should be Visible")
-                .that(getCardPreferenceButtonsView().getVisibility())
-                .isEqualTo(VISIBLE);
-
-        mCardPreference.setSecondaryButtonVisible(false);
-
-        assertThat(getCardPreferenceButtonsView().getVisibility()).isEqualTo(GONE);
-    }
-
-    @Test
-    public void resetLayoutState_buttonGroupIsGone() {
-        mCardPreference.setPrimaryButtonVisible(true);
-        mCardPreference.setSecondaryButtonVisible(true);
-        mCardPreference.onBindViewHolder(mHolder);
-
-        mCardPreference.resetLayoutState();
-
-        assertThat(getCardPreferenceButtonsView().getVisibility()).isEqualTo(GONE);
-    }
-
-    private View getCardPreferenceButtonsView() {
-        return mHolder.findViewById(R.id.card_preference_buttons);
-    }
-
-    private Button getPrimaryButton() {
-        return (Button) mHolder.findViewById(android.R.id.button1);
-    }
-
-    private Button getSecondaryButton() {
-        return (Button) mHolder.findViewById(android.R.id.button2);
-    }
-}
diff --git a/tests/spa_unit/src/com/android/settings/widget/CardPreferenceTest.kt b/tests/spa_unit/src/com/android/settings/widget/CardPreferenceTest.kt
new file mode 100644
index 0000000..0483e36
--- /dev/null
+++ b/tests/spa_unit/src/com/android/settings/widget/CardPreferenceTest.kt
@@ -0,0 +1,240 @@
+/*
+ * Copyright (C) 2024 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.settings.widget
+
+import android.content.Context
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.test.assertContentDescriptionEquals
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsNotDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithContentDescription
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performClick
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settings.R
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class CardPreferenceTest {
+
+    @get:Rule val composeTestRule = createComposeRule()
+    private lateinit var context: Context
+
+    @Before
+    fun setUp() {
+        context = ApplicationProvider.getApplicationContext()
+    }
+
+    @Test
+    fun enableDismiss_whenEnable_shouldBeDisplayed() {
+        composeTestRule.setContent { buildCardPreference(enableDismiss = true) }
+
+        composeTestRule.onNodeWithContentDescription("Dismiss").assertIsDisplayed()
+    }
+
+    @Test
+    fun enableDismiss_whenDisable_shouldBeDisplayed() {
+        composeTestRule.setContent { buildCardPreference(enableDismiss = false) }
+
+        composeTestRule.onNodeWithContentDescription("Dismiss").assertIsNotDisplayed()
+    }
+
+    @Test
+    fun primaryButton_whenVisible_shouldBeDisplayed() {
+        val expectedPrimaryButtonText = "You can see me"
+        composeTestRule.setContent {
+            buildCardPreference(
+                primaryButtonText = expectedPrimaryButtonText,
+                primaryButtonVisibility = true,
+            )
+        }
+
+        composeTestRule.onNodeWithText(expectedPrimaryButtonText).assertIsDisplayed()
+    }
+
+    @Test
+    fun primaryButton_whenInvisible_shouldBeDisplayed() {
+        val expectedButtonText = "You cannot see me"
+        composeTestRule.setContent {
+            buildCardPreference(
+                primaryButtonText = expectedButtonText,
+                primaryButtonVisibility = false,
+            )
+        }
+
+        composeTestRule.onNodeWithText(expectedButtonText).assertIsNotDisplayed()
+    }
+
+    @Test
+    fun primaryButtonAction_whenClick_performAction() {
+        val buttonText = "click me"
+        var clicked = false
+        composeTestRule.setContent {
+            buildCardPreference(
+                primaryButtonText = buttonText,
+                primaryButtonVisibility = true,
+                primaryButtonAction = { clicked = true }
+            )
+        }
+
+        composeTestRule.onNodeWithText(buttonText).performClick()
+
+        assertThat(clicked).isTrue()
+    }
+
+    @Test
+    fun primaryButtonContentDescription_whenSet_shouldBeExists() {
+        val expectedText = "this is a content description"
+        val buttonText = "primary-button"
+        composeTestRule.setContent {
+            buildCardPreference(
+                primaryButtonText = buttonText,
+                primaryButtonContentDescription = expectedText,
+                primaryButtonVisibility = true,
+            )
+        }
+
+        composeTestRule.onNodeWithText(buttonText).assertContentDescriptionEquals(expectedText)
+    }
+
+    @Test
+    fun secondaryButton_whenVisible_shouldBeDisplayed() {
+        val expectedSecondaryButtonText = "You can see me"
+        composeTestRule.setContent {
+            buildCardPreference(
+                secondaryButtonText = expectedSecondaryButtonText,
+                secondaryButtonVisibility = true,
+            )
+        }
+
+        composeTestRule.onNodeWithText(expectedSecondaryButtonText).assertIsDisplayed()
+    }
+
+    @Test
+    fun secondaryButton_whenInvisible_shouldBeDisplayed() {
+        val expectedButtonText = "You cannot see me"
+        composeTestRule.setContent {
+            buildCardPreference(
+                secondaryButtonText = expectedButtonText,
+                secondaryButtonVisibility = false,
+            )
+        }
+
+        composeTestRule.onNodeWithText(expectedButtonText).assertIsNotDisplayed()
+    }
+
+    @Test
+    fun secondaryButtonAction_whenClick_performAction() {
+        val buttonText = "click me2"
+        var clicked = false
+        composeTestRule.setContent {
+            buildCardPreference(
+                secondaryButtonText = buttonText,
+                secondaryButtonVisibility = true,
+                secondaryButtonAction = { clicked = true }
+            )
+        }
+
+        composeTestRule.onNodeWithText(buttonText).performClick()
+
+        assertThat(clicked).isTrue()
+    }
+
+    @Test
+    fun secondaryButtonContentDescription_whenSet_shouldBeExists() {
+        val expectedText = "found bug yay"
+        val buttonText = "secondary-button"
+        composeTestRule.setContent {
+            buildCardPreference(
+                secondaryButtonText = buttonText,
+                secondaryButtonContentDescription = expectedText,
+                secondaryButtonVisibility = true,
+            )
+        }
+
+        composeTestRule.onNodeWithText(buttonText).assertContentDescriptionEquals(expectedText)
+    }
+
+    @Test
+    fun resetLayoutState_shouldRemoveThePrimaryButton() {
+        val buttonText = "9527"
+        val cardPreference =
+            CardPreference(context)
+                .apply {
+                    primaryButtonText = buttonText
+                    primaryButtonVisibility = true
+                }
+                .also { it.buildContent() }
+
+        cardPreference.resetLayoutState()
+        composeTestRule.setContent { cardPreference.Content() }
+
+        composeTestRule.onNodeWithText(buttonText).assertDoesNotExist()
+    }
+
+    @Test
+    fun resetLayoutState_shouldRemoveTheSecondaryButton() {
+        val buttonText = "4567"
+        val cardPreference =
+            CardPreference(context)
+                .apply {
+                    secondaryButtonText = buttonText
+                    secondaryButtonVisibility = true
+                }
+                .also { it.buildContent() }
+
+        cardPreference.resetLayoutState()
+        composeTestRule.setContent { cardPreference.Content() }
+
+        composeTestRule.onNodeWithText(buttonText).assertDoesNotExist()
+    }
+
+    @Composable
+    private fun buildCardPreference(
+        iconResId: Int? = R.drawable.ic_battery_status_protected_24dp,
+        primaryButtonText: String = "primary text",
+        primaryButtonContentDescription: String? = "primary description",
+        primaryButtonAction: () -> Unit = {},
+        primaryButtonVisibility: Boolean = false,
+        secondaryButtonText: String = "secondary button",
+        secondaryButtonContentDescription: String? = null,
+        secondaryButtonAction: () -> Unit = {},
+        secondaryButtonVisibility: Boolean = false,
+        enableDismiss: Boolean = true,
+    ) =
+        CardPreference(context)
+            .apply {
+                this.iconResId = iconResId
+                this.primaryButtonText = primaryButtonText
+                this.primaryButtonContentDescription = primaryButtonContentDescription
+                this.primaryButtonAction = primaryButtonAction
+                this.primaryButtonVisibility = primaryButtonVisibility
+                this.secondaryButtonText = secondaryButtonText
+                this.secondaryButtonContentDescription = secondaryButtonContentDescription
+                this.secondaryButtonAction = secondaryButtonAction
+                this.secondaryButtonVisibility = secondaryButtonVisibility
+                this.enableDismiss(enableDismiss)
+            }
+            .also { it.buildContent() }
+            .Content()
+}